Skip to content

feat(ui): logic reads as sentences; XPath behind "code"; advanced panel in Logic/Display/Messages/Raw (#19) - #29

Open
PrjShrestha wants to merge 17 commits into
masterfrom
9f_sentence_editor
Open

PrjShrestha wants to merge 17 commits into
masterfrom
9f_sentence_editor

Conversation

@PrjShrestha

@PrjShrestha PrjShrestha commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #19. Last slice of the T9 epic (#9). Stacked on #28 (9g) → #27 → #26 → #25 → #24 → #23; merge in that order and this PR's diff collapses to the 9f commit.

What: the row's advanced panel is regrouped into Logic, Display, Messages and Raw. Each logic column is one sentence ("Show this question when …", "Filter the choice list when …") with a plain-English readback using question and choice labels; the XPath and the "✎ build" modal sit behind a per-column "code" toggle; collapsed rows summarise their logic in words.

Why this was necessary: even with pickers, the panel led with column names and an XPath box for each, so the first thing an author saw was code. Reviewers reported that authors did not know which box did what, and that the calculation box shown on every question invited mistakes.

What it solves: an author reads what a row does in words, edits by picking, and only meets XPath by choice. A hand-written rule the sentence editor cannot show opens with its XPath visible, so nothing is locked away, and every write still goes through the same serializer.

Demo video and the "what an author can do" table: #19 (comment)


What changed

The row's advanced panel is four groups: Logic, Display, Messages, Raw.

  • Logic — one sentence-shaped editor per column: Show this question when …, Filter the choice list when … (select rows only), and the Validation panel (T9e: Validation panel: presets per type, messages per language, no-normalise recogniser #18) for the constraint. The column name stays as a small tag so the XLSForm vocabulary is never hidden, only de-emphasised.
  • Plain-English readback from the first committed clause, using question labels and choice labels: This row shows when: Chair rise includes Pass.
  • XPath behind "code" — the raw cell and the "✎ build" modal sit behind a per-column toggle. A hand-written rule the sentence editor cannot show opens with its XPath visible, so the author always has something to edit.
  • Collapsed summary in words — shows when Last menstrual period has an answer · 1 validation rule · computed.
  • "Compute the value as…" only for calculate rows or rows that already compute; a plain question gets a one-click "+ compute this value…".

Bytes are unchanged. Every write still goes through the same serializer; a displayed-but-unedited rule is re-emitted in its original spelling (../lmp_date != '' stays ../lmp_date != '', .<=100 stays .<=100).

Tests

  • client/tests/sentence-editor.spec.ts — cold-start journey (pick, read back, code toggle, collapsed summary, bytes on disk); ../field relevants reopen as sentences and save byte-identical; an unparseable rule opens with its XPath.
  • Existing specs updated for the layout (strip addressed by .cond-strip-unified[data-column=…], "+ insert" → "Apply", specs reaching "✎ build" click "code" first). geriatric-build 9 and the geriatric helper address the modal's "string" checkbox by name (the 9c picker added a second checkbox to the rule row).

Demo recordings

client/tests/t9-demos/ + playwright.demo.config.ts: one captioned, slow-motion recording per sub-issue (9a–9g) written to client/demo/t9/<ticket>.webm (gitignored; not part of the suite).

pnpm --filter @cht-ui/client exec playwright test -c playwright.demo.config.ts

Validation

  • pnpm --filter @cht-ui/shared build && pnpm --filter @cht-ui/shared test green; pnpm typecheck clean; eslint clean on touched files (pnpm lint fails on master already).
  • node scripts/corpus-sweep.mjs — identical to the master baseline (pre-existing extraSheets / contact-summary drift only).
  • e2e: only the four pre-existing master failures remain (demo 1 & 4, geriatric-build 7 & 8).

PrjShrestha and others added 10 commits October 1, 2026 17:19
…lected" reopen as clauses (#15)

Two defects met at ruleToClause in conditionReducer.ts:

(a) clauseToRule emitted `${f}` and `not(${f})` as kind 'raw', so on
    reopen the parser handed back a raw rule, hydrateColumn fell to
    rawFallback and every control was disabled. They now have a real
    parser kind, `truthy` { field, negated }, that serializes to exactly
    the two spellings the builder has always written. Spacing-divergent
    forms (`${ f }`, `not( ${f} )`) stay raw via the self-check.

(b) `${f} != ''` / `${f} = ''` parsed cleanly as kind 'answered' and were
    dropped at the same spot. They now map to the ref / not clause and
    carry the author's spelling as Clause.source, which clauseToRule
    re-emits while it still describes the clause. An unedited reopen
    writes the same bytes; `${f} != ''` is never rewritten to `${f}`.

Pinned separately: a bare `${f}` fixture exercises only the truthy path,
`${f} != ''` only the answered path. All four reducer tests and the
three positive parser round-trip tests fail on bff69cb and pass here.

Three UI consumers that switch exhaustively on Rule['kind'] gained a
branch for the new kind (modal row, decisions prose, calc prose).

Verified: shared tests 792 pass / 0 fail; typecheck clean; corpus sweep
output byte-identical to master; a cell-level serialize(parse(x)) check
over 4034 relevant/constraint/choice_filter cells in seven configs shows
the same 37 pre-existing drifts as master and none new; 21 relevant cells
now open as clauses that were raw before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…uilder (#15)

Two Playwright specs on a throwaway copy of the mini-config fixture:

- write `${lmp_date}` and `not(${danger_signs})` through the strip, save,
  assert the bytes via the API, reload, and reopen both rows as clauses
  (no "hand-written" status, undo-last-clause present, insert enabled);
- the fixture's existing `${lmp_date} != ''` relevant opens as a clause,
  re-inserts with zero edits as the same bytes, and survives a save.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… function forms on `.` (#14)

Real validation rules are written against the answer itself and real
relevants use relative paths; the shared parser only knew `${field}`
subjects, so 774 of the 777 constraints in seven real configs opened as
plain text. This slice is shared-only and additive.

New in shared/src/xlsform/operand.ts: a closed operand grammar (`.`,
`${f}` / `../f` with the spelling kept, literals, whitelisted ODK + CHT
calls, `+ - * div mod`, parens). On top of it, four additive rule kinds:

- expr-comparison  `. >= 0`, `string-length(.) <= 100`,
                   `int(format-date(today(),'%Y')) + 57 >= int(.)`,
                   `. > max(coalesce(${a}, 0), ...)`, `. <= today() - 30`
- predicate        `regex(., '...')`, `selected(., 'none')`, `not(...)` of those
- not-group        `not(selected(., 'none') and count-selected(.) > 1)`
- always-true      `true`, `true()`, `1` (text carried, never rewritten)

Each carries the clause verbatim as `source`; the serializer re-emits it
while it still parses to the same rule, so `.<=100` and `. <= 100` both
open AND save back byte-identical, and only a rule the author changed
gets canonical spacing. `../field` on the existing comparison / selected
/ answered / truthy kinds is a `refSpelling: 'relative'` flag, re-emitted
exactly as written in either direction. The reducer attaches `source`
to any hydrated clause whose canonical emission would differ, so a
`../field` rule opens in the inline strip and saves back unchanged.

Also: the self-check now runs on all-raw chains too. Splitting on the
combinator rejoined `a and  b` with one space (six distinct real FCHV /
LMP constraints); such a chain is now one raw rule, byte-identical.

Consumers that switch exhaustively on Rule['kind'] show the new kinds
as the text the author wrote (modal row, decisions / calc prose); a
change in the modal turns the rule into a raw fragment, as before.

Measured on the seven analysis configs (777 constraint cells):
  constraint  682 / 777 open fully structured (3 before; 51 are placeholders)
  relevant   1839 / 3050
  drift        0 / 3919 cells (serialize(parse(x)) === x on every cell)
  `../` share: 410 relative refs vs 4433 ${} refs; 329 cells use `../`

Tests: operand grammar; serializer-exercising round trips from
non-canonical fixtures for every form in the ticket; `../` hostile
fixtures live for both halves (byte identity and opens-as-clause); the
all-raw self-check; an "additive" guard that every pre-T9a fixture still
yields its old kind; Playwright: seeded `.` / `../` cells survive
open-and-save byte-identical, the modal opens a `.` constraint as rows,
a `../` relevant opens in the strip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s hidden; choice labels in pickers and readback (#16)

The "show this question when" dropdown listed about 230 rows of a real
pregnancy form by name in sheet order, every *_note, r_* summary row,
__* hidden output and plumbing calculate included, with no search, no
labels and no grouping (UX review finding 2, P0). One component now
serves every place a rule picks a field: the inline strip, the "build"
modal, the calculation builder's field references, and the strip's value
cell when the value is another question.

shared/src/xlsform/fieldMeta.ts (pure, node-tested): per named row, the
resolved label (first non-empty in locale order), the innermost section
(group label or name, both `begin group` spellings), the field kind, and
whether the row is technical: note, r_*, __*, hidden type/appearance. A
calculate is NOT technical: the harvest calculate that re-exports
../inputs/contact/sex is the sanctioned way to reach a contact value.

client/src/ui/SurveyFieldPicker.tsx: [search] [select] [show technical
rows]. The select stays native and keeps `ref-chip-select`, so keyboard
behaviour and every existing e2e `selectOption` keep working; options
read "Label (name)", grouped by section as optgroups. Typing narrows by
label OR name and turns the select into a visible list, so matches show
on the first keystrokes and one click picks; picking clears the search.
Technical rows are withheld until the toggle. The current value is always
kept in the list so a saved selection is never stranded. Metadata comes
from a FieldMetaContext provided once per form by FormEditor; callers
keep passing the dependency-ordered name list, so the picker never offers
a field the caller withheld.

FormEditor: the v0.3 "Typical for this check" / "Other fields" partition
and its "Show all fields" checkbox are gone (the review found the box
read as unchecked while everything was shown); op-typicality survives as
ordering inside each section and never hides a field. The value cell
shows choice labels ("Vaginal bleeding (vaginal_bleeding)") and the
readback chips use labels; the written cell keeps the name. The free-text
value cell no longer says `value or ${other_field}`: an "another question"
toggle swaps it for the same picker and writes `${name}`.

Found on the way: the search box grew on focus, so a mousedown on
"+ insert" blurred it, shrank it, and moved the button out from under the
cursor before mouseup; the click was lost. No width change on focus now.

Tests: fieldMeta node tests (labels, sections, technical reasons,
search); Playwright field-picker.spec.ts (search by name and by label,
technical toggle, labels in value picker and readback with the name on
disk, "another question" value, the modal uses the same picker, and on
the real 280-row geriatric form "lmp" lists a handful grouped by section,
skipped when that config is absent). The three v0.3 picker specs are
rewritten for the section grouping and the technical toggle. Open-and-
save is unaffected: corpus sweep output byte-identical to master.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…scroll to and highlight the new row (#17)

Clicking a type tile in the add-question picker committed immediately:
no step for required, hint or validation, and the new row was appended
after the hidden __* outputs, off-screen, with no scroll and no
highlight (UX review finding 3, P0).

QuestionTypePicker: a `configure` step between a question tile and the
commit — required, a hint per visible language, and a Validation slot
(constraint expression with the existing "✎ build" modal, plus a
constraint_message per language; 9e replaces the slot with presets).
Enter anywhere commits. "add without details" is the one-click behaviour
for this question; "Always skip this step" remembers it
(localStorage `cht-ui-builder.oneClickTiles`). Structural tiles, the
lineage sentinel, hidden rows and edit-type reopens never get the step.
A select's choices step leads into the configure step the same way.
PickerCommit gains an optional `details` block; FormEditor writes
`required = yes`, `hint::<loc>`, `constraint`, `constraint_message::<loc>`
only for the values the author set, so an untouched step adds exactly
the row the one-click flow added.

Insert position: "+ Question" lands directly after the row the author is
on (the last focused card, tracked by a focus-capture on the survey tab),
via the new shared `insertIndexAfterRow` (surveyEdits.ts, node-tested).
A begin-group row counts as "inside the group" (first child), any other
row is followed by its new sibling, so every pair stays balanced. With no
current row it falls back to `defaultInsertIndex` (before the trailing
plumbing calculates). After commit the new row is scrolled into view,
focused and flashed for 2.5 s; a row hidden in Simple mode flips the
editor to Full first.

The Validation slot's field list is the same dependency-ordered,
unique-name, non-plumbing list the row card uses (`pickableFieldsBefore`,
now one function), computed for the insert position.

Tests: surveyEdits.insertAfter.test.ts (after a top-level row, inside a
group, after a begin row, trailing-plumbing fallback, unknown id; each
asserting structural balance). Playwright add-question-configure.spec.ts:
age (integer) with required, hint, constraint and message lands right
after lmp_date, flashed and in view, with those cells on disk and every
other row's cells unchanged; "add without details"; a question added
while on a row inside a group lands inside, after it, balanced; the
remembered preference commits on the tile click.

Suite: the build specs were written against one-click tiles, so the
Playwright profile seeds the preference ON (storageState in
playwright.config.ts, which also covers specs that bypass setup.ts);
the new spec clears it. demo-1's row-order expectations now reflect
insert-after-current-row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ssages beside the rule; never normalises (#18)

Validation is where a program says what a correct answer is, and the
tool could show 3 of 777 real rules. With `.` readable (9a) most real
rules are a dozen shapes a non-developer recognises by name. This slice
gives them a panel and replaces both constraint builders.

shared/src/validation/presets.ts (pure, node-tested): the preset model
(between, compare-value, compare-field, compare-today, days-from-today,
months-from-today, after-all-of, text-length, allowed-chars, pattern,
int-value, bs-year, choice-alone, count-selected, always-true, code),
`parseValidation` (one parsed rule → one preset; two adjacent numeric
bounds → "between"; an `or` chain, a grouped or unparseable cell → one
code item), `emitPreset` (canonical XPath), `presetsFor` (the menu per
question kind; today() for dates, now() for date-times),
`suggestMessage` and `presetComplete`.

THE RECOGNISER NEVER NORMALISES. Every item keeps `source`, the rule
text as written; `serializeValidation` re-emits it while it still reads
as the same preset and writes the canonical spelling only for an item
the author changed. `. <= 100 and . >= 70` is displayed as "Between 70
and 100" and saved as `. <= 100 and . >= 70`; edit the maximum and that
item becomes `. >= 70 and . <= 99` while its siblings keep their spelling.

relevantParser.ts: ParsedExpression gains an optional `separators`
(the text between rules exactly as written — ` and\n`, ` and  `), set
only when a join is not canonical and honoured by serializeRelevant
while it still fits the rule count and spells the combinator. Real
configs break ~90 chains across a newline or a double space; they were
raw, now they open. Additive: canonical cells and consumer-built
expressions carry no separators.

client/src/ui/ValidationPanel.tsx: a list of sentence-shaped preset
rows with inputs, "+ Add rule" per question kind (plus a plain
expression), a code toggle, constraint_message per visible language
with a suggested text that stops as soon as the author writes their
own, and (row editor) the required checkbox with required_message per
language beside it. `true` / `true()` / `1` show "This rule always
passes: no validation". Incomplete presets stay on screen without
writing a broken expression; only complete items reach the cell. The
rule and a suggested message are written in ONE row update (two
updates in a tick each started from the same stale row and the second
won).

Mounted in the row editor in place of the constraint expression field
(constraint_message inputs leave the hints block; required_message
leaves the raw overrides), and in the add-question configure step's
Validation slot (9d) in place of the expression box + modal. The
inline strip no longer offers the constraint column.

Measured on the seven analysis configs (777 constraint cells): 575 open
entirely as presets, 61 as presets plus a plain-text item, 51 are
placeholders now labelled, 90 stay plain text (mixed and/or inside
not(), curly quotes, decimal-date-time arithmetic). Parser-level: 730 of
777 structured. Drift 0 of 777 through parseValidation →
serializeValidation, and 0 of 3919 cells through the parser.

A form holding every preset's canonical output (40 constraints) compiles
with pyxform 4.5 xls2xform, the step cht-conf runs (Docker is not
running on this machine; cht-conf itself is installed).

Tests: presets.test.ts (every round trip from a non-canonical fixture
through the serializer; edited-item canonicalisation; stale source
ignored; every catalogue entry emits and parses back to itself;
separators), relevantParser separators tests, Playwright
validation-panel.spec.ts (age with Between 0 and 20 in the picker →
sheet → reopened preset; reverse-order between, tight `.<=today()` and
`true()` displayed as presets and saved byte-identical, then one edited
item canonical; text length, date not in the future, select-many
choice alone, required message, cells asserted on disk). Two earlier
specs updated for the panel replacing the constraint box and modal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd constraint match at runtime on a live CHT (#20)

The June ticket (#9) had two acceptance criteria nobody had run.

four-builders.spec.ts — acceptance 3: in one journey on a scratch copy
of the fixture, the inline strip writes a relevant
(`selected(${chair_rise}, 'pass')`) and a choice_filter
(`selected(${danger_signs}, 'vaginal_bleeding')` on a later row — the
picker only offers earlier fields), the Validation panel writes a
constraint (`. >= 0 and . <= 20` with its message), and the calculation
builder writes `${gravidity} + 1` on a fresh calculate row; every cell is
read back through the API after a UI save.

live-instance-check.spec.ts — acceptance 4: on a renamed copy of the
fixture form, author by picking only (gravidity shows when chair_rise
includes "pass"; accepts 0..20 with a message), set the sheet's form_id
through the builder's API, deploy that one form with cht-conf 6.5.0
inside the cht-ui-builder image (`--add-host` so the TLS name resolves
to the host), then as the CHW on the local CHT 5.2 instance: the
question is hidden, shows after "Pass", rejects 25 with the authored
message and refuses to submit (no report), accepts 10 and the report is
in CouchDB with fields.gravidity = "10". The report and the form docs
are removed afterwards; the record id is printed. Skipped when the
instance or Docker is not reachable.

Run on 2026-10-01 against the poc_demo CHT 5.2.0 instance at
https://127-0-0-1.local-ip.medicmobile.org:10445 (NSSD config):
report 01a0f7da-9c2b-766d-90aa-3aabfd8dd6be, fields.gravidity=10.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…el in Logic/Display/Messages/Raw (#19)

T9f, the last slice of the T9 epic (#9). The row's advanced panel is now
four groups. Each logic column is one sentence-shaped editor: "Show this
question when …", "Filter the choice list when …" (select rows only),
and the Validation panel for the constraint. The readback is plain
English from the first committed clause, using question and choice
labels. The XPath cell and the "✎ build" modal sit behind a per-column
"code" toggle; a rule the sentence editor cannot show opens with its
XPath visible so the author always has something to edit. The collapsed
row summarises its logic in words ("shows when Chair rise includes Pass
· 1 validation rule · computed"). "Compute the value as…" shows only
for calculate rows or rows that already have a calculation; a plain
question gets "+ compute this value…".

Bytes are unchanged: every write still flows through the same
serializer, and a displayed-but-unedited rule is re-emitted in its
original spelling (`../lmp_date != ''` stays `../lmp_date != ''`).

Tests
- client/tests/sentence-editor.spec.ts: cold-start journey (pick, read
  back, code toggle, collapsed summary, bytes on disk) and the `../`
  reopen + byte-identical save; an unparseable rule opens with its XPath.
- Existing specs updated for the new layout: the strip is addressed by
  `.cond-strip-unified[data-column=…]`, "+ insert" is "Apply", and specs
  that reach the "✎ build" modal click the strip's "code" first.
- geriatric-build 9 / helpers: the modal rule row's "string" checkbox is
  addressed by its name (the 9c picker added a second checkbox).

Demos
- client/tests/t9-demos/*.demo.spec.ts + playwright.demo.config.ts: one
  captioned, slow-motion recording per sub-issue (9a–9g), written to
  client/demo/t9/<ticket>.webm (gitignored). Ignored by the main config.

Validation: shared build+test green; typecheck clean; lint clean on the
touched files (pnpm lint fails on master already); corpus sweep
unchanged against the master baseline; e2e: only the four pre-existing
master failures remain (demo 1 & 4, geriatric-build 7 & 8).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PrjShrestha added a commit that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PrjShrestha and others added 3 commits October 2, 2026 12:05
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o/t9; recorder also writes the .mp4

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the recorder keeps the copy in sync

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It is a large, stacked change (9a–9g) whose core byte-stability invariant and central sentence-editor UI are only partially visible in the presented diff, warranting final human review.

2 open findings
What changed in this PR

This PR lands the final slice (9f, #19) of the T9 "complex logic" epic for cht-ui-builder. The diff presented spans the whole stacked series (9a→9g), so it includes the operand grammar parser, field metadata, condition-reducer projection logic, the shared field picker, the add-question configure step, and a large set of Playwright specs plus demo-recording infrastructure. The user-facing goal is to make a row's logic read as plain-English sentences, hide XPath behind a per-column "code" toggle, and regroup the advanced panel into Logic/Display/Messages/Raw — all while keeping every serialized cell byte-for-byte unchanged for rules the author didn't touch.

Changes:

  • Sentence-shaped inline editors for relevant/choice_filter with plain-English readback, XPath behind a "code" toggle, and a collapsed word summary; "Compute the value as…" gated to calculate rows or opt-in.
  • Shared SurveyFieldPicker (search + section-grouped labels + technical-row toggle) wired into the rule builder, calculation builder, and the new add-question configure step; conditionReducer preserves the author's original spelling via a source field so untouched rules round-trip byte-identically.
  • Extensive test updates: new sentence-editor.spec.ts, four-builders.spec.ts, field-picker and parser round-trip tests, plus a gitignored per-sub-issue demo-recording harness (t9-demos/, playwright.demo.config.ts).
File Description
shared/​src/​xlsform/​operand.ts New closed XPath operand grammar (tokenizer/parser/serializer) underpinning the .-subject rule kinds.
shared/​src/​xlsform/​fieldMeta.ts New per-row metadata (label, section, kind, technical flag) feeding the picker.
shared/​src/​conditionBuilder/​conditionReducer.ts Projects truthy/answered/../field rules to clauses; sets source only when canonical re-emission diverges, preserving bytes.
shared/​src/​xlsform/​surveyEdits.ts Adds insertIndexAfterRow for "insert after current row".
shared/​src/​index.ts Re-exports fieldMeta/presets.
client/​src/​ui/​SurveyFieldPicker.tsx New shared searchable, section-grouped picker with technical-row toggle and Enter-to-pick.
client/​src/​ui/​QuestionTypePicker.tsx New configure step (required/hint/validation) with the Validation panel mounted compact.
client/​src/​ui/​RelevantRuleBuilder.tsx New truthy/comparison/predicate rows; FieldPicker now wraps SurveyFieldPicker with a custom-name escape.
client/​src/​ui/​CalculationBuilder.tsx, DecisionsView.tsx Field refs use the shared picker; prose/human-lines for the new rule kinds via serializeRule.
client/​tests/​*.spec.ts, t9-demos/​*, playwright*.config.ts New/updated specs for the sentence editor and four-builder journey; gitignored demo-recording harness.
*.css Styling for the sentence editor, picker, and validation panel.

Two minor test-file issues were flagged inline: a stale example in the four-builders.spec.ts header comment, and a tautological (always-true) assertion in field-picker.spec.ts. The shared parser/reducer logic, field metadata, and picker all appear correct and preserve the round-trip invariant.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment on lines +233 to +234
expect(lmp.every((n) => /lmp/i.test(n) || true)).toBe(true); // label matches are allowed too
expect(lmp).toContain('lmp_approx');
* emitted cells on disk.
*
* relevant the inline strip (condition builder) → `selected(${chair_rise}, 'pass')`
* choice_filter the same strip on the choice_filter column → `${chair_rise} = 'pass'`
@PrjShrestha
PrjShrestha requested a review from binokaryg October 8, 2026 09:12
PrjShrestha and others added 4 commits October 13, 2026 20:16
…, recorded

tests/t9-demos/cho_supervisory_form.demo.spec.ts drives the UI only: 11
sections, 43 rows, 14 choice lists in English and Nepali, hints, multiline
widgets and five show/hide rules picked in the sentence strip; the sheet is
read back and the rules asserted on disk. demo-helpers gains DEMO_OUT so a
recording can land in its own folder under client/demo. The replay notes
sit beside the video in client/demo/cho-supervisory-form/README.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ping, pauses after every action

demo-helpers gains hclick / htype / hselect and a cursor overlay in the
init script (an arrow that glides to the pointer and a ring on mousedown).
The CHO Supervisory Form take uses them throughout and runs with no
slow-motion, since slowMo would also slow every keystroke; DEMO_TYPE_MS and
DEMO_PAUSE_MS tune the pace.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…code; context builder fixes

Two human-paced recordings for a PM/PO audience (client/demo/pih-hh-survey):
build the HH Survey from the design workbook inside the CHT baseline project
and set its properties; then deploy from the Deploy tab to a fresh local CHT,
create a health-facility user in the CHT admin, and fill the survey on a
household as that user, reading the report back from CouchDB.

Builder fixes found by the deploy video:
- ContextExpressionBuilder emitted `contact.contact_type === '<id>'` for the
  CHT's four classic contact types, whose documents carry `type`; the CHT
  then refused the form ("not authorized"). Classic ids now emit
  `contact.type`, custom ids keep `contact.contact_type`
  (form-context-types.spec.ts updated).
- Adding the first context rule appended to the `true` placeholder,
  producing `true && …`; it now replaces it.

Vite's dev server ignores client/demo and test-results so recordings written
during a run never trip its watcher.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T9f: Sentence-shaped inline rule editor for relevant and choice_filter; advanced panel regrouped

2 participants