Skip to content

Commit 1e18a07

Browse files
test(spec): the first ui/ file group's test titles state each cited decision in words instead of a tracker number (stage 20) (#21895)
Part of #20749 Clause-②: no Stage 20 of this card: the next area of class (e), the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on #20513. This stage takes the first name-ordered `ui/` group: the 32 test files directly under `packages/spec/src/ui/` from `action-confirm-params-guard.test.ts` to `component-record-block-field-security.test.ts`. Those files carried 97 messages and 102 tracker ids, citing 65 records. 101 of those ids now either state what their record decided, in words (form D), or are dropped where the title already says it. One id stays, because an assertion in the same file reads it (below). Text only: no assertion, identifier, test count or code comment changes, and no file is renamed. ## Census at the base (`e085a8c3be`) Instruments: `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`), `census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), `census-wide.cjs` (md5 `c98410a19529c439adb0afbfb00026a2`) and `dirtable.cjs` (md5 `dda605c54745b4a60cc14c9a686e4eff`), byte-identical to the copies stages 10 to 19 used. A literal counts as a test title when its folded message is argument 0 of a `describe` / `it` / `test` call, `.each` / `.skip` / `.only` chains included. Everything else is an "other" string. The base is `e085a8c3be`, stage 19's landing and the claim's base. Both instruments read **860 messages / 908 ids in 190 files**, the seat's reading and stage 19's head reading. | directory | files | messages / ids | titles | other | |:--|--:|--:|--:|--:| | `ui/` (this PR: 32 of the 84 files) | 84 | 396 / 419 | 378 / 401 | 18 / 18 | | `api/` | 40 | 189 / 201 | 181 / 193 | 8 / 8 | | `system/` | 34 | 154 / 165 | 128 / 138 | 26 / 27 | | (files directly in `src/`) | 30 | 118 / 120 | 117 / 119 | 1 / 1 | | `ai/` | 1 | 2 / 2 | 0 | 2 / 2 | | `contracts/` | 1 | 1 / 1 | 0 | 1 / 1 | | **total** | **190** | **860 / 908** | **804 / 851** | **56 / 57** | The group reads **97 messages / 102 ids in 32 files**, the seat's figures file for file: | file (under `ui/`) | messages / ids | titles | other | |:--|--:|--:|--:| | `action-confirm-params-guard.test.ts` | 4 / 4 | 4 / 4 | 0 | | `action-description.test.ts` | 3 / 3 | 3 / 3 | 0 | | `action-dispatch-contract.test.ts` | 4 / 4 | 4 / 4 | 0 | | `action-doubled-redirect.test.ts` | 1 / 1 | 1 / 1 | 0 | | `action-newtaburl-pair.test.ts` | 1 / 1 | 1 / 1 | 0 | | `action-on-success.test.ts` | 3 / 4 | 3 / 4 | 0 | | `action-param-carryover.test.ts` | 1 / 2 | 1 / 2 | 0 | | `action-param-default-value.test.ts` | 3 / 3 | 3 / 3 | 0 | | `action-params.test.ts` | 9 / 9 | 9 / 9 | 0 | | `action-requires-confirmation-docblock.pin.test.ts` | 5 / 6 | 3 / 4 | 2 / 2 | | `action-row-update.test.ts` | 5 / 5 | 5 / 5 | 0 | | `action.test.ts` | 12 / 12 | 12 / 12 | 0 | | `app-nav-expanded-alias.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app-nav-target-exclusivity-export.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app-strictness-batch19.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app.test.ts` | 9 / 9 | 9 / 9 | 0 | | `aria-carrier-tombstones.test.ts` | 1 / 1 | 1 / 1 | 0 | | `assembled-view-artifact-type.test.ts` | 1 / 1 | 1 / 1 | 0 | | `assembled-views.test.ts` | 1 / 1 | 1 / 1 | 0 | | `bulk-action.test.ts` | 4 / 4 | 4 / 4 | 0 | | `calendar-config-allday-prescription-17054.test.ts` | 2 / 2 | 2 / 2 | 0 | | `chart-aggregate.test.ts` | 1 / 1 | 1 / 1 | 0 | | `chart.test.ts` | 7 / 9 | 7 / 9 | 0 | | `component-action-element-rows-20371.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-action-row-endpoint-21005.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-element-navigation-17987.test.ts` | 4 / 4 | 4 / 4 | 0 | | `component-form-custom-fields-sections-typed.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-default-filters.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-export-options-members.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-pagination-accept-set.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-props-unknown-members.pin.test.ts` | 2 / 2 | 1 / 1 | 1 / 1 | | `component-record-block-field-security.test.ts` | 5 / 5 | 5 / 5 | 0 | | **32 files** | **97 / 102** | **94 / 99** | **3 / 3** | The three "other" strings are `action-requires-confirmation-docblock.pin.test.ts:168` and `:175` (two `expect` messages) and `component-props-unknown-members.pin.test.ts:322`. The claim calls the third one an `expect` message too; it is the `ruling` value of a ledger entry. 12 more test files in the same name range carry no id and are not touched. - **Controls.** Lit: `ui/component.test.ts`, outside the group, reads 70 ids at the base and at the head. Dark: `action-requires-confirmation-docblock.pin.test.ts` reads 0 at the head while 4 of its comment lines still carry a number. Planted in scratch copies of head files: an id put into an `app-nav-expanded-alias.test.ts` title reads 1 / 1, and an id put into a `bulk-action.test.ts` comment reads 0. - **A wider pattern** (any `#` plus digits) reads the same as the gate pattern in 29 of the 32 files at the base. The other three differ only by 16 hex colour literals (`'#0070F3'` in `app.test.ts`, `'#8B5CF6'` in `bulk-action.test.ts`, the `colors` arrays in `chart.test.ts`), which this PR does not touch. - **At the head:** 764 messages / 807 ids in 159 files. The 32 files read 1 / 1 (the kept `:322`), `ui/` reads 300 / 318, and no other file moved. ## How the area was chosen `ui/` has no subdirectory, so it is taken like `data/`, in name-ordered file groups near the ~100-id bound. Stage 19's re-cut named this group at 102 ids, and this census reads 102, so no re-cut was needed. **Named for the next stages** (cut from the head census, 764 / 807): - `ui/` 318 ids in 53 files, about three stages. The next group nearest 100 runs from `component-record-blocks.test.ts` to `dashboard.test.ts`: 7 files, 103 messages / 109 ids (98 titles / 103 ids, 5 other). `component.test.ts` alone carries 70. Cutting one file earlier gives 88. That group holds five "other" strings: `dashboard-chart-structure-refusal.test.ts:94` (two ids) and `dashboard.test.ts:124` (two ids), which read like placeholders (`#111`, `#222`), and `dashboard.test.ts:205` (#5022). - `api/` 201, two stages. `system/` 165, two. The files directly in `src/`, 120, one. - The three docblock needles (`ai/build-progress.test.ts` ×2, `contracts/approval-service.test.ts` ×1), one stage, with an at-tier review. The id this PR keeps (`component-props-unknown-members.pin.test.ts:322`) fits there too. ## The three "other" strings: two rewritten, one kept - **`action-requires-confirmation-docblock.pin.test.ts:168` and `:175` are not needles.** Each one is the failure message (the second argument of `expect(value, message)`) of an assertion whose expected value carries no id: `.toEqual([])` over the docblock's positive `confirmText` claims, and `.not.toMatch(/confirmText/)` over the classifier's body. Nothing compares the message text. Both are rewritten and declared to the text-only tool. - **`component-props-unknown-members.pin.test.ts:322` is kept.** Its `ruling: 'decision card #21704, fork 4, letter B (record 5979239990)'` is the expected value of the assertion at `:417` in the same file, `expect(reason.kind === 'opaque' && reason.ruling, key).toMatch(/#21704/)`, and the `ruling` slot is typed as naming the record that holds the member (`:188`). Removing the id turns `:417` red, and moving that regex would change assertion text, which this stage does not do. It is not a docblock needle, since it reads a value in its own file, but it is held the same way. It is reported for the needle stage. ## What each id became - **36 literals (41 ids)** now state a decision in words. - **13 literals (13 ids)** get their subject back in words, where the number stood for a thing, such as "the #7428 pair rule". - **47 literals (47 ids)** drop a number the title already explains. Every cited record was read with its comments through REST: 62 answer 200 and 3 answer 404. Three citations are cross-repo (`objectui#3139`, `objectui#3382`, `objectstack-ai/objectui#11610`); all three were read from objectui and answer 200. Where a record answers 404, or answers 200 without the decision, the decision was read from what landed: - **#11753** (404): its ruling, recommendation A, is quoted in its spec-half card #11992. - **#17987** (404): the landing commit `e233db9dbb` ("declare element-level `navigation` on object-kanban / object-calendar and give object-timeline its ComponentPropsMap row"), executing objectui#8652's ruling B. - **#18177** (404): the landing commit `adabccf5fb` ("BulkActionParamSchema is strict and declares dependsOn"), decision batch #146 item 4, letter A. - **#3896** (200): the record is a sharing-rule REST defect, closed with no comment. The "close-out" the title names is what landed under that citation: the `action-inert-keys-removed` conversion in `conversions/registry.ts` ("capability claims nothing enforced") and the `shortcut` / `bulkEnabled` tombstones in `ui/action.zod.ts`. - **#3701** (200, closed with no comment): the convention as `ui/chart-aggregate.ts` writes it down. **Stated in words:** | record(s) | literal (under `ui/`) | now reads | the decision | |:--|:--|:--|:--| | #7278 | `action-confirm-params-guard.test.ts:148` | "… — the one-dialog shape the confirm question migrated TO" | Maintainer option 1: drop `confirmText` and carry the question as the param dialog's description, one decision in one dialog. | | #7367 | `action-description.test.ts:35` | "ActionSchema.description — the line the param dialog shows" | `description` joins the action contract, shaped like `label`, as the param dialog's description line. | | #17319 | `action-dispatch-contract.test.ts:43`, `:77` | "… (and still true: the declared dispatch contract is an authoring key)"; "an action's dispatch contract speaks `bulkActionDefs`' own vocabulary" | Ruling A: an action declares its bulk dispatch contract in the bulk def's own `execution` vocabulary, and a view that wires it the other way is refused at validate. | | #11519 | `action-doubled-redirect.test.ts:24` | "ActionSchema — doubled post-success navigation is refused, with no precedence field" | Refuse the doubled channel; no `precedence` field. | | #9566, #9474 | `action-on-success.test.ts:13` | "ActionSchema.onSuccess — one closed post-success navigation key for api and script actions" | Ruled together: one closed `onSuccess` key (`navigate` + `openIn`) for both types. | | #4352 | `action-on-success.test.ts:141`; `action.test.ts:1365` | "type scope — api and script only, refused on any other type, never silently ignored"; "… the publish gate resolves to it, so a `body` off `script` is refused at publish" | Outlet 1: the runtime follows the spec, and contradictory type-scoped metadata errors at publish instead of being ignored. | | #11992, #11753 | `action-param-carryover.test.ts:17` | "ActionParamSchema.carryOver — seeded from the row, shown read-only, submitted verbatim" | Recommendation A on #11753, executed by #11992. | | #20740 | `action-param-default-value.test.ts:250` | "… refuses a zone-suffixed `time` value — a time of day is a zone-less wall clock" | `ClockTimeValueSchema` narrowed to the zone-less wall clock. | | #5568 | `action-params.test.ts:139` | "names `_selectedIds` when the caller sent `selectedIds` — the declared channel for a selection" | Verified and closed: `params._selectedIds` is the working declared channel; the reported gap was not one. | | #5613 | `action-params.test.ts:294` | "accepts the DUAL-EMIT shape the runtime emits through the rename window — …" | Contract first, then `roles` → `positions` with a deprecation window in which the runtime emits both keys. | | #7828, #13865 | `action-requires-confirmation-docblock.pin.test.ts:128`, `:150`, `:168`, `:172`, `:175` | "… docblock names only the declared signals the classifier reads"; "would flag the retired sentence …"; "… re-seeds the retired leg — only declared semantics classify" (message); "… still does not read `confirmText` — only declared semantics classify"; "… reopen the declared-semantics ruling before the docblock …" (message) | #7828 option A: `actionLooksDestructive` classifies on declared semantics only (`mode`, `variant`), never on UI copy. #13865 retired the docblock sentence that still named `confirmText`. | | #3405 | `action.test.ts:85`, `:141` | "inline lookup reference target — `reference`, the FieldSchema spelling"; "… at `reference`, the one key an author writes" | `ActionParamSchema` gains `reference`, named as `FieldSchema.reference` so the spelling authors already write is the legal one. | | #15811 | `action.test.ts:217` | "… at the SLOT, which needs a `source` to evaluate, not at the lowering" | Ruling A: every engine-evaluated expression slot requires a non-blank `source`. | | #20323 | `action.test.ts:1116` | "Action ARIA Integration (retired — no action surface ever read it)" | Retire `action.aria`: no action surface reads it. | | #6888 | `action.test.ts:1513` | "`global_nav` is retired — it rendered nowhere" | Direction 2: retire `global_nav`, which rendered nowhere in the running app. | | #3896 | `action.test.ts:1612` | "audit close-out — retired shortcut/bulkEnabled, capability claims nothing enforced" | Read from what landed (above). | | #5016 | `action.test.ts:1650` | "action param option vocabulary — declared only where a renderer delivers it" | Ruling B, on condition that it lands with the renderer and after a per-key liveness audit; `visibleWhen` is declared, `color` / `default` / `icon` / `disabled` stay refused. | | #4001 | `app-strictness-batch19.test.ts:99` | "批 19, unknown keys refused — the `verify` check …" | Every authorable surface goes strict; spelled as stage 18 spelled the 批 20 titles. | | #5320 | `assembled-views.test.ts:51` | "AssembledViewArtifactSchema — the declared home for non-container view artifacts" | Fork ruling B: a declared, portable home for non-container view artifacts. | | #4457 | `bulk-action.test.ts:20` | "BulkActionDefSchema — the def shape is typed, not `z.any()`" | Type the def that was `z.record(z.any())`. | | #17054 | `calendar-config-allday-prescription-17054.test.ts:106` | "what declaring `allDayField` did NOT open" | Ruling A: `CalendarConfigSchema` declares `allDayField`. | | #3701 | `chart-aggregate.test.ts:45` | "result-column naming convention — rows keyed by the raw field names" | Read from what landed (above). | | #17751 | `chart.test.ts:271` | "Chart ARIA Integration — retired, no renderer ever applied it" | Retire `ChartConfigSchema.aria`. | | #4001, #5583 | `chart.test.ts:476` | "批 15 — the two chart sites left open on a measurement, since CLOSED as strict objects" | #5583: both schemas became strict objects. | | #5022 | `chart.test.ts:631` | "ChartDrillDownSchema — the honest subset, every key one ObjectChart reads" | Declare `drillDown` with only the keys `ObjectChart` reads. | | `objectui#3382`, #5435 | `chart.test.ts:721` | "target: 'navigate' is ACCEPTED — the chart renderer delivers it now" | `ObjectChart` gained `'navigate'`, so the union gained the member. | | `objectui#11610` | `component-form-custom-fields-sections-typed.pin.test.ts:496` | "§5 the grid widget's eight field-level keys, camelCase since objectui renamed them" | objectui renamed the eight keys to camelCase with no dual read. | | #19046 | `component-object-grid-pagination-accept-set.pin.test.ts:172` | "both arms refuse a … pageSize — the view and component arms no longer disagree" | The grid component arm refuses the page sizes the view arm refuses. | | #18159 | `component-record-block-field-security.test.ts:174` | "`requiredPermissions` is declared on the three blocks as a capability set (instruments A and B)" | Ruling A: the key follows the #19186 ruling, an ADR-0066 capability set. | **Subject back in words** (13 literals): "#7428 —" becomes "the `confirmText` + `params` guard" or "the pair guard" (`action-confirm-params-guard.test.ts:130`, `:171`), and "the #7428 pair rule" becomes "the `confirmText` + `params` pair rule" (`action-row-update.test.ts:280`); "the surfaces #7367 …" becomes "the surfaces the action `description` key …"; two `#17319 —` prefixes become "`execution` on an action" and "dispatch-contract default"; "the pre-existing probes (#9474)" becomes "the spellings tried before `onSuccess` existed"; "the bare filter #14175 declared" becomes "the bare filter the facade was first typed with"; "#14092 — boundaries" becomes "the declarative row update — boundaries"; "#17631's shape" becomes "the never-parsing gate it once built"; "§6 #21464 is closed out" becomes "§6 the `z.unknown()` member sweep is closed out"; and two `#18159 —` prefixes become "the three record blocks" and "the field-security pair". **Dropped where already stated** (47 literals, 47 ids). A number goes only where the title already says its decision. Examples: "ActionSchema — newTabUrl requires opensInNewTab: true (#11842)"; "ActionSchema — the `execute` alias is REMOVED (#3855)"; the `#15124 —`, `#15117 —` and `#5779 —` prefixes, each in front of the rule it names; the four `[#17987]` prefixes on `component-element-navigation-17987.test.ts`; "— the shape is closed, so its accept means something (#18177)"; "retired fail-open area gates (#4651)". `(ADR-0049)`, `(ADR-0066 D4)` and `(ADR-0078)` stay: they cite decision records by number, not tracker ids. **No file is renamed.** Four file names carry a number (`calendar-config-allday-prescription-17054`, `component-action-element-rows-20371`, `component-action-row-endpoint-21005`, `component-element-navigation-17987`); they are not this card's. ## Readers - **Test-name filters:** none. No tracked script, workflow or package config passes `-t` / `--testNamePattern`. - **Snapshots:** none. No `__snapshots__` directory is tracked under `packages/spec`, and none of the 32 files calls a snapshot matcher. - **Projects:** one of the 32 files runs in the `repo` project: `action-requires-confirmation-docblock.pin.test.ts` is listed in `packages/spec/vitest.repo-tests.json`, because it reads `packages/runtime/src/action-execution.ts`. The other 31 run in `local`. - **By substring:** every old literal, plus a window around each id (290 needles), was searched with `git grep` at the base, across the tracked tree outside its own file. No gate, doc, filter, snapshot or `scripts/check-*.mjs` self-test reads one. The 11 hits: - 7 code comments in `ui/component.zod.ts` citing `objectstack-ai/objectui#11610`, and one in `ai/agent.test.ts:193` ("#3896 close-out"); - one release-owned line, `content/docs/releases/v17/17-0.mdx:326`; - one sibling title in `runtime` (`action-engine-facade-find-envelope.test.ts:80`, "#15124 — …"). None reads a spec test title. - **The files by name:** a few gates and ledgers name group files by path, never by title: `scripts/check-parse-guard.mjs` reads a code line of `app.test.ts`, `packages/spec/test-typecheck-debt.json` keys `app.test.ts` and `chart.test.ts` on error signatures, and `vitest.repo-tests.json` lists the docblock pin. ## Text-only proof Stage 10's scratch tool (`textonly10.cjs`, md5 `d5e4801dbb4329ab1984da91e92fc47c`) compares base and head file by file on three legs: 1. **Skeleton:** the full AST, with string pieces masked. It must be identical. 2. **Comments:** every comment, byte-equal. 3. **Strings:** each changed string leaf must sit in a test-call title position or on a declared line, must carry a tracker id before, and must carry no `#` plus digits after. This stage declares two lines, `action-requires-confirmation-docblock.pin.test.ts:168` and `:175`. - **Result:** 32 of 32 files SAME on all three legs, with the per-file counts predicted in writing before the run. - **Totals:** 96 changed string leaves in 96 literals: 94 titles and 2 declared. The diff's `+` and `-` lines are exactly the 96 planned lines, and every file keeps its line count. - **Controls (12 of 12 as predicted, on scratch copies, each anchor hit once):** identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string given an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; an `it.each` row given an id VIOLATION; an undeclared `expect` message changed VIOLATION; a title re-split into a `+` chain DIFF; a declared message given an id back VIOLATION; the kept `:322` ruling value edited VIOLATION. - **Templates and tables:** the one template title (`component-object-grid-pagination-accept-set.pin.test.ts:172`) changes only after its `${label}` span. No `.each` title, `%s` / `$name` placeholder or table row changes. **Test counts:** the 32 files were run at the base, in a separate base worktree, and at the head, with `--project local --project repo`. Both sides read 959 tests in 32 files, all passed, with the same count and status sequence per file in 32 of 32. 513 full test names change, and no full name repeats on either side. Each changed name equals the base name with the planned replacements applied. The comparison script flags one name: its plan entry spells `’` as the source does, and the printed name carries the decoded character. With the escape decoded, that name matches too. ## Changeset: `skip-changeset` Measured, not assumed: - `npm pack --dry-run` of `@objectstack/spec` lists 2068 files. 0 of the 32 touched files are in it, and no `*.test.ts` at all. The controls `src/ui/action.zod.ts` and `dist/index.mjs` are in it. - In the built `dist/`, a new phrase and an old literal each read in 0 files. The control `Unrecognized key` reads in 42. So this PR publishes nothing, and no changeset is added. ## Verification (at `fa39bcf62e`) - `pnpm turbo run build` over all packages: 71 / 71, through the shared verify lock (`VERDICT command-exit 0`). - `@objectstack/spec`: - `vitest run --project local`: 616 files, 18426 passed, 1 todo. - `typecheck`: exit 0, including `check:test-typecheck` (52 files / 246 errors / 135 pinned signatures held). Its program holds all 32 touched files, counted with `tsc --listFilesOnly -p tsconfig.test.json`. - `check:generated`: all 15 generated artifacts up to date, against the `dist/` the build above wrote. - **Gates:** `dispatch-gates --commands` derived 79 families, the same set as stage 19, and all 79 exit 0. `--ran` reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN, every family with its exit code recorded. The five roster families whose rosters sit under a touched directory were also run, and each exits 0: `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing` and `check:filter-alias-parity`. - **ESLint, a proven narrowing:** `--no-inline-config` over the 32 files reads 0 errors and 0 warnings. The population comes from ESLint's own config: 32 configured, 0 ignored. No file sets `parserOptions.project` or `projectService`, so no untouched file's verdict can move. - `check-governed-merges --test`: NOT governed, 192 changed lines (+96 / -96). - A control-byte scan over the 32 files finds none. ## `main` since the base Re-fetched just before this PR opened, `origin/main` was two commits past the base (`67c544ccca`: #21887, #21891). Neither touches any of the 32 files, and neither touches `packages/spec`: their 9 files are in `service-datasource`, `qa/dogfood`, the QA checklist and one changeset. So `main` was not merged. `git merge-tree` onto `67c544ccca` is clean. None of the 13 open PRs touches the 32 files. ## Acceptance notes - **The kept id** (`component-props-unknown-members.pin.test.ts:322`, read by `:417`) is held for the needle stage, as above. - **Same-id test titles in this card's later stages** go with those stages. 30 lines in `packages/spec/src` cite ids this PR handled, for example `api/api-error-code-type.test.ts:71` ("[#19920] …"), `system/job.test.ts:836` ("retired job.id (#4667)"), `ui/view-strictness-batch18.test.ts:91` ("#4001 批 18 — …"), `ui/view.test.ts:3144` ("(#3896 close-out)") and `ui/component.test.ts:3518` ("(#19514)"). `ui/view.test.ts:3386` cites `objectui#5435`, a different record. - **Same-id test titles in other packages** are their lanes' test-string shares. A search of `describe` / `it` / `test` lines outside `packages/spec/src` finds 44 lines citing ids this PR handled, in 11 packages: `lint` 16 (6 files), `runtime` 9 (6), `cli` 5 (3), `plugin-approvals` 3 (2), `spec/scripts` 3 (2), `objectql` 2 (2), `plugin-security` 2 (2), and one each in `plugin-sharing`, `qa/dogfood`, `rest` and `service-automation`. Examples: `runtime/src/action-execution-destructive.test.ts:86` ("(#7828 Option A)"), `lint/src/validate-react-page-props.test.ts:1008` ("since objectui#3382 (#5435)"), `cli/test/i18n-extract-action-description.test.ts:26` ("(#7367)"). Some of the `#3896` hits there (`plugin-sharing`, `qa/dogfood`) cite the sharing-rule record itself. - **Code comments still carry ids** in these files and their sources, for example the `[#13865]` header of the docblock pin, the `#20323` comment above `action.test.ts:1116` and the `objectstack-ai/objectui#11610` comments in `ui/component.zod.ts`. Comments are not this card's share, and none is touched here. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7665c54 commit 1e18a07

32 files changed

Lines changed: 96 additions & 96 deletions

‎packages/spec/src/ui/action-confirm-params-guard.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const guardIssue = (result: ReturnType<typeof ActionSchema.safeParse>) =>
4646
? undefined
4747
: result.error.issues.find((i) => i.path.join('.') === 'confirmText');
4848

49-
describe('#7428 — `confirmText` + non-empty `params` is refused on ActionSchema', () => {
49+
describe('`confirmText` + non-empty `params` is refused on ActionSchema', () => {
5050
it('refuses the pair, at the `confirmText` path', () => {
5151
const result = ActionSchema.safeParse({
5252
...base,
@@ -127,7 +127,7 @@ describe('#7428 — `confirmText` + non-empty `params` is refused on ActionSchem
127127
});
128128
});
129129

130-
describe('#7428 — what the guard must NOT touch', () => {
130+
describe('what the `confirmText` + `params` guard must NOT touch', () => {
131131
it('accepts `confirmText` on a param-LESS action — the confirm is the only dialog', () => {
132132
const result = ActionSchema.safeParse({
133133
...base,
@@ -145,7 +145,7 @@ describe('#7428 — what the guard must NOT touch', () => {
145145
expect(result.success).toBe(true);
146146
});
147147

148-
it('accepts `params` + `description` — the shape #7278 migrated TO', () => {
148+
it('accepts `params` + `description` — the one-dialog shape the confirm question migrated TO', () => {
149149
// If this ever goes red the guard has swallowed its own remedy and the two
150150
// migrations have nowhere to land.
151151
const result = ActionSchema.safeParse({
@@ -168,7 +168,7 @@ describe('#7428 — what the guard must NOT touch', () => {
168168
});
169169
});
170170

171-
describe('#7428 — the guard is scoped to ActionSchema by SCHEMA BOUNDARY', () => {
171+
describe('the pair guard is scoped to ActionSchema by SCHEMA BOUNDARY', () => {
172172
it('BulkActionDefSchema still ACCEPTS `confirmText` + non-empty `params`', () => {
173173
// The pinning test the boundary ruling asks for. This pairing is INTENDED
174174
// on the bulk surface: per that schema's own describe() text the params are

‎packages/spec/src/ui/action-description.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { ObjectTranslationDataSchema, TranslationDataSchema } from '../system/tr
3232
*/
3333
const base = { name: 'approval_reject', label: 'Reject', target: 'rejectApproval' } as const;
3434

35-
describe('ActionSchema.description (#7367)', () => {
35+
describe('ActionSchema.description — the line the param dialog shows', () => {
3636
it('accepts a plain string, matching `label`\'s I18nLabel contract', () => {
3737
const result = ActionSchema.safeParse({
3838
...base,
@@ -125,7 +125,7 @@ describe('ActionSchema.description (#7367)', () => {
125125
});
126126
});
127127

128-
describe('the surfaces #7367 deliberately does NOT widen', () => {
128+
describe('the surfaces the action `description` key deliberately does NOT widen', () => {
129129
it('an action PARAM still routes `description` to `helpText`', () => {
130130
// `ACTION_PARAM_KEY_ALIASES.description = 'helpText'` is a PARAM-surface
131131
// entry and stays correct: the param's help line is `helpText`, and the key
@@ -163,7 +163,7 @@ describe('the surfaces #7367 deliberately does NOT widen', () => {
163163
});
164164
});
165165

166-
describe('actionTranslationSchema.description (#7367)', () => {
166+
describe('actionTranslationSchema.description', () => {
167167
it('accepts the key at the object-scoped address the resolver walks first', () => {
168168
const result = ObjectTranslationDataSchema.safeParse({
169169
_actions: {

‎packages/spec/src/ui/action-dispatch-contract.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const undeclaredAction = {
4040
};
4141
const recalcSelection = { ...undeclaredAction, execution: 'aggregate' as const };
4242

43-
describe('#17319 — the defect, reproduced (and still true: this is an authoring key)', () => {
43+
describe('the defect, reproduced (and still true: the declared dispatch contract is an authoring key)', () => {
4444
it('hands the SAME action opposite input under the two wirings, with zero diagnostics', () => {
4545
// One declared param — everything else in each bag is a builtin the author
4646
// cannot declare and the gate must admit.
@@ -74,7 +74,7 @@ describe('#17319 — the defect, reproduced (and still true: this is an authorin
7474
});
7575
});
7676

77-
describe("#17319 — the vocabulary is `bulkActionDefs`' own", () => {
77+
describe("an action's dispatch contract speaks `bulkActionDefs`' own vocabulary", () => {
7878
it('accepts exactly the def`s two options on the action, and no third spelling', () => {
7979
expect(BulkActionExecutionSchema.options).toEqual(['perRecord', 'aggregate']);
8080

@@ -102,7 +102,7 @@ describe("#17319 — the vocabulary is `bulkActionDefs`' own", () => {
102102
});
103103
});
104104

105-
describe('#17319 — accept, refuse, and the key spellings', () => {
105+
describe('`execution` on an action — accept, refuse, and the key spellings', () => {
106106
it('accepts both declared contracts and keeps the value verbatim', () => {
107107
expect(ActionSchema.parse(recalcSelection).execution).toBe('aggregate');
108108
expect(ActionSchema.parse({ ...undeclaredAction, execution: 'perRecord' }).execution).toBe('perRecord');
@@ -135,7 +135,7 @@ describe('#17319 — accept, refuse, and the key spellings', () => {
135135
});
136136
});
137137

138-
describe('#17319 — ⛔ no silent default for an undeclared action', () => {
138+
describe('⛔ no silent dispatch-contract default for an undeclared action', () => {
139139
it('parses an action that omits the key, and leaves it ABSENT', () => {
140140
const parsed = ActionSchema.parse(undeclaredAction);
141141
expect(parsed.execution).toBeUndefined();

‎packages/spec/src/ui/action-doubled-redirect.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { getMetadataTypeSchema } from '../kernel/metadata-type-schemas';
2121

2222
const base = { name: 'open_sso_portal', label: 'Open SSO portal' };
2323

24-
describe('ActionSchema — doubled post-success navigation (#11519)', () => {
24+
describe('ActionSchema — doubled post-success navigation is refused, with no precedence field', () => {
2525
describe('refusal pin — the statically-knowable doubled declaration', () => {
2626
const doubled = {
2727
...base,

‎packages/spec/src/ui/action-newtaburl-pair.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { getMetadataTypeSchema } from '../kernel/metadata-type-schemas';
2323

2424
const base = { name: 'open_sso_portal', label: 'Open SSO portal' };
2525

26-
describe('ActionSchema — newTabUrl requires opensInNewTab: true (#11842)', () => {
26+
describe('ActionSchema — newTabUrl requires opensInNewTab: true', () => {
2727
describe('refusal pins — the lone key, on every shape that can carry it', () => {
2828
const lone = {
2929
...base,

‎packages/spec/src/ui/action-on-success.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getMetadataTypeSchema } from '../kernel/metadata-type-schemas';
1010

1111
const base = { name: 'copy_as_new_version', label: 'Copy as new version' };
1212

13-
describe('ActionSchema.onSuccess (#9566/#9474)', () => {
13+
describe('ActionSchema.onSuccess — one closed post-success navigation key for api and script actions', () => {
1414
describe('accept pins', () => {
1515
it('accepts the full shape on a type:api action', () => {
1616
const r = ActionSchema.safeParse({
@@ -138,7 +138,7 @@ describe('ActionSchema.onSuccess (#9566/#9474)', () => {
138138
});
139139
});
140140

141-
describe('type scope — api and script only (the #4352 enforcement shape)', () => {
141+
describe('type scope — api and script only, refused on any other type, never silently ignored', () => {
142142
it.each(['url', 'modal', 'flow', 'form'] as const)('refuses onSuccess on a type:%s action', (type) => {
143143
const r = ActionSchema.safeParse({
144144
...base,
@@ -154,7 +154,7 @@ describe('ActionSchema.onSuccess (#9566/#9474)', () => {
154154
});
155155
});
156156

157-
describe('the pre-existing probes now land on prescriptions, not bare rejections (#9474)', () => {
157+
describe('the spellings tried before `onSuccess` existed now land on prescriptions, not bare rejections', () => {
158158
it('a top-level `redirect` names the onSuccess shape', () => {
159159
const r = ActionSchema.safeParse({ ...base, type: 'api', target: '/t', redirect: '/x' });
160160
expect(r.success).toBe(false);

‎packages/spec/src/ui/action-param-carryover.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import { describe, it, expect } from 'vitest';
1515
import { ActionParamSchema } from './action.zod';
1616

17-
describe('ActionParamSchema.carryOver (#11992, #11753 ruling)', () => {
17+
describe('ActionParamSchema.carryOver — seeded from the row, shown read-only, submitted verbatim', () => {
1818
describe('accept pins', () => {
1919
it('accepts carryOver on a field-backed defaultFromRow param and carries it in the parse output', () => {
2020
const r = ActionParamSchema.safeParse({

‎packages/spec/src/ui/action-param-default-value.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const CASES: Array<{ label: string; param: Record<string, unknown>; accepted: bo
128128
},
129129
];
130130

131-
describe('#6970 ActionParamSchema.defaultValue — authored defaults meet the param value contract', () => {
131+
describe('ActionParamSchema.defaultValue — authored defaults meet the param value contract', () => {
132132
for (const { label, param, accepted } of CASES) {
133133
it(`${accepted ? 'accepts' : 'rejects'}: ${label}`, () => {
134134
const issue = defaultValueIssue(param);
@@ -151,7 +151,7 @@ describe('#6970 ActionParamSchema.defaultValue — authored defaults meet the pa
151151
});
152152
}
153153

154-
it('#16077 carries the rename, not a member type error, on a renamed structured default', () => {
154+
it('carries the rename, not a member type error, on a renamed structured default', () => {
155155
// The action-param gate and the field gate share ONE core
156156
// (`checkLiteralDefaultValue`), so the positional issue read cost this
157157
// surface the same prescription. Pinned here as well as at the core
@@ -247,7 +247,7 @@ describe('#6970 ActionParamSchema.defaultValue — authored defaults meet the pa
247247
});
248248
});
249249

250-
describe('[#20740] the submit door refuses a zone-suffixed `time` value', () => {
250+
describe('the submit door refuses a zone-suffixed `time` value — a time of day is a zone-less wall clock', () => {
251251
it('`validateActionParams` answers `invalid_shape` naming the param; a zone-less wall clock passes', () => {
252252
for (const value of ['10:00Z', '10:00+08:00', '10:00:00-0530']) {
253253
const issue = submitIssue({ name: 'at', type: 'time', defaultValue: value });

‎packages/spec/src/ui/action-params.test.ts‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('validateActionParams (ADR-0104 D2)', () => {
7171
expect(ACTION_PARAM_BUILTIN_KEYS).toContain('objectName');
7272
});
7373

74-
it('allows the aggregate-dispatch key _selectedIds on a param-declaring action (objectui#3139)', () => {
74+
it('allows the aggregate-dispatch key _selectedIds on a param-declaring action', () => {
7575
// The renderer's aggregate bulk dispatch injects `_selectedIds` next to
7676
// the user-collected params; strict mode must not 400 the whole call for
7777
// a key the author can never declare.
@@ -133,10 +133,10 @@ describe('validateActionParams (ADR-0104 D2)', () => {
133133
* file cannot otherwise see.
134134
* - Widen the match to fuzzy/Levenshtein → the two "no hint" cases go RED.
135135
*/
136-
describe('#5622 — near-miss built-in hint on unknown_field', () => {
136+
describe('near-miss built-in hint on unknown_field', () => {
137137
const declaresFormat: ResolvedActionParam[] = [{ name: 'format', type: 'text' }];
138138

139-
it('names `_selectedIds` when the caller sent `selectedIds` (the #5568 road)', () => {
139+
it('names `_selectedIds` when the caller sent `selectedIds` — the declared channel for a selection', () => {
140140
const issues = validateActionParams(declaresFormat, { format: 'png', selectedIds: ['dev_1', 'dev_2'] });
141141

142142
expect(issues).toHaveLength(1);
@@ -279,7 +279,7 @@ describe('#5622 — near-miss built-in hint on unknown_field', () => {
279279
* have meant the contract had led its producer into a break, which is exactly
280280
* what a deprecation window exists to prevent.
281281
*/
282-
describe('#5779 — ActionSession `positions` canonical + `roles` deprecated alias', () => {
282+
describe('ActionSession `positions` canonical + `roles` deprecated alias', () => {
283283
it('PRESERVES `positions` through a parse instead of stripping it', () => {
284284
const parsed = ActionSessionSchema.parse({
285285
userId: 'usr_1',
@@ -291,7 +291,7 @@ describe('#5779 — ActionSession `positions` canonical + `roles` deprecated ali
291291
expect(parsed.positions).toEqual(['sales_rep', 'org_admin']);
292292
});
293293

294-
it('accepts the DUAL-EMIT shape #5613\'s runtime half will produce — both keys, one value', () => {
294+
it('accepts the DUAL-EMIT shape the runtime emits through the rename window — both keys, one value', () => {
295295
// The window's defining property: same array under both spellings, so a
296296
// reader migrates by changing the key it reads and nothing else. Pinned
297297
// here on the contract side BEFORE the producer exists, because that
@@ -447,7 +447,7 @@ export type FindQueryIsEngineQueryOptionsWithoutContext =
447447
export type FindQueryCarriesNoContextKey =
448448
Assert< Eq< 'context' extends keyof FindQuery ? true : false, false > >;
449449

450-
describe('#15124 — ActionEngineFacade.find takes the engine query envelope, never a bare filter', () => {
450+
describe('ActionEngineFacade.find takes the engine query envelope, never a bare filter', () => {
451451
it('types the second parameter as the published `EngineQueryOptions` (the tsc channel)', () => {
452452
// The value-level half of `FindQueryIsEngineQueryOptionsWithoutContext`
453453
// above: a literal
@@ -479,7 +479,7 @@ describe('#15124 — ActionEngineFacade.find takes the engine query envelope, ne
479479
.every((q) => typeof q === 'object')).toBe(true);
480480
});
481481

482-
it('REFUSAL PIN — the bare filter #14175 declared no longer type-checks (the trap is inverted, not narrowed)', () => {
482+
it('REFUSAL PIN — the bare filter the facade was first typed with no longer type-checks (the trap is inverted, not narrowed)', () => {
483483
// This is the pin #14175 recorded as a MEASURED GAP, flipped. The envelope
484484
// that returned `[]` in silence in the reporting app is now the RIGHT
485485
// spelling (the positive controls above), and the bare filter that used to
@@ -513,7 +513,7 @@ describe('#15124 — ActionEngineFacade.find takes the engine query envelope, ne
513513
expect([whereNotFilter, fieldsNotArray, limitNotNumber]).toHaveLength(3);
514514
});
515515

516-
it('#19237 REFUSAL PIN — `context` is not an envelope key on THIS facade (ADR-0049 remove arm)', () => {
516+
it('REFUSAL PIN — `context` is not an envelope key on THIS facade (ADR-0049 remove arm)', () => {
517517
// The key the engine honours and this facade does not. It was declared
518518
// here and unenforced between #15124 and #19237: the write below
519519
// type-checked, and the runtime stamped the facade's own elevated context
@@ -572,7 +572,7 @@ type DeleteIds = Parameters<ActionEngineFacade['delete']>[1];
572572
// way to `unknown`. Exported for the same reason the sibling pins are.
573573
export type DeleteIdsAcceptsOneOrMany = Assert< Eq< DeleteIds, string | string[] > >;
574574

575-
describe('#15117 — ActionEngineFacade.delete accepts one id or an array, both as contract', () => {
575+
describe('ActionEngineFacade.delete accepts one id or an array, both as contract', () => {
576576
it('types the second parameter as `string | string[]` (the tsc channel)', () => {
577577
// The value-level half of `DeleteIdsAcceptsOneOrMany` above: literals
578578
// annotated with the slot type, so the runtime run exercises the same

‎packages/spec/src/ui/action-requires-confirmation-docblock.pin.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ function classifierBody(): string {
125125
return source.slice(open, end);
126126
}
127127

128-
describe('`ai.requiresConfirmation` docblock states the #7828 signal set (#13865)', () => {
128+
describe('`ai.requiresConfirmation` docblock names only the declared signals the classifier reads', () => {
129129
it('anchors on the live docblock and the live classifier', () => {
130130
// Anti-vacuity for every assertion below: both extractions must have found
131131
// real text, or "names no retired signal" passes by reading nothing.
@@ -147,7 +147,7 @@ describe('`ai.requiresConfirmation` docblock states the #7828 signal set (#13865
147147
expect(doc, "the docblock must name `variant:'danger'`").toMatch(/`variant:\s*'danger'`/);
148148
});
149149

150-
it('would flag the pre-#13865 sentence as a positive `confirmText` claim (self-test)', () => {
150+
it('would flag the retired sentence as a positive `confirmText` claim (self-test)', () => {
151151
// Verbatim, the sentence this card retired. Without this the assertion
152152
// below could pass simply because the prose stopped naming `confirmText`.
153153
const before =
@@ -165,14 +165,14 @@ describe('`ai.requiresConfirmation` docblock states the #7828 signal set (#13865
165165
it('presents no positive `confirmText` claim', () => {
166166
expect(
167167
positiveConfirmTextClaims(flatten(requiresConfirmationDoc())),
168-
'a `confirmText` sentence with no negation re-seeds the leg #7828 Option A retired',
168+
'a `confirmText` sentence with no negation re-seeds the retired leg — only declared semantics classify',
169169
).toEqual([]);
170170
});
171171

172-
it('the classifier still does not read `confirmText` (#7828 Option A)', () => {
172+
it('the classifier still does not read `confirmText` — only declared semantics classify', () => {
173173
expect(
174174
classifierBody(),
175-
'`actionLooksDestructive` reads `confirmText` again — reopen #7828 before the docblock '
175+
'`actionLooksDestructive` reads `confirmText` again — reopen the declared-semantics ruling before the docblock '
176176
+ 'in `packages/spec/src/ui/action.zod.ts` may name it',
177177
).not.toMatch(/confirmText/);
178178
});

0 commit comments

Comments
 (0)