Skip to content

test(spec): the first ui/ file group's test titles state each cited decision in words instead of a tracker number (stage 20) - #21895

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20749-test-strings-k
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20749-test-strings-k

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 (drillDown 是 objectui 读的活能力,却不是 spec 声明的任何键 —— #3752 的迁移处方曾指向它 #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

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:

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

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


Generated by Claude Code

…ecision in words instead of a tracker number (stage 20)

The 32 test files directly under packages/spec/src/ui/ from
action-confirm-params-guard.test.ts through
component-record-block-field-security.test.ts: 94 test titles and two
expect messages. Where the cited record decided something the title
did not already say, the title now says it in words; where the title
already said it, the number is dropped. Text only: no identifier,
assertion, comment or non-test file changes.

One id stays: component-props-unknown-members.pin.test.ts:322, the
`ruling` value the in-file assertion at :417 matches by the id.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 5, 2026
@github-actions github-actions Bot added the size/m label Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 67c544cccab757d5c27296a4265246c7e3dfffc0 → packageMentionDocs.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 16:45
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 16:45
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 1e18a07 Oct 5, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20749-test-strings-k branch October 5, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

protocol:ui size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants