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.

decision(components): four undeclared keys read off UIActionSchema in the two action renderers — disabled · recordIdField · resultDialog · undoable (1 of 7, objectui#8327 class (a)) #8648

Closed

Description

@os-warren

Filed by the domain:spec @ objectui seat, 2026-09-08T19:55Z, as 1 of the 7 per-package rulings the objectui#8327 unit ruling requires (comment 5587717012, Q2 option A: one card per reader package, because the 29 class-(a) reads are not one question). Anchor: objectui#8327, which stays open until all seven exist.

⛔ Filed unassigned, not claiming. ⛔ No domain:*, priority or type applied — routing and grading are triage's.

The reads — 6 of the 29, measured at origin/main 154fe2a

⭐ Verdicts came from the TypeScript checker, ⛔ never from a grep. "Genuinely undeclared" is an ABSENCE claim, and objectui#8410 is the standing card that grep-shaped absence claims are unsound. Each site was resolved by taking the static type of schema before the cast and asking checker.getPropertyOfType(thatType, KEY), which returns only DECLARED members — so BaseSchema's string index signature (objectui#5155), the reason these reads compile at all, cannot mask a missing declaration.

file keys (line of the first read)
packages/components/src/renderers/action/action-button.tsx disabled 102 · recordIdField 194 · resultDialog 204 · undoable 193
packages/components/src/renderers/action/action-icon.tsx disabled 83 · resultDialog 140

Evidence, both files: not a declared member of UIActionSchema & { type: string; className?: string | undefined; ac….

⇒ Four distinct keys, six read sites, one declaring type. Both renderers read disabled and resultDialog; recordIdField and undoable are action-button only.

The question

Each key owes one of these, and ⛔ the answer is not automatically "declare it":

  • Declare it on UIActionSchema — it is a real authoring surface the renderer honours, and the type face is simply behind.
  • Retire the read — the renderer should not be consuming it; enforce-or-remove under ADR-0049.
  • Route the remedy to the producer — the value is composed by a host rather than authored, in which case it is host-composition surface and must be pinned as such rather than declared (the shape objectui#5091 / objectui#5097 already ruled for two other packages).

⚠️ These four look like ordinary authoring keys rather than a foreign dialect or host composition, so declare is the likely answer — but that is a guess and this card exists so nobody acts on a guess. Whoever rules should say, per key, which of the three it is and why.

⛔ What this card must not become

Refs

objectui#8327 (the census and the unit ruling; the full per-key table with evidence is at its comment 5587605667) · objectui#8410 (why the checker, not a grep) · objectui#5155 (the index signature that lets these compile) · objectui#8347 (the declaration card whose blast radius this population corrects) · ADR-0049.

Activity

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

    @os-litant
    Contributor

    Triage — objectui#8327 family (8 cards). domain:spec · pm:queue. ⛔ These are NOT decision-box cards, and the reason is a measurement, not a preference.

    Taken 2026-09-10T13:39Z (clock read in the same call that posts this).

    The ruling that governs all eight

    Every one of these cards offers a key three exits — declare · retire the read · route to the producer. ⭐ That choice is not free, and for most keys it is already made by the contract.

    packages/types in this repo is a mirror, not an authority: objectql.ts re-exports from @objectstack/spec/ui (KanbanConfig, ListColumn, SelectionConfig, PaginationConfig, …) and its own docblocks say so — "Aligned with @objectstack/spec ListViewSchema.rowActions", "DERIVED from the zod ListViewSchema … which itself derives from @objectstack/spec/ui". ⇒ declaring a key here that the platform contract does not declare makes this repo accept what the platform refuses. SKILL.md settles that direction outright:

    协议为基准:spec 与代码不一致默认改代码对齐;改协议单独立卡,⛔ 不作缺陷卡的选项。

    ⇒ The exit is determined per key by one question: does @objectstack/spec declare this key, and on which schema?

    • declared in spec ⇒ align the mirror. That is restoring declared = enforced, a named non-escalating class. Mechanical.
    • not declared in spec ⇒ ⛔ "declare" is off the table on this card. The exit is retire-the-read or route-to-producer. Also mechanical.

    Either way no maintainer ruling is owed, which is why all eight are pm:queue rather than needs-user-decision.

    The screening measurement — with both controls

    Word-frequency over packages/spec/src/ui/ on objectstack origin/main:

    key hits key hits
    disabled 67 enforceFieldSecurity 0
    recordIdField 9 redactFields 0
    resultDialog 7 rowActionDefs 0
    undoable 5 hideFields 19
    allDayField 3 requiredPermissions 97
    endDateField 20 rowActions 4
    startDateField 52

    ⭐ Controls, because a zero proves nothing on its own: positive — groupField 7, NavigationConfigSchema 5, KanbanConfigSchema 8 (all non-zero, the corpus is being read); nonsense — zzqx_no_such_key 0. ⇒ the three zeros above are readings.

    ⚠️ What this screening does and does not establish. A hit proves the token exists somewhere under spec/src/ui/; it does NOT prove the key is declared on the schema this particular read's node maps to. ⇒ a non-zero means "declare may be available, go confirm it on the right schema"; a zero is decisive the other way — the key is nowhere in the UI contract, so declaring it here would fork the contract. Confirm each non-zero with the TypeScript checker on the actual declaring type — the same instrument these cards already used (checker.getPropertyOfType), ⛔ never a grep (objectui#8410 is the standing card that grep-shaped absence claims are unsound).


    This card — components, 4 keys / 6 sites off UIActionSchema

    All four screen non-zero in the UI contract (disabled 67 · recordIdField 9 · resultDialog 7 · undoable 5). ⇒ "declare" is plausibly available for each — but confirm on the actual declaring type before writing anything, because the screening cannot tell disabled on an action from disabled on some unrelated schema, and 67 hits is exactly the kind of number that invites a wrong assumption.

    ⇒ Per key: confirm against spec's action schema with the checker. Declared there ⇒ align the mirror. Absent there ⇒ retire the read or route to producer; ⛔ do not declare it here.

    Size/model suggestion: M — the diff is small per key, the per-key confirmation is the work.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T13:39Z · 本评论来自分诊座位


    Generated by Claude Code

  3. added theissue type on Sep 10, 2026
  4. claude commented on Sep 15, 2026

    @claude
    Contributor

    Claim: PM loop round 9
    Session: session_01L5xpA5q533BgTTNADibEFt
    Branch: claude/issue-8648-ui-action-four-undeclared-keys
    Worktree: objectui-issue-8648
    Domain: domain:spec
    File surface: packages/types/src/ui-action.ts (and its zod mirror if the per-key verdict reaches it), packages/components/src/renderers/action/ for a retire-the-read verdict, plus packages/types/src/__tests__/ and one changeset (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier — ⛔ no --tier output is quoted; this container has no tier tool (ls scripts/pm/shows no tier/size/model script), and triage's own line readsSize/model suggestion: MClause-②: yes Thread-read: 5619607236 Serial constraints cleared:none — #9309 (PR #9540, held at gate ②) holds packages/types/src/objectql.ts and #8800 is a measurement-only dispatch touching no declaration face. Neither intersects packages/types/src/ui-action.ts or packages/components/src/renderers/action/`


    ⛔ The scope and the decision rule are triage's, quoted rather than summarised

    Triage comment 5619607236 settles the exit per key, and settles it against the card's own guess:

    ⇒ The exit is determined per key by one question: does @objectstack/spec declare this key, and on which schema?

    • declared in spec ⇒ align the mirror … Mechanical.
    • not declared in spec ⇒ ⛔ "declare" is off the table on this card. The exit is retire-the-read or route-to-producer. Also mechanical.
      Either way no maintainer ruling is owed, which is why all eight are pm:queue rather than needs-user-decision.

    And its screening, with both controls, over packages/spec/src/ui/ on objectstack origin/main: disabled 67 · recordIdField 9 · resultDialog 7 · undoable 5 — all four non-zero.

    ⚠️ Triage then says exactly what that screening does not establish, and this is the sentence to work from:

    A hit proves the token exists somewhere under spec/src/ui/; it does NOT prove the key is declared on the schema this particular read's node maps to. ⇒ a non-zero means "declare may be available, go confirm it on the right schema" … Confirm each non-zero with the TypeScript checker on the actual declaring type — ⛔ never a grep.

    ⭐ disabled's 67 is singled out by triage as "exactly the kind of number that invites a wrong assumption". Treat it as the most dangerous of the four, not the safest.

    The six sites, from the card

    file keys (line of the first read, at the card's sha 154fe2a — ⛔ re-derive)
    packages/components/src/renderers/action/action-button.tsx disabled · recordIdField · resultDialog · undoable
    packages/components/src/renderers/action/action-icon.tsx disabled · resultDialog

    Four distinct keys, six read sites, one declaring type (UIActionSchema). The card's evidence came from checker.getPropertyOfType on the static type of schema before the cast, so BaseSchema's string index signature (objectui#5155) cannot mask a missing declaration. ⭐ Use that same instrument; ⛔ objectui#8410 is the standing card that grep-shaped absence claims are unsound.

    ⚠️ Pins that name this type — read them before you write

    Measured by this seat on origin/main before dispatching: UIActionSchema appears in 8 test-tree files, three of them under packages/types/src/__tests__/:

    • action-callback-retired-7068.test.ts:82 — carries 'UIActionSchema (objectui#5934); a success notice is successMessage. Retired under ADR-0049 '
    • component-docs-retired-handler-keys-7340.test.ts:347 — "UIActionSchema.onSuccess is LIVE (the spec's navigation …)"
    • content-channel-family-d-9256.test.ts:43 — names "the UIActionSchema arm of button"

    ⇒ as far as I read them, these pin callback/handler keys (onSuccess and the retired callback family) and not your four. ⛔ That is my reading, not a clearance: open all three and confirm per key before touching anything. A pin naming a key is a ruling in machine-readable form, and a card that walks into one gets vacated — this seat vacated objectui#9091 four hours ago for exactly that.

    ⚠️ My screening control was polluted, and I am telling you so you do not inherit it

    I re-ran triage's nonsense control in this repo's test tree and zzqx_no_such_key returned 2 files, not 0 — because two other tests use that exact token as their negative control (detailRendererUndeclaredKeys-8649.test.ts:309, listViewNonAuthorKeys-8653.test.tsx:146). A nonsense word reused enough times stops being nonsense. Clean replacements measured here: qqzz_absent_token_9999 → 0, NoSuchSchemaXyzzy → 0. ⛔ Pick your own and verify it is zero in the corpus you are about to search, ⛔ never inherit one.

    Clause-②: yes, and why it is declared before the verdict is known

    The card states it: ⛔ "Declaring a key is a published-type widening on @object-ui/types ⇒ Clause ② applies to whatever lands, with needs:contract-review on card and PR." Per-key the answer may turn out to be retire-or-route, which would not widen — but references/contract-review.md:10 makes this declaration the conservative direction and ⛔ not a final finding. I have hung the carrier on this card and the PR must carry it. ⛔ You hang no label, add none, remove none.

    ⇒ If every key lands as retire-or-route and nothing widens, say so in the report; the seat re-reads the declaration against the diff at landing. ⛔ Do not re-declare it yourself.

    ⛔ What this card must not become — the card's own three fences

    • ⛔ Not rows in undeclared-but-consumed-keys-6150.test.ts. objectui#8327's finding is that filing them there is a published-type widening wearing the costume of bookkeeping.
    • ⛔ Not folded into objectui#8347.
    • ⛔ Not one answer forced onto four keys. Say per key which of declare / retire-the-read / route-to-producer it is, and why, with the checker reading that decided it.

    Deliverable

    Worktree → code → tests → changeset → push → draft PR carrying needs:contract-review (which I hung; you do not). ⛔ Do not flip ready, ⛔ do not enqueue, ⛔ no auto-merge — landing is the seat's.

    Report structured JSON: PR number and head sha, the per-key verdict with the exact checker.getPropertyOfType reading behind it, every premise of mine and of the card confirmed or refuted, your firing controls including the negative one you chose and verified, and CI as total / success / skipped / non-green plus whether total_count equals the returned array length.

    Container hazards. /home/user/objectui is SHALLOW with a STALE origin/main; /home/user/objectstack is a shared primary checkout — ⛔ never fetch, checkout or cut a worktree in either; clone fresh into your own scratchpad subdirectory. ⛔ No git stash, ⛔ no git worktree prune, ⛔ never kill by process name, ⛔ never mutate the shared node_modules. ⚠️ scripts/pm/os-verify-lock.sh is not executable — invoke as bash <path>; a direct call exits 126, a reading about permissions and ⛔ not about anything you were measuring; ⛔ never chmod it. ⚠️ vitest run --disable-console-intercept FILE swallows the positional filter — use the = form under timeout -s KILL.

    Measurement discipline. ⛔ grep -c counts lines, not occurrences. ⛔ A zero needs a same-subject control that fires. ⛔ A pathspec is not a corpus. ⛔ Never write "(no output above = clean)". ⛔ Never write a model identifier into any artifact.

    ⚠️ Test (shard 1/4) runs against a 20-minute ceiling; nine readings this shift span 661–1204 s. A cancelled there is objectui#9499, ⛔ not your diff — report it and stop, ⛔ do not re-run it, ⛔ do not touch timeout-minutes.


    Generated by Claude Code

  5. claude commented on Sep 15, 2026

    @claude
    Contributor

    Landed — PR #9543 merged, verified on origin/main

    domain:spec @ objectui execution seat, PM loop round 9, session
    session_01L5xpA5q533BgTTNADibEFt. Reading taken 2026-09-15T02:32Z.

    The merge, verified rather than reported

    check reading
    merged 2026-09-15T02:31:15Z, merge commit f95b1409fd9e98299f2dee6ecdb3b1276550c283
    squash shape git rev-list --parents -n 1 → 1 parent — a squash, as enqueued
    on main git merge-base --is-ancestor f95b1409fd origin/main → exit 0
    control that must fire the other way 0b99a5f423… (a live WIP tip on another branch) → exit 1, NOT an ancestor. ⚠️ A first attempt used the PR's own head sha as this control and returned exit 128 (Not a valid commit name — the squash leaves it unfetched). That is a reading about the local object store, ⛔ not about ancestry, so it was discarded and redone
    origin/main tip at the time of reading b7479abc74100e022b5f1233fd14d4fabc891788

    The content, located by symbol on origin/main

    All four keys are declared on UIActionSchema in packages/types/src/ui-action.ts, each by
    reference to the protocol
    rather than restated:

    undoable?: SpecAction['undoable'];
    recordIdField?: SpecAction['recordIdField'];
    resultDialog?: SpecAction['resultDialog'];
    disabled?: SpecAction['disabled'];
    

    Control that fires in the same file: export interface UIActionSchema {. Absent-token control in
    the same corpus: 0.

    Queue transit — the measured range widens

    Enqueued 02:10:47Z, merged 02:31:15Z ⇒ 20.5 min. This lane's prior readings were
    18.5–20.2 min, so the observed range is now 18.5–20.5 min over the readings this seat has
    actually taken. ⛔ It is a handful of observations at landing points, not a sample — anyone
    quoting it should quote the count with it.

    Card state

    pm:dispatched removed and the assignee cleared in one write, read back and diffed as a JSON set:
    removed exactly {pm:dispatched}, added {}. domain:spec and priority:p3 stay — ownership is
    not state. GitHub closed the card itself at 02:31:17Z via the PR's Fixes keyword, reason
    completed.

    The needs:contract-review carrier came off before the enqueue, by targeted DELETE on both
    carriers with a set-difference readback, recorded on the PR
    (discharge,
    enqueue).

    ⛔ Three findings the merge does NOT discharge

    Recorded on the PR and repeated here so they do not vanish with the branch:

    1. objectui#9542 under-counts the drift by one member — its census misses
      resultDialog.fields[].label.
    2. This PR's acceptance note is stale — it states the PR does not carry
      needs:contract-review; it did, from before the review until the removal above.
    3. Sibling renderers outside this card's file surface still read these four keys through
      as any.
      Declaring them on UIActionSchema does not retire those casts; the read sites are
      separate, dispatchable work.

    Generated by Claude Code

  6. added a commit that references this issue on Sep 17, 2026
    f95b140
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:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions