feat(types): declare the four action keys the two renderers read off UIActionSchema — disabled · recordIdField · resultDialog · undoable - #9543
Conversation
…`UIActionSchema` `disabled`, `recordIdField`, `resultDialog` and `undoable` were read at eight sites across `action-button.tsx` and `action-icon.tsx` through `as any`, and none was a declared member of the type. Each key was decided on its own reading with `checker.getPropertyOfType` on the static type of `schema` BEFORE the cast, never with a grep: all four are DECLARED on `@objectstack/spec`'s `ActionSchema`, the schema this interface mirrors, so all four align the mirror. The casts are removed in the same change, because a cast at the read site defeats the declaration while a membership instrument still reports the member as present. A new pin re-derives the contract side every run and goes red if a cast returns. Removing the `resultDialog` cast made the compiler name a second defect it had been hiding: `@object-ui/core`'s `ResultDialogSpec` hand-copies the contract's block and has drifted on three `I18nLabel` members. That repair is in another package and turns on an i18n-resolution decision, so it is filed separately; the write side carries a documented, pinned narrowing meanwhile. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewobjectui#9543 at head Where the readings were takenFresh clone of
Claim population — 24, with a proved boundEnumeration A —
1 · The per-key verdict, re-derived per keyInstrument. A one-off program built over The pre-cast type at all eight sites, one distinct string across all of them:
Controls in the same pass, so none of the four absences is vacuous.
⭐ What decides ⇒ All four take the declare branch, each on its own reading. The dev's per-key verdict is confirmed. The naming differs harmlessly — the report says 2 · Are the reads actually enforced now?Yes, measured at the binding, not at the text. 8 read expressions of the four keys across the two renderers — Six cast reads remain in the same two files — Screen for leftovers of the four, published with its rule: CORPUS = every path listed by 3 · The pin — four ablation legs, all fired, all restored by blob hashBaseline from the committed state: pin 16 passed (16); Counting rule for every marker below:
⇒ The pin can fail, in both halves the dev claimed and in two more this seat added. The two claimed legs reproduce exactly (exit 2 with 9 The pin's own runtime calibration also fires inside the green run: the shape walker opens both contract schemas with more than two keys each, 4 · Clause ② — measured, both halvesHalf one: does the published
⇒ exactly these four members, and nothing else. Half two: does the accept set move TO the contract's, or past it? For each key, taking
Direction control in the same program: And nothing else moves. No runtime behaviour changes — proved, not asserted. Type-stripped, comment-stripped emit of both renderers at the merge-base and at this head: byte-identical, 4881 bytes for 5 · The declared out-of-scope finding — judged, ⛔ not waved throughThe drift is real, and I re-derived it. Was stopping there right? Yes. The repair lands in Is the interim narrowing sound? Sound enough, and I will say exactly in what sense. Measured:
③ Landing readings —
|
Contract-review discharge — PR #9543 / card #8648Posted before the Verdict being discharged against
Read at Why the carrier was hung, and what the review did with it Card #8648 declares
⇒ something genuinely widens, the carrier was not a false alarm, and the widening is bounded by the contract. Other gates at this same head
What is being removed, and from where
Label sets before removal — PR #9543: Three findings the review recorded are NOT discharged by this comment and do not block the merge: objectui#9542 under-counts the drift by one member ( Generated by Claude Code |
Enqueue record — PR #9543Taken at Carrier removal (the promise made in the discharge record, discharged)
Both diffs are exactly The three pre-landing gates, re-taken at the landing head① same-form PASS at the landing head. Verdict comment ② carrier agreement, re-run after the verdict landed and after the label removal. ③ check census. Before the ready flip: Landing
Measured queue transit on this lane has been 18.5–20.2 min. Not discharged by this landing — three findings that ride forwardThe review returned PASS on this PR's contract question. Three findings it raised are not
Generated by Claude Code |
Fixes #8648
Four keys, six (key, file) pairs, eight read expressions, one declaring type — and four separate readings that happened to agree. ⛔ Not one answer forced onto four: the instrument below asks the question once per key, and its inline sibling is where the same question comes back no.
The decision rule, and the instrument that answered it
Triage settles the exit per key with one question — does
@objectstack/specdeclare this key, and on WHICH schema? — because@object-ui/typesis a MIRROR of the platform contract, not an authority over it. Every verdict here came fromchecker.getPropertyOfTypeon the static type ofschemabefore the cast, ⛔ never from a grep (objectui#8410 is the standing card that grep-shaped absence claims are unsound).The pre-cast type at all eight sites, as the checker prints it (generic spelled as a word — see the note at the bottom):
UIActionSchema AND-TYPE OBJ-OPEN type: string; className?: string OR undefined; actionType?: string OR undefined; OBJ-CLOSEgetPropertyOfTypeActionSchemaInlineActionSchemadisabledrecordIdFieldresultDialogundoableControls in the same pass, so none of the above is vacuous:
labeldeclared on every one of the four types;endpointandenableddeclared on the mirror and absent on spec'sActionSchema(so "the mirror equals the contract" is not what the instrument is reporting);refreshAfterdeclared on the inline sibling, which is where two of the four are absent; and a negative control declared nowhere.⭐
disabledis the one triage singled out, and it is the one this discriminates. A word-frequency screen over the UI contract answers "present" for it loudly (67 hits at triage; 71 when re-run). That reading decides nothing — the contract also refusesdisabledon its own inline action schema, which spec's own inline test asserts from the other side. Per-schema is the only reading that separates them, and it is re-derived on every run rather than written down.What landed
@object-ui/types—UIActionSchemadeclares all four, each by derivation from the contract (SpecAction['…'], the form this file already uses foronSuccess/bodyExtra/bodyShape), so none can drift.disabledinherits all three arms the contract accepts: literal boolean, raw CEL string, and a{ dialect, source }envelope.@object-ui/components— all eight read sites are un-cast.Expectmust refusefalse;Equalmust refuseneverand must refuseany—anybeing exactly what the casts produced).Alignment, not a widening past the contract. The accept set of this face moves to the platform's and never beyond it: a document already valid everywhere else stops being refused here with
TS2353. ⛔ Nothing that worked stops working, and no runtime behaviour changes — all eight reads were already honoured; what changes is that the compiler checks them.⛔ The card's three fences, all held. ⛔ No rows added to the
undeclared-but-consumed-keys-6150ledger. ⛔ Nothing folded into objectui#8347. ⛔ No single answer forced onto four keys — each verdict has its own reading behind it, in the table above and in the pin.as anywas hiding TWO defects, and the second is filed, not fixedRemoving the read-side cast on
resultDialogmade the compiler report that@object-ui/core'sResultDialogSpec— whose own docblock claims it mirrors the contract's block — hand-writestitle/description/acknowledgeasstringwhere the contract declaresI18nLabel. So this repo refuses what the platform accepts on those three members, andActionResultDialogrenders the member straight into JSX.That repair lands in
@object-ui/coreplus the dialog's own resolver, and its second half is a real i18n-resolution decision rather than a mechanical one — outside this card's declared file surface. ⛔ Not guessed at here. It is filed as objectui#9542 (that card stays open; this PR does not address it).Meanwhile the WRITE at both forward sites carries a narrowing assertion to
ActionDef['resultDialog']— strictly narrower than theas anyit replaces, since every other member of the forward literal is compiler-checked again. It is documented at both sites as a ledger entry and pinned two ways: it cannot regress toas any, and it goes red when objectui#9542 lands and the assertions go.Verification
Every number below is a reading with its command; ⛔ nothing is asserted from absence of output.
Reverse verification — the pin can fail, both halves, from the committed state. Each leg: mutate → prove it reached disk → rebuild → prove it reached
dist→ measure → restore → prove the restore is byte-identical.disableddeclaration from the mirror): source marker hits 1 → 0, injected marker 1;@object-ui/typesbuild exit 0;distmarker 1 → 0, so the mutation reached the artifact the pin reads.tsc -p tsconfig.test.jsonthen exits 2 with 9error TS, namingTS2339on the type pins andTS2353on the repro literal, plusTS2339at the renderer's own read site. Restore:git diff HEADempty, blob on disk equals blob at HEAD, rebuild exit 0,distmarker back to 1.action-button.tsx): un-cast hits 1 → 0, re-cast 1; the pin's source-text leg goes red —1 failed | 15 passed. Restore byte-identical.16 passed (16),type-check9 successful, 9 total.Tests and type-check (all through the shared verify lock,
VERDICT command-exit 0):pnpm exec vitest run packages/components/src/renderers/action/→ Test Files 19 passed (19) · Tests 299 passed (299)turbo run type-check --filter=@object-ui/types --filter=@object-ui/components→ Tasks: 10 successful, 10 totalturbo run build --filter="@object-ui/components^..." --filter="@object-ui/types^..."→ Tasks: 7 successful, 7 totalGates, each exit code captured before any pipe:
check:changeset-presencecheck:new-line-citations0 new citation(s), enforcement report-onlycheck:action-forward-paritypayload excess-property CHECKEDcheck:control-bytescheck:spec-symbols1384 files scanned … 0 untriaged collisionscheck:comment-mask-corpuscheck:test-path-rootscheck:handler-key-readscheck:changeset-claimsPlus a control-character self-scan over all five changed files: no hit (grep exit 1).
Acceptance notes
needs:contract-review. The card carries it, and this seat was instructed in as many words that it hangs no label, adds none and removes none — labels are the claiming seat's alone. Flagged rather than resolved either way. Something does widen here: all four keys land as declare, so Clause ② is live and the declaration is not a false alarm.description,openIn,locations— remain in both renderers although all three are already declared members ofUIActionSchema. Noted, ⛔ not filed and ⛔ not fixed: they are outside this card's four keys, and they are a different class from it (a redundant cast over a present declaration, not a read with no declaration behind it). The successor that will touch these forward blocks is the rest of the objectui#8327 family.AND-TYPE,OBJ-OPEN,OBJ-CLOSEandORstand where the compiler prints the punctuation. Written this way deliberately, ⛔ not a transcription error.Generated by Claude Code