Skip to content

T9a: Parser reads real rules: . as subject, ../field alias, function forms #14

Description

@PrjShrestha

Parent: T9 (#9) · Importance: Must have · Size: M · Depends on: nothing. Unblocks 9e and 9g.

Why. The shared parser only recognises comparisons whose subject is ${field}. Real configs write
validation rules against the answer itself as . (. >= 0 and . <= 20), and real relevants use
relative paths (../lmp_approx). Across 777 constraint rules in seven configs the builders open
3. Nothing in the UI can improve until the parser reads what real forms contain. This slice is
shared-only and carries the round-trip risk, so it ships alone, before any UI depends on it.

Scope.

  • relevantParser.ts: accept . as a comparison subject. Values may be a number, a quoted string,
    ${field}, ../field, today(), now(), today() ± N, date('…').
  • Accept ../field as an alias of ${field} for subject and value. ⚠ Re-emit exactly as written;
    never rewrite ../field to ${field} or back.
  • Recognise the function forms on . that real configs use: string-length(.), regex(., '…'),
    selected(., '…'), count-selected(.), int(.), int(format-date(., '%Y')),
    max(coalesce(${a}, 0), …), add-date(today(), …), difference-in-months(., today()),
    decimal-date-time(.).
  • Recognise true, true() and 1 as an always-true rule kind so the UI can label it. ⚠ Never
    rewritten.
  • ⚠ Additive only. New rule kinds or new entry points; parseRelevant / parseRelevantGrouped
    return shapes seen by the five existing consumers do not change.
  • The parse → serialize self-check stays authoritative: anything that does not re-serialize
    byte-identical is raw text. .<=100 and . <= 100 both round-trip unchanged.
  • node --test round-trip case per new form in shared/src/xlsform/, each calling the serializer
    and starting from a non-canonical fixture.
  • Convert the ../field hostile fixtures from { todo: true } to live for the byte-stability half;
    only the "opens as clauses" half stays todo until 9e / 9f.
  • QA: measure the ../ vs ${} share across the seven configs and record it in the plan folder.

Not in scope. Any UI. The preset recogniser (9e). The function catalogue (separate issue).

Acceptance.

  1. scripts/corpus-sweep.mjs shows zero drift on all seven configs after the change.
  2. A test over the 777-rule corpus reports how many constraint rules now parse structurally; the
    number is recorded in the README of the plan folder (target after 9e: about 705 of 726).
  3. Every new round-trip test starts from a non-canonical fixture and asserts
    serialize(parse(x)) === x.
  4. pnpm typecheck is clean with no change to any consumer of parseRelevant, which proves the
    widening is additive.

Validate.

pnpm --filter @cht-ui/shared build && pnpm --filter @cht-ui/shared test
pnpm typecheck && pnpm lint
node scripts/smoke-parser.mjs <config>/forms/app/pregnancy.xlsx   # Round-trip stable: YES
node scripts/corpus-sweep.mjs

Plan: docs/plans/9_complex_logic_calculation_relevant_constraint/README.md · Analysis: validation-rules-v2.md §"Proposed support" 1, §"Test plan". Line anchors against bff69cb: relevantParser.ts:370 is the ${}-only comparison regex.

Activity

  1. PrjShrestha commented on Oct 6, 2026

    @PrjShrestha
    CollaboratorAuthor
    t9a.mp4

    Note

    What an author can do after this ticket · PR #24 · demo client/demo/t9/t9a.mp4 (attached below)

    Rules written by hand about the answer itself (.) or with ../field now open in the builder as structure instead of a "could not be parsed" text box.

    You want Do this in the UI Sheet gets / keeps
    See a hand-written . >= 0 and . <= 20 as a rule show advanced → Validation reads Between 0 and 20; cell unchanged
    See a tight .<=100 as a rule show advanced → Validation reads at most 100; cell stays .<=100
    See a ../lmp_date != '' relevant as a sentence show advanced → Logic reads Last menstrual period has an answer; cell stays ../lmp_date != ''
    Re-apply or save without edits Apply / Save every cell byte-identical, including spacing and ../ spelling

    Unchanged on disk: everything. This ticket only reads; 682 of 777 real constraints now open structurally (was 3).
    Still code: rules mixing and/or without parentheses, decimal-date-time arithmetic, curly quotes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions