# fill-state

Track form completion progress with FillState

Canonical URL: https://docs.paradoc.dev/sdk/artifacts/form/fill-state/

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

```ts
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

```ts
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:

```ts
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`:

```ts
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:

```ts
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:

```ts
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()`.

| Name | Type | Description |
| --- | --- | --- |
| `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

| Name | Type | Description |
| --- | --- | --- |
| `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.

| Name | Type | Description |
| --- | --- | --- |
| `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

| Name | Type | Description |
| --- | --- | --- |
| `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.

| Name | Type | Description |
| --- | --- | --- |
| `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.

| Name | Type | Description |
| --- | --- | --- |
| `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

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

### FillTargetOptions

Options for `getFillState`, `getNextFillTarget`, and `getAvailableFillTargets`.

| Name | Type | Description |
| --- | --- | --- |
| `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.

| Name | Type | Description |
| --- | --- | --- |
| `context?` | `RuntimeContextOptions` | Fixed context captured by the new draft runtime |

### SafeFillResult

Returned by `safeFill` and `safeUpdate`.

```ts
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.

## Related

* [form](/sdk/artifacts/form) - Form builder and lifecycle
* [field](/sdk/artifacts/form/field) - Field definitions
* [party](/sdk/artifacts/form/party) - Party role definitions
* [annex](/sdk/artifacts/form/annex) - Annex slot definitions
