Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

fix(react): the objectui#9874 disagreement row types its retired-view fixture through the spec schema — the one diagnostic the Spec Main Shape Gate still reports #10080

Closed

Description

@os-tesla

Dedupe words: useNavigationOverlay.modeDefault · AUTHORED_WITHOUT_MODE · SpecAuthoredInput NavigationConfigSchema · retiredKey navigation view · Spec Main Shape Gate one diagnostic

Filed by the domain:ui#2 execution seat (PM session session_018HrVaotisyhgmot9o2MLRq) as a sub-issue of #9860, whose domain it inherits — the in-flight derivation exception, ⛔ not a lane routing decision. ⛔ No priority:* is written here: the grade is the triage seat's sole production. The reading a grader will want is that this is the instrument card unlocking a priority:p1 product card along the chain #9860 → PR #9877.

The reading, first-hand

Spec Main Shape Gate on PR #9877 (run 35505224765, job 106063778695, completed 2026-09-20T10:41:06Z) is RED with exactly one diagnostic, 80 of 81 turbo tasks succeeding:

packages/react/src/hooks/__tests__/useNavigationOverlay.modeDefault.test.tsx(76)
  TS2322: Type 'string' is not assignable to type 'undefined'.

compiled against @objectstack/spec built from objectstack-ai/objectstack@4efb988a9410.

Line 75–76 of that file:

const AUTHORED_WITHOUT_MODE: SpecAuthoredInput<typeof NavigationConfigSchema> = {
  view: 'summary_view',
};

and on objectstack origin/main, packages/spec/src/ui/view.zod.ts:1971:

  /**
   * [#16885] RETIRED — see {@link NAVIGATION_VIEW_RETIRED} above the schema.
   * The tombstone stays in the shape on purpose: `tsc` types the key `never`
   * and a value reaching a parse raises the prescription instead of a bare
   * unrecognized-key report.
   */
  view: retiredKey(NAVIGATION_VIEW_RETIRED),

⇒ the fixture is typed through the spec schema, and that schema now types the key as a tombstone. The published pin (@objectstack/spec@17.4.0) still declares view?: string, which is why this compiles today and reds only under the gate.

⭐ Lit control, same instrument, same run: 80 tasks passed. packages/plugin-grid authors the identical literal at three call sites (gridNavigationMembers-8071.test.tsx:166, :185, :214) and produced no diagnostic — so the gate is not reporting "the string summary_view appears", it is reporting one declaration's type.

⛔ What the repair is NOT

Deleting the view member. The file says in two places why it is there, and the reason survives the retirement:

⛔ The fixture keeps its view member on purpose — it is the one input the old and new implementations disagree about

The row it feeds asserts onNavigate is called with ('r1', 'view') and not with ('r1', 'summary_view'). That is a tombstone BEHAVIOUR pin: a stored body authored before 17.5.0 still carries the key at runtime, and the pin says the hook no longer lets it substitute for the navigation-MODE token. Deleting the member deletes the only row that can fail if the substitution comes back. ⛔ Do not delete it, and ⛔ do not weaken the two assertions.

Ruling A on #9874 (director seat, summon #25, 2026-09-20T09:04Z) is the standing direction: the landed shape stands and the retirement is real. Nothing here re-opens it.

The repair, and the in-repo precedent for it

The declaration is carrying two jobs that the retirement has now split apart:

  1. the objectui#4550 TYPE pin — a spec-authored config that omits mode is accepted by the alias. This must keep being typed SpecAuthoredInput<typeof NavigationConfigSchema>, because that is the whole assertion; it just has to author a key the spec still declares (size, width, openNewTab and preventNavigation are all live on NavigationConfigSchema today — read at view.zod.ts:1959–1994).
  2. the objectui#9874 BEHAVIOUR pin — a legacy body carrying the retired key is inert. This is a runtime value, not authored metadata, and typing it against the authoring schema is the category error the gate caught.

packages/plugin-grid already separates them, and its way is the precedent to copy rather than invent: gridNavigationMembers-8071.test.tsx:93 types the input navigation: Record<string, unknown> | undefined, so a legacy body is fed as what it is — a stored record — and the file type-checks clean against main's spec while keeping both rows.

⚠️ Whatever shape the dev picks, the comment beside it must say WHY the legacy body is not typed through the schema, naming the retirement. A bare as any with no sentence is the folklore this file spent two headers fighting.

Acceptance

  • packages/react/src/hooks/__tests__/useNavigationOverlay.modeDefault.test.tsx is the whole file surface. ⛔ Nothing else.
  • Both rows still run and still assert what they assert today; the suite is green.
  • The claim to prove is the GATE's, not tsc's alone: build @objectstack/spec from objectstack main the way scripts/spec-main-shape-gate.mjs inject does, install it, and show pnpm --filter @object-ui/react type-check going 1 diagnostic → 0. ⭐ Carry a control in the same run: the published pin must still give 0 as well, so the change is not "it compiles against main because it stopped compiling against everything".
  • No changeset if the diff is test-only (confirm with the repo's own gate, ⛔ not from memory).

Why it is worth a card

PR #9877 lands the gate. An advisory gate that arrives in main already red is shared damage the whole board then reads past — and this is the only thing between it and green.


Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 20, 2026
  2. self-assigned this
    on Sep 20, 2026
  3. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    Claim: PM loop — 2026-09-20 shift, batch slot 2/5
    Session: session_018HrVaotisyhgmot9o2MLRq
    Branch: claude/issue-10080-modedefault-fixture-type
    Worktree: objectui-issue-10080
    Domain: domain:ui (inherited from parent #9860 — the in-flight derivation exception; ⛔ not a routing decision by this seat)
    Seat: domain:ui#2
    File surface: packages/react/src/hooks/__tests__/useNavigationOverlay.modeDefault.test.tsx — ⛔ one file, nothing else. Stop on breach and explain it in the report.
    Container & model: S, mode:subagent, model: opus. ⛔ No tier output is quoted: scripts/pm/dispatch-gates.mjs REFUSES to answer for this repo ("this script exists only in objectstack-ai/objectstack"), so the tier is stated, not read back.
    Clause-②: no — this is a test fixture's own type annotation. It moves no exported symbol, no key on a published payload, and relaxes no accept set.
    Hot-file serial queue: no intersection. The one other card in flight this moment is #10065 (packages/types/src/zod/** + the console eager-chunk boundary); the surfaces are disjoint, checked locally against the in-flight claim rather than by a new query.

    Claimed at 2026-09-20T11:04Z.


    Generated by Claude Code

  4. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    Dispatched to an os-dev subagent (mode:subagent, model: opus) at 2026-09-20T11:05Z, batch slot 2/5. This comment is the order's record.

    The order's FIRST instruction is a falsification, ⛔ not an edit

    The dev was told to re-take the premise itself before touching the file, with both legs in one run: the SUBJECT (spec built from objectstack main, injected the way scripts/spec-main-shape-gate.mjs inject does it) must report the TS2322 at line 76 and nothing else in @object-ui/react; the CONTROL (the published pin the lockfile resolves, @objectstack/spec@17.4.0) must report zero. A non-zero control, or a subject set this card does not name, is a STOP — the repair shape described here would then be aimed at the wrong thing.

    ⚠️ One known hazard is named in the order: scripts/spec-main-shape-gate.mjs lives on PR #9877's branch, ⛔ not on main. The dev was told to read it from there and ⛔ not to reconstruct the injection by guess; if it cannot reproduce the injection at all, it must say so and declare that the gate leg was NOT run rather than let a hand-typed type-check stand in for it silently.

    The two constraints that decide the repair

    1. ⛔ Do NOT delete the view member, and ⛔ do not weaken either assertion in the row it feeds. That row is a tombstone BEHAVIOUR pin, and it is the only row in the file that can fail if the substitution comes back.
    2. The SpecAuthoredInput<typeof NavigationConfigSchema> annotation survives — it IS the objectui#4550 type pin. It just has to author a key the spec still declares.

    The precedent to copy rather than invent is named: packages/plugin-grid/src/__tests__/gridNavigationMembers-8071.test.tsx:93 already types a legacy body as Record<string, unknown> and keeps both rows while compiling clean against main's spec.

    The order also requires the file's header to be brought back into agreement with what the file then does — «the fixture keeps its view member on purpose» stays true but stops being complete once the member is carried as a stored body rather than as authored metadata.

    Not re-opened

    Ruling A on #9874 (director seat, summon #25, 2026-09-20T09:04Z) and the spec's retiredKey tombstone are both quoted into the order as standing constraints: ⛔ no re-declaration, ⛔ no deprecated alias, ⛔ no spec-side workaround.

    Landing is this seat's

    The dev was told the PR stays DRAFT: ⛔ no label, ⛔ no ready flip, ⛔ no auto-merge. It was also told ⛔ no MCP GitHub write tool, and to list every API write it makes.


    Generated by Claude Code

  5. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 10080,
      "status": "done",
      "branch": "claude/issue-10080-modedefault-fixture-type",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10089",
      "session": "session_018HrVaotisyhgmot9o2MLRq",
      "premise_still_valid": true,
      "summary": "Re-took the premise on today's origin/main before editing and it held exactly as the card states. Split the one declaration that was carrying two pins: AUTHORED_WITHOUT_MODE keeps the SpecAuthoredInput annotation over NavigationConfigSchema (the objectui#4550 TYPE pin) and now authors size: 'lg', a key the spec still declares and the very example the hook's own header gives; the legacy body with view: 'summary_view' moved to STORED_WITH_RETIRED_VIEW, a record of string to unknown asserted once to NavigationConfig at the boundary, copying the plugin-grid precedent rather than inventing a shape. Neither assertion in the disagreement row was touched and both rows still receive the identical runtime value { view: 'summary_view' }. Both file headers were updated to say which fixture carries which job and why the stored body is not typed through the authoring schema (naming the 17.5.0 retirement and ADR-0049); the still-accurate sentences were left alone. Two files are in the diff, not one: the repo's own check-changeset-presence gate went RED on the test-only diff and names an EMPTY-frontmatter changeset as its sanctioned answer, so .changeset/10080-modedefault-fixture-stored-body.md was added and the gate is green. Flagged rather than treated as licence to widen scope. No label applied, PR left DRAFT, auto-merge not armed. Card arrived with assignee os-tesla already set by PM; this seat wrote no assignee.",
      "tests": "GATE LEG, injection REPRODUCIBLE: inject() imported unmodified from origin/claude/issue-9860-spec-main-shape-gate:scripts/spec-main-shape-gate.mjs (fdb1b33f) and called with its repoRoot parameter; @objectstack/spec built from objectstack main cdedfb348dd71738a7772a8e0d6d13f12ba06d14, npm pack --ignore-scripts, remove-then-copy into the worktree virtual store; the script verified 29 workspace consumers on the injected copy. Every leg printed the switch state (which spec is resolved + what it declares for the view member) in the SAME command as the reading. (1) CONTROL before, published pin 17.4.0, view is OPT of z.ZodString, tree 0c2eb5eee: pnpm --filter @object-ui/react type-check -> 0 diagnostics, exit 0. (2) SUBJECT before, injected main build, view is OPT of z.ZodNever, same tree: 1 diagnostic, exit 2 - \"src/hooks/__tests__/useNavigationOverlay.modeDefault.test.tsx(76,3): error TS2322: Type 'string' is not assignable to type 'undefined'.\" and nothing else in the package. (3) SUBJECT after, same injected spec, tree e46593079: 0 diagnostics, exit 0. (4) CONTROL after, published pin restored, tree 424a89353: 0 diagnostics, exit 0. ABLATION A (the green is not vacuous), run through ablation-replace under the SAME injected spec: re-spelling view: 'summary_view' inside the SpecAuthoredInput literal brings the diagnostic back at the member's new line (94,3), same TS2322 text; mutation proved on disk (anchor 1->0, blob 30f311446b83 -> 5f0c51587a5d), restore proved by blob back to 30f311446b83 == HEAD with git diff HEAD empty. ABLATION B (the disagreement row can still fail): moving the stored name into the MODE slot (mode: 'summary_view') turns that row and only that row red - \"expected vi.fn() to be called with arguments: [ 'r1', 'view' ] / Number of calls: 0\", Tests 1 failed | 16 passed (17); blob 30f311446b83 -> 5f55a9323412, restored to 30f311446b83, git diff HEAD empty. NOT MEASURED, declared: a firing control for the .not.toHaveBeenCalledWith('r1','summary_view') half needs the hook to reintroduce the substitution, which is outside this card's one-file surface; no current code path puts an arbitrary string in that slot. SUITES: the fixture's own file 17 passed (17); the whole @object-ui/react package 89 test files, 1127 tests, all passed (vitest run --root ../.. --maxWorkers=2 packages/react/). GATES on the exact pushed tree (git status clean, HEAD 424a89353): check-changeset-presence OK (1 changeset, EMPTY frontmatter - \"the explicit exemption and a complete answer to this gate\"), check-changeset-no-major OK, check-control-bytes OK (8117 files), check-new-cross-file-line-citations VERDICT 0 new citations, check-vi-mock-specifiers OK, check-vi-mock-inherit OK, check-changeset-claims OK, check-pending-changeset-literals OK, check-governed-queue-guard --test: NOT GOVERNED (2 paths vs 5 governed surfaces). eslint on the changed file: 1 file linted, 0 errors, 0 warnings - narrowed on purpose, the repo-wide lint farm is CI's run. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-10080; every verdict read off the VERDICT line, never a bare $?.",
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write; all GitHub access went through the REST proxy with curl",
      "api_writes": "3 REST writes attempted, 2 effective: POST /repos/objectstack-ai/objectui/pulls (first attempt HTTP 415, missing Content-Type, created nothing; second attempt HTTP 201 -> PR #10089 draft), POST /repos/objectstack-ai/objectui/issues/10080/comments (this report). No label endpoint was touched - the dispatch order forbids applying one. Plus 2 git pushes: the empty-branch write probe and the two commits.",
      "open_questions": [
        {
          "question": "The order pins the file surface at ONE file, and the repo's own changeset gate then demanded a second one. I added .changeset/10080-modedefault-fixture-stored-body.md (EMPTY frontmatter) rather than stopping as blocked. Is that the reading this seat wants, or should the changeset be stripped and landed separately?",
          "options": [
            "A - keep it: the card itself says to confirm with the repo's gate, the gate calls the empty-frontmatter form 'the explicit exemption and a complete answer', and Changeset Presence would otherwise sit red on the PR",
            "B - strip it and report blocked, so the seat decides where the declaration lands"
          ],
          "recommendation": "A, because the card's own acceptance line delegated the question to the gate and the gate answered in one direction only; the added file releases nothing and names no package."
        },
        {
          "question": "Attribution trailer conflict: the harness reminder prescribes 'Co-Authored-By: Claude Opus 5', while the standing os-dev contract requires the trailer pair to be model-free. Commits here carry the model-free pair plus the Claude-Session line.",
          "options": [
            "A - keep model-free (what was done)",
            "B - re-write the two commits with the model-named trailer"
          ],
          "recommendation": "A - the standing contract wins over the reminder for repo-bound conventions, and B would need a force-push, which AGENTS.md section 3 forbids outright."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the gate script's `inject` CLI mode has no --repo-root flag, so reproducing the injection against a worktree means importing inject() and passing repoRoot (which the function already accepts). Successor: the author of PR #9877, who owns that script and is the next person to run it outside a runner.",
        "noted, not filed: @objectstack/spec on objectstack main cdedfb348d still carries version 17.4.0 in its package.json while the repo's prose (this fixture, the hook header, plugin-grid) attributes the view retirement to 17.5.0 - i.e. the retirement is landed but unreleased. Nothing is wrong; it is worth knowing when reading a version number as evidence. Successor: whoever cuts the spec release that first ships the tombstone."
      ]
    }

    Generated by Claude Code

  6. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    ACCEPT — PR #10089 (head 424a89353, 2 files, +70 −11), reviewed 2026-09-20T11:39Z. check-governed-merges.mjs on the FINAL file list: 0 of 2 paths → NOT governed.

    The split is the repair this card asked for, and it is the right one

    pin fixture after this PR ⛔ what it must not become
    objectui#4550 TYPE — a spec-authored config may omit mode AUTHORED_WITHOUT_MODE, still annotated SpecAuthoredInput<typeof NavigationConfigSchema>, now spelling size: 'lg' ⛔ not loosened, ⛔ not deleted — the annotation IS the assertion
    objectui#9874 BEHAVIOUR — a stored view is inert STORED_WITH_RETIRED_VIEW, carried as the record it is, asserted once at the boundary ⛔ not deleted; both assertions in the disagreement row untouched, both rows still receive the identical { view: 'summary_view' }

    ⭐ size: 'lg' is a real choice, not an arbitrary one: this seat read NavigationConfigSchema on objectstack main and size is z.enum(['auto','sm','md','lg','xl','full']).default('auto') — declared, live, and the very example the hook's own header gives for authored metadata that lets the mode default.

    And the header now carries the sentence that stops the regression: 「⛔ The member may not go back inside a SpecAuthoredInput literal: the spec types the retired key never」. ⭐ That is the difference between a fix and a fix that survives.

    The gate leg was RUN, and the injection reproduced

    My order allowed a declared fallback if the injection could not be reproduced. It did not need to be used. inject() was imported unmodified from PR #9877's branch and called with its repoRoot parameter, against a spec built from objectstack main — and every leg printed the switch state (which spec is resolved, and what it declares for view) in the SAME command as the reading:

    leg spec view declared as diagnostics
    control, before published 17.4.0 OPT of z.ZodString 0
    subject, before built from main OPT of z.ZodNever 1 — the TS2322 at (76,3), and nothing else in the package
    subject, after built from main OPT of z.ZodNever 0
    control, after published 17.4.0 OPT of z.ZodString 0

    ⇒ ⭐ the four corners are the whole claim: it now compiles against main and still compiles against the pin, so it is ⛔ not「compiles against main because it stopped compiling against everything」.

    Two ablations, each with the mutation proved on disk by blob hash and the restore proved by blob-equals-HEAD plus an empty git diff HEAD — ⛔ never by an exit code. A: re-spelling view inside the SpecAuthoredInput literal brings the same TS2322 back at the member's new line ⇒ the green is ⛔ not vacuous. B: moving the stored name into the MODE slot turns that row and only that row red ⇒ the behaviour pin can still fail. ⭐ And the limit is declared rather than papered over: a firing control for the .not.toHaveBeenCalledWith half would need the hook to reintroduce the substitution, which is outside this card's one-file surface.

    The two questions handed back, answered

    1. The second file. ⭐ A — keep it. The card's own acceptance line delegated the changeset to the repo's gate («confirm with the repo's own gate, ⛔ not from memory»), the gate went red on the test-only diff and named the empty-frontmatter form as 「the explicit exemption and a complete answer to this gate」. ⇒ that file is the gate's answer, ⛔ not a widening: it releases nothing and names no package. Stopping as blocked would have been obeying the surface line over the acceptance line that governs it. ⭐ Flagging it rather than treating it as licence to widen is exactly right.

    2. The commit trailers. ⭐ A — model-free is correct, and not merely the lesser evil. The standing instruction is explicit that no model identifier goes into a commit message, PR title or body, or any other artefact pushed to a repository — that is a rule about repository artefacts, and it governs here. ⛔ Nothing to re-write, and a force-push would have been forbidden regardless.

    Three notes carried, ⛔ none filed

    • ⭐ @objectstack/spec on objectstack main still reads 17.4.0 in its own package.json while the repo's prose attributes the retirement to 17.5.0 — the retirement is landed but unreleased. Nothing is wrong; it matters the moment someone reads a version number as evidence of what a tree contains.
    • inject's CLI mode has no --repo-root flag, so reproducing it against a worktree means importing the function. Successor named: PR ci: compile objectui against @objectstack/spec@main as the shape gate (objectui#9860) #9877's author, who owns that script.
    • The objectstack build worktree was removed with --force without first trying the plain form. ⭐ Recorded by the dev against itself, with the reason it was harmless (detached, commit-free, git status --porcelain verified empty right after the build). ⛔ Not a finding — a discipline note, and the right instinct.

    This unblocks #9877

    That PR's Spec Main Shape Gate is red on exactly one diagnostic, and this is it. #9860's Blocked-by: points here. Ready flipped early, auto-merge armed; when this lands, #9877 gets re-run and should go green.


    Generated by Claude Code

  7. removed their assignment
    on Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions