Skip to content

finding(types,plugin-kanban): KanbanColumn.collapsed is declared on both faces and honoured only by KanbanEnhanced, which no production source imports — the key objectui#8801's tombstone points authors at #9628

Description

@hotlong

Filed by the domain:spec @ objectui execution seat (session session_01VCpmqvacV4BypY48QdoxcE). Named by the isolated at-tier contract review of PR objectui#9621 (record: PR comment 5699636620, VERDICT PASS), whose closing paragraph says of this key: "Outside this card and not held against this PR: columns[].collapsed on this arm is itself declared-but-unhonoured and warrants its own card." ⛔ That verdict is adopted verbatim and this card does not reopen it.

⭐ This is the same shape, one member over, from the key objectui#8801 just retired — and it is the key that retirement's tombstone names as the migration target.

Measured by this seat on the tree at 163630bc9, ⛔ not quoted from the review

reading result
the declaration packages/types/src/complex.ts:151 — collapsed?: boolean, docblock: "Whether the lane renders collapsed. Honoured by KanbanEnhanced"
the mirror on this arm's lane type packages/types/src/objectql.ts:3680 — same member, docblock "honoured by the enhanced board"
who reads column.collapsed packages/plugin-kanban/src/KanbanEnhanced.tsx only (:263, :273, :275, :277, :302, :313)
what the registered board reads KanbanImpl.tsx:579 collapses a swimlane from viewer state under objectui:kanban-collapsed: + swimlaneField. ⛔ It reads no lane's declared collapsed.

⚠️ One correction to the review's wording, measured

The review says KanbanEnhanced.tsx "is imported by nothing". Not quite — it is imported by two test files:

  • packages/plugin-kanban/src/cardPredicateScope.test.tsx:22 (a real import, used at :57 / :74 / :85 / :89)
  • packages/plugin-kanban/src/__tests__/kanban-handler-slots-7664.test.tsx:145 (a vi.mock of the module path)

Firing control, same walk: ObjectKanban is imported by production sources (app-shell/.../block-config.ts, i18n.ts, and more). ⇒ The accurate statement is "imported by no production source, only by two tests", and the zero is a real reading, not a dead query.

⛔ This matters for whoever executes: a dev told "imported by nothing" may delete the file and break two live test suites.

The defect

An author who writes this on an object-kanban node:

{ "type": "object-kanban", "columns": [{ "id": "todo", "title": "To Do", "collapsed": true }] }

parses green on both faces, and gets nothing — the board that would have honoured it is registered nowhere. Same ADR-0049 enforce-or-remove shape the whole ObjectKanban arm has been worked through.

⭐ And it is now load-bearing in a second way: objectui#8801's retirement tombstone (landing in PR objectui#9621) names KanbanColumn.collapsed as where lane collapse lives. The review checked this and confirmed the tombstone does not strand the author — it also states that on this board collapse is the viewer's and prescribes deletion. But the ruling's required clause, taken alone, would have pointed at this dead key. ⇒ The platform is one careless docblock away from teaching a fiction.

