docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(spec): narrow the expression-bindable button row to the spelling its key reaches - #14321

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing
Sep 2, 2026
Merged

docs(spec): narrow the expression-bindable button row to the spelling its key reaches#14321
os-zhuang merged 1 commit into
mainfrom
claude/issue-13672-bindable-keys-docblock-narrowing

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#13672

Ruled outcome: option 3 — docblock narrowing (ruling comment 5479231731, director seat, maintainer verbatim, 2026-08-31). The machine face does not move.

What was wrong

EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT is keyed on the component type string exactly as authored, and expressionBindableTextKeysFor does an own-property lookup on it. The button row's evidence citation named two objectui renderers:

 | `button` | `schema.label` (`form/button.tsx`, `action/action-button.tsx`) |

but action/action-button.tsx registers under action:button — a different key, with no row, answering the empty set. The citation promised coverage the lookup cannot deliver. The defect is a contradiction inside the protocol prose, not a fault in the lookup: precise own-property lookup on the authored string, with no row meaning the empty set, is the contract working as designed.

What changed — prose only

  1. The button row now cites form/button.tsx alone.
  2. A new docblock section, "Rows are keyed on the AUTHORED type string, prefix and all", records the general rule: a namespace-prefixed spelling (action:button, ui:button, the mcp: family) is a different key from the bare name and answers the empty set by construction, not by oversight. It also records why prefix-stripping is the wrong repair — it would in the same motion grant rows to element:button and page:card, whose renderers take config from the evaluated properties bag and never read these keys at the node's top level.
  3. action:button and ui:button are recorded as deliberately out on the two measured grounds the ruling named: zero pull (objectui corpus census of 736 JSON documents / 2747 typed nodes — 5 action:button nodes, 0 of them carrying an expression in label, and 0 ui:-prefixed spellings of any kind), and this module's own admission rule that a row arrives with its own measurement while a type string with no row IS the empty set.
  4. The reopen path is stated: a named requirement for an expression-bound action:button label adds a row here, carrying that row's own measurement.

On the reasoning the ruling struck down: it rejected the semantic claim carried by the earlier triage recommendation, because action/action-button.tsx renders the author-written schema.label directly. That reasoning appears nowhere in this diff, in any wording. The docblock records the affirmative measurement in its place — the read-back half is real, and only the pull is missing.

The test file — a bounded in-place fix, declared with its evidence

packages/spec/src/ui/expression-bindable-text-keys.test.ts carried the same retracted citation in the comment above its carriage assertion (line 62: "form/button.tsx (and action/action-button.tsx) read schema.label"). Same defect class as item 1, correct form fixed by the ruling itself, no other claim on the file, same gate family — so it was corrected in the same motion rather than left standing as the surviving copy of a citation the docblock had just retracted. It was declared in the claim comment's file surface before the first edit, not discovered after the fact. Scan that bounded it, measured at the base commit a39b02a6: git grep -n "action-button.tsx" a39b02a6 -- packages/spec/src returns exactly two hits — the docblock row (.zod.ts:58) and this comment (.test.ts:62). Both are in this diff, and nothing else in the spec sources carried the citation. Two further hits exist elsewhere under packages/spec and are deliberately untouched: liveness/action.json (evidence strings for unrelated action properties) and CHANGELOG.md (shipped release history).

The same file now pins that the machine face did not move. action:button and ui:button joined the empty-set loop, so a later prefix-stripping "fix" cannot move the lookup in silence:

  • expressionBindableTextKeysFor('button') still yields ['label'] — pin already present, unchanged.
  • expressionBindableTextKeysFor('action:button') still yields [] — pin added.

Clause-②: no

