Skip to content

Commit ad381fd

Browse files
fix(spec): the submitBehavior property help carries no ruling date (#22322)
Fixes #22093 Clause-②: no The card's last remainder, (a): the form view `submitBehavior` describe in `packages/spec/src/ui/view.zod.ts` no longer ends in a ruling date. Help text only: no key, type, accept/reject or export change, with a `patch` changeset for `@objectstack/spec`. The diff reaches `skills/objectstack-ui/references/react-blocks.md`, a Tier H governed surface, through the generator alone, so this PR stays a draft and lands on the maintainer's approval (route A of the seat's comment `6047193742`). ## What changed | Where | Before | After | | :--- | :--- | :--- | | `FormViewSchema.submitBehavior` describe (`view.zod.ts`), the property help that Studio, the reference docs and the published skill show for this key | "Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11)." | "Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped." | Every fact the sentence stated stays. The ruling's date and its tracker reference (#7496) now live in the code comment directly above the describe, in the form PR #22125 used for the other 22 rows, and that comment's paragraph that deferred this edit to this card is gone. ## Regenerated projections (generator output, not hand-edited) `pnpm --filter @objectstack/spec check:generated` after the edit named exactly two stale artifacts, `check:react-blocks` and `check:docs`. `check:generated --fix` regenerated those two and nothing else, and a re-run reads all 15 artifacts up to date. | File | Rows changed | Reading | | :--- | :--- | :--- | | `skills/objectstack-ui/references/react-blocks.md` | 1 (the `submitBehavior` row) | The row used to be clipped at the generator's 160-char limit ("… URL-escaped (ruled…"). The new sentence is 153 chars, so the row now reads whole: "… URL-escaped." | | `content/docs/references/ui/view.mdx` | 5 `submitBehavior` rows | "(ruled 2026-08-11)" dropped; nothing else moves | | `content/docs/references/api/protocol.mdx` | 2 `submitBehavior` rows | same | Published-skill readings (`skills/**` is token-ratcheted by `check-skills-token-ratchet`, counting `ceil(utf8 bytes / 4)`), at `4e4111ca0` → this head: - `react-blocks.md`: 115 → 115 lines; 13,633 → 13,624 bytes; 3,409 → 3,406 tokens. - the whole `skills/` package (65 files): 13,343 → 13,343 lines; 627,340 → 627,331 bytes; 156,835 → 156,833 tokens. Net −9 bytes, no new content, no re-wrap. ## Pin `packages/spec/src/ui/view-submit-redirect-url.test.ts` gains section 6. It reads the `submitBehavior` help from the published JSON Schema projection (`projectPublishedJsonSchema`, the projection `gen:react-blocks` and `gen:docs` render from) and asserts: the positive control (the sentence still names `Post-submit behavior`, the `redirect` arm, `relative-only`, `{{record.field_name}}`, and ends on "URL-escaped."), that it matches neither the file's `RULING_DATE` pattern nor a tracker id, and that the source node's `.description` equals the projection. Reverse verification (the fix committed first; one-off, no file kept): `view.zod.ts` restored to its `4e4111ca0` bytes with `git restore --source` (tree only; blob `e60abeaa9` == the base blob; old-marker count 1, new-marker count 0), the file ran **2 failed / 52 passed**, the two new cases by name. Restored with `git checkout HEAD --` (blob `48b21f8d9` == HEAD; `git diff HEAD` empty; porcelain clean), **54 passed**. The pin imports the schema from `src` by relative path, so no dist leg applies. ## Verification (at `acba7086d`, under `os-verify-lock` on a shared box) - `pnpm --filter @objectstack/spec build`: exit 0 (held 128s). - The eight spec test files that pin `submitBehavior` (incl. `scripts/nested-shape.test.ts`): 8 files, 700 tests passed. - `pnpm --filter @objectstack/spec typecheck` (tsc, `check:scripts-typecheck`, `check:test-typecheck`, which compiles the test layer): exit 0. - `check:generated`: 15 of 15 artifacts up to date. - `eslint --no-inline-config --format json` on the two changed TS files: 2 files, 0 errors, 0 warnings. Population: `eslint.config.mjs` `files: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`; no `parserOptions.project` (no type-aware linting), so this diff cannot move the verdict on any untouched file. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `acba7086d` derived 114 commands (6 paths, +67/−16 vs merge base `4e4111ca0`). Each ran with its exit code captured before any pipe: 110 exit 0 (three docs gates, `check:doc-formula-expressions`, `check:doc-security-posture` and `check:docs-transcript-drift`, first refused on an unbuilt `@objectstack/lint`; after `turbo run build --filter='@objectstack/lint...'` under the lock they ran green); 3 exit 3 PREREQUISITE NOT MET, read from CI (`check:skill-examples` needs the client-react dist, `check:dual-build-cjs-loads` and `check:lean-entry-closure` need every package's dist); `check:type-check-debt` claimed NOT-MEASURED (its re-measure is a whole-workspace build; CI). `--ran` reconciliation: 114 derived, 110 run, 4 NOT-MEASURED, 0 UNRUN. The 49-row artifact-roster block also ran: 45 exit 0; 3 exit 2 NOT WIRED without PR context (`check-closing-target-claim`, `check-partof-closing-keyword`, `check-single-claim-paths`; the Part-of/closing-keyword gate re-run with `PR_BODY` set to this body: exit 0); `check:published-readme-exports` exit 3 (every package's dist; CI). ## 维护者速读(草稿) **改了什么:** 表单视图 `submitBehavior` 属性的帮助文案(Studio 属性面板、参考文档、发布的 `objectstack-ui` 技能里显示的是同一句)不再以「(ruled 2026-08-11)」结尾;规则本身一字未改。裁决日期与 #7496 进了代码注释。`react-blocks.md`、`view.mdx`、`protocol.mdx` 的对应行由生成器重生成,`react-blocks.md` 那一行因此不再被截断。 **为什么改:** 这是 #22093 的最后一项:作者看到的帮助文案里不该出现内部裁决日期。其余 22 行已在 PR #22125 落地;这一行投影到受管的发布技能目录 `skills/**`,所以单独成 PR、等维护者批准。 **风险与代价(含回滚):** 纯文案,不改接受/拒绝行为、键、类型或导出;`@objectstack/spec` patch 变更集。`react-blocks.md` 行数不变、净减 9 字节。回滚 = revert 本 PR。 **席位意见:** (席位填写) **你要做的:** 批准(approve)本 PR 一次;之后由席位落地。 ## Acceptance notes - `packages/spec/scripts/lib/generated-output.ts` records that the governed-merge register carves generator-owned files inside `skills/**` out of the human-merge fork when the queue leg reproduces them byte-exact. Whether that applies to this PR is the seat's read; this PR follows the dispatch route (draft, Tier H). Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 16096e8 commit ad381fd

6 files changed

Lines changed: 67 additions & 16 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The form view `submitBehavior` property help no longer ends in a ruling date (#22093)
6+
7+
Clause-②: no
8+
9+
Studio, the reference docs and the published `objectstack-ui` skill show this describe as the property help. It still says that on the `redirect` arm `url` is relative-only, interpolates only declared record fields as `{{record.field_name}}` and is URL-escaped; the "(ruled 2026-08-11)" parenthetical is gone, and the ruling's date and tracker reference live in the code comment above the describe. Wording only: no key, type, accept/reject or export change. The `react-blocks.md` row and the `view` / `protocol` reference pages are regenerated from it.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
17461746
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
17471747
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
17481748
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
1749-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
1749+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
17501750
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
17511751
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
17521752
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -1831,7 +1831,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
18311831
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
18321832
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
18331833
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
1834-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
1834+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
18351835
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
18361836
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
18371837
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |

‎content/docs/references/ui/view.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ Form-view select option — the object-field option shape minus the per-option `
400400
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
401401
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
402402
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
403-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
403+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
404404
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
405405
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
406406
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -1817,7 +1817,7 @@ Tab configuration for multi-tab view interface
18171817
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
18181818
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
18191819
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
1820-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
1820+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
18211821
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
18221822
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
18231823
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -1902,7 +1902,7 @@ Tab configuration for multi-tab view interface
19021902
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
19031903
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
19041904
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
1905-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
1905+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
19061906
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
19071907
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
19081908
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -2177,7 +2177,7 @@ This schema accepts one of the following structures:
21772177
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
21782178
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
21792179
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
2180-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
2180+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
21812181
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
21822182
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
21832183
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -2367,7 +2367,7 @@ This schema accepts one of the following structures:
23672367
| **subforms** | `{ childObject: string; relationshipField?: string; columns?: object[]; amountField?: string; … }[]` | optional | Inline master-detail child collections |
23682368
| **defaultSort** | `never` | optional | [REMOVED] `form.defaultSort` was removed in @objectstack/spec 17.0.0 (audit close-out) — nothing read it: a related list inside a form sorts by its own list view's `sort`. Delete the key and set the sort on the related list view instead. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
23692369
| **sharing** | `{ enabled?: boolean; publicLink?: string; password?: string; allowedDomains?: string[]; … }` | optional | Public sharing configuration for this form |
2370-
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped (ruled 2026-08-11). |
2370+
| **submitBehavior** | `{ kind: 'thank-you'; title?: string; message?: string } \| { kind: 'redirect'; url: string; delayMs?: integer } \| { kind: 'continue' } \| { kind: 'next-record' }` | optional | Post-submit behavior. On the `redirect` arm, `url` is relative-only and interpolates only declared record fields as `{{record.field_name}}`, URL-escaped. |
23712371
| **buttons** | `{ submit?: object; cancel?: object; reset?: object }` | optional | Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm. |
23722372
| **defaults** | `Record<string, any>` | optional | Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values;). |
23732373
| **aria** | `never` | optional | [REMOVED] `form.aria` was removed in @objectstack/spec 17.0.0 (audit close-out) — no form renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. The form renderer emits its own semantic markup; report gaps as renderer issues rather than per-view attribute overrides. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |

0 commit comments

Comments
 (0)