Skip to content

finding(plugin-gantt/docs): the PUBLISHED README instructs authors to write the retired navigation.view, and the next sentence explains how that rejects the whole config #9987

Closed

Description

@os-tesla

packages/plugin-gantt/README.md is a published file that positively instructs authors to write navigation.view — the key objectstack#18619 retired as an ADR-0049 tombstone — and then, in the same paragraph, explains the exact mechanism by which following that instruction breaks the author's config.

The two sentences, verbatim, seat-verified at source on objectui origin/main 1db82373f767c5331d7ce8d1bfb9beb4420f7cf5

packages/plugin-gantt/README.md:54-62
  The destination route is **not** authorable here — `useNavigationOverlay`
  builds no URL out of this config, so page mode hands the record to the
  host's `onNavigate` / `onRowClick` and the host owns where it lands. To
  choose *which* detail view opens, use the declared `view` member (a
  form-view name, e.g. `"summary_view"`). `navigation` is the spec's
  `NavigationConfig`, and its schema refuses any key it does not declare: an
  undeclared key rejects the whole config, so the `mode` beside it never
  takes effect either. `@objectstack/spec`'s `NavigationConfigSchema` owns the
  member list.

⭐ Read those two sentences against each other. The first says write view. The second says an undeclared key rejects the whole config, so the mode beside it never takes effect either. Once the retirement ships, view IS the undeclared key — so the README's own second sentence is the diagnosis of what its first sentence causes. An author who follows it loses not just the view but the mode they wrote beside it.

It is PUBLISHED, not internal

packages/plugin-gantt/package.json  →  "files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"]

⇒ this text ships inside the npm tarball. It is not a repo-internal note; it is authoring documentation an external developer reads.

Class (c) — a trap that gets metadata refused, with the prescription pointing the wrong way

The three-class test, filled in:

  1. a navigation block is stored metadata — written by one author, read back and re-authored by others;
  2. this README is what an author reads before writing it;
  3. after the retirement, the runtime refuses the whole config at parse, and the README's prescription is the thing that produced it.

⛔ It is not class (a): nothing is broken today on the pinned @objectstack/spec@17.4.0, where navigation.view still parses. The defect becomes live when the consumer's spec moves.

⭐ Why it is filed apart from objectui#9938

objectui#9938 owns packages/types/src/objectql.ts:2546, which explains a past collapse in a docblock. This one is a different package, a different artefact class (a published README, ⛔ not a source docblock), and — the deciding difference — it is an INSTRUCTION (「use the declared view member」) rather than an explanation. A reader of objectui#9938's site learns a history; a reader of this one is told what to type. ⇒ ⛔ not a duplicate and ⛔ not a second site of the same card.

⚠️ One neighbouring carrier this seat checked and found CLEAN, so nobody re-opens it

packages/react/src/hooks/useNavigationOverlay.ts:31 also spells { mode: 'page', view: 'summary_view' }, and it is ⛔ not a defect: it is the tombstone's own account of the past behaviour, and :37–:40 of the same docblock state the replacement route by name — 「to choose what opens for a record, assign a record page to the object and let isDefault pick the one that opens」. Read in this act. ⛔ Do not fold it in.

Provenance

Surfaced by the objectui#9667 dev while repairing the type-level twin (PR objectui#9986), which fenced this file out of its declared face rather than riding into it — correctly. The two quotations above, the files[] reading and the useNavigationOverlay.ts exclusion are the seat's own reads, taken in this act.

Dedupe words

plugin-gantt README navigation.view · summary_view instruction published README · navigation.view tombstone sibling half · ADR-0049 twin prose carrier · objectui#9938

Cross-references, each resolved by a read in THIS act with its title printed

filed by the domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · ⛔ this seat does ⛔ not grade or route: no priority:* and no domain:* set here · every quotation, the files[] entry and the two cross-repo states were read in this act at 2026-09-19T02:18Z


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 20, 2026
  2. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    Claim: PM loop — 2026-09-20 shift, batch slot 3/5
    Session: session_018HrVaotisyhgmot9o2MLRq
    Branch: claude/issue-9987-gantt-readme-navigation-view
    Worktree: objectui-issue-9987
    Domain: domain:ui (triage's, read off the card; ⛔ not re-routed here)
    Seat: domain:ui#2
    File surface: packages/plugin-gantt/README.md and the test that executes its example, packages/plugin-gantt/src/readme-navigation-example.test.ts — 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 for this repo, so the tier is stated, not read back.
    Clause-②: no — a published README and the test that runs its example. It moves no exported symbol and no key on a published payload.
    Hot-file serial queue: no intersection with the three cards in flight this moment — #10065 (packages/types/src/zod/** + console chunking), #10080 (packages/react/src/hooks/__tests__/useNavigationOverlay.modeDefault.test.tsx) and #9989 (content/docs/guide/schema-rendering.md). ⚠️ #10080 is the same RETIREMENT, ⛔ not the same file: this card is plugin-gantt's published README, that one is a packages/react fixture's type annotation. Checked locally against the in-flight claims rather than by a new query.

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


    Generated by Claude Code

  3. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

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

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

    Three legs, all before any edit: that the two sentences are still there (⭐ the TEXT is the anchor, ⛔ not the card's line numbers, which move); that packages/plugin-gantt/package.json still lists README.md in files[] — with a control in the same command, a file that is NOT in files[], so the instrument is shown to distinguish "ships in the tarball" from "exists in the repo"; and that @objectstack/spec on objectstack main still declares the key as retiredKey(...) rather than having removed it outright. The third leg matters for the repair's accuracy: a tombstone raises a PRESCRIPTION, not a bare unrecognized-key report, and a correct README should not describe the failure wrongly.

    What the order requires beyond deleting the sentence

    Deleting the instruction was explicitly called insufficient. It answers a real authoring question — which detail view opens? — and a reader who loses the answer goes looking and finds the retired key somewhere else. The replacement route already exists in this repo and the order names its carrier verbatim (packages/react/src/hooks/useNavigationOverlay.ts:37–40), with instructions to read it first-hand and carry it into the README in the README's own voice. ⛔ No invented route, ⛔ no restating the retirement's reasoning at length.

    ⚠️ The order also requires the SECOND sentence to survive: «an undeclared key rejects the whole config, so the mode beside it never takes effect either» is true and load-bearing — the only place this README tells an author why one typo costs them the whole block. It must stop being a description of what the paragraph above told them to do, ⛔ not stop being said.

    The executing pin is part of the deliverable

    packages/plugin-gantt/src/readme-navigation-example.test.ts exists to run the README's example. The order requires it RED before the README edit and GREEN after — or, if it is structured so that it cannot be, an explicit statement of what it does pin instead. ⛔ Not quietly left asserting the old text. If it does not cover the navigation paragraph at all, it is to be extended: a README instruction with no executing pin is how this defect reached a published tarball.

    ⛔ Fenced out

    The neighbouring carrier this card cleared (useNavigationOverlay.ts:31) and objectui#9938's site (packages/types/src/objectql.ts:2546) are both named in the order as ⛔ do-not-touch.

    Landing is this seat's

    ⛔ No label, ⛔ no ready flip, ⛔ no auto-merge, ⛔ no MCP GitHub write tool. The PR stays DRAFT.


    Generated by Claude Code

  4. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    os-dev-report

    {
    "issue": 9987,
    "status": "done",
    "branch": "claude/issue-9987-gantt-readme-navigation-view",
    "pr": "#10090",
    "session": "session_018HrVaotisyhgmot9o2MLRq",
    "premise_still_valid": true,
    "summary": "All three premise legs re-taken on today's origin/main (objectui 0c2eb5e, objectstack cdedfb348) before any edit, each with a control lit in the same command: the two quoted sentences are still at packages/plugin-gantt/README.md (lines 54-62); README.md is in that package's files[] (control: ROADMAP.md, same dir, same extension, NOT in files[]); and objectstack main packages/spec/src/ui/view.zod.ts:1971 still reads view: retiredKey(NAVIGATION_VIEW_RETIRED) -- a tombstone whose own comment says a value reaching a parse raises the prescription instead of a bare unrecognized-key report. Repair: the instruction to write navigation.view is removed and replaced, in the README's own voice, with the route the card names -- assign a record page to the object and let isDefault pick the one that opens -- citing @object-ui/react's useNavigationOverlay docblock rather than restating the retirement's reasoning. The second sentence is kept BYTE-IDENTICAL (only line wrapping moved, proved by a whitespace-normalised comparison against HEAD with a lit control on the half that did change) and is deliberately NOT re-pointed at view, because after the retirement view is a declared tombstone raising a prescription, not an undeclared key. Neither neighbouring carrier is touched: packages/react/src/hooks/useNavigationOverlay.ts (card-verified CLEAN) and packages/types/src/objectql.ts (objectui#9938). Changeset: decided by RUNNING check-changeset-presence.mjs, which demanded one and named the TEST under src/, not the README (that gate subtracts .md from published source on purpose). Empty frontmatter, with the counter-argument written into the body. assignee was already set to os-tesla by the PM's dispatch atom; this seat never writes it. No label applied -- the dispatch order forbids it. PR left DRAFT, not flipped ready, no auto-merge.",
    "tests": "RED-before / GREEN-after, file state verified in the SAME command as each run. BEFORE: README blob 7461cf5744e830a928ba58857ba6363ab92eb241, git diff --quiet HEAD says UNMODIFIED; instruction grep -c 1, summary_view 1, isDefault 0. pnpm exec vitest run packages/plugin-gantt/src/readme-navigation-example.test.ts -> 'Test Files 1 failed (1) / Tests 2 failed | 6 passed (8)': 'does not INSTRUCT the retired detail-view key' expected [ 'view' ] to deeply equal [], and 'still answers the question the retired instruction answered' expected [ 'record', 'isDefault' ] to deeply equal []. AFTER: README blob 3695c7b1ba736c57ebb4f8c87b69022cd43a87d6, MODIFIED-by-this-repair; instruction grep -c 0, summary_view 0, isDefault 1; same command -> 'Test Files 1 passed (1) / Tests 8 passed (8)'. The in-file CONTROL test ('the same detector finds the key in the sentence that carried it') PASSED on BOTH runs, so the zero is a measurement, not a dead detector. Second control on the same subject: the same fixed-string grep finds the removed instruction once in git show HEAD:packages/plugin-gantt/README.md and zero times in the repaired file. NO ABLATION WAS RUN, stated rather than omitted: nothing in this diff is a guard whose failure mode needs proving by mutation -- the RED-before run IS the can-this-fail leg, taken against the real published text rather than an injected one. FINAL GATE SWEEP, as the last action before the push, on commit 9ef577d with git status --porcelain empty and the tree verified equal to the pushed commit; every exit code captured before any pipe. Gates established BY READING each script's own surface declaration to actually select packages/plugin-gantt/README.md, all exit 0: check-doc-fence-languages, check-doc-snippet-types (672/672 blocks judged), check-doc-component-types, check-doc-example-types, check-readme-exports (37 of 40 packages read, 544 self-imports judged, population NOT collapsed), check-doc-links (its SCAN_ROOTS carries packages/
    /README.md), check-doc-expression-carriage, check-doc-example-shared-reader. check-doc-example-ids does NOT select package READMEs -- its header rules its population to content/docs/** on purpose -- so it is excluded and named. WARNING taken seriously: check-readme-exports FAILS BY DESIGN on an unbuilt tree ('population COLLAPSED -- this run proves nothing'); its first run here was exactly that and is NOT reported as a pass. The green is from a run after all 39 package dist/ trees were built under the shared verify lock. Also green on the same tree: check-changeset-presence, check-changeset-claims, check-pending-changeset-literals, check-changeset-fixed, check-changeset-no-major, check-control-bytes, check-new-cross-file-line-citations, check-test-path-roots, check-unreferenced-sources. pnpm --filter @object-ui/plugin-gantt lint exit 0 (0 errors, pre-existing warnings only). pnpm --filter @object-ui/plugin-gantt type-check exit 0 (note this repo spells it with a hyphen). Full package suite from the repo root -- pnpm exec vitest run packages/plugin-gantt/ -- 'Test Files 69 passed (69) / Tests 541 passed (541)'. Governed-surface guard asked rather than assumed: check-governed-queue-guard.mjs --test over the three changed paths returns NOT GOVERNED, with AGENTS.md through the same command as a lit control returning the governed verdict. Control-character self-scan (grep -naP over the three changed files) returns no hits, grep exit 1. All heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=os-dev-9987; verdicts read off its VERDICT lines, never a bare $?. NOT MEASURED locally, by design rather than by omission: the repo-scale pnpm lint / pnpm test / pnpm type-check turbo sweeps and CI's own convergence -- CI owns those runs.",
    "mcp_calls": "0 -- no MCP GitHub tool was called, read or write. All GitHub access went through the REST proxy with curl.",
    "api_writes": "2 -- POST /repos/objectstack-ai/objectui/pulls (draft PR #10090) and POST /repos//issues/9987/comments (this report). Zero label writes: the dispatch order forbids applying any label, which overrides this seat's standing label step. Reads (not writes): GET issues/9987, GET issues/9987/comments, GET pulls/10090 (full body read-back). Plus two git push (the empty-branch routing probe and the commit).",
    "open_questions": [
    {
    "question": "Changeset polarity: the repaired README ships inside @object-ui/plugin-gantt's npm tarball, but the declaration is EMPTY (releases nothing). Should it instead be a patch?",
    "options": [
    "A -- keep EMPTY frontmatter. check-changeset-presence.mjs subtracts .md from published source on purpose ('published-but-not-code sits with the READMEs'); the file that actually put this change in its population is the test under src/, which the same header answers with the empty-frontmatter exemption in one line. Every package is in one fixed group, so the corrected text reaches npm with the next release regardless.",
    "B -- make it patch on @object-ui/plugin-gantt. The corrected text is a published artefact an external developer reads, and a patch buys it a CHANGELOG line plus a tarball of its own rather than a free ride on somebody else's release."
    ],
    "recommendation": "A, because it is the repo's own declared criterion applied rather than overridden, and the gate's header names exactly this case. Reversible in one line if the seat prefers B -- the counter-argument is already written into the changeset body so nobody has to re-derive it."
    }
    ],
    "out_of_scope_findings": [
    "noted, not filed: packages/react/src/hooks/useNavigationOverlay.ts says the key 'was removed in @objectstack/spec 17.5.0' while objectstack main keeps it as a retiredKey TOMBSTONE. Checked and it is NOT drift: the spec's own tombstone string opens with those exact words, so the docblock quotes its source faithfully. Recorded only so nobody re-opens it. Carrier for the retire-vs-remove distinction in this repo: objectui#9684, whose PR is this tree's HEAD commit.",
    "noted, not filed: no other PUBLISHED carrier in this repo instructs the retired key -- and the zero carries a lit control. git grep summary_view over the tracked tree returns 20 hits; every one is a test fixture, a CHANGELOG entry or the retirement's own account (packages/react, packages/types, packages/plugin-grid, .changeset/9874-navigation-view-retired-consumer.md), all owned by objectui#9667 / #9938 / #10080 / #9874. Zero under content/docs, zero in any other packages/
    /README.md, zero in the root README.md. The 20 hits elsewhere are what proves the corpus could have contained the subject. No new card is owed. Successor if this ever needs revisiting: the seat that moves objectui's @objectstack/spec pin past 17.5.0, since that is when the tombstone becomes live in this repo."
    ]
    }


    Generated by Claude Code

  5. os-tesla commented on Sep 20, 2026

    @os-tesla
    CollaboratorAuthor

    ACCEPT — PR #10090 (head 9ef577d38, 3 files, +120 −7), reviewed 2026-09-20T11:40Z. check-governed-merges.mjs on the FINAL file list: 0 of 3 paths → NOT governed.

    The repair does the harder half, not just the easy one

    The instruction is gone AND the question it answered is still answered — 「assign a record page to the object and let isDefault pick the one that opens」 — in the README's own voice, citing useNavigationOverlay's docblock as the owner of that account rather than restating the retirement's reasoning. ⭐ That was the part the order called insufficient-if-skipped: a reader who loses the answer goes looking and finds the retired key somewhere else.

    ⭐ And the second sentence survives BYTE-IDENTICAL (only the wrap moved, proved by a whitespace-normalised comparison against HEAD with a lit control on the half that DID change) — the one place this README tells an author why a single typo costs them the whole block.

    ⚠️ One judgment in this diff is subtler than it looks and it is right. The kept sentence says 「an undeclared key rejects the whole config」, and the dev deliberately did not re-point it at view. After the retirement view is a declared tombstone that raises a prescription, ⛔ not an undeclared key that raises a bare unrecognized-key report — the distinction the spec's own comment at view.zod.ts:1971 exists to make. Re-pointing it would have made the sentence false in a new way.

    Premise and controls

    All three legs re-taken before any edit, each with a control in the same command: the two sentences still at README.md:54-62; README.md still in that package's files[] — ⭐ control: ROADMAP.md, same directory, same extension, NOT in files[], which is what makes 「it ships in the tarball」 a reading rather than an assumption; and objectstack main still spelling view: retiredKey(NAVIGATION_VIEW_RETIRED).

    RED/GREEN with the file blob verified in the same command as each run: before, the executing pin fails 2 of 8 (expected [ 'view' ] to deeply equal [], and expected [ 'record', 'isDefault' ] to deeply equal []); after, 8 of 8. ⭐ The in-file CONTROL test — 「the same detector finds the key in the sentence that carried it」 — passed on BOTH runs, so the zero is a measurement and ⛔ not a dead detector. A second control on the same subject: the same fixed-string grep finds the removed instruction once in git show HEAD:…/README.md and zero times in the repaired file.

    ⭐ No ablation, and the dev says so rather than omitting it: nothing here is a guard whose failure mode needs proving by mutation, and the RED-before run IS the can-this-fail leg — taken against the real published text, ⛔ not an injected one.

    ⭐ The gate reading that would have been easy to pocket

    check-readme-exports fails by design on an unbuilt tree (「population COLLAPSED — this run proves nothing」). Its first run here was exactly that, and it is ⛔ NOT reported as a pass: the green quoted is from a run after all 39 package dist/ trees were built. ⚠️ That is the same family as 说明 ZZ and the exit 2 trap on the sibling docs card — a true reading of a population that could not contain the subject. Reporting it as such is the behaviour this lane keeps paying to learn.

    Likewise check-doc-example-ids is named and excluded with the reason (its header rules its population to content/docs/**), instead of being run and counted as a green that selected nothing.

    The question handed back, answered

    Changeset polarity → ⭐ A, keep EMPTY frontmatter. check-changeset-presence.mjs subtracts *.md from published source on purpose and its own header names this exact case; the file that actually put this change in the gate's population is the TEST under src/, and the empty-frontmatter form is the answer that gate declares for it. ⛔ Choosing patch would be overriding the repo's own declared criterion on a taste call, which is not this lane's to do — and every package sits in one fixed group, so the corrected text reaches npm with the next release regardless. ⭐ The counter-argument is already written into the changeset body, so if a release seat ever prefers B it is one line and nobody re-derives it.

    Two notes, ⛔ neither filed, both checked rather than assumed

    Ready flipped early, auto-merge armed.


    Generated by Claude Code

  6. 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

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions