Skip to content

chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile - #7685

Merged
os-sam merged 33 commits into
mainfrom
claude/issue-7122-spec-pin-17-3-0
Sep 7, 2026
Merged

os-sam merged 33 commits into
mainfrom
claude/issue-7122-spec-pin-17-3-0

Conversation

@claude

@claude claude Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Part of #7122

⚠️ Three measurement epochs, not one. §7a is measured on 7be4b2456; §7's table on 901c4b867; §3's arithmetic on 34a1578ef. Each section names its own head — do not read one head across all of them. Base merged: origin/main f5d2acc35. ⚠️ §9 supersedes that base — origin/main bdb4dbd58 is merged in at this head, and §9 carries its own measurements. Everything the previous round left red is now green locally, including the two ceilings the maintainer authorised raising.

⭐ This round applies the contract review's required remediation, which ruled needs:contract-review not dischargeable as it stands. The two items on this seat are done: the lookup guard's whitespace-only refusal is now a declared divergence rather than a silent one (§6 — and the message it prints is accurate for all four refused states), and the three package entries that graded objectui's own breaking behaviour patch are raised to minor with the breaking semantics written into the changeset bodies (AGENTS.md:239). §6 is replaced with the reviewer's own wording; two test-quality nits went with them. All of it is in §7a, measured on a freshly merged tree.

⛔ Still draft. ready not flipped, not enqueued, auto-merge not armed — review first.

⭐ What this round changed

CI check at 2761082b7 (last CI read) at 901c4b867
Type Check 1 blocker ✅ 81 / 81, --continue
Test (4 shards) ❌ 7 files / 24 assertions ✅ 2,555 files, 32,677 tests, 0 failures
Bundle Analysis ❌ over aggregate and vendor-objectstack ✅ exit 0 — ceilings raised, below
everything else ✅ ✅

⚠️ The previous body's red set (8 assertions / 6 files) was a measurement of a tree two heads back and has been rebuilt from this seat's own run, not patched. The real set on 2761082b7 was 7 files / 24 assertions. Section 2 gives the reconciliation.

1. origin/main was merged first, and it barely moved the payload

fa73d58a9 had already merged 900f8d995; this round merged 6bca0e4e8 on top (7 commits, clean, no conflicts, no file overlap with anything repaired here).

⚠️ The merge was worth doing for correctness — it makes the baseline the tree CI weighs, and keeps BASELINE.commit's "differs only by this file" claim true — but ⛔ it did not recover bytes. CI measured both sides: pre-merge e328e369c 3468.0 KB, merged 5ac59ff43 3467.8 KB. 6bca0e4e8's ActionCallback deletion is worth 0.2 KB. That is the finding; it is not a saving.

⛔ origin/main was deliberately not chased further. It moved again during the build (3f93fe47d); a tree that matches CI's merge ref exactly is impossible by construction, and the drift is what headroom is for.

⭐ Merged again before the §7a push, because by then the branch was 38 commits behind: f5d2acc35 is merged in at 7be4b2456, clean, no conflicts, 136 files, and — the reason it was not optional — the aggregate ceiling's 45.8 KB of headroom had been sized against ~4 commits of drift, not 38. ⛔ It did not cost bytes: see the merged-tree re-measure in §7a. No manifest dependency and no lockfile line moved in the merge (two scripts entries in the root package.json, one type-check script in packages/vscode-extension), so no reinstall was needed.

2. The real red set, and where each one went

24 assertions in 7 files, measured here before any repair and reproducing CI's aggregate exactly. All 24 are @objectstack/spec 17.3.0's, none is a regression this branch wrote.

family files reds disposition
A — the #7496 token submitRedirect.test.ts (console), WizardForm./ObjectForm.submitRedirect.test.tsx 14 fixed once, as a family
B — stale exemptions registry-inputs-spec-parity.test.ts 5 3 exemptions harvested
C — negative pins whose premise evaporated masterDetailFormTypeManifest.test.ts, LocationField.optionalKeys.test.tsx 2 re-derived, not inverted
D — the ninth capability CapabilityMultiSelectField.specParity-6285.test.tsx 3 carried, with labels

Already red at the previous body's f389bec90, or new since? All four families are 17.3.0 effects and were present on the branch before the main merge — they were invisible to the old catalogue because that catalogue was written from a --project unit run, which is a subset: pnpm test (what CI shards) also runs the per-package projects and the dom projects, where every one of these 24 lives. ⭐ Nothing appeared because of the merge, and nothing disappeared without being fixed.

A — 14 assertions, one fix, and why it qualified as a family

Every one was expected '(message)' to contain '#7496', across five distinct refusal messages, while the sibling toContain('RELATIVE path only') assertions stayed green. 17.3.0 kept every refusal, its reasoning and its prescription, and restated provenance as (ruled 2026-08-11).

So the pins asserted the citation form, not the citation — the objectui#7702 defect shape, which main has since corrected in its own lane. They now assert the durable property through a documented CITES_ITS_RULING, which does the job the #7496 assertion existed for: discriminating the spec's own prose from a locally hand-written sentence (the load-bearing mutation probe in ObjectForm.submitRedirect.test.tsx's header depends on exactly that).

⚠️ Corrected in §7a, and the review was right to catch it. That regex was first written /\(ruled \d{4}-\d{2}-\d{2}\)|#\d{3,}/, and the loose alternative discriminated nothing — this repo's own hand-written messages routinely cite objectui#NNNN, so a local sentence satisfied it, which is exactly the case the assertion exists to reject. It was also unnecessary: measured on both installed artifacts, 17.2.0 prints (ruled 2026-08-11 on #7496) and 17.3.0 prints (ruled 2026-08-11), so the issue number never appears outside that parenthesis and (ruled + a date already matched both. It is now /\(ruled \d{4}-\d{2}-\d{2}(?: on #\d{3,})?\)/ — both upstream spellings still match, a bare local #7122 no longer does.

⛔ Not re-pinned to the new prose verbatim, which would move the brittleness one release along instead of removing it.

B — three exemptions the contract outgrew

Four of the five reds were one fact wearing four assertion names, two of which appear to contradict each other ("a published key must lose its entry" vs "spec no longer declares object-grid.defaultSort"). Resolved against the installed artifact, not the assertion names: neither reading is right. 17.3.0 made object-grid.defaultSort an ADR-0087 D2 tombstone — member is z.never().optional(), description opens [REMOVED] … removed in @objectstack/spec 17 (ADR-0049), and safeParse answers invalid_type expected: 'never' at that path. It left the authorable set while staying listed, which is precisely how the eight tombstones before it were harvested. Its exemption, its GA-pending entry and its carve-out row are deleted.

The block's prose claiming a tombstone "would NOT make them stale here" is corrected — its own cited counter-example, the record_picker trio, was harvested exactly that way.

The fifth emptied OFF_SPEC_ARM_EXEMPTIONS entirely. Both arms converged upstream in the direction their own reasons named, measured with the gate's own probes:

  • element:number.filter accepts [] and judges content at filter.0 — objectui#6206's first option, "widening the spec entry to the ViewFilterRule array form every sibling filter uses";
  • object-grid.data answers invalid_union at data.provider, i.e. the discriminated ViewDataSchema shape ObjectGridSchema.data already resolved to — objectui#6207's "convergence is upstream".

⛔ Neither was closed by editing a declaration; both declarations are byte-identical. objectui#6206 and objectui#6207 are closable as resolved-upstream.

C — two negative pins, re-derived rather than inverted

  • object-master-detail-form.formType pinned that the manifest enum does not make a value unpublishable. 17.3.0 closed that gap deliberately — invalid_value naming the honoured pair, plus a bespoke prescription for the near-miss wizard citing ADR-0001 and the renderer measurement behind it. Graded as an intended tightening, so the pin now records that both authorities agree, checks each independently, keeps a positive control on simple/tabbed, and pins the refusal's shape so "the enum closed" cannot be confused with a dropped key or a cross-field rule.
  • LocationField.optionalKeys pinned that the location value schema is non-strict. 17.3.0 closed the shape (unrecognized_keys, naming the retired latitude/longitude pair and prescribing the rename). The test carried a note for this exact event — "if the spec ever turns strict, the guard could then be delegated to it" — and the re-derived answer is no: the fence guards the object handed to onChange, and no parse stands on that path, so strictness changed the consequence of a spread regression (a loud refusal at the next parsing boundary instead of a silent survival), not the widget's exposure to it. The key-level assertions stay and now say why; the strictness itself is pinned.

D — the ninth platform capability

17.3.0 declares manage_org_presentation and the curated set carried eight, so the picker fell back to the registry's English label in every locale — the exact defect objectui#6285 filed when manage_sharing did this. objectui#6285 already ruled the curated set is the spec's set in both directions, so carrying the name executes that ruling.

The English label is the spec artifact's own (Manage Organization Presentation), read off the installed build. It is authored everywhere the widget's docblock requires: useFieldTranslation.ts and all ten locale packs. ⚠️ The nine non-English strings are composed from each pack's own established sibling vocabulary (manage_org_users, manage_platform_settings) rather than machine-translated — a native review pass is welcome on them; nothing behavioural depends on the wording. check:i18n-keys (now 114 exactly-checked members, was 113), check:i18n-drift and check:i18n-dead-keys all exit 0.

3. ⭐ The ceiling raise — authorised, and what the bytes buy

Raising a gate ceiling is a gate weakening and sits on the manual floor, so it is a human's decision. The authorisation, verbatim:

维护者 sam@objectstack.ai 于本轮明确授权:「抬上限,把 7685 弄绿」

recorded on #7122 as decision batch 1 item 1 = B + A — a one-time, cause-recorded adjustment of exactly the measured delta, with the upstream card filed alongside.

⛔ This is not routine growth and must not be read as one. The residue is @objectstack/spec 17.3.0's own browser-dist growth: +292.2 KB gzip on the spec package alone, whose measured mechanism is that 17.3.0 lengthened the Zod .describe() doc strings shipped in the browser build. It is authoring documentation prose, delivered on every page load.

⛔ It is not duplication. That was the larger, separate problem and this branch already fixed it by moving the @objectstack/* family together in the lockfile: −671 KB, with markers unique to 17.2.0 falling from 92.3% to 1.0% of 104 and the single survivor accounted for. No chunk entered or left the closure; the eager chunk count is unchanged.

⇒ ⭐ The honest long-term fix is upstream, and this ceiling is its marker, not its answer. A describe()-stripped browser build returns ~292 KB to every consumer of the spec, not just this console. Filed as objectstack#16063, and recorded in the gate as the restore condition: when it lands, re-measure and bring ceiling and baseline back down together.

The arithmetic, so the sensitivity constraint is checkable by reading

Four constants, two ceiling/baseline pairs. Measured by pnpm build (exit 0, 43/43) reading apps/console/dist/eager-closure.json on 34a1578ef — ⛔ not taken from CI's report, not from an earlier round, not extrapolated.

constant from to
MAX_EAGER_CLOSURE_GZIP_BYTES 3,268,000 3,597,000
BASELINE 3,222,314 · 48 / 517 · 3d257c85a 3,551,191 · 50 / 518 · 34a1578ef
PER_CHUNK_GZIP_CEILINGS['vendor-objectstack'] 967,000 1,254,000
PER_CHUNK_BASELINE['vendor-objectstack'] 948,461 1,235,029
pair ceiling − baseline threshold ratio retiring pair's ratio
aggregate 3,597,000 − 3,551,191 = 45,809 91,136 0.50x 45,686 = 0.50x
vendor-objectstack 1,254,000 − 1,235,029 = 18,971 91,136 0.21x 18,539 = 0.20x

Both are strictly below the 91,136-byte regression the gate exists to catch, so it still reds on a repeat of objectui#5266 — the ceiling is not decorative. Both match the proportion the file already used rather than a newly invented one. Headroom also absorbs the unclosable merge-ref gap: CI's merged-head reading is 3,551,027 bytes against this tree's 3,551,191, a 164-byte difference.

BASELINE.commit names 34a1578ef. ⚠️ The usual invariant — the named tree differs from the pushed tree only by the checker, its unit test and this changeset — held when the raise was written (observed, not argued: the re-read build reported 43/43 tasks CACHED on that commit), and is no longer true of the current head: 7be4b2456 merged 38 commits of origin/main plus three authored commits on top of it. The baseline is nonetheless still sound, and by measurement rather than by argument — §7a re-ran check:eager-closure on the merged tree and the aggregate came in at 3,551,183, eight bytes below the pinned 3,551,191. Read §7a's re-measure as the live figure; this section is the derivation that produced the constants.

⛔ No other ceiling was moved. The three per-chunk lines that still pass were left exactly as they are, and they are reported here because two are tight:

chunk measured ceiling headroom
i18n-locales 449,305 455,000 5,695 (0.06x)
ui-components 394,968 399,000 4,032 (0.04x)
framework 68,550 71,000 2,450 (0.03x)

⚠️ The ten capability labels added by this PR cost +141 bytes in i18n-locales, which fits — so this is not a third ceiling/baseline pair and is deliberately not presented as one. ⛔ No exemption added, no import made lazy.

check:eager-closure exits 0 with Ceiling sensitivity ✅ on all five ceilings. Ceiling freshness reports "not applicable to a local run" (it compares a merge ref against the branch it would land on); CI evaluates it.

4. The two rescue commits — what was verified, and what was changed

9c1a1ac5f and 2761082b7 were PM rescue commits carrying work two killed agents never gated, both marked INCOMPLETE AND UNREVIEWED. Nothing in them was assumed correct. Every load-bearing claim was re-measured against the installed 17.3.0 artifact:

rescued content verdict
MetadataService.ts + MetadataFieldsPage.tsx — the lookup guard ✅ verified, kept. Its central claim re-measured independently: of FieldSchema's 49 declared types, exactly lookup and master_detail are refused at path reference on a minimal document, and no other type is refused at all. Empty-string reference is refused too, which is what the guard's .trim() matches. 2761082b7 had been mid-repair of a block the first agent inserted inside another function's docblock; the relocation is correct and the file now type-checks
user:profile three-site removal (placeholders.tsx, known-schema-types.ts, block-types.ts) ✅ verified, kept. PageComponentType measured at 32 options with user:profile and element:form both absent — exactly the rescue commit's claim, and exactly ruling B1
ObjectSchemaClientExtensions retirement (field-types.ts) ✅ verified, kept. ObjectSchema measured at 43 keys with editMode present, so the local member was a genuine fork; retiring it executes the pin's own docblock prescription (ruled item 2)
plugin-detail sections input description ✅ verified against the spec's section-entry keys; documentation only
export-options-spec-parity.test.ts (2761082b7) ✅ verified, kept, and NOT redundant with #6956. #6956 is on main and merged cleanly; this is one assertion inside it re-pointed at the prescriptive half. Both new substrings confirmed present in 17.3.0's message
the two rescue changesets ✅ accurate as written; three more added
⚠️ a ceiling or budget edit ❌ absent from both, as their messages said. Done here, section 3

Changed rather than kept: nothing in the rescue commits was reverted. The corrections this round made are in files the rescue commits did not touch (sections 2A–2D), plus the budget file.

5. ✅ Ownership settled — the lookup guard lives in #8076, and it is no longer here

#7122's ruling of 2026-09-05T23:00Z (item 4, A1) says the designer hunks "belong to #7714's PR — the driver either moves them there or drops them from this branch; ⛔ not both places."

When this section was first written it named the blocker exactly, and every clause of it was true: #7714 was pm:queue, unassigned, with no PR, so A1's second branch could not be executed from this seat. That is no longer true. #7714 has a PR — #8076 — and it landed on main at 2026-09-07T01:58:56Z as bdb4dbd58. For the interval between that merge and this one the guard existed in both places, which is the one state A1 forbids.

⇒ A1's second branch is taken, and this branch is the side that drops. main is merged in at fed2432dc. Five files conflicted — all of them the guard, none of them anything else — and every one was settled by taking bdb4dbd58's content wholesale, then verified byte-for-byte against that commit's own blobs rather than by reading the diff:

file disk blob at this head bdb4dbd58's blob
packages/app-shell/src/services/MetadataService.ts 17aa6cfa6 17aa6cfa6
packages/app-shell/src/services/MetadataService.specKeyReference.test.ts 631c62e6b 631c62e6b
packages/plugin-designer/src/MetadataFieldsPage.tsx 270c8dff5 270c8dff5
packages/plugin-designer/src/MetadataFieldsPage.specKeyReference.test.tsx 62068fbee 62068fbee
packages/plugin-designer/src/MetadataFieldsPage.retiredIndexed.test.tsx 7349c02e2 7349c02e2

⛔ Nothing was salvaged from this branch's copy, deliberately. #8076 re-derived the guard instead of inheriting it and found the inherited shape wrong — "The master_detail cases were wrong about reachability, in both files" — so merging the two versions, or keeping any part of this one, would have carried that error forward under the appearance of a reconciliation.

⚠️ Checked before resolving, because "take theirs" silently drops co-located work: each of the five files' branch-side diff against the merge base f10bcd497 was read in full first, and every hunk in all five is the guard — the two writers' guard block and its call site, the two pins for it, and the one reference: 'account' fixture line the guard forced onto retiredIndexed's owner_id. Nothing non-guard was lost. This branch's other spec-17.3.0 parity edits in the same two packages live in files that did not conflict (MetadataService.retiredObjectEnabled.test.ts, MetadataService.specKeyObjectPayload.test.ts, the metadata-admin previews and inspectors) and are untouched.

The work is preserved in this branch's history at 9c1a1ac5f regardless, and #8076's copy is the one that ships.

6. Clause ② — yes, on two authored repairs and on the inherited set

Authored by this branch (objectui's own accept/refuse or surface changes):

  • ⛔ The lookup refusal is NOT authored here any more. The bullet that used to lead this list — @object-ui/app-shell's MetadataService and @object-ui/plugin-designer's MetadataFieldsPage refusing a lookup / master_detail field with no target before any PUT — is fix(app-shell,plugin-designer): a half-filled relationship field stays client-side and is never PUT (objectui#7714) #8076's work (objectui#7714), and it reaches this branch by merge, not by authorship (§5). Measured after that merge, against bdb4dbd58: git diff --name-only bdb4dbd58 -- packages/plugin-designer is empty, and packages/app-shell's remaining 15 files are the spec-17.3.0 parity work already itemised under Contract-neutral below — the ValidationPanel unreachable label, the flow-inspector renames, the palette-exclusion ledger, and pin re-points. ⇒ This branch asserts nothing about relationship-target refusal in either direction; the declared whitespace divergence and its upstream card objectstack#16126 travel with fix(app-shell,plugin-designer): a half-filled relationship field stays client-side and is never PUT (objectui#7714) #8076.
  • @object-ui/components registerPlaceholders() no longer registers user:profile; a page naming it renders "Unknown component type" in opted-in hosts. @object-ui/cli objectui check now warns on that type (exit code unchanged). Ruled B1; upstream objectstack#14159.
  • @object-ui/core SPEC_ACTION_KEYS gains operation / patch, so classifyActionKeys and the dev-mode unknown-key warning stop reporting them (ruled item 3; recorded in the inventory because the forward-parity gate's owed set has no runtime reader for either key). @object-ui/types ObjectSchemaClientExtensions is retired to Record<never, never>; ObjectSchemaMetadata.editMode is now carried by the spec (ruled item 2).

Inherited from @objectstack/spec 17.3.0, reaching this repo's console build and test corpus through the lockfile (consumers on ^17.x already resolve 17.3.0 — npm latest since 2026-09-04): formType narrows to simple | tabbed (invalid_value); object-grid.defaultSort is an ADR-0087 tombstone (invalid_type expecting never); FieldSchema.reference required on the two relationship types; PageComponentSchema refuses user:profile by name — which the Studio save gate (validateMetadataDraft → PageSchema) now enforces; element:number.filter record form and object-grid.data array form refused; location values strict. ⛔ None of these is a product-path change in objectui except the Studio user:profile refusal: the Studio gate does not validate block properties, no runtime path parses ComponentPropsMap, and objectui's manifest gate already refused formType: 'wizzard' (objectui#5939).

Contract-neutral: the #7496 re-pin, the two re-derived negative pins, the exemption harvests, the manage_org_presentation label (selectability is the server registry's; only the label localises), the flow-inspector renames (not on any published entry), the ValidationPanel label, the data-objectstack type alias, the ceiling raise (§3).

✅ The tier contract review has run and passed (fable tier, maintainer-authorised). It ruled the PR not dischargeable as first written and required four changes, all now applied and verified on this head: the guard's stricter-than-spec .trim() declared rather than dropped; @object-ui/app-shell / plugin-designer / types regraded patch → minor with the breaking semantics stated; this section rewritten in the reviewer's own words; and the two CITES_ITS_RULING pin-quality nits fixed. needs:contract-review is discharged on that basis. ⚠️ Two of those three regrades have since been withdrawn along with the guard (§5, §9): @object-ui/app-shell is back to patch and @object-ui/plugin-designer carries no entry at all, because this branch no longer changes it. @object-ui/types keeps its minor. Removing an authored refusal narrows what this PR asserts and widens nothing, so this is not read as re-opening needs:contract-review — stated rather than assumed. ⛔ It corrected two claims the PM had made publicly — the @object-ui/console publish-boundary consequence was ungrounded, and manage_org_presentation changes a label, not an option; both retractions are in the PR thread.

7. Gates

Exit codes captured before any pipe (cmd (redirect) file 2(redirect)1; EXIT=$?), and every verdict read from the gate's own printed line rather than a bare $?.

gate result
pnpm build 0 — 43 / 43
turbo run type-check --continue 0 — 81 / 81. ⭐ Run with --continue, so this is a complete blocker list, not a list truncated at the first failure
pnpm test — 4 shards, the CI invocation 0 — 639 + 639 + 639 + 638 = 2,555 files, 32,677 tests, 2 skipped, 0 failures
check:eager-closure 0 — sensitivity ✅ on all five ceilings
scripts/__tests__/check-eager-closure-budget.test.ts 0 — 97 / 97. Owed because this diff edits a gate script; four assertions moved with the constants
the other four suites reading that script 0 — 110 / 110
check:i18n-keys · check:i18n-drift · check:i18n-dead-keys 0
check:control-bytes · type-check:scripts 0
check-changeset-presence 0 — 53 source files of 13 released packages, 9 changesets, 0 manifests moved
eslint --no-inline-config on all 21 changed source/test files 0 errors (24 pre-existing no-explicit-any warnings)

⚠️ The unit-suite figure is the full pnpm test, not --project unit. The previous body's suite numbers came from the latter, which is a subset — that is why its red set was smaller than CI's.

7a. The contract-review remediation, and its gates on the merged tree 7be4b2456

What changed, file by file. 8 source/test files and 3 changesets.

file change
packages/app-shell/src/services/MetadataService.ts the .trim() divergence declared in the docblock (measured both ways, upstream card named); the refusal message split into four accurate diagnoses via describeUnusableTarget
packages/plugin-designer/src/MetadataFieldsPage.tsx the same declaration and the same four-state message, kept word-for-word with its sibling — they are the objectui#5761 parity family
…/MetadataService.specKeyReference.test.ts · …/MetadataFieldsPage.specKeyReference.test.tsx the divergence is now pinned in both directions — the spec accepts ' ', this writer refuses it, and the refusal says so on screen. ⚠️ Designed to go red when objectstack#16126 lands: that red means retire the declaration, not weaken the guard
apps/console/…/submitRedirect.test.ts · plugin-form/…/{Wizard,Object}Form.submitRedirect.test.tsx CITES_ITS_RULING tightened (§2A)
packages/plugin-form/src/submitRedirect.test.ts was the odd file out — it pinned the literal 'ruled 2026-08-11' while its three siblings pinned the shape, i.e. the same brittleness one word along; now uses CITES_ITS_RULING too
.changeset/7122-lookup-target-required-before-save.md @object-ui/app-shell + @object-ui/plugin-designer patch → minor, breaking semantics stated, divergence declared
.changeset/7122-spec-17-3-0-parity-reconciliation.md @object-ui/types patch → minor, with both breaking consequences named: editMode leaves the exported extension type, and interface → alias ends declaration merging
.changeset/7122-spec-17-3-0-pin-repointing.md records the regex tightening and the fourth file (test-only, empty frontmatter, releases nothing)

Gates. Exit codes captured before any pipe, all on the merged tree.

gate result
pnpm build 0 — 43 / 43
turbo run type-check --continue 0 — 81 / 81, complete blocker list
pnpm test — 4 shards, the CI invocation success × 4 — 2,569 files, 32,933 tests, 2 skipped, 0 failures, read from CI's four Test shards on 7be4b2456: 643 / 8,425 · 642 / 8,466 · 642 / 8,477 · 642 / 7,565. Locally, shard 1/4 of the merged-tree sweep also exited 0; shards 2–4 were still running when this seat handed off, so CI is the complete merged-tree evidence and the local sweep is corroboration, not the record
check:eager-closure 0 — sensitivity ✅ on all five ceilings, ⛔ with the four budget constants untouched
node scripts/check-changeset-presence.mjs 0 — 53 source files of 13 released packages, 9 changesets, 0 manifests moved
node scripts/check-changeset-fixed.mjs · check-changeset-no-major.mjs 0 · 0 — the three raises are minor, which AGENTS.md:239 prescribes; a major would push all 39 fixed-group packages off @objectstack's major
check:control-bytes 0 — 6,378 tracked text files
check:designer-field-key-parity · check:spec-symbols · check:unreferenced-sources · check:handler-key-reads · check:upstream-port-parity 0 — the gates that read the two writers, or the ported files, directly
eslint --no-inline-config through each package's own lint binary, all 8 changed files 0 errors (11 pre-existing no-explicit-any warnings, none on a touched line)

⭐ The merged-tree closure re-measure — the margin, on the record

38 commits of main cost the eager closure nothing, and returned 613 bytes on framework. ⛔ No constant was edited; every ceiling below is the one this PR already carries.

line §3's measurement at 34a1578ef after the merge (7be4b2456) ceiling headroom vs the 91,136-byte regression
aggregate closure 3,551,191 3,551,183 3,597,000 45,817 0.50x
vendor-objectstack 1,235,029 1,235,029 1,254,000 18,971 0.21x
i18n-locales 449,305 449,305 455,000 5,695 0.06x
ui-components 394,968 394,968 399,000 4,032 0.04x
framework 68,550 67,937 71,000 3,063 (was 2,450) 0.03x

The three lines the review flagged as passing but tight are all still passing: two are byte-identical across the merge and framework gained 613 bytes of room. The aggregate came in 8 bytes below the pinned BASELINE.gzipBytes, so the raise the maintainer authorised still covers the payload with the same 0.50x proportion it was sized at, and no second raise is needed or taken.

8. Still open, with owners

item owner
@object-ui/types spec floor ^17.0.0 vs ^17.3.0 #7688. ⛔ Not raised here, as instructed
the +292 KB itself — a describe()-stripped browser build objectstack#16063
@objectstack/spec accepts a whitespace-only reference where it refuses '', so objectui is compensating for it (§6, §7a) objectstack#16126 — filed this round; a finding, not a fix for this PR
four public type exports removed in a minor objectstack#15843
object-grid.defaultSort / arm-exemption convergence objectui#6206, objectui#6207 — closable as resolved-upstream
lookup-guard ownership ✅ settled — A1's second branch taken; #8076 owns it, this branch dropped it (§5, §9)
record:details section-entry designer controls deferred feature card (ruled item 5)

9. ⭐ This round — main merged, the guard dropped, and what it cost

Head f7c4d7855, base bdb4dbd58. Two commits: the merge and its conflict resolution (fed2432dc), and the changeset withdrawal (f7c4d7855). ⛔ No budget constant moved, no ceiling raised, and apps/console/src/__tests__/registry-inputs-spec-parity.test.ts was not touched — it auto-merged and carries #8210's fix unmodified.

What the merge brought, and why it was the whole point

origin/main bdb4dbd58 carries the two commits this change exists for: #8076 (objectui#7714), the guard that supersedes this branch's copy (§5), and #8210 (objectui#8204), which makes invalid_type decisive in refusesKind. 40 commits behind at the start, 253 files staged by the merge.

The changeset withdrawal — decided by measurement, not by reading

.changeset/7122-lookup-target-required-before-save.md graded @object-ui/app-shell and @object-ui/plugin-designer minor because of the guard, with the breaking semantics and the whitespace divergence in its body. Every claim in it now belongs to #8076's .changeset/7714-lookup-draft-stays-client-side.md, which arrived with the guard.

The regrade question was answered mechanically against bdb4dbd58, not from the body:

git diff --name-only bdb4dbd58 -- packages/plugin-designer   =>  (empty)
git diff --name-only bdb4dbd58 -- packages/app-shell         =>  15 files
  • @object-ui/plugin-designer — this branch changes nothing in it any more, so the entry declared a release for no change at all.
  • @object-ui/app-shell — still changed, but only by spec-17.3.0 parity work, which already carries its own accurate declaration at the grade it warrants: .changeset/7122-spec-17-3-0-reconciliation.md (patch), which names those changes specifically. So app-shell is regraded, minor → patch: the minor claim is withdrawn and the pre-existing patch entry stands. Rewriting the deleted file's body instead would have duplicated that declaration.

⭐ The parity test improved, exactly as predicted — 2 reds to 1

pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
  Test Files  1 failed (1)
       Tests  1 failed | 180 passed (181)

⚠️ The before figure of 2 is carried from the dispatch, not re-measured on this seat — reported as inherited rather than as this round's measurement.

The five resolved files, re-measured against 17.3.0 — the one thing the merge could not inherit

#8076's pins were written and gated against main's installed 17.2.0; this branch's lockfile resolves 17.3.0. That combination existed nowhere before this merge, so it was measured rather than assumed:

pnpm exec vitest run  (the two writers' pins, the retiredIndexed fixture,
                       and the two sibling spec-count pins)
  Test Files  5 passed (5)
       Tests  59 passed (59)

⭐ check:eager-closure re-measured — every ceiling held, ⛔ no constant moved

Exit 0, sensitivity ✅ on all five ceilings. Measured at f7c4d7855 by pnpm build (exit 0, Tasks: 43 successful, 43 total) reading apps/console/dist/eager-closure.json; 50 of 518 chunks eager, unchanged.

line §7a at 7be4b2456 now, f7c4d7855 delta ceiling headroom vs the 91,136-byte regression
aggregate closure 3,551,183 3,553,536 +2,353 3,597,000 43,464 0.48x
vendor-objectstack 1,235,029 1,235,094 +65 1,254,000 18,906 0.21x
i18n-locales 449,305 451,772 +2,467 455,000 3,228 0.04x
ui-components 394,968 392,953 −2,015 399,000 6,047 0.07x
framework 67,937 70,267 +2,330 71,000 733 0.01x

⭐ CI's own budget comment on the pushed head agrees to the printed digit — Eager closure (gzip, 50 chunks) 3470.3 KB / 3512.7 KB · PASS, posted 2026-09-07T02:11:10Z. CI weighs the pull-request merge ref and this is the branch tree, so the agreement is a corroboration rather than a tautology.

⚠️ framework is the line to watch, and it is a finding rather than a problem here. 40 commits of main cost it 2,330 bytes and its headroom fell from 3,063 to 733, so the next kilobyte that lands in that chunk reds this gate. ⛔ Not raised — moving a budget constant is on the manual floor and this change carries no authorisation for it; it is reported so the next seat is not surprised by it. The aggregate lost 2,353 bytes of the 45,817 §7a measured and still stands at 0.48x, the proportion §3 sized it at.

Gates

Exit codes captured before any pipe (redirect first, then read $?), and each verdict quoted from the gate's own printed line.

gate result
pnpm build 0 — Tasks: 43 successful, 43 total
turbo run type-check --continue 0 — Tasks: 81 successful, 81 total, so this is a complete blocker list
pnpm test — 4 shards, the CI invocation ❌ 1 assertion, and it is the expected one. Read from CI's four shards on this exact head f7c4d7855: shards 1 / 3 / 4 success; shard 2/4 `1 failed
check:eager-closure 0 — sensitivity ✅ on all five ceilings, ⛔ every constant untouched
check-changeset-presence 0 — 48 source files of 12 released packages, 8 changesets, 0 manifests moved
check-changeset-no-major · check-changeset-fixed 0 · 0
check:control-bytes 0 — 6,557 tracked text files
check:designer-field-key-parity · check:spec-symbols · check:unreferenced-sources · check:handler-key-reads · check:upstream-port-parity · check:governed-queue-guard 0 — the gates that read the two writers directly, plus the ported-file and governed-surface legs
eslint --no-inline-config on all five resolved files 0 errors — 1 pre-existing no-explicit-any warning at MetadataService.ts:810, the same one #8076 recorded as byte-identical on main
check:spec-floors ❌ 1 — pre-existing, and §8's second row already owns it

⚠️ check:spec-floors is red and is NOT this round's doing. It reports @object-ui/types [floor-too-low]: packages/types/dist/spec-report.d.ts references FilterCondition, which @objectstack/spec@17.0.0 does not export, so the floor wants ^17.3.0 against the declared ^17.0.0. That is exactly the item §8 assigns to #7688 with "⛔ Not raised here, as instructed". Checked rather than assumed: the FilterCondition reference is not new in this branch's packages/types/src diff against bdb4dbd58, and no file this round touched is in packages/types.

⛔ Still draft. ready not flipped, not enqueued, auto-merge not armed.


Generated by Claude Code

Moves the `@objectstack/spec` resolution in `pnpm-lock.yaml` from 17.2.0 to
17.3.0. Every one of the 30 manifests that declare the dependency already
carried a range admitting it (`^17.0.0` / `^17.1.0` / `^17.2.0`), so the
declared specifiers are untouched and no published dependency declaration
changes by a byte; `check-changeset-presence` confirms the range independently
("0 of them a manifest whose published contract moved").

The bump is committed on its own because the reconciliation it forces is not
mechanical: 17.3.0 reddens 14 test files / 36 assertions across six packages,
and at least four of those reds are public-contract decisions rather than pin
updates. The PR body carries the full measured red set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.

Step Outcome
Build packages failure
Check console performance budget skipped

See the workflow run for details.

No package size report: it is only generated from a complete package build, so a partial one is never shown.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.

Step Outcome
Build packages failure
Check console performance budget skipped

See the workflow run for details.

No package size report: it is only generated from a complete package build, so a partial one is never shown.

…bjectstack/spec 17.3.0 (objectui#7122)

Three repairs, each prescribed by the thing that failed.

1. `SchemaDiffEntryKind` gained `unreachable` in 17.3.0, so app-shell's
   deliberately-total `DIFF_LABEL` map stopped compiling — TS2741, the single
   error behind the `Build packages: failure` CI reported on this branch. The
   map is total on purpose so an upstream addition fails the build instead of
   rendering a blank cell; that mechanism worked. Labelled "Not checked —
   remote unreachable", following the spec's own ruling that this kind asserts
   NOTHING about the remote schema and must never be surfaced as "schema
   changed".

2. 17.3.0 began exporting `FlowNodeLike` from `@objectstack/spec/system`, so
   app-shell's two same-named local declarations became shadows. Measured
   rather than assumed: the spec's `id?` is OPTIONAL where the inspector's is
   REQUIRED, and assignability runs one way only, so importing the spec export
   would silently widen away the guarantee `locateFlowNode`'s callers rest on.
   Genuine dialects ⇒ renamed to `InspectorFlowNode` and `ScopeFlowNode`, with
   the tripwire rows the guard asks for in `page-nav-misc-spec-parity.test.ts`.

3. The `BreakpointName` / `BreakpointColumnMap` ALLOW entries were written
   self-expiring: they covered the interval while objectstack#11027's
   retirement was merged but unreleased, and their own text said to delete them
   on the pin bump and move the names to the absence pin. 17.3.0 published the
   retirement, ratchet 3 failed them as excusing nothing, so both are deleted
   and both names are pinned absent instead.

`node scripts/check-spec-symbol-derivation.mjs` — exit 0 (was 1 with three
findings): 1345 files scanned, 18 declared dialects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 4137.7 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-C18P77W3.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ❌ over its ceiling
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.37KB 46.41KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…the prescription (objectui#7122)

`@objectstack/spec` 17.3.0 stripped the `#NNNN` issue-number citations from its
refusal messages while keeping the prescriptive half intact. Sixteen assertions
were pinned on the stripped half and went red; each is re-pointed at what the
refusal still carries, which is the half the pins' own comments name as the
point — "the prescription is the half that makes the refusal actionable for an
author; asserting only `success === false` would stay green if it were reduced
to 'Invalid input'".

- `submitRedirect.test.ts` (13): `#7496` -> `ruled 2026-08-11`. The refusal still
  cites its governing ruling, now by date rather than by issue number; the
  key-name and non-genericity assertions are untouched.
- `export-options-spec-parity.test.ts`: `/8010|1301/` -> the surviving format
  list and the `os migrate meta` command, i.e. the actionable repair itself.
- `report-chart-query-spec-parity.test.ts`: `'#4876, ADR-0049 D2'` -> the ADR
  reference, which survived, plus the `Delete the key.` prescription.

Separately, the `PreviewModeConfig` doc-provenance ratchet FIRED and is retired
exactly as its own docblock prescribed ("correct the README line … and then
delete this guard, which has no reason to outlive the sentence it protects").
⚠️ The docblock expected that at spec major 18 — objectstack#11846 registered
the retirement in `RETIRED_DEFS_BY_MAJOR[18]` — and it arrived in a MINOR
instead, one of four public type exports 17.3.0 removed with no major signal.
The capability is untouched: `previewMode` is host-supplied and stays.

Verified: `pnpm exec vitest run` over the five affected files — 5 passed,
150 tests passed, exit 0. The edited gate script's own pin suites
(`check-spec-symbol-derivation.test.ts`, `check-doc-component-types.test.ts`) —
2 passed, 108 tests, exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…0 bump forced (objectui#7122)

The branch previously declared 'no release' with empty frontmatter, which was
true while its only diff was the lockfile. It no longer is: `@object-ui/app-shell`
needed a source fix to compile against 17.3.0 at all, and `@object-ui/auth`'s
README carried a provenance claim 17.3.0 falsified. Both are declared as patches;
the lockfile-only changeset keeps its own empty-frontmatter declaration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin labels Sep 5, 2026
…ation` / `patch` (objectui#7122)

`SPEC_ACTION_KEYS` declares itself as "every property `@objectstack/spec`'s
`ActionSchema` declares", and `actionKeys.pin.test.ts` walks the schema and fails
the day that drifts. 17.3.0 added `operation` and `patch` — the declarative
single-record field write mirroring a list view's `bulkActionDefs` — so the
inventory was stating something false.

Restating the spec, NOT adopting a feature, exactly as the `description` entry
above it records for the same situation: `ActionDef` still does not declare
either key, no runner branch reads them, and no action surface forwards them.
That omission is deliberate under the maintainer's 2026-09-05 ruling on this
bump — record them as justified omissions now, forward only a key a runtime
actually reads once its semantics are read from upstream, no speculative
forwarding.

⚠️ Deliberately checked rather than assumed: the inventory feeds
`KNOWN_ACTION_KEYS`, which `classifyActionKeys` consults — and that function only
builds `unknown` / `retired` lists for a dev-mode console warning. It rejects
nothing and drops nothing, so widening it does not move what any published
`@object-ui/*` package accepts or refuses. Clause-② stays `no`.

Verified: `pnpm exec vitest run packages/core/src/actions/` — 24 files, 427
tests, exit 0. `node scripts/check-action-forward-parity.mjs` — exit 0, still
19 justified omissions / 7 known gaps, so no new gap was opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 4137.7 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-DFqvb1w2.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ❌ over its ceiling
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.37KB 46.41KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…al (objectui#7122)

`scripts/__tests__/doc-version-claims.test.ts` is a ratchet over version
literals on doc surfaces, and my first correction of this sentence introduced
one. Its prescription is explicit — prefer DELETING the literal and pointing at
the truth instead, because that is the only spelling that cannot go stale
(objectui#3645 froze a spec range across 36 READMEs for thirteen majors exactly
this way). The sentence now names the upstream retirement and the fact that the
resolved spec no longer exports the symbol, with no version to go stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 4137.7 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-DFqvb1w2.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ❌ over its ceiling
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.37KB 46.41KB
plugin-dashboard (index.js) 132.87KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

os-sam and others added 3 commits September 5, 2026 13:36
…dedupes

`@objectstack/client`, `core`, `formula` and `lint` at 17.2.0 each pin
`@objectstack/spec` EXACTLY at 17.2.0. Resolving the spec alone to 17.3.0
therefore stopped satisfying them, and the console shipped TWO copies of the
spec: `vendor-objectstack` went 926.1 -> 1877.6 KB gzip and the eager closure
went 3186.1 -> 4137.7 KB against a 3191.4 KB budget, with no chunk entering or
leaving the closure.

Moving the family in the lockfile collapses the duplicate. Every declared range
already admitted 17.3.0 (`^17.0.0` on all four), so no manifest's published
contract moves by a byte -- `check-changeset-presence` independently reports
"0 of them a manifest whose published contract moved". The lockfile was
resolved by raising the four ranges temporarily, resolving, then restoring the
manifests and re-installing, so `pnpm update`'s two side effects are absent:
no declared range moved, and `zod` stays at 4.4.3 rather than splitting the
`@objectstack/*` peer context into two variants (which would have re-introduced
the very duplication this commit removes).

Measured after: eager closure 3466.1 KB, `vendor-objectstack` 1206.1 KB. The
duplicate IS gone -- markers unique to the 17.2.0 artifact fall from 92.3% to
9.2% presence in the chunk, and `defaultValue` occurrences fall 413 -> 258
against a 203 baseline. What remains is real growth, not duplication: the spec's
own browser dist is +292.2 KB gzip at 17.3.0. `check:eager-closure` therefore
stays RED by 274.7 KB, which no repair inside this repo can answer.

The family bump forces exactly one source reconciliation, and no new test reds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…undary

`@objectstack/spec` 17.3.0 implements the maintainer's 2026-08-25 ruling on
objectui#6140 / objectui#6153 (Option A): `SelectOptionSchema.description` and a
type-gated `rows` become declared, authorable keys. Two pin files still asserted
the 17.2.0 truth -- that the spec REFUSES both -- so they were measurably false,
and one of them was the only remaining `Type Check` blocker (TS2344 at lines 101
and 124). Re-pointing them executes the ruling; it makes no new one.

Measured against the installed 17.3.0 rather than read from a changelog:
`SelectOptionSchema` declares six keys (`color`, `default`, `description`,
`label`, `value`, `visibleWhen`); `rows` is accepted on `textarea`, `markdown`,
`html` and `richtext` and refused on `text` / `select` as a `custom` refinement
rather than `unrecognized_keys`; `icon` and `disabled` are still refused by
name, at `options.0` when routed through `FieldSchema`.

The boundary moved, it did not disappear, so every re-point keeps a refusal
opposite it:

- `description` and `rows` are asserted ADMITTED **and** value-enforced (a
  wrongly-typed `description` is `invalid_type` at its key; `rows: 0` is
  `too_small`, `rows: '4'` is `invalid_type`). A merely-tolerated key passes the
  first assertion and fails the second, which is what makes the pair a reading.
- `icon` / `disabled` carry the refusal half, so a schema that had gone
  permissive cannot pass either file.
- The four inert rich-text keys (`toolbar` / `preview` / `minHeight` /
  `maxHeight`) are newly pinned as still-refused on all four multiline types.
  The ruling's expansion stops at `rows` (objectui#7635 records this: do not
  widen), and that door has to be seen to stay shut.
- `rows`'s type gating is pinned in both directions, because "declared" is not
  "declared everywhere".

In the convergence file the type-level halves moved for a reason worth stating:
`SelectOptionBase` derives the spec's keys BY REFERENCE, so `description`
arrived on BOTH faces with no edit to the type at all. The form face's
pre-convergence equality therefore became a SET DIFFERENCE -- the same idiom the
object-metadata face has used for `default` since the convergence -- and the
"the two faces differ on exactly `value`" assertion now says so directly instead
of excusing `description` from the comparison.

NOT done here, deliberately: the prose in `packages/types/src/field-types.ts`
and `select-option.ts` still describes the 17.2.0 boundary and is now false in
the other direction. Those comment sites are objectui#7635's declared surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…ntroduced

`@object-ui/cli` failed `type-check` with `TS2321: Excessive stack depth
comparing types 'Plugin & ExportedAPI' and 'Plugin'` in
`src/utils/workspace-vite.ts`, a file this branch never touched.

Cause, measured rather than inferred: the CLI's type-check program held TWO
postcss copies. `@tailwindcss/postcss@4.3.3` depended on `postcss@8.5.26` while
`autoprefixer@10.5.4` resolved its auto-installed peer to `postcss@8.5.28`, so
`Plugin` on one side and `Plugin & ExportedAPI` on the other were structurally
distinct types and the comparison exceeded the instantiation budget.
`origin/main` has a single `autoprefixer@10.5.4(postcss@8.5.26)` and no 8.5.28
at all, which is why it is green.

The duplicate is NOT from this round's family bump: `postcss@8.5.28` is present
at `36baa6d57`, and the family-bump commit's lockfile diff touches neither
postcss nor autoprefixer. It arrived with `aabc527cb`, the branch's first
lockfile bump, whose own diff shows `postcss@8.5.26 -> 8.5.28` alongside the
spec move. It stayed invisible because `turbo run type-check` aborts at its
first failing task, and `@object-ui/types` failed before `@object-ui/cli` ever
ran -- so "the 2 TS2344 are the ONLY Type Check blocker" was never a reading the
old measurement could support.

Collapsed in the lockfile only, by the same route as the `@objectstack/*` family:
all eight manifests that declare postcss already declare `^8.5.26`, which admits
8.5.28, so the ranges were raised temporarily, resolved, then restored and
re-installed. No declared range moved; `postcss@8.5.26` leaves the graph
entirely and `@tailwindcss/postcss` now shares the single 8.5.28. `postcss@8.5.23`
is a separate consumer, present on main too, and is untouched.

After: `@object-ui/cli` `tsc --noEmit` exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
Brings the branch up to f10bcd4. One conflict, in
packages/plugin-detail/src/index.tsx, on the `record:details` `sections`
input entry — resolved by keeping BOTH sides:

  - from main (cf1d29e, ComponentInput.of): the new `of: 'object'` key;
  - from this branch: the rewritten `description` prose, which is
    contract-reviewed content.

The two edits are provably orthogonal on that line: main's description
value is byte-identical to the merge base's (909 bytes both), and the
merge-base line declared no `of` key at all, so neither side touched what
the other changed. The adjacent `fields` entry was in the same conflict
hunk only by adjacency — this branch never touched it, so it takes main's
version verbatim (`of: 'string'` added).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
The map in `packages/plugin-list/README.md` is keyed by
`NonNullable<ListViewSchema['viewType']>`, so it is total by construction
and a member added to the union fails the block. The map arrived on main
(objectui#8110) written against @objectstack/spec 17.2.0's nine members;
this branch pins the spec at 17.3.0, which declares a tenth, `page`. The
two are only in the same tree as of this merge, which is why the branch's
own Doc Snippet Type Check ran green and the failure appears here:

  packages/plugin-list/README.md:200:7  TS2741: Property 'page' is missing
  in type '{ grid: …; tree: string; }' but required in type
  'Record<NonNullable<"map" | … | "page" | …>, string>'.

The gloss is read off the installed 17.3.0 artifact, not guessed:
`node_modules/@objectstack/spec/src/ui/view.zod.ts:1655` declares the
member as "Mount an already-published page", and the two refusals that
police it — VIEW_PAGE_MOUNT_NEEDS_PAGE_NAME (:1492) and
VIEW_PAGE_MOUNT_HAS_COLUMNS (:1502) — both say the view has no rows and
no column list of its own. Phrased like its nine siblings: one terse
rendering of what the reader sees, naming no driving field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3467.2 KB 3512.7 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-C8z7B2ef.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.05KB 113.97KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.12KB 50.63KB
fields (index.js) 242.54KB 61.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.29KB 13.18KB
plugin-charts (index.js) 70.35KB 19.68KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.23KB 43.64KB
plugin-detail (index.js) 248.63KB 63.90KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.46KB 14.57KB
plugin-list (index.js) 113.34KB 27.72KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.48KB 20.81KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…this branch

Part of #7122.

`origin/main` at `bdb4dbd58` carries two things this branch needs:

- **#8076** (objectui#7714) — the lookup / master_detail target guard, merged at
  01:58Z. Ruling A1 on #7122 says the guard belongs in exactly one place; #8076
  is that place, so this branch's own copy goes away entirely.
- **#8210** (objectui#8204) — the `refusesKind` `invalid_type` classifier fix,
  which retires one of the two `registry-inputs-spec-parity` reds.

Five conflicts, all of them the guard, all resolved by taking `bdb4dbd58`'s
content wholesale and verifying it byte-for-byte against that commit's blobs:

  packages/app-shell/src/services/MetadataService.ts
  packages/app-shell/src/services/MetadataService.specKeyReference.test.ts
  packages/plugin-designer/src/MetadataFieldsPage.tsx
  packages/plugin-designer/src/MetadataFieldsPage.specKeyReference.test.tsx
  packages/plugin-designer/src/MetadataFieldsPage.retiredIndexed.test.tsx

Nothing was salvaged from this branch's copy. #8076 re-derived the guard rather
than inheriting it and found the inherited shape wrong about `master_detail`
reachability in both files, so merging the two versions would have carried that
error forward.

Checked before resolving: each of the five files' branch-side diff against the
merge base `f10bcd497` is guard-only, so taking main's whole file drops no
non-guard work. The branch's other spec-17.3.0 parity edits in the same two
packages live in files that did not conflict and are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…longer authors it

Part of #7122.

`.changeset/7122-lookup-target-required-before-save.md` graded
`@object-ui/app-shell` and `@object-ui/plugin-designer` as `minor` *because of*
the lookup / master_detail target guard, with the breaking semantics and the
declared whitespace divergence written into its body. Every one of those claims
now belongs to #8076's `.changeset/7714-lookup-draft-stays-client-side.md`,
which arrived on `main` with the guard itself.

Determined mechanically against `bdb4dbd58`, not by reading the body:

  git diff --name-only bdb4dbd -- packages/plugin-designer  =>  empty
  git diff --name-only bdb4dbd -- packages/app-shell        =>  15 files

- `@object-ui/plugin-designer`: this branch changes nothing in it any more, so
  the entry declared a release for no change at all.
- `@object-ui/app-shell`: still changed, but only by the spec-17.3.0 parity
  work — the ValidationPanel `unreachable` label, the flow-inspector renames,
  the palette-exclusion ledger, and three pin re-points. That surface already
  carries its own accurate declaration at the grade it warrants,
  `.changeset/7122-spec-17-3-0-reconciliation.md` (`patch`), which names those
  changes specifically. So app-shell IS regraded here, from `minor` to `patch`:
  the `minor` claim is withdrawn and the pre-existing `patch` entry stands.

Rewriting this file's body instead of deleting it would have duplicated that
declaration; nothing in it survives the guard's departure.

Arbiter, exit code captured before any pipe:

  node scripts/check-changeset-presence.mjs   => exit 0
    "48 source file(s) of 12 released package(s) changed, and this change
     declares 8 changeset(s)"
  node scripts/check-changeset-no-major.mjs   => exit 0
  node scripts/check-changeset-fixed.mjs      => exit 0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3470.3 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-CUsy7KE2.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.99KB 113.90KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 187.90KB 52.14KB
fields (index.js) 243.14KB 61.38KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.67KB 13.25KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.56KB 63.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.35KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

os-dev-report

{
  "issue": 7122,
  "status": "done",
  "branch": "claude/issue-7122-spec-pin-17-3-0",
  "pr": "https://github.com/objectstack-ai/objectui/pull/7685",
  "premise_still_valid": true,
  "summary": "Follow-up on the existing PR #7685 — no new branch, no new PR, no force-push, no history rewrite. Merged origin/main bdb4dbd58 (commit fed2432dc), which brought both #8076 (the lookup guard, merged 01:58Z) and #8210 (the registry-inputs-spec-parity refusesKind fix). Exactly 5 files conflicted, all of them the guard, and every one was resolved by taking bdb4dbd58's content wholesale — verified byte-for-byte against that commit's blobs, not by reading the diff; nothing was salvaged from this branch's copy, so #8076's re-derived master_detail reachability is what ships. Before resolving, each of the five files' branch-side diff against merge base f10bcd497 was read in full: every hunk in all five is the guard (the two writers' block plus call site, the two pins, and the one reference:'account' fixture line the guard forced onto retiredIndexed's owner_id), so taking main's whole file dropped no non-guard work — the branch's other spec-17.3.0 parity edits in the same two packages are in files that did not conflict and are untouched. .changeset/7122-lookup-target-required-before-save.md was deleted (commit f7c4d7855) after answering the regrade question mechanically: packages/plugin-designer now has zero diff against bdb4dbd58, and packages/app-shell's remaining 15 files are spec-17.3.0 parity already declared at patch by .changeset/7122-spec-17-3-0-reconciliation.md — so app-shell IS regraded minor->patch by withdrawing the minor claim, and rewriting the body would only have duplicated an existing declaration. PR body updated: §5 replaced (A1's second branch taken, guard lives in #8076, with the blob-hash table), §6 heading and first bullet rewritten so clause ② no longer claims the refusal, §6's contract-review paragraph annotated where two of its three regrades were withdrawn, §8's ownership row settled, and a new §9 carrying this round's measurements. §3's ceiling arithmetic and authorisation quote are byte-identical to the pre-edit body (verified by diffing the §3 region) — likewise §7 and §7a.",
  "tests": "ALL exit codes captured before any pipe (redirect first, then read $?); every verdict quoted from the gate's own printed line. pnpm build => exit 0, 'Tasks: 43 successful, 43 total'. turbo run type-check --continue --concurrency=2 => exit 0, 'Tasks: 81 successful, 81 total' (complete blocker list). PARITY, the headline measurement: pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts => 'Test Files 1 failed (1) / Tests 1 failed | 180 passed (181)'. The count is 1, as predicted. The surviving red is registry-inputs-spec-parity.test.ts:2711 — specMemberVerdict('record:activity','types','array','Account') expected 'refuses-content', received 'accepts' — i.e. #8137's calibration control, untouched. The retired one is 'member declarations are derived from single-kind member contracts' (line 2740), now passing; #8210 (580b0fdf4) changed only that test file plus a changeset, so its whole fix is self-contained there. NOT MEASURED BY ME: the before-figure of 2 is carried from the dispatch, not re-measured on this seat. NOVEL-COMBINATION CHECK (the one thing the merge could not inherit — #8076's pins were gated against main's installed 17.2.0, this branch resolves 17.3.0): the two writers' pins + retiredIndexed fixture + the two sibling spec-count pins => 'Test Files 5 passed (5) / Tests 59 passed (59)'. FULL SUITE, the CI invocation: CI ran all four shards on this exact head f7c4d7855 — shards 1/3/4 success; shard 2/4 '1 failed | 8728 passed (8729)', 657 of 658 files, and its single failure is the same line 2711 assertion. Locally shard 1/4 was also run under the shared verify lock => exit 0, '658 files, 8642 passed, 1 skipped'. DECLARED NARROWING: local shards 2-4 were deliberately stopped once CI had answered on the identical commit, rather than hold the shared verify lock ~30 more minutes for a duplicate reading; CI is the record and local shard 1/4 is corroboration. Note scripts/**/*.test.ts is in the root vitest 'unit' project include, so the four shards discharge the gate-script-edit obligation for check-eager-closure-budget.mjs and check-spec-symbol-derivation.mjs. check:eager-closure => exit 0, 'Ceiling sensitivity ✅ on all five ceilings', WITH ALL FOUR BUDGET CONSTANTS UNTOUCHED (verified: the printed ceilings are the pinned 3,597,000 / 1,254,000 / 455,000 / 399,000 / 71,000). Exact bytes at f7c4d7855, read from apps/console/dist/eager-closure.json, 50 of 518 chunks eager: aggregate 3,553,536 / 3,597,000 (headroom 43,464 = 0.48x); vendor-objectstack 1,235,094 / 1,254,000 (18,906 = 0.21x); i18n-locales 451,772 / 455,000 (3,228 = 0.04x); ui-components 392,953 / 399,000 (6,047 = 0.07x); framework 70,267 / 71,000 (733 = 0.01x). Deltas vs §7a's 7be4b2456 reading: aggregate +2,353, vendor-objectstack +65, i18n-locales +2,467, ui-components -2,015, framework +2,330. No ceiling exceeded, nothing raised. CI's own budget comment on the pushed head agrees to the printed digit: 'Eager closure (gzip, 50 chunks) 3470.3 KB / 3512.7 KB · PASS'. check-changeset-presence => exit 0, '48 source file(s) of 12 released package(s) changed, and this change declares 8 changeset(s)'. check-changeset-no-major => exit 0. check-changeset-fixed => exit 0. check:control-bytes => exit 0, 6,557 tracked text files. check:designer-field-key-parity, check:spec-symbols, check:unreferenced-sources, check:handler-key-reads, check:upstream-port-parity, check:governed-queue-guard => exit 0 each. eslint --no-inline-config on all five resolved files => exit 0, 0 errors, 1 pre-existing no-explicit-any warning at MetadataService.ts:810 — the same one #8076 recorded as byte-identical on main. RED, and NOT this round's doing: check:spec-floors => exit 1, '@object-ui/types [floor-too-low]' — packages/types/dist/spec-report.d.ts references FilterCondition, absent from @objectstack/spec@17.0.0, so the floor wants ^17.3.0. That is exactly the item PR §8 assigns to #7688 with 'Not raised here, as instructed'. Checked rather than assumed: the FilterCondition reference is not new in this branch's packages/types/src diff against bdb4dbd58, and no file this round touched is under packages/types. No ablation was run — this change authors no new guard or gate, so there is nothing whose load-bearing-ness an ablation would prove; the byte-for-byte blob comparison against bdb4dbd58 is the evidence that applies.",
  "mcp_calls": "3 — pull_request_read (PR 7685 body), get_job_logs (CI shard 2/4 on f7c4d7855), search_issues (one targeted dedupe before filing #8241). Everything else went over repo-scoped REST, which was probed working first; REST /search/issues answered 403, so the dedupe leg switched channel to MCP as prescribed. That search returned 3 results (non-empty), so no control-word leg was owed.",
  "open_questions": [],
  "out_of_scope_findings": [
    "filed as #8241: check:eager-closure's `framework` chunk is at 70,267 / 71,000 — 733 bytes of headroom, 0.01x the 91,136-byte regression the gate catches. Gate is GREEN; the point is the margin. The 2,330-byte growth arrived with 40 commits of main, not with anything this branch authored. Labelled `finding`, no assignee, no pm:queue. The issue states explicitly what was NOT measured: whether main alone is already at this margin — that single reading decides whether it is a repo-wide condition or a branch artifact. No ceiling was moved, and the issue explicitly does not ask for one."
  ]
}

Generated by Claude Code

…on main

Part of #7122.

26 commits, 107 files. The one this branch has been waiting for is **#8259**
(objectui#8137): the `record:activity.types` calibration control at
`registry-inputs-spec-parity.test.ts` is re-pointed from `'Account'` — which
`@objectstack/spec` 17.3.0 genuinely accepts, so the assertion had become false
— to `''`, which 17.3.0 still refuses on content (`too_small` at `[0]`, no
`invalid_type`). That was the single red on this branch's last CI run
(1 failed | 8728 passed).

`apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` auto-merged:
this branch has authored nothing in it since `f10bcd497`, so it takes main's
copy — #8212's lazily-registered-block coverage and #8259's fix both — whole.

One conflict, `e2e/live/ci/backend.env`, and it is not a repair of anything
this branch did:

- kept **this branch's** `OBJECTSTACK_VERSION=17.3.0` / `OBJECTSTACK_REF`, which
  is the entire point of the change;
- took **main's** `BETTER_AUTH_VERSION=1.7.2` block (#8239) verbatim.

⚠️ The pin is KEPT, deliberately. Its own docblock says to retire it "in the PR
that bumps OBJECTSTACK_VERSION past the upstream fix" — and 17.3.0 is not past
it. Measured, not assumed: this branch's own `Live E2E (informational)` run
already pinned 17.3.0 and still failed with objectstack#16186's signature, so
the upstream fix is not in 17.3.0 and dropping the pin here would break the lane
this PR is the first to exercise at 17.3.0.

`pnpm-lock.yaml` auto-merged and `pnpm install --frozen-lockfile` exits 0
("Lockfile is up to date"); `@objectstack/spec` still resolves 17.3.0
everywhere, so `ci-cd-pipeline-doc.test.ts`'s backend.env/lockfile equality
still holds. main's one manifest change is `packages/components` dropping
`recharts`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E

Copy link
Copy Markdown
Collaborator

Heads-up from the seat that caused this branch's one merge conflict — plus three landing deltas that have changed under it

⛔ Not claiming this card. objectui#7122's claim is held by session_0114Ytxr5sM1vdW19Y9WAx6E and item 2 names that session as this PR's driver. I am session_01YBWFb5YgMU5dw8p2VKj16S (the domain:ui PM seat) and have touched nothing here. Posting because one of the four items below is my doing and the driver should not have to reverse-engineer it.

Measured against origin/main 0fa7a9c83, branch head f7c4d7855: 25 behind / 30 ahead, merge-base bdb4dbd58, and a git merge-tree dry run reports exactly one conflict.

1. ⚠️ The conflict is e2e/live/ci/backend.env, it is mine, and both sides must survive

objectui#8239 (objectui#8084 defect ①) landed hours ago and added a BETTER_AUTH_VERSION key plus an npm overrides block to that file. This branch edits the same file to move OBJECTSTACK_VERSION to 17.3.0, because scripts/__tests__/ci-cd-pipeline-doc.test.ts pins backend.env in step with the @objectstack/spec in pnpm-lock.yaml.

⭐ They are substantively compatible — this is a textual conflict, not a design one. @objectstack/plugin-auth@17.3.0 declares @better-auth/core: ^1.7.2, and objectui#8239 pins the family to exactly 1.7.2, which satisfies that range. So after the bump the pin still resolves, the lane still boots, and e2e/live/ci/better-auth-pin.mjs still passes.

⇒ Resolve by keeping both hunks. ⛔ Do not drop the better-auth block to take the version bump — that reopens objectstack#16186 (@better-auth/core@1.7.3 removed createLocalAccountIssuer; every fresh install floats to it) and puts Live E2E back to red across the whole repo. ⛔ Do not drop the version bump either; ci-cd-pipeline-doc.test.ts will refuse it.

The guard runs on every start and fails by name (PIN-NOT-DECLARED / PIN-NOT-RESOLVED / EXPORT-MISSING), so a resolution that silently loses the pin reddens loudly rather than quietly. Ping me if you want the merge lap done for backend.env specifically — that hunk is mine and I am happy to own it.

2. ⭐ The §5 allocation has answered itself: the lookup-guard hunks are already on main

objectui#7714 was closed as completed at 2026-09-07T01:58:57Z by os-sam. Measured on main just now:

packages/app-shell/src/services/MetadataService.ts:230        function describeUnusableTarget(...)
packages/plugin-designer/src/MetadataFieldsPage.tsx:251       function describeUnusableTarget(...)
packages/app-shell/src/services/MetadataService.specKeyReference.test.ts        present
packages/plugin-designer/src/MetadataFieldsPage.specKeyReference.test.tsx       present

⇒ ruling A1's second branch was taken — its owner took the hunks. Under that ruling they must now come off this branch, or they land twice, which A1 forbids in as many words.

⚠️ One loose end I could not settle from outside: .changeset/7122-lookup-target-required-before-space.md… correction, .changeset/7122-lookup-target-required-before-save.md is NOT on main. So either that work landed under a different changeset or none is on record for it. Worth checking before this branch's copy is deleted rather than deleting it blind.

3. ⭐ The eager-closure restore condition on this PR is now void

objectstack#16063 was ruled 「16063 c」 (director seat, decision batch #59, recorded on objectui#7122 at 2026-09-06T15:02Z): the +292.2 KB gzip growth of @objectstack/spec's browser dist is accepted upstream permanently — no prose stripping, no short-describe convention will land.

⇒ this PR's §3 still says "when it lands, re-measure and bring ceiling and baseline back down together." That instruction is now wrong and, left in the budget's cause note, points the next bump's author at a condition that will never occur. The one-time cause-recorded adjustment is the standing baseline; the cause note should say so.

4. The unblocking, for the record

objectui#7122's Blocked-by: #7714 is satisfied — #7714 landed first, exactly as the 2026-09-05T22:4xZ maintainer ruling sequenced it. Contract review already PASSed on content at 7be4b245 and needs:contract-review is stripped. So the remaining preconditions are the merge lap (⛔ merge, never rebase — 30 commits ahead), item 2 above, and item 3 above.

No action requested of me and none taken. Happy to hand over anything measured here.


Generated by Claude Code

…with an empty shape

Part of #7122.

The `main` merge brought #8212's census gate onto the 17.3.0 pin for the first
time, and it did exactly what it exists to do: it named three
`ComponentPropsMap` entries this gate neither judges nor accounts for.

    accounts for every spec-carried block …
      + ["cloud-connection:panel", "marketplace:installed-list", "mcp:connect-agent"]
    states the size of the population it judges
      - specCarried: 42   + specCarried: 45

One fact, two assertions. All three are new in `@objectstack/spec` 17.3.0 and
MEASURED on the installed artifact, not inferred: each resolves to an object
schema with ZERO top-level keys, so neither direction of this gate has a
question to ask about them. That is the ledger's `EMPTY SPEC SHAPE` class, and
the entries are asserted against `specTopLevelKeys(type) === []` — self-clearing
the day upstream gives one of them an authoring surface.

⚠️ EMPTY SPEC SHAPE is recorded rather than NOT REGISTERED even though both
ledger arms happen to pass here, and the difference is truth, not convenience:
`@object-ui/app-shell` DOES register all three propless
(`CloudConnectionPanel.tsx`, `InstalledListWidget.tsx`, `ConnectAgentWidget.tsx`),
and this file does not import app-shell — so the NOT-REGISTERED arm would be
satisfied by an import-graph accident while stating something false about the
repo. `app:launcher` and `global:notifications` are ledgered on exactly this
reasoning already.

⛔ Not a gate weakening, and the distinction is checkable rather than asserted:
no threshold moved, no exemption widened what the gate tolerates, and `judged`
and `registeredPropless` are unchanged at 29 and 7 — the pin ADDED three blocks
rather than moving any across the partition.

Also de-counts one sentence in the ledger's docblock. "the recorded reason even
for the two the app shell does register" was a LIVE figure that this change
makes five and nothing pins; it now names no count and says why.

    pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
      Tests  198 passed (198)      (was 2 failed | 196 passed)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.0 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-C9O51AEk.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.99KB 113.90KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.14KB 61.38KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 48.03KB 13.29KB
plugin-charts (index.js) 71.38KB 19.92KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.56KB 63.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Part of #7122. Closes the `[floor-too-low]` finding on objectui#7688 for
`@object-ui/types`.

`packages/types/dist/spec-report.d.ts` names `FilterCondition` from
`@objectstack/spec`, which `17.0.0` does not export, so `^17.0.0` was a claim
the artifact does not support. `scripts/check-spec-range-floors.mjs` names
`^17.3.0` as the lowest published version carrying every symbol the package
references, and raising the range is the remedy the gate itself prescribes —
⛔ never a tolerant re-declaration on this side (objectui#5793).

⚠️ THIS IS BUMP-CAUSED, NOT PRE-EXISTING, and objectui#7688 records the
opposite. Measured on both sides today rather than reasoned about:

  main (spec 17.2.0)   `Spec Range Floors` — the last EIGHT scheduled runs are
                       all `success`, most recently at `c2e3cee2c` 04:27Z. CI
                       builds the workspace, so none is the `no-artifact` shape
                       that card's own caveat warns about.

  this tree            `pnpm check:spec-floors -- --cross-check` (the workflow's
  (spec 17.3.0)        own invocation, after `pnpm build` 43/43) exits 1 with
                       exactly this finding; 278 (subpath, symbol) pairs across
                       19 published packages judged, 49 entry points cross-
                       checked. With this raise it exits 0 on the same tree.

That card's justification — `runtimeFilter?: FilterCondition` is byte-identical
in the 17.2.0 and 17.3.0 artifacts — is a claim about the SPEC's artifact. The
gate reads what `tsc` emits for THIS repo while resolving against the installed
spec, which is a different thing. ⛔ The mechanism behind the divergence is NOT
measured here and is deliberately not guessed at; only the outcome is.

Why it could not wait for objectui#7688: the PR-facing copy of this gate is
push-only and by its own header "cannot make anyone's pull request red", but the
blocking copy runs on the PUBLISH path. Left alone, the first symptom would have
been a cancelled release rather than a red check.

Graded on `@object-ui/types`'s existing `minor` entry, which already carries a
breaking-surface declaration; the raise is written into its body. Breaking for a
consumer pinned below 17.3.0, which can no longer resolve the package. Precedent
for a floor above the family minimum: `@object-ui/core` and
`@object-ui/data-objectstack` declare `^17.2.0`, `@object-ui/plugin-detail`
`^17.1.0`.

⛔ Raising a published range was previously ruled out for this branch (#7685 §8,
"Not raised here, as instructed") on the premise that the finding was old debt.
The measurement above retires that premise, and the maintainer authorised the
raise on this branch after seeing it.

The lockfile moves by exactly one line — the recorded specifier. The RESOLUTION
is unchanged at `17.3.0(ai@7.0.65(zod@4.4.3))`, so no dependency moves and
`ci-cd-pipeline-doc.test.ts`'s backend.env/lockfile equality still holds.

Gates on this tree: `check:spec-floors -- --cross-check` 0 (was 1),
`check-changeset-presence` / `-no-major` / `-fixed` 0, `check:phantom-deps` 0,
`check:control-bytes` 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.0 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-C9O51AEk.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.99KB 113.90KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.14KB 61.38KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 48.03KB 13.29KB
plugin-charts (index.js) 71.38KB 19.92KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.56KB 63.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.83KB 14.63KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Merged via the queue into main with commit 639114c Sep 7, 2026
34 checks passed
baozhoutao pushed a commit that referenced this pull request Sep 7, 2026
Conflict in `e2e/live/ci/backend.env` only. objectui#7685 (639114c) moved the
lockfile to `@objectstack/spec@17.3.0` and hand-moved BOTH backend pins with it:
`OBJECTSTACK_VERSION=17.3.0` and `OBJECTSTACK_REF=8a1bad8b8ee7189a54229368400b5b427e3ad5e2`.
This branch had already deleted the second key, so the two sides disagreed about
whether it exists.

Resolved to this branch's shape carrying main's value: `OBJECTSTACK_VERSION=17.3.0`,
no `OBJECTSTACK_REF` key, this branch's rewritten header kept. `BETTER_AUTH_VERSION`
and its paragraph are exactly as #7685 left them — whether 17.3.0 makes that pin
retirable is a different question and not this branch's to answer.

The conflict is itself the second measurement this branch exists to take. The
`@objectstack/cli@17.3.0` tag peels to 8a1bad8b8ee7189a54229368400b5b427e3ad5e2 —
byte-identical to the sha #7685 wrote by hand, on a version this branch had never
seen. Two for two: the derivation reproduces the hand move, and the hand move is
the step that is no longer needed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
… older reason may hide one (objectui#10033) (objectstack-ai#10391)

Fixes objectstack-ai#10033
Clause-②: no

## The card's question, answered from the test's own population logic

**Is the waiver population derived from the spec at all?** Yes. And
retired keys are **in** it, in a special form. That makes this case (b)
of the dispatch.

- **Population.** `covered`, `registeredWithoutInputs` and `accounts for
every spec-carried block` all start from
`Object.keys(ComponentPropsMap)`. The partition test requires `covered`
∪ `registeredWithoutInputs` ∪ `UNJUDGED_SPEC_BLOCKS` to equal that set
exactly, with each block counted once.
- **A stale waiver (a key outside the population) is already caught, in
two places.** The first is the partition's converse (`accounted for a
block ComponentPropsMap does not carry`). The second is the ledger rot
check (`is ledgered but the spec no longer carries it — delete the
entry`). So this PR adds no new tripwire for that case.
- **Retired keys stay in the population, because the spec keeps their
rows on purpose.** The row is one of the retirement's refusal doors.
Readings from the installed `@objectstack/spec` 17.4.0
(lockfile-resolved), taken at base `ea02938cd`, using the
`@object-ui/test-support` tombstone judge:

| type | in `PageComponentType` | in `RETIRED_PAGE_COMPONENT_TYPES` |
row keys listed / tombstoned / authorable | empty bag parses |
|:--|:--|:--|:--|:--|
| `user:profile` | no | **yes** | 0 / 0 / 0 (`z.never`) | refused |
| `element:form` | no | no | 6 / 6 / 0 | accepted |
| `element:filter` | no | no | 6 / 6 / 0 | accepted |
| controls: `cloud-connection:panel`, `marketplace:installed-list`,
`mcp:connect-agent` | no | no | 0 / 0 / 0 | accepted |

Three things follow from that reading:

- **Mechanism assumption 3 is half false.**
`RETIRED_PAGE_COMPONENT_TYPES` has exactly one member, `user:profile`.
`element:form` left `PageComponentType` through the **element-grain**
retirement (objectstack#9249, listed in the spec's 17.3.0 changelog),
not through that table. `element:filter` left the same way earlier
(objectstack#9220, 17.1.0). So "read the spec's retired set" as that
table alone would not cover `element:form`.
- **Excluding retired rows from the population is the wrong way round.**
A registration of a retired type in this repo would drop out of
`covered` together with its row, and nothing would judge it.
objectui#8176 built this ledger to end exactly that absence. With the
rows kept in, such a registration is judged (every input it publishes is
off-spec) and is also counted twice, which the partition refuses.
- **Both waiver reasons the card names are false today.** `user:profile`
is false in both halves, as the card measured. `element:form` cites a
`block-types.ts` palette exclusion that says "no renderer — use the
object-bound `object-form` block". That entry was deleted by commit
`639114c4d`, the delivery of objectui#7122 through PR objectstack-ai#7685. It now
appears in that file only as a note that the type is RETIRED UPSTREAM.

## What changed (test-only, one file plus an empty changeset)

1. **`isRetiredUpstream(type)`** reads both of the spec's retirement
spellings, and never a list kept here. A type counts as retired when it
is out of `PageComponentType` **and** one of these holds:
   - it is named in `RETIRED_PAGE_COMPONENT_TYPES` (retired by name);
- its row lists keys and every one of them is a tombstone (retired at
element grain).

Leaving the enum is required but is not enough on its own: the controls
above are outside the enum too, and the predicate answers `false` for
them.
2. **A third ledger class, `RETIRED UPSTREAM`.** The rot check asserts
two things for it: `isRetiredUpstream(type)` is true, and the accepted
key set is empty. If upstream restores the type or gives its row a key,
the entry goes red.
3. **The class-closing guard.** An entry under `EMPTY SPEC SHAPE` or
`NOT REGISTERED, DELIBERATELY` now fails when the spec retires its type.
That is the check that stops a hand-written reason from outliving a
retirement, which is what happened to both entries here.
4. **Entries rewritten.** `user:profile` and `element:form` now say what
is true, citing the upstream ruling and landing (objectstack#14159,
objectstack#15112, objectstack#9249) and the objectui-side
reconciliation (objectui#7122).
- ⚠️ `element:filter` moves too. This is an in-place fix under the
same-class exemption, and I am naming it here because the claim's file
surface names only the other two entries. It was retired at element
grain by objectstack#9220 (17.1.0), and its `EMPTY SPEC SHAPE` reason
never mentioned that. The new guard turns exactly that state red, so
leaving it behind would not pass. It is the same file, the same defect
class and the same gate, with nothing new to verify.
5. **The docblock** now has three classes (it said two) and a paragraph
on why a retired block stays in the population.

The census does not move: `specCarried` 45, `judged` 29,
`registeredPropless` 7, `ledgeredUnjudgeable` 9. It is the same nine
entries under corrected reasons.

**Changeset.** The dispatch expected none. `node
scripts/check-changeset-presence.mjs` refused without one, because it
counts any file under a released package's `src/`, test files included.
So the PR adds `.changeset/10033-parity-ledger-retired-upstream.md` with
an **empty frontmatter**, which declares that nothing is released. The
gate now passes.

## Verification record at head `4893d87cb`

Every exit code is captured before any pipe, and each verdict line is
quoted from the gate's own output.

| step | result |
|:--|:--|
| `pnpm exec vitest run
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
scripts/__tests__/check-installed-spec-pin-claims.test.ts` (repo root,
under `os-verify-lock.sh`). The second file is the only other test that
reads this file, by content. | `Test Files 2 passed (2)` · `Tests 228
passed (228)` · `VERDICT command-exit 0` |
| same parity file at base `ea02938cd`, before the edit (control: the
stale reasons were green then) | `Tests 199 passed (199)` |
| `pnpm exec turbo run build --filter='@object-ui/console^...'
--concurrency=2` (the dependency closure the type-check reads, under the
lock) | `Tasks: 34 successful, 34 total` · `VERDICT command-exit 0` |
| `pnpm --filter @object-ui/console type-check` (script echoed: `tsc
--noEmit && tsc -b tsconfig.node.json --force`) | exit 0. `tsc
--listFiles -p apps/console/tsconfig.json` includes
`registry-inputs-spec-parity.test.ts` (1 hit), so the test file is in
the checked set. |
| eslint on the touched file (`apps/console`, the console's own `eslint
.` config) | exit 0 · `--format json`: 1 file, 0 errors, 0 warnings.
**This is a narrowing, with its evidence:** (1) the population comes
from eslint's own config, where `isPathIgnored` is false for the test
file and true for the changeset; (2) the file count comes from the JSON
output; (3) type-aware linting is off (`calculateConfigForFile` shows no
`parserOptions.project` or `projectService`), so this diff cannot change
the verdict on any file it does not touch. |
| `pnpm check:control-bytes` | `OK (scanned 8435 tracked text file(s);
skipped 85 binary)` |
| `pnpm check:new-line-citations` | `VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0` |
| `node scripts/check-changeset-presence.mjs` | exit 0 · `1 source
file(s) of 1 released package(s) changed, and this change declares 1
changeset(s)` (empty frontmatter) |
| `pnpm check:changeset-claims` | exit 0 (report-only). It names one
pending changeset that mentions this file,
`.changeset/8067-component-input-member-kind.md`. I read that paragraph:
it is about `page:header.actions` and is still true. |
| `check-changeset-no-major` · `-overwrite` · `-fixed` ·
`check-pending-changeset-literals` · `check-installed-spec-pin-claims` ·
`check-test-path-roots` ·
`check-vi-mock-{specifiers,inherit,override-shape}` ·
`check-type-check-coverage` · `check-lint-coverage` | all exit 0 |
| `node scripts/check-governed-queue-guard.mjs --test` over both paths |
`NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none
matched.` |

objectui has no `dispatch-gates.mjs`. I derived the gate list by hand
from `.github/workflows/*.yml`, keeping the gates whose scripts read
test sources, `apps/console` or `.changeset/`. The full `pnpm lint` and
`pnpm test` shards are left to CI.

## Ablation: three legs, from the committed state

Each leg used `node ../objectstack/scripts/ablation-replace.mjs` in wrap
mode, with the test running under the lock. I wrote down the expected
result before each run: exactly one failed test, the rot check, naming
the mutated type. The observed direction was red on all three, as
expected.

| leg | mutation (landing proven: anchor count 1 → 0, blob changed) |
result |
|:--|:--|:--|
| 1 | `user:profile` entry → its exact pre-fix text | `1 failed / 198
passed` · `user:profile is retired upstream, and its ledger reason does
not say so — rewrite the entry as RETIRED UPSTREAM` |
| 2 | `element:form` entry → its exact pre-fix text | `1 failed / 198
passed` · `element:form is retired upstream, and its ledger reason does
not say so …` |
| 3 | specificity control: `cloud-connection:panel` (outside the enum,
empty shape, not retired) relabelled `RETIRED UPSTREAM` | `1 failed /
198 passed` · `cloud-connection:panel is ledgered as retired upstream,
and the spec does not retire it …` |

Every restore was `git checkout HEAD --` on the absolute path, proven by
blob == HEAD (`f714fdd786dc`), `git diff HEAD` empty and `git status
--porcelain` empty. The control run at the base (199 passed) shows that
legs 1 and 2 describe a state that was green before this PR.

## Acceptance notes

- Nothing is filed. There are no class a/b/c findings from this run.
- AGENTS.md objectstack-ai#11 asks for opportunistic repair of cross-file `path:line`
citations in a file you touch. This file has older ones, outside the
ledger entries, and I left them alone because the claim's surface is the
ledger. Nobody is carrying that repair.
- `ai:chat_window`'s `NOT REGISTERED, DELIBERATELY` reason was re-read
against `placeholders.tsx` and `block-types.ts` and is still true. The
new guard also passes for it.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…spec's object-level checks (objectui#7715) (objectstack-ai#10421)

Fixes objectstack-ai#7715
Clause-②: yes

Implements ruling B1 (director seat, comment 5564943016, decision batch
objectstack-ai#67, maintainer 「同意」): objectui's spec-derived zod mirrors re-attach the
spec's exported object-level checks. This is an accept-set **narrowing**
of `@object-ui/types`, so the contract review runs at
`CONTRACT_REVIEW_TIER`; the `needs:contract-review` label is the seat's
to hang (this PR writes no labels). Changeset:
`.changeset/7715-mirrors-carry-spec-object-checks.md`
(`@object-ui/types: minor`, breaking semantics in the body).

Implemented by the `os-dev` agent for the `domain:ui` seat 2 dispatch,
session `https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN`. Head
is `65df5b3`, which merges `origin/main` at `f475557` (merge commit
`8d62d1b`) and adds one test-only commit, `65df5b3`, answering the `Spec
Main Shape Gate` failure and the at-tier review's item ③-1. Round-0 code
and test measurements below were taken on `843e125`; the zod sources and
the changeset have not changed since. The round-1 readings are on
`65df5b3` and are listed under Local verification.

## What changed

- `packages/types/src/zod/objectql.zod.ts`: `ListViewSchema` ends in
`.superRefine(checkListViewCalendarVisualization)`, imported from
`@objectstack/spec/ui`.
- `packages/types/src/zod/layout.zod.ts`: `PageNodeSchema` ends in
`.superRefine(checkPageSourceCompleteness)`.
- `packages/types/src/__tests__/spec-object-refinements-7715.test.ts`
(new): the tripwire, the Page pin and the census described below. It
imports spec SCHEMAS only. Every spec `check*` function is read at run
time, by name, from the entry point's module namespace, so a spec that
adds or drops a check still compiles and the census fails by row name
instead.
- `packages/types/src/__tests__/imported-defaults-8317.test.ts`: the two
check functions are added to `REFINEMENT_EXCEPTIONS` (the
import-boundary census reads every spec value a mirror imports; a
chained check FUNCTION is the case that list exists for).

Both attachments call the spec's own function, so the refusal is the
spec's text, byte for byte. No check body is copied into objectui.

## Step zero: the objectui#7122 option-A tripwire was not on `main`

- `git grep` over the tree for a list-view parse carrying `'calendar'`
in `allowedVisualizations` without a `calendar` block: no such test. The
single `packages/types` hit, `p2-spec-exports.test.ts`, parses the
spec's own `AppearanceConfigSchema` and never touches objectui's
`ListViewSchema`, so it is not the tripwire.
- The file list of PR objectstack-ai#7685 (64 files, read through REST) contains no
ListView tripwire. The objectui#7122 dev report records the leg-6 pin as
"written and green (WIP, unpushed)".
- So the pin landed first, as `b46d8b3` (test only). On that tree it was
**red in the "spec refuses, objectui accepts" direction**: `Tests 1
failed | 2 passed (3)`, and the failure was `expected true to be false`
on `ListViewSchema.safeParse(...).success`. After `0731fa0` it is green.

## Per-site enumeration (the contract-review claim)

Object-level checks are read from each upstream spec object's
`_zod.def.checks` at the resolved `@objectstack/spec` **17.4.0**. The
export list comes from the installed package (the `check*` functions of
`@objectstack/spec/ui`: `checkGlobalFilterDateDefaultValue`,
`checkListViewCalendarVisualization`, `checkListViewPageMount`,
`checkPageSourceCompleteness`), which verifies H1.

| # | objectui site | spec object | object-level checks | verdict | why
|
|---|---|---|---|---|---|
| 1 | `NavigationAreaSchema` (`app.zod.ts`) | `NavigationAreaSchema` | 0
| nothing to attach | none upstream |
| 2 | `SpecAppFields` → `AppComponentSchema` (`app.zod.ts`) |
`AppSchema` | 0 | nothing to attach | none upstream |
| 3 | `DashboardWidgetSchema` (`complex.zod.ts`) |
`DashboardWidgetSchema` | 0 | nothing to attach | none upstream at
17.4.0 |
| 4 | `SpecDashboardFields` → `DashboardComponentSchema`
(`complex.zod.ts`) | `DashboardSchema` | 0 | nothing to attach | none
upstream (the `globalFilters` items are the separate row below) |
| 5 | `SpecPageFields` → `PageNodeSchema` (`layout.zod.ts`) |
`PageSchema` | 1 | **attached**: `checkPageSourceCompleteness` | reads
`kind` and `source`; the node carries both by reference (neither is in
`PAGE_SPEC_EXCLUDED` or overridden) |
| 6a | `ListViewSchema` (`objectql.zod.ts`) | `ListViewSchema` | 2 |
**attached**: `checkListViewCalendarVisualization` | reads
`appearance.allowedVisualizations` (carried by reference) and only
whether `calendar` is present; the local `CalendarConfig` override keeps
`calendar` optional, so "absent" means the same on both faces |
| 6b | `ListViewSchema` (`objectql.zod.ts`) | (same) | (same) | **not
attachable**: `checkListViewPageMount` | it reads `type`, which on this
node is the component discriminator `'list-view'`; the spec's view kind
rides as `viewType`. Attached as-is it would refuse every `pageName`,
including a valid page mount (pinned: the spec accepts its own page
mount, and the check run on objectui's spelling of it refuses at
`pageName`), and its remedy tells the author to write `type: 'page'`,
which the node's literal refuses. The `page` list-view kind is retired
upstream (objectstack#17063, noted in core's `UNDRAWABLE_VIEW_KINDS`),
and this check is gone from objectstack `main` at `5581d30` |
| — | `GlobalFilterSchema` (`complex.zod.ts`, a `.shape` spread and not
a `specFieldsExcept` site) | `GlobalFilterSchema` | 1 | already carried
| its own `superRefine` re-parses the spec-owned keys through the spec
schema. Pinned: objectui refuses `defaultValue: 'last_7_dayz'` with the
spec's message. Left unchanged |

The six spec objects carry **no unexported object-level check**: every
check they carry at 17.4.0 has a named export (H1's per-site question).

The census pin (`spec-object-refinements-7715.test.ts`) keeps this table
live. For each site, the attached and not-attachable lists must add up
to the spec object's own check count. The `check*` functions the spec's
`ui` entry point exports, read from its runtime module namespace, must
equal the names in the table. A check the spec adds later therefore
fails by site name. Objectstack `main` already carries
`checkDashboardWidgetStageOrder` and
`checkDashboardWidgetMetricMeasureArity`, which are not in 17.4.0, so
the next spec bump **will** turn row 3 red. That is intentional.

## Verdict-change measurement (authored documents)

| corpus | changed verdict | how measured |
|---|---|---|
| `apps/` | **0** | legs 1 and 2 below |
| `examples/` | **0** | legs 1 and 2 below |
| `content/` | **0** | legs 1 and 2 below |
| hotcrm | **NOT MEASURED** | not reachable from this container: not in
this session's repository scope, and no checkout is present. ⛔ Not
asserted as zero |

- **Leg 1: trigger census (all file types, tracked files only).** The
calendar check can only fire on a document that spells
`allowedVisualizations`, and `git grep -n allowedVisualizations -- apps
examples content` finds 0 hits. The controls in the same corpora and on
the same channel do hit: `appearance` in 3 files, a `list-view` literal
in 6 files. The page check can only fire on a literal `kind` of `html`,
`react` or `jsx`. Across the three corpora those spell 8 document
literals. Only 1 of them is a `type: 'page'` node (the 6678 console
test, which is always passed a non-empty source), and all 8 carry
`source`. The other hits are prose and comments.
- **Leg 2: parse census.** Every tracked `.json` file (471) and every
`json`/`jsonc` fence in `.md`/`.mdx` (210) under the three corpora was
parsed, and every nested object with `type: 'list-view'` or `type:
'page'` was walked. That found 2 list-view nodes and 16 page nodes, and
**neither newly attached check fires on any of them**. 30 fences do not
parse as JSON; leg 1 covers them. Positive control: one list-view node
and one page node, both synthetic, were injected into the same walk, and
both checks fired on them (2 of 2).

## Reverse verification (both legs, on committed heads)

Each run mutated one line through `ablation-replace.mjs` in WRAP mode
(anchor hit exactly 1, landing proven by marker count and blob hash,
restore proven by `blob == HEAD` and an empty `git diff HEAD`), then ran
the 7715 test file. The test imports the mirror sources directly, and
the root vitest config aliases `@object-ui/types` to `src`, so no `dist`
rebuild sits between the mutation and the run.

- Removing `.superRefine(checkListViewCalendarVisualization)`: the
tripwire went red (`Tests 1 failed | 14 passed (15)`) and was restored
(`4f7e91951ff1`). The direction is the expected one, red.
- Removing `.superRefine(checkPageSourceCompleteness)`: both Page pins
went red (`2 failed | 13 passed`) and were restored (`2ade6eeb933e`).
- Deleting the `checkListViewPageMount` census entry: the ListView
census row and the export-set pin went red (`2 failed | 13 passed`),
which proves the census can fail. Restored.
- The first ListView attempt used the replacement `;`, which also occurs
elsewhere in the file. The tool refused it before any test ran
(replacement count did not rise) and restored the file. That attempt
took no measurement; the next attempt used a distinct marker.

All three legs were re-run at `65df5b3` with the same results (`1 failed
| 14 passed`, `2 failed | 13 passed`, `2 failed | 13 passed`), because
round 1 changed how the census reads the export set.

## Local verification (round 0 at `843e125`, round 1 at `65df5b3`)

- At `65df5b3`: `pnpm exec vitest run packages/types/` gave `Test Files
230 passed (230)` and `Tests 5133 passed (5133)`; `pnpm --filter
@object-ui/types type-check` exited 0; the downstream sweep,
re-enumerated on the merged tree, gave `Test Files 83 passed (83)` and
`Tests 1622 passed (1622)`.
- Gates, each exit 0 at `843e125`: `check-changeset-presence`,
`check-changeset-no-major`, `check:changeset-claims` (report-only; it
flags 27 pending changesets that name `objectql.zod.ts` or
`layout.zod.ts`, and the ones that mention these two schemas were
re-read, none falsified), `check:new-line-citations` (0 new),
`check:control-bytes`, `check:esm-specifiers`, `check:self-import`,
`check:phantom-deps`, `check:spec-symbols`,
`check:component-surface-parity`, `check:installed-pin-claims`,
`check:pending-changeset-literals`, `check:test-path-roots`.
- `Spec Main Shape Gate` (compiles objectui against `@objectstack/spec`
built from objectstack `main`): **red at `a4835b6`** (CI job
107888507933, one diagnostic: TS2305,
`spec-object-refinements-7715.test.ts` line 47, no exported member
`checkListViewPageMount`). The test imported that check by name, and
objectstack `main` removed the export (objectstack#17063). Reproduced
locally against `7e6ca1787aa9` with the workflow's recipe (sparse build,
`npm pack`, `spec-main-shape-gate.mjs inject`) and the type-check leg
narrowed to `@object-ui/types`: the same single diagnostic, `report`
exit 1. **Green at `65df5b3`** against the same commit: `report` exit 0
with '✅ objectui type-checks against `@objectstack/spec` at that
commit.'. Against spec main the census now fails at RUN time, not
compile time, by row name: DashboardWidgetSchema, ListViewSchema, the
export-set pin and the page-mount measurement (`4 failed | 11 passed`).
At the pinned 17.4.0 it is `15 passed`.
- `check-governed-queue-guard --test` over the 5 changed paths reports
NOT GOVERNED.
- `check:spec-floors` is **NOT MEASURED as a gate**: it exits 1 on 17
`no-artifact` findings, all on packages this PR does not touch, because
it needs the whole workspace built, which is CI's run. After `pnpm
--filter @object-ui/types build`, it judged `@object-ui/types` (whose
dist now references both new imports) and reported no finding for it.
The declared floor `^17.4.0` carries both names in
`dist/ui/index.d.mts`.
- `eslint --no-inline-config` over the 4 touched source and test files:
0 errors, 1 warning. The warning (`no-explicit-any` on the existing
`options` bag check) was already on the base, one line higher. The
repo-wide `pnpm lint` is CI's run.

## Acceptance notes (observations, not filed)

- **Nested spec checks under locally overridden keys** fall outside B1.
They are not object-level checks of the six objects, and none is
exported. The spec has checks inside `navigation[]` (App and
NavigationArea), `regions[].components[].visibleWhen` (Page), and
`userFilters.tabs[].filter[]` and `conditionalFormatting[].condition`
(ListView). objectui replaces each of those keys with a local shape that
is declared broader than the spec's. Whether any of them accepts
something the spec refuses was **not measured**. The census reading is
recorded here so a future card can start from it.
- `GlobalFilterSchema` could switch from its delegating re-parse to
`.superRefine(checkGlobalFilterDateDefaultValue)`. No accept set would
change, and it is not a `specFieldsExcept` site, so it is left as is.
- Docs: no page in `content/` authors either refused shape (leg 1), so
no doc text changes.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants