Skip to content

finding(types): AppComponentSchema inherits the renderer's hidden predicate from BaseSchema while its Zod mirror takes the spec's app-catalogue hidden boolean — one name, two meanings #7542

Description

@os-project-manager

Found while widening BaseSchema.hidden under objectui#7455 (maintainer ruling 2026-09-03, option A). Filed unassigned and out of that card's scope: the ruling's scope is BaseSchema on both faces plus the docs row and pins, and this is a different pair.

Part of the same key family as objectui#7530, but ⛔ not a duplicate of it: #7530 asks about the CEL envelope OBJECT form on three keys. This is about the app node taking two different hidden keys, one per face.

The measurement

Measured on @objectstack/spec@17.2.0 (the version the lockfile pins), by resolving AppSchema.shape at runtime:

key declared by the spec's AppSchema? accepts a boolean accepts a string
visible NO — —
disabled NO — —
hidden YES yes no

packages/types/src/zod/app.zod.ts builds the mirror as BaseSchema.extend(SpecAppFields.shape).extend({ ... }), and SpecAppFields excludes six keys — name, label, description, navigation, areas, contextSelectors — with hidden NOT among them. So on the Zod face the spec's hidden lands AFTER the base's and overrides it.

packages/types/src/app.ts declares interface AppComponentSchema extends BaseSchema and does not restate hidden at all, so on the TS face it inherits whatever BaseSchema says.

Why this surfaced now

Before #7455 both faces read boolean and the collision was invisible. #7455 widened BaseSchema.hidden to boolean | string, which moved the TS side of this pair and not the Zod side — so zod-mirror-parity.test.ts turned red naming exactly this pair, and the widening PR seeded a KnownDrift entry recording it.