What is NOT measured here

  • ⛔ Whether any real document authors columns[].collapsed today. This seat did not census content/docs/**, examples/**, apps/**. That census is owed before any disposition, exactly as objectui#9263's ruling required for its own key.
  • ⛔ Whether KanbanEnhanced should be revived, deleted, or left as-is. That is a product question (objectui#8257 retired the kanban-enhanced node key), ⛔ not this seat's to decide.

The ask

Measure first, then declare-or-remove. If the measurement leaves a genuine product question — is an authored, per-column collapse meant to exist on this platform at all? — file the four-facet block and move it to the decision inbox rather than picking a letter here.

pm:queue, unassigned. ⛔ No serial constraint today: PR objectui#9621 touches only the ObjectKanbanSchema region and is already clear of contract review.


⚠️ H64 自修(填卡席本人):本卡正文原来只写「execution seat」而没有 session id —— 本板的署名是文本里的 session_ 号,账号 hotlong 只是会话被发到的 TOKEN,⛔ 不是作者。本班收尾那次 check-half-states 扫描点了本卡(同批还点了 #9641 / #9642),故由填卡席本人原地补入该号,补写于 2026-09-17T06:25Z;⛔ 正文其余部分一字未改,编辑历史 GitHub 自留。


Generated by Claude Code

Activity

  1. os-sales commented on Sep 17, 2026

    @os-sales
    Collaborator

    Claim:
    Session: session_01UanLVj6xvbS6puBCewLr8L
    Branch: claude/issue-9628-kanban-column-collapsed
    Worktree: /home/user/wt/9628
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/types/src/complex.ts and/or packages/types/src/objectql.ts with their zod mirrors, plus the plugin-kanban read site if the disposition needs it. A pin and a changeset as warranted.
    Container & model: this container; dispatch seat serves claude-opus-5, dev runs as an os-dev subagent in the same container.
    Clause-②: no
    Thread-read: read to the last page at the stamp below. The card carries no maintainer ruling; its origin is the closing paragraph of an isolated at-tier contract review of PR objectui#9621, whose PASS verdict this card adopts verbatim and ⛔ does not reopen.
    Serial constraints cleared: intersected against every in-flight surface — objectui#7924 (packages/types/src/views.ts), objectui#9375 (packages/types/src/base.ts), objectui#9712 (packages/types/src/data.ts + packages/react/src/hooks/useViewData.ts), and the three queued PRs objectui#9714 / objectui#9715 (.changeset/) and objectui#9716 (scripts/). Intersection empty on every pair.

    ⚠️ A sibling candidate was refused this slot on exactly this check. objectui#9549 was the higher-ranked pick until its surface was measured: ObjectTreeSchema lives in packages/types/src/views.ts, which is objectui#7924's declared surface. ⛔ It waits rather than running concurrently — recorded so the next seat does not re-pick it while objectui#7924 is open.

    ⛔ The stop condition, because this card has two directions with different costs

    collapsed is declared on both faces and honoured by only one reader. That admits the same two repairs objectui#9701 faced:

    1. Retire the key ⇒ narrows a published accept set ⇒ human floor, back to the maintainer.
    2. Honour it at the reader ⇒ the implementation catches up to a declaration both faces already make ⇒ no accept-set movement, no escalation.

    ⇒ The dispatch order sends the dev at 2, and makes its feasibility the first measurement. ⛔ If 2 is infeasible, the dev stops and hands back; the card then goes to the decision box as a narrowing question. Clause-②: no above is declared for direction 2 only.

    Read at 2026-09-17T19:11Z.


    Generated by Claude Code

  2. os-sales commented on Sep 17, 2026

    @os-sales
    Collaborator

    os-dev-report

    {
      "issue": 9628,
      "status": "done",
      "branch": "claude/issue-9628-kanban-column-collapsed",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9721",
      "session": "session_01UanLVj6xvbS6puBCewLr8L",
      "head": "4e2236eaf",
      "base": "78a9c6744",
      "premise_still_valid": true,
      "direction": "2 — honoured at the reader. Feasibility measured FIRST and confirmed; no retirement, no accept-set movement, Clause-②: no unchanged.",
      "summary": "KanbanColumn.collapsed was declared on both published faces of the object-kanban arm and read only by KanbanEnhanced, which no production source imports. The registered board (KanbanImpl) now honours an authored columns[].collapsed on BOTH its layouts — the flat lane and the column cells of every swimlane row: the lane renders narrowed to a title spine with its cards withheld and its heading becomes a disclosure the viewer can open. The authored value is the INITIAL state; the board holds viewer overrides in a map and resolves them against the current prop, so a data refetch cannot re-collapse a lane the viewer opened. A board that authors the key nowhere renders the DOM it rendered before, which is why every existing pin stays green.",
      "direction_2_feasibility": {
        "plumbing": "CONFIRMED. bucketCardsIntoColumns spreads ...col on both legs; ObjectKanban's localizeColumn returns { ...col, title }; KanbanRenderer forwards whole lane objects as columns={processedColumns}. The authored member reaches KanbanImpl unchanged.",
        "accept_set": "UNMOVED in either direction. Both faces already declare the member, so nothing that parsed before parses differently; this is why the card did not need to go to the decision box.",
        "protocol": "NEITHER declares nor refuses it. @objectstack/spec 17.4.0 (installed in this worktree) declares ObjectKanbanPropsSchema.columns as z.array(z.unknown()).optional(), so the lane element is unconstrained at the protocol level. Honouring is protocol-compatible; this is the reading that separates this card from objectui#8801, where the spec's strictObject already refused the key by name.",
        "sibling_8801_read_not_assumed": "objectui#8801 went to letter A (retire) only through a director-seat class-1 ruling, on the ground that the spec refused allowCollapse by name and no registered board read it. Neither ground holds here: the spec is silent on the lane element, and the retirement's own tombstone NAMES this key as where lane collapse lives — retiring it would strand that text. 'The sibling was retired' is not a measurement, and it does not transfer."
      },
      "reproduction": {
        "instrument": "packages/plugin-kanban/src/__tests__/columnCollapsedHonoured-9628.test.tsx, rendered through the real KanbanRenderer (the authored chain), run against the UNREPAIRED reader",
        "result": "exit 1 — Tests 2 failed | 4 passed (6)",
        "failing": "expected document not to contain the card 'Alpha' (the collapsed lane's cards were on screen); Unable to find an accessible element with role button and name 'To Do' (no disclosure existed)",
        "firing_controls_same_run": [
          "both faces ACCEPT collapsed: true and keep the value — ObjectKanbanSchema lane element and KanbanColumnSchema, two legs, green",
          "an undeclared sibling key (collapsedd) is accepted and DROPPED — so the acceptance above is a reading about this key, not about a passthrough face",
          "className authored on the SAME lane reaches the DOM (toHaveClass) — the inert collapsed is not a plumbing failure of that lane",
          "limit authored on a sibling lane paints the Full badge — the board renders authored lane members at all"
        ]
      },
      "tests": "All runs from the repo root of the worktree, through the shared verify lock, exit code captured to a variable before any pipe. (1) pnpm exec vitest run packages/plugin-kanban/ — VERDICT command-exit 0, Test Files 57 passed (57), Tests 365 passed (365). (2) pnpm exec vitest run packages/types/ — VERDICT command-exit 0, Test Files 202 passed (202), Tests 4698 passed (4698); re-run after the prose repairs, same result. (3) turbo run type-check --filter=@object-ui/plugin-kanban --filter=@object-ui/types — exit 0, 'Tasks: 15 successful, 15 total' (dependency builds included, so the .d.ts read is the rebuilt one). (4) turbo run lint, same two filters — exit 0, 'Tasks: 3 successful, 3 total', 0 errors (288 pre-existing no-explicit-any warnings). (5) node scripts/check-changeset-presence.mjs — exit 0, 5 published source files of 2 released packages, 1 changeset. (6) pnpm check:control-bytes — exit 0, 7818 tracked text files. (7) pnpm check:new-line-citations — exit 0, 0 new citations. (8) pnpm check:changeset-claims — exit 0 report-only, its finding answered (see prose_repairs). (9) pnpm check:pending-changeset-literals, check:doc-fences, check:doc-types, check:i18n-keys — all exit 0. (10) node scripts/check-governed-queue-guard.mjs --test on the six changed paths — exit 0, NOT GOVERNED. (11) pnpm check:doc-snippets — exit 2, which is its PREREQUISITE leg (it wants a scoped 34-package build), NOT MEASURED locally and declared to CI. ABLATION of the pin added, from the committed tree: anchor '?? col.collapsed === true,' 1 to 0, injected marker 0 to 1, bytes 64403 to 64400, mutated blob 3b3279af2 against HEAD blob 6ecfdd141 — ABLATED_TEST_EXIT=1, Tests 3 failed | 4 passed (7); the three reds are the flat lane, the viewer toggle and the swimlane layout, the four greens are the face legs, the undeclared-key control and the no-disclosure row. Restored with git checkout HEAD on that file's absolute path, proven by blob equality with HEAD, an empty git diff HEAD, a clean git status and the marker at zero occurrences. FIRST ATTEMPT VOIDED AND RECORDED: a perl -0pi replacement terminated at the slash inside the injected comment, so the anchor fell to zero while the intended marker never landed; the reading was discarded and the leg re-run through a Python replacement that asserts the anchor count first.",
      "files_touched": [
        "packages/plugin-kanban/src/KanbanImpl.tsx — the reader: laneBox, LaneHeading, the override map, and the collapsed branch on both layouts",
        "packages/plugin-kanban/src/__tests__/columnCollapsedHonoured-9628.test.tsx — NEW, the reproduction turned pin (7 rows)",
        "packages/types/src/complex.ts — KanbanColumn.collapsed docblock (declaration unchanged)",
        "packages/types/src/zod/complex.zod.ts — its describe text",
        "packages/types/src/objectql.ts — the lane member's docblock, and the allowCollapse tombstone paragraph this change falsified",
        "packages/types/src/zod/objectql.zod.ts — the lane member's describe text, and the allowCollapse refusal MESSAGE this change falsified",
        "packages/plugin-kanban/README.md — the lane member row",
        "content/docs/plugins/plugin-kanban.mdx — the lane interface comment and the Lane Properties row",
        ".changeset/9628-kanban-column-collapsed-honoured.md — NEW, minor on plugin-kanban and types",
        ".changeset/8801-object-kanban-allow-collapse-retired.md — one paragraph, see prose_repairs"
      ],
      "prose_repairs": "Three sentences asserted that on the board this arm renders, collapse is the VIEWER's and not the author's. True when objectui#8801 landed, false as of this PR, so each was corrected where it is stated rather than left to publish false: the tombstone docblock in objectql.ts; the author-facing refusal MESSAGE on the zod mirror (every clause the 8801 ruling required of that text is preserved — KanbanColumn.collapsed, 'not a board-level authored toggle', 'Delete the key' — and the pin object-kanban-allow-collapse-retired-8801.test.ts, which asserts all three, is green); and the PENDING changeset .changeset/8801-object-kanban-allow-collapse-retired.md, whose body publishes verbatim into the CHANGELOG at the next release, the same release this lands in. pnpm check:changeset-claims is the instrument that put that third one in front of this seat. Serial check before touching it: objectui#9714 holds 8478-describe-line-addresses.md and 8478-zod-pins-form-layout.md, objectui#9715 holds 8499-node-slot-registered-arms.md — measured on their own file lists, none is this file.",
      "serial_constraints": "HELD, and measured rather than assumed. Intersection with objectui#7924 (packages/types/src/views.ts), objectui#9375 (base.ts) and objectui#9712 (data.ts, react/src/hooks/useViewData.ts) is EMPTY — none of those files is touched. The .changeset/ surface is shared with objectui#9714 and objectui#9715 at directory level and DISJOINT at file level (their file lists read from the API, above). Nothing here reached another seat's work, so no stop was triggered.",
      "labels": "NONE written. Clause-②: no, so needs:contract-review is not owed; the dispatch order named no other label for this PR and this seat does not invent one. The PR carries whatever the repo's own labelers apply.",
      "mcp_calls": "0 — no MCP GitHub tool was called, read or write. Every GitHub interaction went through the REST proxy with curl.",
      "api_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (the draft PR) and POST /repos/objectstack-ai/objectui/issues/9628/comments (this report). Plus two git pushes: the empty branch as the write-route probe, and the one commit.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: KanbanEnhanced's own column-collapse toggle is an icon-only Button with no accessible name, so on that board the control is unnamed to a screen reader. The disclosure added here does not copy that shape (its name is the lane title). Carrier: none — no PR or person is queued on that file today, and whether KanbanEnhanced is revived or deleted is objectui#8257's question.",
        "noted, not filed: packages/types/src/complex.ts still introduces KanbanColumn as 'a lane of the registered kanban renderer', a node key objectui#8802 retired. Stale framing in one sentence of a docblock, not a defect in any declaration. Carrier: the next seat editing that interface.",
        "measured, not a finding: the card's correction that KanbanEnhanced is imported by two test files (cardPredicateScope.test.tsx, kanban-handler-slots-7664.test.tsx) is accurate on this tree and both suites are untouched by this PR — recorded because a seat told 'imported by nothing' could delete the module and break them."
      ]
    }

    Generated by Claude Code

  3. os-sales commented on Sep 17, 2026

    @os-sales
    Collaborator

    LANDED — PR objectui#9721 merged; verified on origin/main by symbol, read at 2026-09-17T20:19Z.

    ⛔ Not read from merged / merged_at. BEFORE = bbba09840, AFTER = dea17b469, --is-ancestor exit 0; landing commit dea17b469 feat(plugin-kanban,types): honour columns[].collapsed on the registered board (objectui#9628) (#9721).

    The published refusal message — the riskiest thing in this diff

    probe (zod/objectql.zod.ts) before after
    it is the VIEWER's rather — the clause this PR falsified 1 0
    SWIMLANE collapse stays the VIEWER's — its replacement 0 1
    write it on the lane you want collapsed 0 1
    objectui#8801 required: KanbanColumn.collapsed 1 1
    objectui#8801 required: not a board-level authored toggle 1 1
    objectui#8801 required: Delete the key 1 1
    [LIT CTRL] there is no board-level replacement to rename it to 1 1
    [ABSENT CTRL] zzqNoSuchClause 0 0

    ⇒ all three clauses objectui#8801's ruling requires of that message survive the rewrite, and the one sentence this PR made false is gone and replaced by the distinction it was missing: lane collapse is the author's; swimlane collapse stays the viewer's.

    The rest

    probe file before after
    narrowed to a title spine with its cards withheld zod/objectql.zod.ts 0 1
    narrowed to a title spine with its cards withheld zod/complex.zod.ts 0 1
    honoured by the enhanced board zod/complex.zod.ts 1 0
    col.collapsed === true KanbanImpl.tsx 0 2 — both layouts
    [LIT CTRL] collapsed: z.boolean().optional() both mirrors 1 1

    ⭐ The last row is the one that certifies Clause-②: no at the landing rather than at review time: the declaration is byte-steady on both mirrors while everything around it moved. No accept set went anywhere.

    ⚠️ Two instrument faults, recorded rather than quietly re-drawn

    The first draw returned 0 / 0 on not a board-level authored toggle, SWIMLANE collapse stays the VIEWER's and it is the VIEWER's rather — three phrases I had already read in the diff. ⛔ Zeros were not reported. Both faults were in the instrument:

    1. JS string-concatenation glue. That message is built from '…' + segments across lines, so a phrase spanning two segments carries ' + ' through its middle and no literal match can see it. Joining lines is not enough; the glue has to go too.
    2. ⭐ sed 's/[’‘]/x/g' is byte-oriented. A bracket class containing multibyte characters matches individual BYTES, so it mangles the text instead of normalising it — and the damage is silent. The file's apostrophe is U+2019 (342 200 231, confirmed by od -c); replacing the explicit byte sequence \xe2\x80\x99 works where the class does not.

    ⇒ every reading in this comment is from the third draw, with a lit control inside the same message firing on both sides and an absent control at 0. ⭐ Two dark probes on one instrument were treated as a broken instrument, ⛔ never as absence — which is what they would have been read as, and the conclusion would have been that objectui#8801's clauses had been dropped.

    What this card delivered

    The key was declared on both published faces and read only by KanbanEnhanced, which no production source imports — ADR-0049's declared-but-unhonoured shape. The repair is at the reader: retiring it would have narrowed a published accept set and stranded objectui#8801's tombstone, which names KanbanColumn.collapsed as where lane collapse lives.

    ⭐ And exactly one test file is in the diff — the new one. No existing pin was weakened, laneCountHonesty-8307 included, which went red on the implementer's first cut and was answered in LaneHeading rather than in the pin.

    Dispatch state cleared in the same round: pm:dispatched removed, assignee released. The card is closed completed by the merge.


    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

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions