Repository navigation
fix(sdui-parser): an html page literal of the wrong type is a compile error, not a warning - #21678
Conversation
…h error Every value reaching checkType is a literal (a braced value the parser cannot materialize is diverted to inert-expression first), so its coarse type is final at compile time, as certain as an enum's closed list. aggregate="count" on object-metric now fails the compile instead of passing os build with a warning. checkMemberTypes follows the same rule: members of a materialized literal are literals too. Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 48a02bf296fdd99115fce63e7d13d274770d92d1 && git checkout 48a02bf296fdd99115fce63e7d13d274770d92d1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin eed2dee481126b6655cfb4a809099aa885c8e52b 79df1db84fb82c96586f224a63c40d7c065c71eb && git checkout -B drift-repro eed2dee481126b6655cfb4a809099aa885c8e52b && git merge --no-ff 79df1db84fb82c96586f224a63c40d7c065c71eb
node scripts/docs-audit/affected-docs.mjs --json eed2dee481126b6655cfb4a809099aa885c8e52b |
Contract reviewServed-tier: ① Changeset
|
…ui#11466, objectstack-ai#11574, objectstack-ai#11578, objectstack-ai#11581 and objectstack-ai#11583) (objectstack-ai#21710) Fixes objectstack-ai#21696 Clause-②: no This moves the bundled Console's objectui pin from `ab1879721595` (objectstack-ai#21625) to objectui `main` as of the write time, `2e818d0b51ecdf8fdd9fcbf4b916bcd7fe9a9cf6`, which is past `973fc20f3`. The Console now carries objectui#11574 (the phantom "(empty)" groups fix), objectui#11578, objectui#11581, objectui#11576, objectui#11583 and objectui#11466 (V1 declared node slot). It also carries objectui's own 17.7.0 package release. ⛔ This is not a release act. Version PR objectstack-ai#21352 is untouched, and no tag, publish or Release was made. Whether 17.7.0 ships with this pin is the maintainer's call. ## Range `ab1879721595..2e818d0b51ec` has 22 non-merge commits. `git merge-base --is-ancestor` exits 0 against objectui `origin/main` for `973fc20f3`, and `2e818d0b51ec` was objectui `main` when it was read (`git ls-remote` just before this PR). From the changesets objectui declared over the range: - 17 releasing changesets, 1 release-nothing, and 6 commits that carry no changeset. - 12 of the 18 changesets were already consumed by objectui's release `b493919c7`. Each was read from the commit that added it. - 2 breaking entries, both annotated by the author and both from one commit. The range was listed by measurement, not copied from the card. Exactly one commit subject in the range carries `!`: `git log --format='%h %s' ab187972..2e818d0b5 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'` gives `83e3f8377 feat(types,react,core,plugin-dashboard)!: … (objectui#11466)`. | objectui landing | commit | note | |---|---|---| | objectui#11466: node slot and `SchemaRenderer`'s `schema` take `DeclaredNode`; the dashboard producers name declared nodes; an envelope's object-metric retires | `83e3f8377` | **declared breaking**, both breaking entries | | objectui#11574: a grouping-field switch paints only the server's groups | `2f54dca53` | smoke below | | objectui#11581 / objectstack-ai#11578 / objectstack-ai#11576: Create View doors share one builder; a refused save is said; chart views bind an ADR-0021 dataset | `b65aa5e65`, `f1c937966`, `d0097af2e` | | | objectui#11583: a refused metadata write on the view, report and draft surfaces is said | `e8c0b9614` | past the card's `973fc20f3` | | objectui#11598: dashboard slot-entry and chart-producer reads compile without `BaseSchema`'s index signature | `2e818d0b5` (the pin itself) | | | objectui#11572 / objectstack-ai#10380: interface and object page relay stored view config | `09036173c`, `068564691` | | | objectui#11588: grid summary footer reads currency/precision from the object field only | `d768c3178` | | | objectui#11216: `object-kanban` declares `grouping` as the spec's `GroupingConfig` | `a7557a7d4` | | | objectui#11573, objectstack-ai#11564: `AnyComponentSchema` / flex child typing | `bdc9049ed`, `b10c68e58` | | | objectui release 17.7.0 | `b493919c7` | removes 2726 consumed `.changeset/*.md` files | ## ADR-0087 disposition of the declared-breaking entries Both entries are `not-required (no-migration-prescription)`. The disposition is in `.changeset/console-2e818d0b51ec.md`, which replaces the script's `adr-0087: TODO` placeholder. | objectui PR | commit | entry | disposition | |---|---|---|---| | objectui#11466 (PR objectui#11512) | `83e3f8377` | A node slot and `SchemaRenderer`'s `schema` prop take `DeclaredNode` | not-required (no-migration-prescription). This is objectui's own TypeScript face (`@object-ui/types` / `@object-ui/react`). No ObjectStack schema declares or references it. | | objectui#11466 (PR objectui#11512) | `83e3f8377` | An `object-metric` node in a dashboard widget's legacy `component` envelope draws the retired-format prompt | not-required (no-migration-prescription). An ObjectStack author cannot write that envelope: `ui/dashboard.zod.ts` refuses a widget's `component` key by name as objectui-internal, and no example authors one. The showcase's `object-metric` page tiles still draw their numbers (smoke below). | `check-adr-0087-registration --base origin/main` gives "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition". `check-changeset-no-major --base origin/main` gives "This diff introduces no `major` bump". ## What changed here - **`.objectui-sha` and `.changeset/console-2e818d0b51ec.md`** were written by `scripts/bump-objectui.sh 2e818d0b51ecdf8fdd9fcbf4b916bcd7fe9a9cf6 --no-commit` with `OBJECTUI_ROOT` set to a read-only clone of objectui. The level was auto-set to `minor`. No `../objectui` sibling exists in this container. - **`scripts/sdui-manifest.record.json`** was re-recorded by `node scripts/gen-sdui-manifest-node.mjs` over the tree that `pnpm objectui:build` built at the pin. **`sdui.manifest.json` is byte-identical**: 107 components, sha256 `0ead67c1111d…` at both pins, so no component input moved. The record moves its pin, its `modulesRoot` and objectui's workspace version (17.6.0 to 17.7.0). - **`packages/sdui-parser/objectui-lockstep.json`** was re-recorded with `pnpm gen:sdui-lockstep` against `OBJECTUI_ROOT=.cache/objectui-2e818d0b51ec`. It records 214 grammar lines (blob `0131f27cf86d`), 25 codes and containment predicate `76c18fb95d1f`. All are unchanged, so no port is owed. objectui's `packages/sdui-parser/src` is byte-identical across the range; only its CHANGELOG and package.json moved. - **The 54 asserting pin citations in `packages/spec/src`** were re-measured at the new pin, not restamped: - Every anchor in each asserting record was resolved against objectui's tree at the old pin and mapped through `git diff -U0 ab187972 2e818d0b5`. - Four cited files changed: - `plugin-dashboard/src/index.tsx` (+13/-1, objectui#11466). Lines were added above the `object-metric` registration, so its start moves `244` to `256` and the icon input moves `269` to `281`. Both lines are byte-identical, still `{ name: 'icon', type: 'string' }`. - `packages/types/src/objectql.ts`. objectui#11216's `grouping` member landed below the cited `limit` member, so `:4720` did not move. - `content/docs/plugins/plugin-kanban.mdx`. A `grouping` row landed below the `limit` row, and the page still teaches `limit: 250`. - `packages/react/src/SchemaRenderer.tsx` (+21/-20, objectui#11466). Only the type import, the `SchemaRendererProps` docblock and the `schema` type changed. The `properties.*` hoist, its strip list and the `createElement` spread did not, so `object-timeline`'s prop channel still carries `data`. - Every other cited file is byte-identical across the hop. - Each record gains a dated 2026-10-04 hop sentence and keeps its `ab1879721` history. - **The six migration entries' corpus counts** were re-taken with `git grep -o -F`. That method first reproduced every `ab1879721` number: 10267 files, `objectstack` 16377, `@objectstack/spec` 6665, `timeout` 1348, `useState` 2476, `TTL` 180, `tenant` 1238, `Span` 491. - The new numbers are 7579 files, 17227, 7134, 1351, 2477, 182, 1317 and 505. The file count drops because objectui's 17.7.0 release removed 2726 consumed changesets. - Every zero is still zero: 98 tokens were checked, the export lists of the three cited spec files plus every named key. The only non-zero tokens are the expected `Span` / `SpanSchema`. The 14 new `Span` hits are `Spanish` prose. - `packages/spec/src/migrations/registry.ts` was regenerated with `gen:migration-registry`. - **`.changeset/objectui-pin-citations-2e818d0b51ec.md`** is a `@objectstack/spec` patch, because the `FormField.span` describe and six migration descriptions name the pin. `content/docs/references/ui/view.mdx` was regenerated by `check:generated --fix`. - **`main` was merged** (`7e0066af7a`) with `scripts/pm/os-regen-merge.sh` in merge commit `a0a35cb30f`, with no conflict hunk. Afterwards: - `check:generated` reports all 15 artifacts current. - main's three new semantic entries (`ui-object-form-fields-names-typed`, `ui-object-gantt-markers-typed`, `ui-object-timeline-mapping-typed`) are present in the regenerated registry. - The pin-citation gate still counts 54 asserting citations. main added historical ones only (108 to 118). No example, test or gate needed adapting, and no code changed outside generated records, citations and changesets. ## Browser smoke: `examples/app-showcase` with the Console built at `2e818d0b51ec` The Console was built by `pnpm objectui:build`. The build log reports: "Console dist matches the objectui pin (objectui@2e818d0b51ec)", "Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5}" and "Console bundle carries THIS tree's @objectstack/spec, and only it". The server ran `pnpm dev -- --fresh --ui --no-watch -p 41853` (with `OS_PORT=41853`) on its own ephemeral DB with the seeded admin. Headless Chromium (`/opt/pw-browsers/chromium`) drove it, with console messages, page errors and every non-2xx response captured. Only the PIDs this run started were stopped. **Dashboards.** objectui#11466 touches the dashboard producers. There are no page errors and no "retired" prompt on any of the four dashboards. | dashboard | metric tiles (value read in the DOM) | charts drawn | console errors | |---|---|---|---| | Delivery Operations `showcase_ops_dashboard` | Active Projects 2, At-Risk (Red) 1, Awaiting Review 2, Total Budget 1,090,000 | 4 | 404 `/api/v1/usage/storage`, 404 `/favicon.ico` | | Revenue Pulse `showcase_revenue_pulse` | Invoices 7, Invoiced Subtotal 1,920, Accounts Signed 3, Accounts (all time) 14, Paid Rate 42.9% | 4 | 404 `/api/v1/usage/storage` | | Chart Gallery `showcase_chart_gallery` | Total Tasks 10, Avg Progress 47.0, Total Spent 616,000 | 13 | 404 `/api/v1/usage/storage` | | System Overview `system_overview` (setup) | Total Users 3, Active Sessions 3, Login Events 3, Config Changes 0 | 2 | 404 `/api/v1/usage/storage` | The Delivery Operations tiles match the REST data on the same server: - `showcase_project?status=active` returns 2 rows, `?health=red` returns 1, and the budget sum is 1,090,000. - `showcase_task?status=in_review` returns 2 rows. - `showcase_task` has 10 rows, matching the Chart Gallery's Total Tasks. - `showcase_account` has 14 rows, matching Revenue Pulse's all-time count. The `object-metric` page tiles that this repo authors also draw: - My Work shows Open Tasks 8, In Review 2 and At-Risk Projects 1. - Command Center (大屏) shows 2 / 8 / 2 / 1 / 14 / 1.1M. Neither page has a page error. **Grouping-field switch (objectui#11574).** The drive was Tasks list, then Group, then Add group field (it selects Title). The select then switched to Priority, Status and Priority. The painted groups are counted from the DOM, and the server answers are each `POST /api/v1/data/showcase_task/query` with `groupBy`, all 200: | step | painted groups | server rows | "(empty)" groups | stuck "Loading grid…" | |---|---|---|---|---| | Title | 10 (one per task) | 10 | 0 | 0 | | Priority | 4: High 3, Low 1, Medium 4, Urgent 2 | 4 | 0 | 0 | | Status | 5: Backlog 2, Done 2, In Progress 2, In Review 2, To Do 2 | 5 | 0 | 0 | | Priority | 4: High 3, Low 1, Medium 4, Urgent 2 | 4 | 0 | 0 | The same sequence painted 13, 8 and 8 groups (9, 3 and 4 phantom) at `ab1879721595`, as objectstack-ai#21625's acceptance notes recorded. **No phantom "(empty)" groups appear at this pin.** **Console errors across the run.** The only ones are the browser's "Failed to load resource: 404" lines for `/api/v1/usage/storage` and `/favicon.ico`, and the run has 0 page errors. The `usage/storage` 404 was already recorded in objectstack-ai#21625's smoke. ## objectstack-ai#21671 lockstep reading - objectui's `packages/sdui-parser/src/validate.ts` at `2e818d0b5` is byte-identical to `ab1879721595`. - `checkType` (`:450-466`) still grades `type-mismatch` with `severity: arms.includes('enum') ? 'error' : 'warning'`. `checkMemberTypes` (`:407-419`) grades `member-type-mismatch` the same way. - This repo grades both `error` since PR objectstack-ai#21678 (`packages/sdui-parser/src/validate.ts:446`, `:506`). **The gap still stands at this pin.** - This repo's "Known lead" note (`packages/sdui-parser/src/validate.ts:336-340`) names objectui's copy "at the `.objectui-sha` pin" without a sha, so it is still true and needs no update. The bump route regenerates nothing there, and `validate.ts` is not edited. - `check:sdui-lockstep` does not compare severity, so this is a reading, not a gate result. ## Gates and tests (head `a0a35cb30f`) - `node scripts/pm/dispatch-gates.mjs --commands` derived 127 commands from the 21-path diff. That is the same set before and after the merge. - At `378d424915` (pre-merge) and again at `a0a35cb30f`, all 127 exited 0. `--ran` reports "127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded. - Also exit 0: - `check:objectui-pin-citations --verify-anchors` with objectui at the pin: 54 asserting citations match `2e818d0b5`, and 7 content assertions are verified. - `check:objectui-bump` (20 assertions across 5 cases), `check:sdui-lockstep`, `check-sdui-manifest`, `check:console-sha`, `check:console-injection`, `check:migration-registry`. - `@objectstack/spec check:generated`: all 15 artifacts current after the merge. - `pnpm --filter @objectstack/spec test`: 612 files, 18185 passed, 1 todo. - `pnpm --filter @objectstack/spec typecheck`: exit 0. - `pnpm --filter @objectstack/sdui-parser test`: 225 passed. - `eslint --no-inline-config` on the 15 changed TS files: 0 errors and 0 warnings. The lint population is `**/*.{ts,…}` (`eslint.config.mjs:971`). The config enables no type-aware linting (`:328`), so this diff cannot move a judgment on an untouched file. Repo-wide `pnpm lint` is left to CI. ## Acceptance notes - `ui/component.zod.ts`'s `object-grid` `columns` record (the⚠️ paragraph on `plugin-grid/src/useColumnSummary.ts:558-568`) says, about `ab1879721595`, that the footer summary reads `currency` / `defaultCurrency` / `precision` / `scale` off an authored column. objectui#11588 (`d768c3178`, inside this pin) retires that read. The record is in the historical spelling and stays true of the pin it names, so it is left as is. - Two anchors in records that cite no sha are outside the pin-citation gate's population and were not re-measured: - `ObjectView.tsx:882`, in `ui/view.zod.ts`'s [objectstack-ai#5074] block and two view tests. - `plugin-dashboard/src/index.tsx:204`, in the `ObjectMetricPropsSchema icon liveness` test. - Both already read an unrelated line at `ab1879721595`, and they shift to `:936` and `:214` here. - Writes: no PR assignee or label was written. This dispatch's write budget names neither. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21671
Clause-②: no (narrowing)
What changed
The html-tier compiler (
@objectstack/sdui-parsercompile) now grades everytype-mismatchaserror, not only the ones whose input declares anenumarm.aggregate="count"on anobject-metric(repo-rootsdui.manifest.jsondeclaresaggregateastype: "object") now fails the compile, soos buildfails on it. Before, it compiledokwith one warning, the build stayed green, and the tile drew no number. The diagnostic code and message are unchanged. No new code and no new gate.The mechanism, measured
The brief assumed that literals and expressions both reach
checkType, and that a signal would have to be passed in to tell them apart. Measured on93a54b87e7, that is not how it works:validateTree(packages/sdui-parser/src/validate.ts) sends a value thatisExprmatches (the parser's deferred$exprmarker) toinert-expression, which is awarning. It callscheckTypeonly in theelsebranch. So every value that reachescheckTypeis already a literal: a quoted attribute (a string), a bare attribute (true), or a braced value thatinterpretBracematerialized in full.interpretBraceis all-or-nothing. Probed:["a", foo],{"a": foo}and{a: 1, b: x.y}each become ONE$exprmarker;[1,2]and{function: "count"}materialize in full. So a container never reaches the type check with an expression inside it.So the expression case is already separate before
checkTyperuns, and no new signal is needed. The change is the severity, plus a restated header that names the second certain fact (a literal's coarse type) next to the enum's closed list. A braced expression still gets theinert-expressionwarning, and a test pins that.checkMemberTypesfollows the same rule (the brief left this to measurement). Its members come from a container that was materialized in full, so each member is a literal too, and a member that no declared arm accepts is just as certain a mismatch. Its header already said "Severity mirrorscheckType's rule", and that stays true.member-type-mismatchis nowerror.Unchanged on purpose: the single-arm
invalid-enumdiagnostic is byte-identical, severity included (the existing pin inunion-arm-type-mismatch.test.tspasses untouched).Census (first step): every stored html page source, compiled against the committed
sdui.manifest.jsonIt was run with
compilefrom@objectstack/sdui-parser(source), not with grep. The page modules were imported and eachkind: 'html'export'ssourcewas compiled (capability-map.page.tsinterpolates, so a regex would have read a different string). Every fenced block inskills/**/*.mdandcontent/docs/ui/*.mdxthat has a lowercase tag was compiled too. A positive control (aggregate="count") was compiled in the same run.examples/app-showcase/src/ui/pages/command-center-jsx.page.ts(CommandCenterJsxPage)examples/app-showcase/src/ui/pages/capability-map.page.ts(CapabilityMapPage)examples/app-showcase/src/ui/pages/start-here.page.ts(StartHerePage)skills/objectstack-ui/rules/pages.md:154block (its line 160 is theobject-metricexample)aggregate={{"function":"count"}}, so PR #21667's fix is confirmed onorigin/mainkind: 'html'page in this repo (bothpackages/metadata/src/__fixtures__/hotcrm-*.artifact.jsonhave 0)skills/**andcontent/docs/ui/**no-rootorforbidden-tag, which shows they are not html-tier sources)aggregate="count"ok=true,[warning] type-mismatch; after:ok=false,[error] type-mismatchThere are zero writers to fix, so no example or skill file changes in this PR.
Pins (
packages/sdui-parser/src/__tests__/literal-type-mismatch-error.test.ts)The inputs are copied verbatim from the tracked
sdui.manifest.jsonand written inline, so the test reads nothing outside its package.aggregate="count": exactly one{ severity: 'error', code: 'type-mismatch', message: 'object-metric prop "aggregate" expected an object' }(the real message has the tag in angle brackets), andok === false.aggregate={{"function":"count"}}: zero diagnostics,ok === true.number(object-kanbanlimit), aboolean(invert) and anarray(filter) input: each gives oneerrortype-mismatch, andok === false.aggregate={count}), and a container that holds an expression: each gives exactly onewarninginert-expression, notype-mismatch, andok === true.Three existing pins described the old rule, and they were updated: the non-enum union case and the single string-arm case in
union-arm-type-mismatch.test.ts, and the member severity inmember-type-mismatch.test.ts. Their codes and messages are unchanged. Only severity,ok, and the wording that called these "byte-identical" were edited.os buildprobe (the html-tier path throughpackages/cli/src/utils/sdui-manifest.ts)A scratch project with one
kind: 'html'page, the committedsdui.manifest.jsoncopied beside its config, and the CLI run from source (bin/run-dev.js build):aggregate="count"aggregateprop expected an object,Build completeAuthor-time rules failed (1 issue), a failure that theaggregateprop expected an objectaggregate={{"function":"count"}}Build completeThe before leg is a one-off ablation run from the committed fix. It used
scripts/ablation-replace.mjs(anchor hit, 1 marker on disk), thenpnpm --filter @objectstack/sdui-parser build, thenablation-dist-preflight.mjs(marker present indist/, exit 0). After the probe it was restored withgit checkout HEAD --:git diff HEADwas empty and the blob matched HEAD (86cc5784). The package was rebuilt, the--absentpreflight passed for both readings, and the probe was re-run with exit 1. No permanent test file was left behind.Tests run (at
79df1db84f)pnpm --filter @objectstack/sdui-parser test: 14 files, 225 tests passed.typecheck: exit 0.sdui-parser(distchecked: 0 copies of the old ternary left).pnpm --filter @objectstack/lint exec vitest run: 119 files, 5627 tests passed.pnpm --filter @objectstack/metadata-protocol exec vitest run: 209 files passed and 3 skipped, 3463 tests passed. CLI unit tier, limited to the 4 files that compile html pages (src/utils/sdui-manifest.test.ts,test/validate-build-gate-parity.test.ts,test/platform-page-i18n-parity.test.ts,test/i18n-section-coverage.test.ts): 129 tests passed. The rest of the CLI unit tier and its integration tier are left to CI.node scripts/pm/dispatch-gates.mjs --commandsgave 63 derived commands. 61 exited 0, includingcheck:sdui-lockstep,check:nul-bytes,check:cross-package-test-inputs,check-adr-0087-registrationandcheck-changeset-no-major. NOT MEASURED:check:dual-build-cjs-loads:PREREQUISITE NOT MET, becauseembedder-openaiandservice-cluster-redishave nodist/(packages this diff does not touch). NOT MEASURED:check:type-check-debt: it is a whole-tree tsc ratchet and hit the 240s local timeout. CI runs both..tsfiles, with--no-inline-config --format json: 4 files, 0 errors, 0 warnings. The repo config has noparserOptions.project(type-aware linting is off), so this diff cannot change the lint result of any file it does not touch. CI runs the fullpnpm lint.Changeset
.changeset/21671-html-literal-type-mismatch-error.md:@objectstack/sdui-parserminor. Clause-② conflict for the seat to resolve: the claim statesClause-②: no, and this body carries that line verbatim. The changeset declaresClause-②: no (narrowing), because a page that used to compile (and save, where the host has a component manifest) is now refused. Earlier PRs treated a new refusal at an authoring door as an accept-set narrowing (21459,20827). The changeset therefore carries the**BREAKING**header, aminorbump under the launch-window convention, and an ADR-0087not-required (no-migration-prescription)disposition: no key, declaration or stored shape moves.check-adr-0087-registrationandcheck-changeset-no-majorboth pass, whether the body line has the arm or not (both were simulated locally).objectui lockstep (declared, not acted on)
objectui has its own copy of this validator,
packages/sdui-parser/src/validate.ts, at the.objectui-shapinab187972. It still has the old ternary in bothcheckMemberTypes(:418) andcheckType(:465). After this PR the two copies agree on codes, messages and the accepted grammar, and differ only in severity.check:sdui-lockstepcompares grammar, codes and the containment predicate, not severity, so it passes. This repo's copy is the stricter one (save gate andos build). The dangerous direction, a page that saves clean and then renders inert, cannot come from this difference. The lockstep header invalidate.tsnow records this lead. The port belongs to objectui's lane, and this PR does not write to objectui.Acceptance notes
label={42}). That was the narrower reading in the triage title ("a string literal against a declared non-string input"). The claim and the brief specify the general rule ("a literal whose coarse type no declared arm accepts"), and the measurement shows that every value at this point is a literal, so the general rule is the one implemented. The census found no writers of either form in the repo.Generated by Claude Code