Skip to content

[plugin-kanban] declare the four array/object-armed spec keys object-kanban reads — data · cardFields · grouping · conditionalFormatting (objectui#8201 slice 2a) #8313

Description

@os-justin

Slice 2a of objectui#8201, split by package under the PM ruling Q2 = B on that card. Filed by the domain:ui PM seat. ⛔ Not claimed.

Why this is its own card rather than the back half of one PR

Each member pin has to be measured at its own sink, and objectui#8223 proved how easily a sink is assumed wrong: its sort pin could never have been an identity pin, because convertSortToQueryParams builds a new map — and objectui#8212's landed rider note had predicted the opposite. Six sinks in one PR is exactly the size that invites an assumed identity pin.

Splitting by package also keeps each PR inside the plugin package's own __tests__ dir, where all three worked precedents already live.

Scope — four keys, all array/object-armed

object-kanban (both tags: plugin-kanban:object-kanban and view:kanban):

key declared arm
data array
cardFields array
grouping z.unknown()
conditionalFormatting z.unknown()

All four were measured genuinely spec-declared in slice 1 — a control key drew unrecognized_keys on the same safeParse call shape while none of these was refused by name, so "the spec declares it" is a verdict here, not a vacuous read. ⛔ objectui#8172's limit trap does not apply to any of them.

⛔ Out of scope: object-kanban.quickAdd — ruled a premature carve-out on objectui#8201 (Q1 = A), citing objectui#8285 as its owner. Its entry is rewritten, not deleted, and the ceiling drops accordingly. Whether that rides in this PR or its own is the dev's call; say which.

⚠️ The three-part obligation each declaration owes

Since objectui#8212 landed, declaring an array/object-armed key owes:

  1. the inputs entry;
  2. deletion of its UNPUBLISHED_EXEMPTIONS entry in the same change (the stale check reddens otherwise);
  3. a MEMBER_PINS entry — ⛔ never a member-pin exemption. MEMBER_PIN_EXEMPTION_CEILING stays 62; an assertion pins four keys by name so a fifth cannot ride that headroom.

⚠️ And both halves of the shrink-only backlog ceiling move (toBeLessThanOrEqual and toBe, same it()). Moving only toBe banks headroom the ratchet exists to refuse — objectui#8223 hit exactly that.

⚠️ Measure each sink; do NOT assume the pin's shape

Three worked precedents, all on main:

  • packages/plugin-calendar/src/__tests__/ObjectCalendar.filterIsNotAConfigSlot-7711.test.tsx
  • packages/plugin-kanban/src/__tests__/ObjectKanban.filterMembersReachTheWire-8176.test.tsx
  • packages/plugin-calendar/src/__tests__/ObjectCalendar.sortMembersReachTheWire-8171.test.tsx

The two filter pins assert the authored value reaches the wire BY IDENTITY (toBe). sort does not. ⚠️ A sharper version of the same trap cost a near-miss on objectui#8269: a resolver answering which field and one answering which column the rows carry (alias ?? field) coincide only when no alias is written. Be explicit about which question each pin asserts.

⭐ Note carried from slice 1: the spec rows for grouping and conditionalFormatting are z.unknown() — exactly like filter and sort. So, as the two landed MEMBER_PINS entries already say, the spec side cannot supply the member contract and the wire (or the read site) is the whole of it.

Related

objectui#8201 (parent, slice 1 = PR #8309) · objectui#8212 (the gate that created the obligation) · objectui#8186 · objectui#8223 · objectui#8285 (quickAdd) · objectui#8308 (the cardTitle/titleField || vs ?? divergence slice 1 surfaced — not this card's, but it touches the same registration).

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 7, 2026
  2. self-assigned this
    on Sep 7, 2026
  3. os-justin commented on Sep 7, 2026

    @os-justin
    CollaboratorAuthor

    Claim: session_01YBWFb5YgMU5dw8p2VKj16S · branch claude/issue-8313-kanban-structured-spec-keys

    PM dispatch. Assignee and this claim are set by the PM seat for the dev seat; the dev inherits both and posts no second claim.

    The card body is the brief — it carries the scope, the three-part obligation, the sink-measurement warning and the precedents. Three additions:

    Wait for slice 1 to land

    PR #8309 (slice 1) is in the merge queue and edits the same ledger file plus packages/plugin-kanban/src/index.tsx. ⛔ Do not start editing until it is on main — this session has twice paid for concurrent edits to registry-inputs-spec-parity.test.ts. Confirm by content, not by sha: toBe(8) present in that file and name: 'groupBy' present in the kanban registration.

    If it has not landed when you pick this up, say so and stop; the PM will re-dispatch. ⛔ Do not rebase around it or reproduce its changes.

    Baseline after slice 1

    The backlog ceiling will read 8 (both assertions). Each key you declare drops it by one. ⚠️ If the quickAdd carve-out (objectui#8285, ruled a premature carve-out on objectui#8201 Q1 = A) has not landed by then, it is still in that 8 — do not count it as yours, and say in the PR body which number you started from and why.

    Bar

    • Zero-hit greps need a control that fires; prefer grep -cF and print the matched lines — ⚠️ this session has been bitten three times by a count that matched prose, a comment, or a different table rather than the thing being asserted. A count is not a reading.
    • Prove every new pin CAN fail: ablate, red BY NAME and per registration (both tags redden on separate named rows), restore BY STATE (git diff HEAD empty AND git hash-object == git rev-parse HEAD:PATH), never by an exit code.
    • ⚠️ pnpm --filter PKG exec vitest run FILE is refused by a guard here (objectui#3378) — it re-roots vitest and reports a pass count having run none of the target files. Run from the repo root.
    • ⚠️ check:governed-queue-guard --test takes paths; with none it returns EXIT_BAD_USAGE while still exiting 0 through the npm script. Pass real paths, or use --self-test.
    • ⚠️ ⛔ Do not add --no-inline-config to lint. That is an objectstack convention; in objectui it manufactures 16 errors across untouched files that CI does not have. The CI-equivalent reading is the package's own eslint ..
    • Build the dependency closure before reading any type-check verdict — a stale dist produces TS2307s that read exactly like a broken import.
    • Clause ②: declaring an input widens the authoring surface. State it and the grounds (objectui#8186 and objectui#8223 both cleared on "the spec already declares the key and the renderer already honours it").
    • Report the parity gate's assertion count before and after and explain any move — slice 1's did not move, and it said why.

    ⛔ Draft PR only — the PM flips and lands. Worktree-first. Never git stash. ⛔ Never touch content/docs/releases/.

    ⚠️ This brief is a reconstruction. This session has had nine corrected by devs, several of which would have caused damage if followed — including a door/path table that had silently lost the fixture column that made each row true. Blunt corrections are wanted.


    Generated by Claude Code

  4. os-justin commented on Sep 7, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report

    {
    "issue": 8313,
    "status": "done",
    "branch": "claude/issue-8313-kanban-structured-spec-keys",
    "pr": "#8328",
    "premise_still_valid": true,
    "summary": "All four array/object-armed keys are DECLARED on the one shared inputs list both object-kanban tags spread, each paying objectui#8212's three-part obligation in the same change: the inputs entry, deletion of its UNPUBLISHED_EXEMPTIONS entry, and a MEMBER_PINS entry (never a member-pin exemption). Both halves of the shrink-only backlog ceiling move 8 to 4; MEMBER_PIN_EXEMPTION_CEILING stays 62 while the member population grows 84 to 88 and the pin count 22 to 26, so four new array keys at once were absorbed by pins and not by headroom. Two new pin files: structuredKeysAreDeclaredAndHonoured-8313.test.ts (declaration, per tag and per key) and ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx (the MEMBER_PINS target for all four). No key was escalated - all four verdicts are measurements. object-kanban.quickAdd is untouched and NOT counted: its carve-out (objectui#8285) had not landed on main when this branch was cut, so I started from 8 and it stays in the remaining 4. Slice 1 (PR #8309) was confirmed landed on my own base by content, including the two-lists-collapsed-into-one shape.",
    "per_key_verdicts": [
    {
    "key": "data",
    "declared_arm": "array",
    "verdict": "declare - honoured",
    "sink": "read TWICE: as a GATE it suppresses the board's own query, and as a VALUE rawData = external || boundData || schema.data || fetchedData selects it and effectiveData REBUILDS every member into a card",
    "question_the_pin_asks": "NOT identity (unlike the two filter pins): what is read INSIDE a member - id (or _id) as the card identity, the groupBy field's value as the lane, the title field, the cardFields cells",
    "evidence": "rows: gate suppresses (0 find calls) with a CONTROL row proving the wait window makes a real query observable; empty [] suppresses too; per-lane card lists; an _id-only, title-less record still becomes a titled card in its lane, which a forwarded array could not produce"
    },
    {
    "key": "cardFields",
    "declared_arm": "array",
    "verdict": "declare - honoured",
    "sink": "resolveKanbanCardFields(schema.cardFields, objectDef), exported and pure",
    "question_the_pin_asks": "WHICH NAMES THE AUTHOR CHOSE - authored order preserved and NOT filtered against the object definition. WHICH CELLS THE CARD CARRIES is a second, narrower question (the card loop also drops a title-duplicating name and an empty value); both measured and shown to differ, which is objectui#8269's trap in this block's terms",
    "evidence": "resolver rows (['amount','ghost'] survives unfiltered; the highlightFields fallback IS filtered; [] and a non-array read as omitted; neither source yields []), plus a render row asserting the dt/dd cells in authored order"
    },
    {
    "key": "grouping",
    "declared_arm": "object",
    "verdict": "declare - honoured at exactly one nested position",
    "sink": "schema.grouping?.fields?.[0]?.field, and only as the FALLBACK for swimlaneField",
    "question_the_pin_asks": "which single nested position is read, and under what precedence - everything else inside grouping, later fields entries included, is inert",
    "evidence": "swimlanes appear keyed by fields[0].field; CONTROL with no grouping and no swimlaneField has no swimlane region at all; an explicit swimlaneField WINS; a second fields entry changes nothing. The declared description states the inertness and a pin row asserts that sentence stays there, so the declaration cannot recommend a write the board would drop"
    },
    {
    "key": "conditionalFormatting",
    "declared_arm": "array",
    "verdict": "declare - honoured",
    "sink": "the { ...schema } spread into effectiveSchema, then KanbanRenderer forwards it to KanbanImpl's getCardStyles. ObjectKanban.tsx names this key NOWHERE",
    "question_the_pin_asks": "which member dialects are evaluated and against what - native { field, operator, value } and spec CEL { condition }, per card, on that card's own record",
    "evidence": "each dialect colours only the matching card with the sibling card as a live non-matching control in the same render; plus a mechanical zero-hit read of ObjectKanban.tsx with a control in the same assertion (conditionalFormatting = 0 occurrences, cardFields = 9)"
    }
    ],
    "escalated_keys": [],
    "tests": "All heavy runs went through the container's shared verify lock; verdicts read from the runner's own summary lines, never a bare $?. GREEN: parity gate 198 passed (198) - same 198 before the change; packages/plugin-kanban/ 30 files, 204 passed (204); pnpm --filter @object-ui/console test 89 files, 1069 passed (1069); plugin-kanban type-check clean; apps/console type-check clean (only AFTER building its closure - before that it reported TS2307/TS2882 across untouched files, the stale-dist signature); eslint . exit 0 in both packages (44 files / 0 errors / 161 warnings, and 189 files / 0 errors / 210 warnings, counts read from --format json at commit 72778ce); --no-inline-config deliberately NOT passed. Type-check coverage of the new tests verified with --listFiles: both new files appear in tsconfig.test.json's program (1 each), matching the pre-existing 8201 pin as control. GATES: check-control-bytes OK (6621 tracked files), check-changeset-presence OK, check-changeset-no-major OK, check-element-data-source-declaration OK, check-unreferenced-sources OK, check-handler-key-read-sites OK, check:spec-floors OK, check-governed-queue-guard --test on all five changed paths = NOT GOVERNED with AGENTS.md as a control that fires (exit 3). check:sdui-registration-pins weighs a built console dist and is declared to CI; repo-wide pnpm lint / pnpm test are CI's. ABLATION - every leg: mutate, prove it reached disk (marker counts before/after plus the matched lines printed, and git hash-object differing from git rev-parse HEAD:PATH), run, restore BY STATE (git diff HEAD empty AND git hash-object equal again), never by an exit code; all legs ran from a committed implementation under a restore trap on absolute paths. (1) Deleting each of the four inputs entries in turn reddens FOUR named rows PER KEY across BOTH registrations (html tier + registration-declares, object-kanban and view:kanban separately) plus the shared-list row, the description row, and two parity-gate rows: 8 failed / 211 passed on each of the four. (2) Member pins reddened by mutating the READ SITE: resolveKanbanCardFields filtering the explicit list reds the three cardFields rows; fields?.[0] to fields?.[1] reds the two grouping position rows; the spread dropping conditionalFormatting reds both dialect rows and the never-NAMES-this-key row. (3) Ceiling BOTH halves at the NEW number: toBe(4) reverted to toBe(8) reds ('the backlog shrank - lower the ceiling above to match'); backlog grown to 5 reds ('the backlog list is shrink-only: expected 5 to be less than or equal to 4'). (4) Obligation 2: re-adding a deleted exemption while its input is declared reds 'carries no stale unpublished-key exemption' naming it. (5) Obligation 3: converting one MEMBER_PINS entry into a member-pin exemption reds 'the member-pin exemption list only ratchets DOWN' (63 vs 62) AND 'the ceiling correction admits exactly the four keys objectui#8176 made visible'. NO BUILD/DIST PREFLIGHT WAS OWED: vitest resolves @object-ui/* to src via the root config's alias map, proved by the parity gate passing at all - the four deleted exemptions would have left four dangling MEMBER_PINS entries if the edited registration had not been visible.",
    "mcp_calls": "4 - create_pull_request, pull_request_read (body read-back), add_issue_comment, and one comment read-back. The card body and the PM claim comment were read through the zero-quota public-repo page payload channel (hasNextPage false, 10 of 10 timeline items), not MCP. A repo-scoped REST write probe returned 403 in this container, so the comment channel is the declared MCP fallback. No dedup search was owed because there are no out-of-scope findings to file.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "brief_corrections": [
    "THE BIGGEST ONE, and it corrected my own work rather than the brief: the data gate is DOUBLY guarded and the obvious guard is the one that never runs. SchemaRenderer spreads non-metadata schema properties as React props, so an authored data arrives BOTH as schema.data AND as ObjectKanban's data prop; hasExternalData = Array.isArray(externalData) is therefore true and the fetch effect returns at its FIRST line, before if (schema.objectName && !boundData && !schema.data) is reached. Three ablation legs: removing either guard alone leaves every row GREEN, removing both reddens the gate rows by name. Anyone writing a pin about data on this block from the read that looks right will write a pin that cannot fail.",
    "A pin about a side effect NOT happening needs a calibrated window, and my first version did not have one. It asserted find had not been called immediately after the first card appeared - which is earlier than the board ever queries, since the fetch is gated on objectDefReady. It could not be made to fail. Both negatives now wait through a settle window and a CONTROL row makes a real query observable through exactly that wait. The brief's 'prove every new pin CAN fail' caught two separate defects in the same three rows; without the ablation both would have shipped as green nothing.",
    "The brief says 'an assertion pins four keys BY NAME so a fifth cannot ride that headroom' as if it were something to write. It already exists on main - 'the ceiling correction admits exactly the four keys objectui#8176 made visible', over NEWLY_JUDGED_UNPINNED_MEMBERS, and its four names are object-calendar.calendar/.dataSource and object-kanban.columns/.dataSource, NOT this card's four keys. Nothing was owed there; I only had to avoid adding a member-pin exemption, and that assertion is one of the two that reds if you do.",
    "The claim comment's 'the backlog ceiling will read 8 (both assertions)' was right, and so was the warning about the collapsed lists. But the brief's framing that the four keys were 'measured genuinely spec-declared in slice 1' is worth one caveat: the spec arms are NOT uniform. data is z.array(z.unknown()) and cardFields is z.array(z.string()) - those DO constrain the container kind, so the 'the spec cannot supply the member contract' sentence the brief attaches to grouping and conditionalFormatting is true of those two only. For data and cardFields the spec fixes the container (and for cardFields the member kind) and says nothing about what the board does with a member.",
    "objectui#8285 (quickAdd) has NOT landed on main as of this branch's base c842594 - object-kanban.quickAdd still carries the generic 'A DECLARATION SOMEONE OWES' reason and the ceiling was still 8. I did not ride it: it is a product ruling, not a measurement. It and this PR both move the ceiling, so whichever lands second resolves a one-line conflict.",
    "objectui has no scripts/pm/os-verify-lock.sh; the lock is container-scoped at /tmp/os-heavy-verify.lock and I drove it through the objectstack copy. Also objectui has no scripts/pm/dispatch-gates.mjs, so the gate family list here was derived by hand from package.json and the workflows rather than from that script - which the base rules explicitly say only answers for the tree it lives in.",
    "The PR is mergeable_state 'behind' (main moved from c842594 to d8ab2dc while I worked). I did not rebase or force-push; the merge queue rebuilds on current main."
    ]
    }


    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

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions