fill-state

Last updated on

Track form completion progress with FillState

Query the fill state of a DraftForm to determine what's been filled, what's still needed, and what to ask next. FillState evaluates conditional visibility, required expressions, and dependency chains to give an accurate picture of form progress.

Examples

Basic usage

const draft = form.fill({
  fields: { tenantName: 'John Doe' }
})

const state = draft.getFillState()

state.summary.completionPercent  // e.g. 25
state.summary.requiredRemaining  // e.g. 3
state.next                       // { kind: 'party', key: 'tenant', required: true, order: 0 }

Progressive fill loop

let draft = form.fill()

while (true) {
  const state = draft.getFillState()
  if (state.summary.requiredRemaining === 0) break

  const target = state.next
  if (!target) break

  const value = await collectValueFromUser(target)

  draft = draft.update({
    [target.kind === 'party' ? 'parties' : target.kind === 'annex' ? 'annexes' : 'fields']: {
      [target.key]: value
    }
  })
}

// Full readiness is a separate check from draft acceptance.
if (!draft.isValid()) throw new Error('Complete the remaining required values')

Including optional targets

By default, candidates only includes required targets. Pass includeOptional: true to include optional fields:

const state = draft.getFillState({ includeOptional: true })
// state.candidates now includes both required and optional targets
// Required targets are listed first (requiredFirst defaults to true)

Checking blocked items

Items with conditional visibility that depends on unfilled fields appear in blocked:

const state = draft.getFillState()

for (const item of state.blocked) {
  console.log(`${item.key} is hidden, waiting on: ${item.blockedBy.join(', ')}`)
}
// e.g. "spouseName is hidden, waiting on: maritalStatus"

Checking rules

Each failed rule comes back as a RuleValidationResult, the same object draft.validateRules() returns, so you can tell which rule failed:

const state = draft.getFillState()

for (const error of state.rules.errors) {
  console.log(`${error.ruleId}: ${error.message}`)
}
// e.g. "depositWithinLimit: Pet deposit cannot exceed $500"

Checking logic issues

An expression that fails to evaluate, such as a computed value that divides by zero, is not a rule result. It is reported in issues, and it blocks completion:

for (const issue of state.issues) {
  console.log(`${issue.path.join('.')}: ${issue.message}`)
}
// e.g. "defs.share: Division by zero"

When a visibility or requiredness condition fails, the form's logic cannot be resolved. The rules are not run, rules.valid is false with no errors, and issues says why.

API

FillState

Returned by draft.getFillState().

phase: 'draft' | 'signable' | 'executed'
Current form phase
summary: FillStateSummary
Progress summary with counts and completion percentage
defsValues: Record<string, unknown>
Current evaluated defs values
rules: { valid: boolean; errors: RuleValidationResult[]; warnings: RuleValidationResult[] }
Failed rules by severity. `valid` is true when every error rule passed, and false when the rules could not be run
issues: EvaluationIssue[]
Logic expressions that failed to evaluate. Any issue blocks completion
openRequired: FillItemState[]
Visible, unfilled, required items
openOptional: FillItemState[]
Visible, unfilled, optional items
blocked: FillItemState[]
Not visible — waiting on dependencies to be filled
done: FillItemState[]
Already filled items
candidates: FillTarget[]
Available fill targets in dependency order, with required targets before optional targets by default and declaration order breaking ties
next: FillTarget | null
First candidate — convenience shorthand for candidates[0]

FillStateSummary

requiredTotal: number
Total number of required items (visible and blocked)
requiredDone: number
Number of required items already filled
requiredRemaining: number
Number of required items still needed
completionPercent: number
Completion percentage (0–100)

RuleValidationResult

One rule's result. rules.errors and rules.warnings hold only failed rules.

ruleId: string
Key of the rule in the artifact's rules section
passed: boolean
Whether the rule passed
message: string | undefined
The rule's message, present when the rule failed
severity: 'error' | 'warning'
Errors make `valid` false; warnings do not

EvaluationIssue

message: string
What failed
path: (string | number)[]
Where the failed expression sits, such as ['defs', 'share']
expression: string | undefined
The expression that failed
originalError: string | undefined
The underlying error message

FillTarget

A candidate target that can be filled next.

kind: FillTargetKind
'field', 'party', or 'annex'
key: string
Field id, role id, or annex id
required: boolean
Whether this target is required
order: number
Stable declaration order used to break ties within a dependency rank

FillItemState

Extended fill target with runtime state. Used in openRequired, openOptional, blocked, and done arrays.

kind: FillTargetKind
'field', 'party', or 'annex'
key: string
Field id, role id, or annex id
required: boolean
Whether this target is required
order: number
Stable declaration order used to break ties within a dependency rank
visible: boolean
Whether this item is currently visible
status: 'hidden' | 'optional' | 'required'
Effective status combining visible and required. hidden < optional < required; required implies visible, so "hidden but required" cannot occur
filled: boolean
Whether this item has been filled
blockedBy: string[]
Transitive unfilled prerequisites gating this item's visibility (the full chain, not just the direct dependency)

FillTargetKind

type FillTargetKind = 'field' | 'party' | 'annex'

FillTargetOptions

Options for getFillState, getNextFillTarget, and getAvailableFillTargets.

requiredFirst?: boolean
Sort required targets before optional in candidates. Default: true
includeOptional?: boolean
Include optional targets in candidates. Default: false

FillOptions

Options for fill and safeFill on FormInstance.

context?: RuntimeContextOptions
Fixed context captured by the new draft runtime

SafeFillResult

Returned by safeFill and safeUpdate.

type SafeFillResult<F extends Form> =
  | { success: true; data: DraftForm<F> }
  | { success: false; error: Error }

How visibility and blocking work

FillState evaluates conditional visible and required expressions on fields, parties, and annexes using the current data. Items are categorized as:

  • openRequired — visible, required, not yet filled. These are what the user needs to answer.
  • openOptional — visible, optional, not yet filled. Available but not blocking completion.
  • blocked — not visible because a dependency hasn't been filled yet. The blockedBy array lists the transitive chain of unfilled prerequisites: if A gates B and B gates C, C's blockedBy includes both A and B. Blocked items unblock automatically when their dependencies are filled.
  • done — already filled, regardless of visibility.

Each item also carries a status ordinal (hidden < optional < required) that folds visible and required together; required implies visible, so a "hidden but required" item cannot exist.

The candidates array contains all open (visible, unfilled) targets in dependency (DAG) order: prerequisites come before the items they gate, and declaration order breaks ties within a rank. When requiredFirst is true (the default), required targets appear before optional ones. next is the first candidate, so following next repeatedly walks the form in an order where nothing is asked before what it depends on.

  • form - Form builder and lifecycle
  • field - Field definitions
  • party - Party role definitions
  • annex - Annex slot definitions

On this page