The path limb trips (packages/spec/src/**); the content limb does not. No accept or reject behaviour changes and no public surface widens or narrows — check:api-surface is green against a fresh build, and the map, the lookup function and every export are byte-identical. needs:contract-review is hung on this PR and on the card for the path limb.

content/docs/references/ui/expression-bindable-text-keys.mdx is the generated projection of this docblock, regenerated by pnpm --filter @objectstack/spec check:generated --fix (which proved it the only stale artifact of 15 and ran gen:docs alone). Never hand-edited.

A patch changeset is included because the prose is consumer-visible: packages/spec's published files carries both dist and src/**/*.zod.ts, and the edited docblock is present in dist/ui/index.d.ts.

Verification — every reading below taken on head 2925aea7

whatcommandresult
dependency closure + spec buildpnpm --filter '@objectstack/spec^...' build then pnpm --filter @objectstack/spec buildexit 0; 34/34 declaration files emitted
module testspnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui/expression-bindable-text-keys.test.tsTest Files 1 passed (1) · Tests 9 passed (9)
spec typecheck, all three legspnpm --filter @objectstack/spec typecheckexit 0 — tsc --noEmit, check:scripts-typecheck, check:test-typecheck ("OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json")
typecheck really covers the editstsc --listFiles on both projectsthe .zod.ts file is in tsconfig.json's program and the .test.ts file is in tsconfig.test.json's — 1 hit each, so the green is not a green over files nothing read
generated artifactspnpm --filter @objectstack/spec check:generated15 of 15 current after the regeneration; check:api-surface and check:docs both green
gate family, re-derived after the final commitnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths — it reads its own change set from the merge base)70 commands over the 4 changed paths; 64 green, 6 PREREQUISITE NOT MET (below)
control bytespnpm check:nul-bytesexit 0 — 7813 files scanned, no raw control bytes
repo-wide lintpnpm lint (eslint . --no-inline-config)exit 0 — the full sweep, not a narrowed one

The 6 commands recorded as PREREQUISITE NOT MET rather than green or red, each on its own printed verdict line, all rooted in this container having only a partial workspace build: check-dev-prereqs ("the workspace is not built"), check-test-completeness (exit 3 — grades a saved turbo run test log, and the family names it with no argument), check:doc-formula-expressions and check:doc-security-posture (exit 3), check:dual-build-cjs-loads (exit 3 — "reads built output, and some package has no dist/"), check:type-check-debt (exit 3). None of them reads any of the four changed paths. check:skill-examples was initially in this group and was converted to a real green by building the @objectstack/client-react closure and re-running it: 259 marked examples across 3 surfaces type-check, including the 9 spec-source-TSDoc blocks that are this diff's own surface. scripts/pm/check-half-states.mjs invoked bare timed out on proxy configuration; its real family command pnpm check:pm-half-states ran green.

Every gate exit code above was captured before any pipe (cmd > log 2>&1; rc=$?), and each verdict is quoted from the gate's own output rather than from a bare $?.

Not done, deliberately

Draft, base main. Not flipped ready, no auto-merge, not enqueued.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21

Generated by Claude Code


Generated by Claude Code

…ng its key reaches
`EXPRESSION_BINDABLE_TEXT_KEYS_BY_COMPONENT` is keyed on the component type
string exactly as authored, and `expressionBindableTextKeysFor` does an
own-property lookup on it. The `button` row's evidence citation named
`action/action-button.tsx`, which registers under `action:button` — a
different key, with no row, answering the empty set. The citation therefore
promised coverage the lookup cannot deliver.
The machine face does not move: the map, the lookup and every export are
unchanged, and `check:api-surface` is green against a fresh build. This is
the protocol prose catching up with its own evidence table.
- `button` row cites `form/button.tsx` alone.
- New docblock section: the table is keyed on the authored type string, so
namespace-prefixed spellings (`action:button`, `ui:button`, the `mcp:`
family) answer the empty set BY CONSTRUCTION — and prefix-stripping is the
wrong repair, since it would also grant rows to `element:button` and
`page:card`, whose renderers read config out of the evaluated `properties`
bag and never touch these keys at the node's top level.
- `action:button` and `ui:button` recorded as deliberately out on the two
measured grounds (zero corpus pull; the module's own admission rule), with
the ordinary reopen path.
- The test file carried the same retracted citation in a comment; corrected,
and the namespaced spellings are now pinned to the empty set so a later
prefix-stripping change cannot move the machine face in silence.
- Reference page regenerated by `gen:docs`, never hand-edited.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GDA48PuRFrHyRfdkBz8m21
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/ui/expression-bindable-text-keys.zod.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 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 8292eb5ad82380508102154c83d84522c56976bfpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 02:30
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit e854a53Sep 2, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13672-bindable-keys-docblock-narrowing branch September 2, 2026 02:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

2 participants

@os-zhuang@claude