The same widening applied to visible (objectui#4581) and disabled (objectui#4580 ruling Q3-A) seeded nothing, because the spec's AppSchema declares neither of those keys. hidden is the only one it declares — which is the asymmetry underneath the asymmetry #7455 removed.

The actual question

The two keys collide in NAME and differ in MEANING:

  • the spec's AppSchema.hidden is an app-catalogue flag — whether the app shows in the switcher;
  • BaseSchema.hidden is the renderer's hide predicate, evaluated by SchemaRenderer's shouldHide chain through hasDeclaredPredicate.

Today an author reading AppComponentSchema in TypeScript is told they may write a predicate string; the validator that runs on their metadata refuses it. Whichever way that is resolved, it is a contract decision about two surfaces, not a mechanical repair.

Some directions, ⛔ none of them chosen here:

  1. Keep the spec's key on both faces for the app node and restate hidden?: boolean on the TS interface, so the app node stops advertising the renderer's predicate it never had. Cheapest, and it makes the two faces agree; it also means the app node cannot use the predicate spelling every other node can.
  2. Give the app-catalogue flag its own name upstream in @objectstack/spec and let the renderer's hidden stay inherited. Correct-looking and the most expensive — it is a spec change with its own producers.
  3. Drop hidden from SpecAppFields so the base's widened key survives on both faces. ⛔ Reads easy and is probably wrong: it makes a spec-derived schema accept, by local divergence, a value the spec refuses.

⛔ Do not close this by editing the ledger

The KnownDrift entry seeded by #7455 records the drift with its reason; it is a ratchet, not a waiver. Deleting the entry without changing either face just hides the collision again.

Refs objectui#7455 · objectui#7530 · objectui#4581 · objectui#4580.

Filed by an ObjectUI development agent (Claude Code) during the implementation of objectui#7455; session reference session_01EMrWaQw3XS5DxTHxp4yRyC.

Activity

  1. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:spec / priority:p3 / pm:queue / finding

    锚定 (anchoring):方向 1(在 TS 接口上重述 hidden?: boolean)与方向 3 都落在 packages/types ⇒ domain:spec。方向 2 是上游 spec 改名,跨仓。

    在 origin/main a472b07 上复核 —— 两个面的构造方式成立

    packages/types/src/zod/app.zod.ts:271   const SpecAppFields = specFieldsExcept(SpecAppSchema.shape, [
    packages/types/src/zod/app.zod.ts:282    * `@objectstack/spec/ui` `AppSchema` (see {@link SpecAppFields}). The drift
    packages/types/src/zod/app.zod.ts:285   export const AppComponentSchema = BaseSchema.extend(SpecAppFields.shape).extend({
    
    packages/types/src/app.ts:364           export interface AppComponentSchema extends BaseSchema {
    

    ⭐ zod/app.zod.ts:285 的 BaseSchema.extend(SpecAppFields.shape) 就是机制本身:spec 的键在 base 之后落,所以它覆盖 base 的。而 TS 面 app.ts:364 只写 extends BaseSchema、不重述 hidden ⇒ 继承 base 的。两个面因此对同一个键取了不同定义。

    ⛔ spec 侧的 runtime 解析(AppSchema.shape 里有 hidden、没有 visible / disabled)本席验不了(未装 spec),采信未复核。但那正是卡片论证的支点,接手者请重测。

    ⭐ 卡片对"为什么现在才浮现"的解释,是它最有价值的部分

    Before #7455 both faces read boolean and the collision was invisible. #7455 widened BaseSchema.hidden to boolean | string, which moved the TS side of this pair and not the Zod side.

    以及那条不对称之下的不对称:

    The same widening applied to visible (#4581) and disabled (#4580 ruling Q3-A) seeded nothing, because the spec's AppSchema declares neither of those keys. hidden is the only one it declares.

    ⭐ 三个同族键、同一次加宽、只有一个炸出台账项 —— 这个对照本身就是诊断:碰撞不在加宽里,在"spec 恰好也声明了这一个键"里。

    定级理由

    priority:p3:

    不上 p2:拒绝是响亮的;影响面是单键单节点。

    ⛔ 硬禁止(卡片明写,本席背书并加重)

    ⛔ Do not close this by editing the ledger. The KnownDrift entry seeded by #7455 records the drift with its reason; it is a ratchet, not a waiver. Deleting the entry without changing either face just hides the collision again.

    ⚠️ 这条要特别当心,因为删掉台账项是让测试变绿的最快路径,而它恰好是错的。

    ⛔ 三条方向的定型 —— 差别很大,⛔ 不要按"哪条最快"选

    方向 落点 型 manual floor
    1. TS 面重述 hidden?: boolean packages/types/src/app.ts 收窄 TS 声明使其与验证器一致 ⇒ Bug/tidy 不踩
    2. 上游给 app-catalogue flag 改名 @objectstack/spec,跨仓 协议变更 踩,且最贵
    ⛔ 3. 把 hidden 从 SpecAppFields 排除掉 packages/types 让本地 schema 接受一个 spec 拒绝的值 踩,且方向错误

    ⭐ 卡片对方向 3 的评价我完全同意并复述:"Reads easy and is probably wrong: it makes a spec-derived schema accept, by local divergence, a value the spec refuses." ⛔ 这是最诱人的一条(一行删除、测试变绿),也是唯一会制造新缺陷的一条。

    方向 1 是唯一不踩人工底线的,代价卡片也如实说了:app 节点从此用不了其他节点都能用的谓词拼写。⛔ 分诊席不裁决,但把这个代价摆明,因为它是选 1 还是 2 的唯一实质分歧。

    ⛔ 给执行席的其他边界

    1. ⛔ 不要与 [Decision] The CEL envelope object is accepted on visible / hidden / disabled by the shared evaluator but declared on none of them — declare it on all three, or refuse it on all three #7530 合并。卡片已正确划界:那张问的是三个键上的 CEL 信封对象形式,本卡问的是app 节点在两个面上取了两个不同的 hidden。相邻、不同。
    2. 动手前重测 spec 侧(见上),⛔ 别照抄卡片那张表 —— spec 版本会变。
    3. 若走方向 1,同 PR 删掉 KnownDrift 项(此时删是正确的:漂移真的消失了),⛔ 并在 PR 说明里写清楚"app 节点不支持谓词拼写"这个新事实,否则下一个作者会当它是 bug 报回来。

    ⛔ 分诊席不认领、不派单、不写码、不裁决方向。


    Generated by Claude Code

  3. self-assigned this
    on Sep 5, 2026
  4. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Claim: domain:spec @ objectui execution seat, session session_01BAZFhALsQsGqxui8sNqM8s, claimed 2026-09-05T17:18Z (PM loop round R1, seventh wave). Assignee set by the PM in the same stroke as pm:queue → pm:dispatched; the dev is a subagent of this session (mode:subagent, no session of its own) — identity is the branch.

    Branch: claude/issue-7542-app-hidden-catalogue-flag · worktree ../objectui-issue-7542 · cut from origin/main = 16a725f9 (17:18Z).
    Clause-②: yes — the published TS face narrows on one node: AppComponentSchema.hidden restated as boolean (the spec's app-catalogue flag) instead of the inherited boolean | ExpressionWire; the validator's accept set does not move (it already refuses a predicate on the app node). The PM seat reviews at tier before it lands.
    Container & model: fable (CONTRACT_REVIEW_TIER), size S, mode:subagent.

    Direction: 1, under a declared veto window. No maintainer ruling exists on this card; triage 5548779181 does not rule but classifies the three directions: 1 (restate hidden?: boolean on the TS interface — Bug/tidy, pulls the declaration back to what the validator enforces, off the manual floor), 2 (rename the app-catalogue flag upstream in @objectstack/spec — a protocol change, manual floor, most expensive), 3 (drop hidden from SpecAppFields — makes a spec-derived schema accept a value the spec refuses; ⛔ wrong). Direction 1 is the only one inside the seat's remit, is reversible (one restated member + one ledger row + a pin), and does not preclude 2, which stays open to the maintainer as an upstream card. The seat therefore dispatches 1 as its recommended default; a maintainer objection reverts it. Recorded in the decision batch.

    Scope: packages/types/src/app.ts — restate hidden?: boolean on AppComponentSchema with a docblock saying it is the spec AppSchema's app-catalogue flag (whether the app shows in the switcher), NOT the renderer's hide predicate, that the predicate spelling every other node accepts is refused here on both faces, and citing #7542; packages/types/src/__tests__/zod-mirror-parity.test.ts — delete the KnownDrift entry 'app.zod.ts#AppComponentSchema': 'hidden' (:932) in the same PR (correct now: the drift disappears — ⛔ never without the face moving); new pin; docs row if a page lists hidden for the app node (measure); changeset @object-ui/types: minor. ⛔ app.zod.ts untouched (direction 3 forbidden), ⛔ BaseSchema.hidden untouched (#7455 / #7530), ⛔ not folded with #7530.

    Premise verified on origin/main 16a725f9 at 17:18Z: zod/app.zod.ts:279 SpecAppFields = specFieldsExcept(SpecAppSchema.shape, ['name','label','description','navigation','areas','contextSelectors']) — hidden not excluded; :293 AppComponentSchema = BaseSchema.extend(SpecAppFields.shape).extend({…}) — the spec's hidden lands after the base's; app.ts export interface AppComponentSchema extends BaseSchema restates no hidden; base.ts:376 hidden?: boolean | ExpressionWire and base.zod.ts:200 z.union([z.boolean(), ExpressionWireSchema]) (the #7530 CEL envelope — the TS face is wider than the card's boolean | string reading now); KnownDrift row at zod-mirror-parity.test.ts:932, seeded by #7455. The spec-side reading (AppSchema.shape.hidden accepts boolean, refuses string; no visible / disabled) is the card's and is NOT re-verified by the seat (node resolution of the pin from the shared checkout failed) — the dev re-measures it through the installed pin first.

    File surface (region level under the 10:5xZ ruling, comment 5551274423): app.ts — the AppComponentSchema block; zod-mirror-parity.test.ts — the one KnownDrift row; new pin; one docs row if any; changeset. Hot files: zod-mirror-parity.test.ts is shared with PR #7776 (#7279, header / UnmirroredDeclared docblock / SPEC_DERIVED_PAIRS comment / a new describe — different regions; it lands first) — merge main before opening and after it lands. Half-state patrol anchor: none exists in objectui (#5986).


    Generated by Claude Code

  5. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Claim (dev): branch claude/issue-7542-app-hidden-catalogue-flag · worktree ../objectui-issue-7542 · cut from origin/main = 669d71bf (17:25Z), origin/main 1b69ca58 (PR #7776) merged before opening · dev of session session_01BAZFhALsQsGqxui8sNqM8s's dispatch (mode:subagent, no session of its own — identity is the branch). Assignee is the PM's and is not touched.

    Premise re-verified on the tree, all five points of the claim hold; the spec-side reading that the seat could not take is now measured through the installed pin (@objectstack/spec@17.2.0, ui entry): AppSchema.shape.hidden is optional(boolean), accepts true / false, refuses a string and the CEL envelope object with invalid_type at hidden; visible and disabled are absent from the shape (30 keys; controls label and active present). Both entry paths (AppComponentSchema.safeParse, safeValidateSchema) refused the predicate string and the envelope at hidden before editing and accepted the booleans — recorded in the PR body. Direction 1 implemented; app.zod.ts and BaseSchema.hidden untouched; the KnownDrift row goes only because the face moved (both directions ablated).


    Generated by Claude Code

  6. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 7542,
      "status": "done",
      "branch": "claude/issue-7542-app-hidden-catalogue-flag",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7782",
      "head": "1d3074f8 (= 6bd44196 implementation + merge of origin/main 1b69ca58 / PR #7776; base 669d71bf, whose packages/types/src/app.ts blob 14d12b7c equals 16a725f9's)",
      "premise_still_valid": true,
      "clause_2": "yes — the published TS face narrows on one node: AppComponentSchema.hidden restated boolean (was the inherited boolean | ExpressionWire); the validator's accept set does not move",
      "line_budget": "n/a",
      "tier": "CONTRACT_REVIEW_TIER; label needs:contract-review attached by MCP label union (REST additive endpoint 403 in this session), read back as [package: types, tests, needs:contract-review] = the union, nothing stripped; PR left draft; not governed (0 of 4 paths, objectstack d701e65)",
      "summary": "Direction 1 implemented under the seat's declared veto window: packages/types/src/app.ts restates hidden?: boolean on the AppComponentSchema block with a docblock naming the collision (spec AppSchema app-catalogue flag taken by reference through SpecAppFields vs BaseSchema's renderer predicate), the refusal of the predicate spelling on both faces, the in-repo reader (filterActiveApps reads hidden !== true, never evaluates), direction 1 as the ruling and direction 2 as the open alternative. The KnownDrift row 'app.zod.ts#AppComponentSchema': 'hidden' is deleted in the same PR because the drift is gone (both faces boolean); header counts 42/63 to 41/62 with a history sentence added in the cumulative style and the dangling 'see that entry' pointer repointed. New pin app-hidden-catalogue-flag-7542.test.ts (8 tests). Changeset @object-ui/types minor. app.zod.ts and BaseSchema.hidden untouched; docs measured, no app-node hidden row exists (the three app-schema.mdx hits are AppMenuItem.hidden), so none moved. Assignee not touched; the PM's assignee field was empty on the payload read, as expected for the shared identity.",
      "spec_reading": "@objectstack/spec@17.2.0 ui entry resolved from packages/types/node_modules (dist/ui/index.js): AppSchema.shape has 30 keys; hidden present, optional(boolean), described 'Hide from the App Switcher; the shell surfaces hidden apps via the avatar menu instead (navigation only — never an access gate)'; visible ABSENT, disabled ABSENT (strict schema refuses them as unrecognized_keys); controls label and active present. safeParse hidden true/false OK; predicate string REFUSED invalid_type at hidden 'expected boolean, received string'; CEL envelope object REFUSED invalid_type at hidden 'expected boolean, received object'.",
      "entry_path_verdicts": {
        "before_669d71bf": "AppComponentSchema.safeParse: hidden true/false OK; predicate string REFUSED invalid_type at path hidden; envelope REFUSED invalid_type at path hidden (received object). safeValidateSchema: true/false OK; string REFUSED root invalid_union with the app arm carrying the hidden issue; envelope REFUSED the same way. Controls: BaseSchema and ButtonSchema accept string and envelope on hidden; safeValidateSchema accepts a button with the string.",
        "after_1d3074f8": "runtime identical (validator unchanged); declaration moved: AppComponentSchema['hidden'] is boolean | undefined (invariant Equal pinned), a predicate string on a typed app document is a compile error (ts-expect-error pinned)."
      },
      "files_changed": [
        "packages/types/src/app.ts (+44: hidden?: boolean with docblock, AppComponentSchema block only)",
        "packages/types/src/__tests__/zod-mirror-parity.test.ts (-28 row+docblock, header count line 42/63 to 41/62 + history sentence, one pointer repointed)",
        "packages/types/src/__tests__/app-hidden-catalogue-flag-7542.test.ts (new, 265 lines, 8 tests)",
        ".changeset/app-hidden-catalogue-flag-7542.md (@object-ui/types: minor)"
      ],
      "gates": [
        {"command": "pnpm --filter '@object-ui/types^...' build", "exit": 1, "head": "669d71bf", "verdict": "ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT — closure is @object-ui/test-support with no build script; nothing to build (n/a, not red)"},
        {"command": "pnpm --filter @object-ui/types build", "exit": 0, "head": "1d3074f8", "verdict": "dist completeness: 1 package(s) complete (124 emitted files verified)"},
        {"command": "pnpm --filter @object-ui/types type-check (tsc, tsconfig.examples.json, tsconfig.test.json)", "exit": 0, "head": "1d3074f8", "verdict": "clean"},
        {"command": "pnpm exec vitest run --maxWorkers=2 packages/types/", "exit": 0, "head": "1d3074f8", "verdict": "Test Files 126 passed (126) · Tests 2233 passed (2233)"},
        {"command": "pnpm --filter @object-ui/types lint", "exit": 0, "head": "1d3074f8", "verdict": "270 problems (0 errors, 270 warnings) — pre-existing no-explicit-any, none in the four touched files"},
        {"command": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "1d3074f8", "verdict": "3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/app-hidden-catalogue-flag-7542.md"},
        {"command": "node scripts/check-changeset-fixed.mjs · check-changeset-no-major.mjs · check-changeset-overwrite.mjs", "exit": "0 / 0 / 0", "head": "1d3074f8", "verdict": "green (No changeset declares a major bump; No pre-existing changeset was modified or deleted)"},
        {"command": "node scripts/check-control-bytes.mjs", "exit": 0, "head": "1d3074f8", "verdict": "check-control-bytes: OK (scanned 6342 tracked text file(s); skipped 85 binary)"},
        {"command": "pnpm check:spec-symbols · check:doc-types · check:doc-fences", "exit": "0 / 0 / 0", "head": "1d3074f8", "verdict": "green"},
        {"command": "node /home/user/objectstack/scripts/pm/check-governed-merges.mjs --test (final four paths)", "exit": 0, "head": "objectstack d701e65", "verdict": "0 of 4 path(s) hit the register — NOT governed"},
        {"command": "pnpm --filter @object-ui/app-shell type-check (read-only consumer sweep)", "exit": 2, "head": "6bd44196", "verdict": "NOT MEASURED — 934 TS2307 'Cannot find module @object-ui/components…' errors: app-shell resolves 29 sibling packages via dist declarations absent in a fresh worktree; zero errors mention hidden or AppComponentSchema; declared narrowing, CI measures it"},
        {"command": "node scripts/check-doc-links.mjs", "exit": "not run", "head": "-", "verdict": "no docs row moved"},
        {"command": "CI on PR #7782", "exit": "in_progress", "head": "1d3074f8", "verdict": "not awaited (report at PR-open time)"}
      ],
      "tests": "Targeted (pre-merge, 6bd44196): vitest app-hidden-catalogue-flag-7542 + zod-mirror-parity → 2 files, 26 tests passed. Final union at 1d3074f8 under the shared verify lock (shared-box seconds, 33s held): build 0, type-check 0, vitest packages/types/ 0 (126 files, 2233 tests). Ablation at 6bd44196 under the lock, trap-restored, absolute paths, blob-hash proof — Leg A: app.ts checked out from 669d71bf (git hash-object 14d12b7c = base blob, 'hidden?: boolean;' count 0, docblock marker count 0) → tsc -p tsconfig.test.json exit 2 with exactly 3 errors: app-hidden-catalogue-flag-7542.test.ts(99,3) TS2344 'false' does not satisfy 'true' (the Equal leg), (186,5) TS2578 unused ts-expect-error, zod-mirror-parity.test.ts(1849,14) TS2322 '\"app.zod.ts#AppComponentSchema\"' is not assignable to 'never' (the parity file naming the pair); vitest on the two files stayed green 26/26 (mutation is on the TS face; runtime legs measure the mirror — expected direction, reported as observed); restored via git checkout HEAD -- PATH: hash b4010c1e = HEAD blob, count 1, git diff HEAD empty. Leg B: the KnownDrift row put back from 669d71bf with the face narrowed (hash 6a35c73e = base blob, row count 1) → tsc exit 2 with 1 error: zod-mirror-parity.test.ts(1871,14) TS2322 naming the pair; vitest exit 1, 1 failed | 25 passed (the pin's ledger leg); restored: hash 9147eee6 = HEAD blob, row count 0, git diff HEAD empty, git status clean. Resolution path is source (../app, ../zod/app.zod, tsconfig.test.json) — no dist in the loop, dist preflight n/a.",
      "mcp_calls": "9 — create_pull_request, add_issue_comment (claim), issue_read get_labels on the PR number (failed: not resolvable as an Issue), pull_request_read get (body read-back), issue_read get_comments (claim read-back), issue_write update (label union), pull_request_read get (label read-back), add_issue_comment (this report), issue_read get_comments (report read-back). Issue body + triage + PM claim read through the zero-quota public-page payload (frontTimelineItems hasNextPage false, 15 items); REST read probe returned 403 (GitHub access not enabled for this session).",
      "deviations": [
        "REST channel 403 for this session: reads went git → public payload; label write went MCP union with compare read-back instead of the additive REST endpoint; declared here as the channel switch.",
        "app-shell read-only type-check NOT MEASURED locally (dist closure of 29 packages absent in the fresh worktree; build does not fit the foreground cap); substitute evidence: grep sweep — no source in app-shell/layout/console authors hidden on an app-typed document, the only AppComponentSchema consumer (useNavigationSync.ts) writes no hidden, and filterActiveApps reads hidden !== true.",
        "Ablation revert target was 669d71bf, not 16a725f9 as the dispatch spelled it: app.ts blob is identical at both (14d12b7c), so the leg is equivalent; the parity file was reverted to 669d71bf for leg B.",
        "PR body ends with the system-reminder attribution form (🤖 Generated with … + session URL line); the platform appended a second session-URL footer on create (the known create-time behaviour) — recorded, not repaired.",
        "The vitest half of leg A stayed green — the dispatch's 'the parity suite goes red naming the pair' was observed under tsc -p tsconfig.test.json (type-level LedgerMismatch), not under vitest; both instruments reported."
      ],
      "open_questions": [
        {"question": "Direction 2 (rename the app-catalogue flag upstream in @objectstack/spec so the app node can inherit the renderer's predicate) remains open to the maintainer; nothing in this PR precludes it.", "options": ["A — leave direction 1 landed and file/keep an upstream spec card for the rename", "B — veto direction 1 and take direction 2 first (cross-repo, with producers)"], "recommendation": "A, because the in-repo readers and the spec producer already treat the key as a boolean, direction 1 is one member + one row + one pin and reversible by one revert, and direction 2 is a protocol change with its own producers"}
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Contract review — PASS at CONTRACT_REVIEW_TIER (in-seat, 2026-09-05T17:50Z) — PR #7782, head 1d3074f8

    Implemented-by: dev subagent of this session's dispatch, branch claude/issue-7542-app-hidden-catalogue-flag (mode:subagent, no session of its own); dev report 5553657368
    Reviewed-by: session_01BAZFhALsQsGqxui8sNqM8s (domain:spec @ objectui seat; served tier read via get_session at 17:49Z: session_context.model and last_served_model both claude-fable-5-1)

    Diff read by ref against origin/main 1b69ca58 (four files, +324 / −30; the app.ts hunk, the parity-file hunk, the changeset and the pin's describe/it list re-read off the branch tip with git diff origin/main... at 17:49Z; app.zod.ts untouched — verified, 0 lines).

    ① Derived judgments — each accept-set and public-surface change named and judged

    • packages/types/src/app.ts, AppComponentSchema block only: hidden?: boolean; restated with a docblock naming the collision (spec AppSchema.hidden app-catalogue flag taken by reference through SpecAppFields vs BaseSchema's renderer predicate), the spec reading through the installed pin (AppSchema.shape.hidden = z.boolean().optional(), the spec's description quoted; visible / disabled absent — which explains why finding(types): BaseSchema under-declares visible (predicate string) and ariaLabel (I18nLabel) — the renderer supports both, five test fixtures now cast past the gap #4581 / finding(types): two competing SchemaNode declarations — core's interface vs types' union #4580's widenings reached this node and finding(types,docs): BaseSchema.hidden is declared boolean, but the renderer evaluates it as a predicate — the third key on visible/disabled's evaluated path, never widened #7455's did not), both refusals measured before the edit (safeParse and safeValidateSchema), the in-repo reader (filterActiveApps keeps an app out of the launcher on hidden !== true, never evaluates), direction 1 as the ruling and direction 2 as the open alternative. Every other member kept. Accept set: the TS face narrows on one node (a predicate string / CEL envelope on a typed app document is now a compile error); the validator's accept set does not move — it refused those spellings at path hidden before and after, on both entry paths, measured with BaseSchema / ButtonSchema controls that still accept them. This is direction 1 exactly as claimed: the declaration pulled back to what the validator has enforced all along; ⛔ direction 3 not taken (app.zod.ts untouched, verified), BaseSchema.hidden untouched on both faces.
    • zod-mirror-parity.test.ts: the KnownDrift row 'app.zod.ts#AppComponentSchema': 'hidden' and its docblock deleted in the same PR — correct, the drift is gone (both faces boolean), and leg B of the ablation proves the ledger refuses the deletion alone (row restored with the face narrowed → TS2322 naming the pair + the pin's ledger leg red). Header counts 42 / 63 → 41 / 62 with a history sentence ADDED in the cumulative style; the dangling "see that entry" pointer re-pointed. No conflict with PR test(types): re-derive ObjectViewSchema's side of the #6058 split as LOCAL and pin the header counts (objectui#7279) #7776's regions (merged before opening, clean). Triage's hard prohibition honoured.
    • New pin app-hidden-catalogue-flag-7542.test.ts (8 tests): invariant Equal of AppComponentSchema['hidden'] with boolean | undefined; BaseSchema['hidden'] still boolean | ExpressionWire | undefined and ButtonSchema['hidden'] equal to it (controls); a @ts-expect-error on a predicate string authored on a typed app document; the mirror accepting true / false and refusing the string and the envelope AT PATH hidden on both entry paths; controls on BaseSchema, ButtonSchema and the union path with a button; the spec reading live through the pin with control key active; the KnownDrift row absent off disk beside a control that the reader sees the ledger and the pair stays in MIRRORS. Ablation: leg A (app.ts reverted to the base blob by hash) → tsc -p tsconfig.test.json exit 2 with exactly three errors (the Equal leg, the unused @ts-expect-error, the parity file's TS2322 naming the pair) while the runtime legs stay green — the right asymmetry for a TS-face-only change, reported as observed; leg B as above; restores proven by blob hash, git diff HEAD empty. Adequate and red-first on both instruments.
    • Docs: measured, no row exists for hidden on the app node (the three app-schema.mdx hits are AppMenuItem.hidden) — none moved; check-doc-links not run for that reason. Accepted.
    • Parity suite green on the merged head (126 files / 2233 tests); three tsc projects green; lint 0 errors on the four touched files.

    ② semver — .changeset/app-hidden-catalogue-flag-7542.md: @object-ui/types: minor, naming the collision, the ruling direction, the TS-only breaking edge (a predicate on an app node no longer type-checks; the app node cannot use the predicate spelling), that authored JSON metadata is unaffected (the validator never admitted it), the reader's agreement, and the removed KnownDrift row. A TS-face narrowing is the minor class this seat has graded all shift; consistent. The card's own requirement (triage boundary 3: write down the new fact that the app node does not support the predicate spelling) is met in the changeset, the docblock and the PR body.

    ③ Boundary flags — open_questions: one — direction 2 (upstream rename) stays open; the dev recommends A (leave direction 1 landed, keep an upstream card if the maintainer wants it). That is the veto-window arrangement the claim declared (5553481421), recorded in the decision batch as item 8; no new maintainer item. Deviations reviewed and accepted (REST 403 channel fallbacks with read-backs; the app-shell read-only type-check NOT MEASURED — a fresh worktree lacks the 29 sibling dists, 934 unrelated TS2307s, zero mentioning hidden / AppComponentSchema, with the grep sweep standing in and CI's farm measuring it; ablation revert target 669d71bf whose app.ts blob equals 16a725f9's; the pnpm --filter '@object-ui/types^...' build non-red — the closure has no build script). No out-of-scope findings. Nothing touches content/docs/releases/.

    Verdict: PASS for PR #7782. Same stroke: needs:contract-review is cleared on the PR with a provenance comment citing this PASS. Landing follows once every check run on 1d3074f8 is completed: success (farm started 17:45Z; Type Check, four shards, Lint, Doc Snippet Type Check, Bundle Analysis, Skill Example Check, README Export Check in progress at 17:48Z) — flip re-check armed. Head moves ⇒ the label is re-hung and the delta re-reviewed.


    Generated by Claude Code

  8. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    Landing — PR #7782 (2026-09-05T18:01Z). Pre-landing three: ① flipped ready at 18:01Z after the PASS above (comment 5553667716) and the carrier cleared on the PR with provenance (PR comment 5553670229); every check run on 1d3074f8 was completed: success or skipped at 18:00Z (32 runs), branch tip re-read 1d3074f8 at 18:00:52Z; ② auto-merge enabled, method SQUASH (the API echoes an empty method string — known platform reading); ③ queue probe running against refs/heads/gh-readonly-queue/main/* (the queue was empty at 18:00Z after PR #7777 landed; origin/main = 554f2b6b). This PR carries Fixes #7542: the card closes on its squash; pm:dispatched is stripped once MERGED is read twice (PR merged: true + origin/main containing the squash). Direction 1 lands under the veto window declared on the claim; direction 2 (upstream rename) stays open to the maintainer — recorded in the decision batch as item 8.


    Generated by Claude Code

  9. os-justin commented on Sep 5, 2026

    @os-justin
    Collaborator

    MERGED (2026-09-05T18:17Z) — PR #7782 landed through the merge queue as squash 2fcefb95 on main. Three readings: PR merged: true (merged_at 18:16:31Z); this card's closed_by_pull_requests reference to PR #7782 reads MERGED and the card closed at 18:16:32Z; git log origin/main at 18:17:10Z shows 2fcefb95 feat(types): restate AppComponentSchema.hidden as the spec's app-catalogue boolean (objectui#7542) (#7782) at the tip. pm:dispatched stripped in this stroke (remaining: finding, priority:p3, domain:spec). Direction 1 is on main under the veto window declared on the claim (5553481421): a maintainer objection reverts it by one revert (the restated member, the removed KnownDrift row, the pin, the changeset); direction 2 (renaming the app-catalogue flag upstream in @objectstack/spec) stays open and is recorded in the seat's decision batch as item 8. Session session_01BAZFhALsQsGqxui8sNqM8s.


    Generated by Claude Code

  10. os-justin commented on Sep 6, 2026

    @os-justin
    Collaborator

    Maintainer ruling recorded — direction 2 = A: direction 1 stays landed; ⛔ no upstream rename card. The veto window declared on the claim is now closed.

    Recorded by the domain:spec @ objectui seat, session session_01BAZFhALsQsGqxui8sNqM8s, 2026-09-06T02:30Z. Provenance (who / verbatim / where): the maintainer (os-justin), live PM chat with this session, replying to decision batch 2 of 2026-09-05T23:1xZ, in which this card was item 8 — tabled with the seat's recommendation A and the four-facet analysis in Chinese. Verbatim reply: 「第二批(4 项,契约形状族) 帮我处理,全部同意」.

    What this settles. PR #7782 (2fcefb95, merged 2026-09-05T18:16Z) landed direction 1 under a declared veto window: AppComponentSchema.hidden is restated on the TypeScript face as the spec AppSchema's app-catalogue boolean, matching what the validator has always enforced; the KnownDrift row is gone because the drift is gone. The window closes with this ruling — direction 1 is now a decision, not a default awaiting objection.

    Direction 2 is declined, on the record: no card is filed upstream in @objectstack/spec to rename the app-catalogue flag so the app node could inherit the renderer's hidden predicate. The four facets as tabled: no measured pull (the only in-repo reader, filterActiveApps, tests hidden !== true; the spec producer writes a boolean; nothing asks to hide an app by expression), one name one meaning is already achieved, the TypeScript face and the validator now agree so an authored predicate is a compile error at the site, and a protocol change with its own producers for a hypothetical need is capability expansion the startup-focus rule refuses.

    Consequence for authors, stated so it is not re-reported as a bug: on an app node, hidden is the app-switcher catalogue flag and takes true / false only; the predicate spelling every other node accepts (hidden: "user.role == 'admin'", or the CEL envelope) is refused on both faces there. That is recorded in the member's docblock, in PR #7782's changeset and now here. The card stays closed; no label change (finding, priority:p3, domain:spec retained).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanefindingpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions