← All notes

Conditional forms: visibility is not permission

Design a conditional invoice field with explicit submission rules, dependency checks and stale-response protection.

Basis of this note

A design extension of the existing form demo. It currently supports six field types, local validation and JSON import; it does not implement conditional rules, remote options or server-side authorization. Examples below are design sketches, not accepted demo configuration.

01 / Begin with a product question

Suppose invoice_title appears only when needs_invoice is yes. A user fills it in and then chooses no. Removing the input from the screen does not tell us whether its value remains in memory, enters the payload or must be cleared.

For a low-sensitivity draft, one possible policy is retain locally but exclude from submission while hidden. Sensitive or invalidated values may instead need clearing. Choose the policy for the business field, and test switching both ways.

02 / Separate four decisions

  • Visibility decides whether a control is presented. It is not an authorization boundary.
  • Editability decides whether the current user can change a value. A disabled control can still have a stored value.
  • Validation decides whether an active field has an acceptable value. Hidden-field validation needs an explicit business rule.
  • Submission decides which fields enter the payload. The server must independently allowlist writable fields and check user permissions.

03 / Prefer bounded rules to executable strings

A rule object can name a target, a source field, an operator and an effect. Validate references, types and operators when importing it. Do not interpret arbitrary configuration text with eval or new Function.

The sketch below describes one proposed rule format. The current checkSchema does not implement these semantics; adding a rules property to a file would not activate linkage.

JSON
{
  "target": "invoice_title",
  "when": {
    "field": "needs_invoice",
    "op": "equals",
    "value": "yes"
  },
  "effect": "visible-and-required",
  "hiddenValuePolicy": "retain-local-exclude-from-submit"
}

04 / Check dependencies before execution

A rule reads one or more fields and changes a target. Record these dependencies. A conservative first version can reject cycles, calculate in topological order and reject conflicting effects on the same target rather than depending on incidental rule order.

A dependency rule is not automatically a state-update rule. Showing a field should not repeatedly overwrite user input with its default. Defaults should normally apply at initialization or after an explicitly defined reset.

05 / Remote options belong to a request context

When a province changes, a pending city request may belong to the previous selection. Clear or invalidate the previous city, start the new request, and accept results only when its sequence and dependency context still match. Cancellation alone is not a proof that no old callback can run.

Account, form revision and selected parent value can all be part of that context. Distinguish loading, empty results and failed requests. A cached selection should not silently remain valid when it is absent from the new permitted options.

06 / Checks I would add before enabling linkage

  • Switch yes → no → yes and check value retention, error messages, payload fields and keyboard focus.
  • Reject unknown field references, unsupported operators, cycles and contradictory effects.
  • Let the older options request finish last; verify that it cannot replace the newer options or re-enable a stale selection.
  • Submit a hidden or read-only field through a direct request; verify authorization on the server once a backend exists.

These are proposed checks for an unimplemented extension. Existing passing form tests do not establish that linkage or authorization is correct.

Sources & further reading

Compiled from code and documents, with AI-assisted drafting and translation. Examples and proposed checks are learning material; only linked evidence establishes what was run.