Skip to content

fix(spec): a page component's visibleWhen names no data root — the shipping renderer mounts none - #23146

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23036-visiblewhen-roots
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23036-visiblewhen-roots

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23036
Clause-②: no

The visibleWhen describe on PageComponentSchema and the 「Ambient roots」 docblock above it now name only what the shipping renderer mounts. They say that no data root is mounted on this surface, as objectstack-ai/objectui#9308 ruling B left it. ⛔ No data root is bound or refused in code, no accept set moves, and there is no renderer change. Text only, plus pins, the regenerated reference page, and a patch changeset.

Spelling note: the schema text writes page state as page. followed by a variable name in angle brackets. That fragment does not survive a GitHub body, so it is written page.VAR below.

What the renderer mounts (the readings the new text states)

All readings are at objectui origin/main f228d710. The four evaluator files (SchemaRenderer.tsx, ExpressionProvider.tsx, hooks/useExpression.ts, containers.tsx) are byte-identical at the .objectui-sha pin 1fb1ced36243 (diff of each file at the two commits: identical).

  • The page component's evaluator. packages/react/src/SchemaRenderer.tsx:665-676 (configEvaluationScope) builds { ...predicateScope, current_user: predicateScope.user, record (only when a row is bound), page }. The memo builds this surface's evaluator from it (:1228-1229), and the visibleWhen leg calls evaluator.evaluateCondition on that evaluator (:1319, the production branch). No data key. The decision is written at :1194: "⛔ data is NOT here, and the absence is the decision (objectui#9308, maintainer ruling 2026-09-13 option B)".
  • The ambient scope, which is the predicate-scope channel. predicateScope is usePredicateScope(), fed through PredicateScopeProvider. In the console its only production feeder is app-shell's ExpressionProvider: git grep PredicateScopeProvider over packages/*/src finds one non-test provider mount, ExpressionProvider.tsx:306. Its buildExpressionScope (ExpressionProvider.tsx:181-190) returns { current_user, user, ctx: { user }, os: { user }, features }. So features and os.user are mounted, and data is not.
  • A page:tabs item's visibleWhen. This is a separate evaluator, packages/components/src/renderers/layout/containers.tsx:642-649. It spreads the row's bare fields, spreads predicateScope, and binds current_user, record: row, data: row and page. The old "record ROW" sentence is still true, so it stays.
  • What a data.* gate does on the component. ExpressionEvaluator.evaluateCondition (packages/core/src/evaluator/ExpressionEvaluator.ts:341 onward) answers a faulting predicate with the fail-soft true, and the SHOW leg negates that into "not hidden". So the component renders whether the predicate is true or false. This matches the card's measurement through SchemaRenderer at d758f2f2 (data.total > 0 and data.total == 0 both SHOWN).
  • The planned API binding. generic SDUI blocks for product pages — stat, usage-bar, checklist — and the renderer half of the api element data source (re-scoped from cloud:home by maintainer ruling 2026-10-12; cloud#2777 part B moves to cloud metadata) objectui#12197 (the renderer half of provider: 'api') is open. isElementDataSourceConfig (packages/core/src/data-scope/element-data-source.ts:169) still admits only a plain object with a string object. No renderer reads the API binding today.

Each changed sentence, FROM → TO

PageComponentSchema.visibleWhen .describe() (published; republished into content/docs/references/ui/page.mdx)

The sentences before and after these are unchanged ("Visibility predicate (CEL) … only when TRUE.", "Contract-bound roots: …", and the closing e.g. example).

  • FROM: "The shipping renderer additionally mounts features, os.user and binds data to the data-source ADAPTER here — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only)."
    TO: "The shipping renderer additionally mounts features and os.user — renderer behaviour, NOT contract-guaranteed (ADR-0068 rules the user object only). It mounts NO data root here: a data.* read faults and a faulting predicate renders the component, so such a gate never gates; the page's row is record."
    Backed by: configEvaluationScope, buildExpressionScope and evaluateCondition above.
  • NEW: "data is declared only for an element whose dataSource is the PLANNED API binding (provider: 'api'), which reads its bound value as data once that binding's renderer ships."
    Backed by: the provider: 'api' describe (page.zod.ts:502, unchanged) and objectui#12197 still open.
  • FROM: "⚠️ data is surface-dependent: on a page:tabs item visibleWhen it is the record ROW instead."
    TO: "⚠️ A page:tabs item visibleWhen is a different evaluator: it binds data to the record ROW."
    Backed by: containers.tsx:642-649.

The TSDoc docblock above visibleWhen (read by whoever opens the file)

  • 「Ambient roots」 FROM: "The shipping renderer additionally mounts features and os.user from app-shell's ExpressionProvider, and binds data."
    TO: "… from app-shell's ExpressionProvider." The rest of the paragraph (no ADR rules those roots on this surface) is unchanged.
  • Measurement line FROM: pin 190fbd01d061 and objectui f2732afe, ExpressionProvider.tsx:59, SchemaRenderer.tsx:463-471.
    TO: pin 1fb1ced36243 and objectui f228d710, ExpressionProvider.tsx:181-190 (buildExpressionScope: current_user, user, ctx.user, os.user, features, and nothing else), and SchemaRenderer.tsx:665-676 (configEvaluationScope, adding record when the page has a row, and page).
  • Section FROM: "⚠️ data is surface-dependent. HERE data is the data-source ADAPTER — what ${data.total} in a properties / props / content value interpolates against. On a page:tabs item-level visibleWhen a separate evaluator (containers.tsx:450-457) binds data to the record ROW instead. Same key name, two bindings; …"
    TO: two sections.
    • "No data root on this surface": ruling B removed data as a runtime expression root (SchemaRenderer.tsx:1194). A data scope reaches a predicate only through the predicate-scope channel, whose one shipping feeder publishes no data. So a data.* read faults and the component renders in both polarities. The row is record. Then: the one data this surface declares is the PLANNED API binding's, read once that binding's renderer ships, and no element has a data to read today.
    • "⚠️ A page:tabs item is a different evaluator": containers.tsx:642-649 binds data to the row and spreads the row's bare fields. That is a binding this surface does not have.
    • The ${data.total} interpolation claim is dropped, not reworded. properties values are evaluated by the same configEvaluationScope evaluator, so data does not resolve there either.

How the two texts agree (dispatch assumption A3)

page.zod.ts:502 (unchanged) says the PLANNED API binding "binds the selected value, which the element reads as data in its expression holes and its visibleWhen". The new visibleWhen text makes both statements true together. Today no data root is mounted. A data read exists only on an element that carries the API binding, and only once that binding's renderer ships. :502 did not have to change, so there is no open question. The new pin holds the agreement: it goes red if the API binding's describe loses its [PLANNED — not yet rendered] marker while this text still calls it planned.

Verification record

Every reading below is at head 8f64f1ae3 (git rev-parse --short HEAD), which is f43d856ca plus 96b07ccb9 plus a merge of origin/main dfb3ba2b2. The merge touched no file in packages/spec.

  • Pins. New packages/spec/src/ui/expression-scope-data-root.pin.test.ts. It checks six things:

    • the mounted-roots sentence names exactly features and os.user, on both faces;
    • each published sentence that names data is one of the three true readings (not mounted, PLANNED binding, page:tabs);
    • the two shapes the retired claim was written in are absent;
    • the page:tabs row reading survives;
    • the contract-bound roots survive;
    • the PLANNED agreement with ElementApiDataSourceSchema's describe holds.

    DARK controls run the classifier over the replaced sentence and require it to fail. They also prove the splitter keeps dotted tokens together, and that a missing anchor throws. The existing expression-scope-app-root.pin.test.ts drops its two data survival controls (toContain('data') on the describe, toContain('binds data') on the docblock), and its app assertions are unchanged.

  • Red first. I committed the fix, wrote BASE bcebd8409's page.zod.ts to disk under a trap … EXIT INT TERM restore, and ran the new pin. On-disk proof: retired-claim grep 1, new-claim grep 0, blob 7519cc64 against HEAD blob 62efbb6f. Result: Tests 5 failed | 6 passed (11). The five red assertions are both mounted-roots sentences (expected [ 'features', 'os.user', 'data' ] to deeply equal [ 'features', 'os.user' ]), both absence assertions, and the PLANNED-binding sentence. Restore was proved by blob 62efbb6f equal to the HEAD blob and an empty git diff HEAD. git status --porcelain was empty afterwards.

  • Spec suite, both projects. pnpm --filter @objectstack/spec exec vitest run --project local --project repo --maxWorkers=2 reported Test Files 717 passed (717) and Tests 20995 passed | 1 todo (20996), with VERDICT command-exit 0.

  • Generated artifacts. pnpm --filter @objectstack/spec build, then check:generated named exactly one stale artifact (content/docs/references/ui/page.mdx, two rows). After gen:docs, the re-run printed ✓ All 14 generated artifacts are up to date.

  • Gate families. I derived them with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 8f64f1ae3: 109 commands. All except pnpm check:type-check-debt were run, which is NOT MEASURED by this dispatch's order. Each exit was captured before any pipe. Results: 102 exit 0 on the first pass. Six exited 3 with PREREQUISITE NOT MET because unbuilt packages meant nothing was measured: @objectstack/lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:lean-entry-closure, and check:dual-build-cjs-loads. After building the @objectstack/lint..., @objectstack/objectql... and @objectstack/client-react... closures, the first five re-ran exit 0. Then I built the rest of the workspace (turbo run build --filter=!@objectstack/docs, 74 tasks, 35 cached), and check:dual-build-cjs-loads re-ran exit 0: 108 published require entry point(s) across 67 package(s) load. --ran reconciliation over the final record (every line carries its exit code): 109 derived famil(ies) accounted for — 108 run, 1 NOT-MEASURED (0 DERIVED from a recorded exit 3, 1 claimed). The one claimed is check:type-check-debt, by order. pnpm --filter @objectstack/spec typecheck exited 0. Both pin files are in tsconfig.test.json's program (tsc --listFilesOnly, counted).

  • Lint. The os validate reading for dispatch assumption A4 is under Acceptance notes.

  • Not run locally, declared narrowing. turbo ls --affected names 82 packages: @objectstack/spec and every dependent. This diff changes .describe() and TSDoc text in one spec file and nothing else in any package. No export, type, accept set or wire shape moves (check:api-surface and check:authorable-surface are green). git grep for the old and new sentences outside packages/spec/src finds only the regenerated reference page and this PR's changeset. So the spec package's own suite is what this change can move. The other 81 packages' suites are CI's Test Core. Blind spot, stated with that claim: turbo ls --affected comes from the package dependency graph, so it cannot see a test that reads another package's source from disk. check:cross-package-test-inputs is green, with every such read declared. The repo-root suites are not on the graph.

  • Changeset. .changeset/23036-visiblewhen-no-data-root.md: @objectstack/spec patch, Clause-②: no. check-adr-0087-registration reads it as non-breaking, so no marker is owed.

Acceptance notes

  • Assumption A4, corrected rule name. The rule that answers a data.-rooted page-component predicate is visibility-root-mislayered (severity warning), not visibility-bare-identifier. data is in @objectstack/formula's SCOPE_ROOTS, so the bare-identifier rule never fires on it. Reading: pnpm --filter @objectstack/lint exec vitest run src/validate-visibility-predicates.test.ts filtered to the page-component and layer cases gave 48 passed | 148 skipped. That covers "a PAGE COMPONENT predicate is still read through the page-side visibility key" and "resolves predicates stored as { dialect, source } envelopes", both [VISIBILITY_ROOT_MISLAYERED] on data.x. The rule's runtime message already says such a predicate "never matches and the element always renders", which agrees with the new describe. No lint change is owed.
  • Same-family residue outside this claim's file surface (not changed here). packages/spec/src/ui/component.zod.ts:994-995, in the TSDoc of the page:tabs item visibleWhen, says "data is the record ROW here, where the component-node evaluator binds it to the data-source ADAPTER". The second half is the claim this PR retires. The same docblock cites containers.tsx:450-457 at pin 190fbd01d061; the lines are 642-649 at 1fb1ced36243. The published describe at :1017 ("binds data to the record ROW (not the data-source adapter)") is still true. The claim's file surface names page.zod.ts only, so I stopped at that fence. Suggested text for whoever carries it: "data is the record ROW here; the component-node evaluator mounts no data root at all (see PageComponentSchema.visibleWhen)". Its pin would be one more TSDoc face in expression-scope-data-root.pin.test.ts.
  • A stale code comment in @objectstack/lint. packages/lint/src/validate-visibility-predicates.ts:343 records a measurement "a page component additionally page and the data-source adapter data". The argument it supports still holds, because data is in SCOPE_ROOTS either way. Comment only, with no author-visible reach. Carrier: none.
  • For the API binding's renderer (objectui#12197). Under ruling B, SchemaRenderer binds no data of its own. So the planned data read in an element's visibleWhen has one existing way in, the predicate-scope channel, which ruling B left un-shadowed for exactly this. This is an observation for that card's carrier, not a decision made here.
  • Labels. Zero label writes: this dispatch's write budget names none.

Generated by Claude Code

…shipping renderer mounts none

The `visibleWhen` describe and its "Ambient roots" docblock said the
renderer "binds `data` to the data-source ADAPTER here". objectui's
SchemaRenderer stopped binding `data` on this surface (ruling B on the
renderer seam's data scope), so a `data.*` gate faults and the component
renders in both polarities. Both faces now name what the renderer mounts
(`features`, `os.user`), state that no `data` root is mounted, tie the one
declared `data` read to the PLANNED API binding, and keep the `page:tabs`
item evaluator's row binding. A new pin holds the claim; the `app`-root
pin drops its two `data` survival controls.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf
`gen:docs` republishes the corrected `PageComponent.visibleWhen` describe
into content/docs/references/ui/page.mdx (both tables that carry it); the
changeset declares `@objectstack/spec` patch, Clause-② no.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G5ZnRrTMiwcHG6iZM19zf
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/layout-dsl.mdx (via PageComponentSchema (symbol, a top-level const))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via PageComponentSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-7.mdx (via PageComponentSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 141 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 1ca7a805ca810bb9eb1c1f6910401f8c582129db → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c8295030a50c522f8bf405713c2d18d01d4670aa — the merge of head 8f64f1ae3484485d9cfc0d1733456cc575aaa11e into base 1ca7a805ca810bb9eb1c1f6910401f8c582129db, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c8295030a50c522f8bf405713c2d18d01d4670aa && git checkout c8295030a50c522f8bf405713c2d18d01d4670aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1ca7a805ca810bb9eb1c1f6910401f8c582129db 8f64f1ae3484485d9cfc0d1733456cc575aaa11e && git checkout -B drift-repro 1ca7a805ca810bb9eb1c1f6910401f8c582129db && git merge --no-ff 8f64f1ae3484485d9cfc0d1733456cc575aaa11e

node scripts/docs-audit/affected-docs.mjs --json 1ca7a805ca810bb9eb1c1f6910401f8c582129db

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1ca7a805ca810bb9eb1c1f6910401f8c582129db → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8f64f1ae3484485d9cfc0d1733456cc575aaa11e
Local-runs: none

PR #23146 (card #23036; body lines Fixes #23036, Clause-②: no), judged on the net diff against main at merge base dfb3ba2b2: 5 files, +261 / −19. Inputs: the card body and its comments 6124818846, 6132762281, 6136306937, 6137432578 and 6137462927; objectui#9308 (ruling B, comment 5651694454) and #17203; the PR body, file list and diff; the check-runs on this head. The cited renderer lines were read in the objectui checkout at the .objectui-sha pin 1fb1ced36243, which this PR does not move. Nothing was built, run or re-run. The spec text writes page state as page. followed by a variable name in angle brackets; it is written page.VAR below.

① Derived judgments

Published surface touched: the .describe() of PageComponentSchema.visibleWhen (packages/spec/src/ui/page.zod.ts:678 at the head) and its two republished rows in content/docs/references/ui/page.mdx (:304, :443). The TSDoc above it (:632–:677) is a source face, judged with the describe because an AI author opening the file reads it.

  1. Mounted-roots sentence — RIGHT. FROM "additionally mounts features, os.user and binds data to the data-source ADAPTER here" TO "additionally mounts features and os.user … It mounts NO data root here: a data.* read faults and a faulting predicate renders the component, so such a gate never gates; the page's row is record." At the pin: react/src/SchemaRenderer.tsx:665-676 (configEvaluationScope) builds { ...predicateScope, current_user: predicateScope.user, record (only for a non-array object row), page }, and the memo builds this surface's evaluator from it at :1228-1229; app-shell/src/providers/ExpressionProvider.tsx:181-190 (buildExpressionScope) returns { current_user, user, ctx: { user }, os: { user }, features } and nothing else; the only production mount of PredicateScopeProvider under packages/ is ExpressionProvider.tsx:306 (the one other hit is packages/react/README.md:87, documentation). So the mounted set is exactly record, current_user, user, ctx.user, os.user, features and page — the union the describe names — and no data; SchemaRenderer.tsx:1194-1206 records the absence as the ruling-B decision. Fail-soft: the production branch at :1319 calls evaluateCondition(raw, { onFault }) with no throwOnError, evaluateCondition (core ExpressionEvaluator.ts:341 onward) answers a fault with true, and the SHOW leg renders on true; the ruling's own implementation report measured an absent data root as data is not defined answered true. That is the card's measurement (data.total greater than 0 and data.total == 0 both SHOWN), and the sentence states it.
  2. PLANNED api-binding sentence — RIGHT, and it agrees with page.zod.ts:502. NEW: "data is declared only for an element whose dataSource is the PLANNED API binding (provider: 'api'), which reads its bound value as data once that binding's renderer ships." :502, unchanged, says "[PLANNED — not yet rendered] … binds the selected value, which the element reads as data in its expression holes and its visibleWhen … until they ship, the binding parses and no block reads it." Both are true together: no data today, and data only under that binding once it is rendered. At the pin no renderer reads an element-level provider: 'api' binding: core/src/data-scope/element-data-source.ts:169-176 (isElementDataSourceConfig) admits only a plain object with a non-empty string object, and the provider: 'api' readers at the pin are the view-level ViewData lane (core/src/adapters/ApiDataSource.ts, resolveDataSource.ts), not a page element's dataSource. The new pin ties the two texts: it goes red when ElementApiDataSourceSchema's describe drops its [PLANNED — not yet rendered] marker.
  3. page:tabs sentence — RIGHT. FROM "data is surface-dependent: on a page:tabs item visibleWhen it is the record ROW instead" TO "A page:tabs item visibleWhen is a different evaluator: it binds data to the record ROW." components/src/renderers/layout/containers.tsx:642-649 at the pin binds data: recordData and record: recordData, spreads the row's bare fields, and binds current_user and page. The word "instead", which implied a data on this surface as well, is gone.
  4. Unchanged clauses — RIGHT to leave. "Visibility predicate (CEL) — component rendered only when TRUE", "Contract-bound roots: record, current_user (ADR-0068 aliases user / ctx.user — one object, three spellings), and page state as page.VAR", the "(ADR-0068 rules the user object only)" fence and the closing e.g. are byte-identical.
  5. Generated page — RIGHT. The two page.mdx rows republish the describe verbatim; nothing else in that file moves.
  6. TSDoc face — RIGHT. 「Ambient roots」 drops "and binds data"; the measurement line is re-pinned to 1fb1ced36243 with ExpressionProvider.tsx:181-190 and SchemaRenderer.tsx:665-676, both verified; the new "No data root on this surface" section cites SchemaRenderer.tsx:1194, verified; "whose one shipping feeder, ExpressionProvider, publishes no data" holds (one production mount, item 1); the page:tabs section cites containers.tsx:642-649, verified. Dropping the ${data.total} interpolation claim is right rather than a reword: evaluateConfigBagInScope (SchemaRenderer.tsx:728-738) builds from the same configEvaluationScope, so data resolves nowhere on this surface. The TSDoc's objectui#9308 cite is a code comment, not a runtime string; the describe carries no tracker number.
  7. "Nothing an author can write changes" — TRUE. The declaration is EvaluatedExpressionInputSchema.optional().describe(…) at both the merge base (:657) and the head (:678); only the string moved. No key, enum, alias, strictness or union arm moves; ElementApiDataSourceSchema was already exported at the merge base (:450), so the new pin's import adds no export; no barrel changes; no wire shape. check:api-surface, check:authorable-surface and check:docs run in Type Check · source gates, success on this head.
  8. Still promised but not done — none found. Left unsaid that an AI author needs — none. The describe names what to gate on instead (record for the row, page.VAR for state in the contract-bound clause) and the changeset gives the one-line fix. Two readings recorded rather than required: (a) "faults" is measured on the CEL envelope, which the describe scopes "(CEL)"; a bare-string predicate takes the legacy path, which the card did not measure; (b) record is bound only when the page has a row, which the TSDoc says and the describe's "the page's row is record" is consistent with.
  9. Residue at the head outside this diff, read for completeness. Hand-written content/docs/ui/pages.mdx:178 ("Binds record, current_user, and page state") and content/docs/protocol/objectui/layout-dsl.mdx carry no data claim for this surface; skills/** none. The component.zod.ts twin is under ③.

② Semver level

.changeset/23036-visiblewhen-no-data-root.md: @objectstack/spec patch, its body carrying Clause-②: no; the PR body's second line is Clause-②: no; the claim 6136306937 declared no. Right. A describe-only correction in a released package is a fix, so patch is both the floor and the level; no accept set, export or wire shape moves (① item 7), so neither Clause-② arm applies and no ADR-0087 disposition marker is owed. It is the declaration #17203 made for the app token, on the same reading: deleting an advertisement of a root that was never in the accept set narrows nothing an author can write. Check Changeset is success on this head.

③ Boundary flags

The dev report 6137432578 lists open_questions: [], two dispatch_conflicts and two out_of_scope_findings; the ACCEPT 6137462927 names four items. Each is answered:

Check-runs on the head, as read. Read at 2026-10-13T03:35Z: 39 check-runs — 28 success, 5 skipped (Console Pin Gate, Temporal Conformance (live PG + MySQL), the Dogfood Regression Gate matrix shard, Dogfood Verify CLI, Packed-tarball smoke (opt-in)), 6 in progress (Test Core shards 2/6 to 6/6 and Type Check · workspace), 0 failure. Of the seven queue-required names: Lint & Repo Gates, Dogfood Regression Gate, Build Core and Governed Surface Queue Guard are success; Temporal Conformance (live PG + MySQL) is skipped by its path filter; Test Core and TypeScript Type Check are aggregate jobs that report after their shards and lanes (Type Check · source gates, consumer gates and debt ledger are success; workspace is running) and had not yet reported. The PR is draft, mergeable_state: blocked, 280 changed lines, no governed path in its file list. This record does not wait on the running checks; their conclusions are the gate verdicts, and the seat reads them before the queue.

Implemented-by: claude/issue-23036-visiblewhen-roots
Reviewed-by: session_019G5ZnRrTMiwcHG6iZM19zf

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 13, 2026 03:48
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 13, 2026 03:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 02bec6c Oct 13, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23036-visiblewhen-roots branch October 13, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

2 participants