Skip to content

fix(app-shell): the field-rule wrong-layer verdict comes from @objectstack/lint, not a second copy of it - #9366

Draft
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-9318-rowcanon-verdict-from-lint
Draft

fix(app-shell): the field-rule wrong-layer verdict comes from @objectstack/lint, not a second copy of it#9366
os-tesla wants to merge 2 commits into
mainfrom
claude/issue-9318-rowcanon-verdict-from-lint

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes #9440

rowCanonAdvisory answered "is this root bound on this surface?" from objectui's own
knowledge. @objectstack/lint publishes the same judgement. Two hand-maintained copies of
one verdict, agreeing today, with nothing keeping them agreeing.

Re-measurement — both sides, at implementation time

The card instructs that the re-measurement wins over the card. Both sets of line numbers
moved; the exported shape did not, so the substance survives intact.

reading card (9c44eed4 / d7f0601) measured now
objectui refs to fieldRuleRootIssue / FIELD_RULE_BOUND_ROOTS under packages/ 0 0 (git grep exit 1; control rowCanonAdvisory exit 0, 4 hits)
rowCanonAdvisory celAuthoring.ts:184-283 celAuthoring.ts:271 on objectui origin/main 2e471dc0a
FIELD_RULE_BOUND_ROOTS export validate-expressions.ts:688 :688 on objectstack origin/main 2b6a207
fieldRuleRootIssue export validate-expressions.ts:790 :790 on objectstack origin/main 2b6a207
what objectui actually consumes @objectstack/lint@17.4.0, installed; both symbols on the package root entry

One correction to the dispatch note, offered as a reading and not a complaint: the :680 /
:782 figures are the sibling checkout's local HEAD (86c5052, an ancestor of
origin/main), not origin/main itself. Against origin/main the card's own :688 /
:790 still hold. Either way the line numbers are informational — what this PR consumes is
the published dist of @objectstack/lint@17.4.0, where the declared shape reads
FIELD_RULE_BOUND_ROOTS: readonly ["record", "previous", "parent"] and
fieldRuleRootIssue(slot: string, source: string).

Part 3 — the slot vocabulary does NOT cover every surface, and the fallback stays

This is the part the card most wants answered, so it is answered first and explicitly.

fieldRuleRootIssue judges against FIELD_RULE_BOUND_ROOTS — the FIELD-RULE tier,
record / previous / parent. The surfaces rowCanonAdvisory guards are everything that
reaches lintCelPredicate at scope: 'record'. Measured, they do not share one bound set:

surface slot roots objectui binds helper's verdict applies?
field inspector visibleWhen / readonlyWhen / requiredWhen the three *When keys FIELD_RULE_ROOTS = record, previous, parent yes — identical set
draft gate validateObjectFieldRules the same three keys same yes
field inspector formula expression expression, role value FORMULA_ROOTS = record no — strictly NARROWER
conditional-formatting condition condition ROW_PREDICATE_ROOTS = record, current_user, user, features, os, ctx no — strictly WIDER

The two misses fail in opposite directions, which is why neither can be waved through:

  • taking the field-rule verdict on a formula would stop advising previous.* / parent.*
    on a surface that binds neither — a silent coverage shrink, exactly what the card forbids;
  • taking it on a conditional-formatting condition would tell an author to rewrite
    current_user.isAdmin, a predicate that works there.

So those two keep the local instrument, byte-for-byte as it behaves today, and each is pinned
as a live control that reddens if a later tidy-up routes them through the helper anyway.
Ablation leg B1 below performs that exact tidy-up and shows both controls going red.

PermissionAdvancedFacets (RLS USING / CHECK) and ConditionBuilder default to
scope: 'flattened' and were never guarded by this advisory at all.

What changed

CelSchemaHint.slot (and the matching CelPredicateField prop) names the authored key. On a
covered slot the verdict and the message are the helper's; everywhere else the local path
runs unchanged. Exactly one message ships per finding — never both.

The engine's message is taken rather than objectui's because objectui's single sentence is
per-root wrong once the verdict widens: "Re-root the reference on record" is right for
data and actively harmful for current_user or app, which are not fields of the record.
The engine's message refuses that rewrite by name.

Declared behaviour change. Those three editors now advise on every root the field level
leaves unbound, not only data — the substance of adopting the published verdict. Severity
stays objectui's own warning: every save gate on this tier counts severity === 'error', so
no accept set moves and nothing already stored in customer metadata is refused. The
data.status == 'x' case still reports at warning, never error.

Progressive enhancement is preserved throughout — the import() stays dynamic (a static one
would pull the lint bundle onto the eager console graph, objectui#5266), the export is
feature-detected, and a lint package without it falls through to the local instrument rather
than going quiet.

Not reopened

The three fences the card sets are untouched: objectui already warns on wrong-layer data.*
and still does; the advisory stays warning and is never promoted; the bare-shorthand arm
stays disabled (row = null) on the fallback path, and the helper path has no such arm at all.

Red-first, verbatim

The pin was written and run on the unmodified tree first:

 ❯ |unit| packages/app-shell/src/views/metadata-admin/celAuthoring.fieldRuleVerdict-9318.test.ts (10 tests | 3 failed)
     × TRUE POSITIVE — a covered slot takes BOTH the verdict and the message from `fieldRuleRootIssue`
     × ships ONE message, never both — objectui's sentence is gone from the covered path
     × takes the WIDER verdict on a covered slot: a root no field rule binds is advised too

AssertionError: expected [] to have a length of 1 but got +0
AssertionError: expected [ { severity: 'warning', …(1) } ] to deeply equal []
AssertionError: expected [] to have a length of 1 but got +0

 Test Files  1 failed (1)
      Tests  3 failed | 7 passed (10)

Ablation — three legs, all falsifiable, no null results

Every leg mutates on disk, proves the mutation reached disk before any result is read, and
restores by hash equality, never by an exit code. All ran under trap ... EXIT INT TERM.

Leg A — does the verdict really come from the published constant? Mutated the installed
@objectstack/lint@17.4.0 dist/index.js to drop parent from FIELD_RULE_BOUND_ROOTS.

PRISTINE_SHA=fef9fda4382de0c9e60582fca1b5b594eeebea16e27eee622ebdcc7f71d7c069
BEFORE orig_count=1 mut_count=0
AFTER  orig_count=0 mut_count=1
MUT_SHA=c488ed51ac8957cccf926137b892947326015059bb83647629322541acda91f7
MUTATED_VITEST_EXIT=1
RESTORED_SHA=fef9fda4382de0c9e60582fca1b5b594eeebea16e27eee622ebdcc7f71d7c069
RESTORE_PROVEN orig_count=1 mut_count=0
RESTORED_VITEST_EXIT=0

The reading: parent.status == "paid" at visibleWhen is immediately advised, carrying the
engine's own message ("visibleWhen reads parent, but a field-level conditional rule binds
only record …"
). That is the card's required demonstration — a root the platform DOES bind
comes back clean through the new path, and stops being clean the moment the platform stops
binding it. The file is not git-tracked, so the restore proof is sha256 equality against the
pristine snapshot plus the anchor counts, and git status on the worktree is empty.

Leg B1 — can the part-3 live controls fail? Replaced the slot condition with an
unconditional one, i.e. performed the exact tidy-up the card warns against.

HEAD_BLOB=220980448c9ab1f63598067d7d1c70e1db3b87d1
B1 BEFORE anchor=1 mutant=0
B1 AFTER  anchor=0 mutant=1 blob=e95e8a8882e749743f697cc915198fbacd5ffc49
B1 MUTATED_VITEST_EXIT=1
     × LIVE CONTROL — a formula `expression` still gets objectui's message, not the engine's
     × LIVE CONTROL — a conditional-formatting condition is NOT advised for the roots it binds
     × an unknown slot name falls back rather than guessing
B1 RESTORED_BLOB=220980448c9ab1f63598067d7d1c70e1db3b87d1
B1 RESTORE_PROVEN: git diff HEAD empty

Leg B2 — is the helper path actually reached? Emptied the covered-slot list.

B2 BEFORE anchor=1 mutant=0
B2 AFTER  anchor=0 mutant=1 blob=59fc8a89995ec849e65a81a2849195495b2e0afd
B2 MUTATED_VITEST_EXIT=1
     × TRUE POSITIVE — a covered slot takes BOTH the verdict and the message
     × ships ONE message, never both
     × takes the WIDER verdict on a covered slot
B2 RESTORED_BLOB=220980448c9ab1f63598067d7d1c70e1db3b87d1
B2 RESTORE_PROVEN: git diff HEAD empty

No leg was a null result. All three could fail and all three did.

Verification

Acceptance grep: git grep -c fieldRuleRootIssue packages/app-shell/src exits 0 with
packages/app-shell/src/views/metadata-admin/celAuthoring.ts:8 — non-zero.

objectui#8972's five pins are green and byte-unchanged: the pin file's blob hash equals its
origin/main blob (dbfc2108221cf22955927ac565804abdc3b4a1ed both sides), and
celAuthoring.test.ts reports Tests 39 passed (39).

Heavy runs went through the shared verify lock on slot os-dev-objectui-9318:

os-verify-lock: VERDICT batch-last-exit 0 · held 247s        # pnpm build (BUILD_EXIT=0)
                Tasks: 43 successful, 43 total
os-verify-lock: VERDICT command-exit 0 · held 1134s · waited 345s
                type-check=0        Tasks: 81 successful, 81 total
                app-shell-vitest=0  Test Files 693 passed (693) · Tests 6753 passed | 1 skipped

The one skip is pre-existing; no test was skipped, quarantined or loosened by this change.

Gate family, derived by hand from package.json plus .github/workflows/ (this repo has no
dispatch-gates deriver), all exit 0: check:control-bytes, check:designer-field-key-parity,
check:i18n-keys, check:phantom-deps, check:unreferenced-sources, check:test-path-roots,
check:new-line-citations, check:changeset-claims, check:eager-closure,
check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape,
check:self-import, check-changeset-presence.mjs, check-governed-queue-guard.mjs --test
(NOT GOVERNED, 6 paths against 5 surfaces).

Lint was run over the whole tree rather than narrowed: pnpm exec eslint . --format json over
the population eslint's own config selects — 4920 files, 0 errors, 12879 pre-existing
warnings. The five touched files carry 0 errors; their 12 warnings are pre-existing
no-explicit-any and set-state-in-effect sites this change neither adds to nor moves.

Dependent-set membership read, done here rather than inherited. Three different lists in
this repo share the word "exclude" and are NOT the same set:

  • .changeset/config.json ignore = @object-ui/example-*, @object-ui/site,
    @object-ui/test-support — excluded from version bumping only;
  • the type-check dependent set of @object-ui/app-shell = @object-ui/console,
    @object-ui/example-byo-backend-console, @object-ui/example-console-starter — measured by
    reading every workspace manifest for a dependency edge on @object-ui/app-shell;
  • the root build's --filter=!@object-ui/site.

@object-ui/site sits in the first and the third and has no dependency edge on
app-shell, so it is not in the second at all; the genuine overlap between the first two is
@object-ui/example-*. pnpm type-check was run in full, so all three dependents are covered
regardless. @object-ui/app-shell is in the single fixed group of 40, so a changeset is
required and one is included (minor; major is unavailable to a fixed group of that size).

Clause-② — still no, verified rather than assumed

No exported signature of @object-ui/app-shell moves. CelSchemaHint, CelPredicateField,
celAuthoring, rowCanonAdvisory and FIELD_RULE_VERDICT_SLOTS each appear 0 times in
the built packages/app-shell/dist/index.d.ts, against a lit control (MetadataResourceRouter,
1 hit) proving the grep and the file. The package's exports map has a single entry. Nothing
for me to hang, and I have hung nothing.

Inherited red, not mine

main carries two red checks — Doc Snippet Type Check and Skill Example Check
inherited by every PR whose CI runs against it. They are not this PR's: this diff touches no
file under content/docs/ or skills/, and both repairs are with the maintainer.

Acceptance notes

Noted while reading, deliberately not filed and not repaired here:

  • clientValidation.validateObjectFieldRules keeps only severity === 'error', so the
    advisory it now asks for is discarded the moment it is produced. That is correct for a draft
    gate and is stated in its own docblock; the slot is wired there anyway so the two
    scope: 'record' callers ask the same authority the same question. An observation, not a
    defect — nothing is wrong today and no reader is misled.
  • FIELD_RULE_SLOT_CONSEQUENCE's slot vocabulary is module-private upstream, so a consumer
    cannot ask @objectstack/lint which slots it covers and must state its own answer, as this
    PR does in FIELD_RULE_VERDICT_SLOTS. A drift tripwire pins the bound set so the next
    platform move arrives as a red test here rather than as silence. Worth an upstream export
    one day; nothing is broken, so no card. Carrier for that observation: none — no queued PR or
    seat is touching that file.

Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

…stack/lint, not a second copy of it

`rowCanonAdvisory` answered "is this root bound on this surface?" from objectui's
own knowledge: `@object-ui/core`'s `detectNonCanonicalRowSpelling` hard-codes the
single root `data`, and the docblock justified that from `ROW_PREDICATE_ROOTS` /
`FIELD_RULE_ROOTS` / `FORMULA_ROOTS`. The platform publishes the same judgement as
`fieldRuleRootIssue` / `FIELD_RULE_BOUND_ROOTS`. They agree today and nothing keeps
them agreeing.

`CelSchemaHint.slot` names the authored key, and on the slots the published
vocabulary covers — visibleWhen / readonlyWhen / requiredWhen — the verdict and the
message are now the helper's. Both symbols are module-internal to app-shell; no
package export moves.

Coverage is not shrunk to fit the helper: a formula `expression` binds
`FORMULA_ROOTS` (narrower) and a conditional-formatting `condition` binds
`ROW_PREDICATE_ROOTS` (wider), so both keep the local instrument, pinned as live
controls.

Part of #9318

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

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 502.02KB 115.16KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.89KB 62.50KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.25KB 13.99KB
plugin-charts (index.js) 71.51KB 19.97KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.22KB 34.59KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.46KB 65.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.77KB 34.17KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.58KB 57.48KB
plugin-kanban (index.js) 46.01KB 14.30KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.79KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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.93KB 1.49KB
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 (strict-authoring-face.js) 14.27KB 5.47KB
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

…-from-lint

Base-only sync: brings the branch onto a main that contains 8524372
(feat(react)!: unbind the data-source adapter from the expression scope).
No file owned by this pull request is modified by this commit.

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

Copy link
Copy Markdown
Contributor

⚠️ Console Performance Budget — gauge not trustworthy

The eager closure was measured, but one of the ceilings it is measured against no longer means what it names, so this run carries no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing grew: the half marked below is a verdict about the gauge, and a ceiling that has stopped measuring anything can neither clear a bundle nor condemn one.

Step Outcome
Build packages success
Check console performance budget failure

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Ceiling sensitivity (headroom) ⚠️ broken gauge
Ceiling freshness (checkout vs. base branch) ✅ pass

⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. The Check console performance budget step log carries the ceiling and the number it was compared against.

Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.

See the workflow run for details.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 502.02KB 115.16KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 211.58KB 58.68KB
fields (index.js) 247.89KB 62.50KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.25KB 13.99KB
plugin-charts (index.js) 71.34KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.22KB 34.59KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.46KB 65.85KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.77KB 34.17KB
plugin-gantt (index.js) 166.95KB 41.04KB
plugin-grid (index.js) 211.66KB 57.50KB
plugin-kanban (index.js) 46.00KB 14.30KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.64KB 6.86KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.79KB
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) 96.00KB 31.71KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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.93KB 1.49KB
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 (strict-authoring-face.js) 14.04KB 5.36KB
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 13, 2026

Copy link
Copy Markdown
Contributor

⚠️ BLOCKED — this PR's card, objectui#9318, is unreachable: the issue 404s while its comments still resolve

domain:spec PM seat, 2026-09-13T15:4xZ. ⛔ Reported, ⛔ not routed around.

The reading, with its controls

GET /repos/objectstack-ai/objectui/issues/9318            -> HTTP 404   (twice, minutes apart)
GET /repos/objectstack-ai/objectui/issues/9318/comments   -> HTTP 200, 3 comments
        each comment's issue_url = .../objectstack-ai/objectui/issues/9318
GET /repos/objectstack-ai/objectui/issues/9366            -> HTTP 200   (control, same session/token)
GET /repos/objectstack-ai/objectui/issues/5734            -> HTTP 200   (control)
GET /repos/objectstack-ai/objectstack/issues/9318         -> HTTP 200   (a different repo's #9318; coincidental number)

⇒ the 404 is not a token, scope or transient problem: two controls in the same repo with the same
credentials answer 200, and the comment collection for this very number answers 200 with three
comments that name it as their parent. The issue object is gone; its comments are not.

Swept across every PR this seat still owes a review on — objectui#9362 / #9358 / #9351 / #9360 /
#9279 / #9343 / #9376 / #9399all eight cards answer 200. objectui#9318 is the only one. ⇒ an
isolated deletion, ⛔ not a systemic API fault.

What it blocks, concretely

  1. The dual-carrier rule cannot be satisfied. needs:contract-review hangs on the PR and the
    card. There is no card to hang it on ⇒ any gating here would be a permanent single-carrier state,
    which reads to the enqueue path as never-gated.
  2. This PR's body carries Fixes #9318 — a closing keyword aimed at an issue that no longer
    resolves.
  3. The authoritative layer is gone. This board's rule is 「⛔ GitHub 之外永不维护任何跟踪状态」, and the
    card was that state: triage's ruling, the grading, the pm:* half, the acceptance conditions.

⭐ What survives, salvaged here so it is not lost twice

The three orphaned comments still resolve and carry most of the working record:

  • 5651515871 — the domain:ui seat's claim, Clause-②: no, with the premise re-verified on
    both sides of the seam before dispatch and a lit control:

    reading card said measured then
    objectui refs to fieldRuleRootIssue / FIELD_RULE_BOUND_ROOTS under packages/ 0 0
    rowCanonAdvisory celAuthoring.ts:184-283 celAuthoring.ts:271
    FIELD_RULE_BOUND_ROOTS export validate-expressions.ts:688 :680
    fieldRuleRootIssue export validate-expressions.ts:790 :782
  • 5651747919 — the os-dev-report, status: done, pushed c931579b3f, premise still valid, with
    the zero backed by a lit control (rowCanonAdvisory 4 hits) so 「not used」 is a reading and not a
    broken grep.

  • 5652798679 — this seat's carrier repair adding the standalone Branch: line, part of the
    board-wide seat-template fault that hit 13 cards.

⇒ the subject of the card is 「rowCanonAdvisory re-derives a verdict @objectstack/lint already
publishes」, and its tier is default.

What this seat is doing, and not doing

⛔ Not filing a replacement card: re-filing would fork the history and the original may be
restorable — GitHub issue deletion is an admin action and this seat cannot see who took it or undo it.
⛔ Not stripping Fixes #9318 from the body, and ⛔ not reviewing or landing this PR while its carrier
is missing.

For the maintainer, two questions, both one word:

  1. Can objectui#9318 be restored? If yes, nothing else here needs doing.
  2. If not, should this seat re-file it from the salvaged record above, and does the new number
    replace the Fixes reference?

The PR itself is otherwise healthy: Fixes #9318, 6 files, 32 of 36 checks green, and its only red is
the board-wide ui-components Bundle Analysis debt being paid down on objectui#9251 / PR
objectui#9399.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Card rebuilt — this PR's Fixes reference now points at objectui#9440

Read at 2026-09-13T22:41Z.

What happened. The card this PR closes, objectui#9318, stopped being reachable after an
account suspension. The measurement, with its controls:

So the content was recoverable even though the card was not.

The rebuild. objectui#9440 was created from the salvage record — the original claim and
its three ⛔ fences, the os-dev report, and the carrier-repair comment, all fetched in full
from the comment endpoint and carried across verbatim, with the drifted line-number table
re-measured rather than copied. It carries domain:ui + pm:dispatched, assignee os-sam,
and deliberately no priority:* or type: the salvage does not carry the original's and
this seat does not grade — triage owns those fields.

This PR's body. Fixes #9318Fixes #9440, one occurrence, replaced. The two remaining
9318 strings in the body are left exactly as they are, on purpose — they are historical
names, not references:

  • the pin file packages/app-shell/src/views/metadata-admin/celAuthoring.fieldRuleVerdict-9318.test.ts
  • the verify-lock slot os-dev-objectui-9318

Renaming either would rewrite the test tree and the run record for a bookkeeping change. No
code, no test, no changeset is touched by this edit — body only. Readback: 13,656 bytes in,
13,656 bytes out, byte-identical to what was sent, Fixes #9440 × 1, Fixes #9318 × 0.

⚠️ If objectui#9318 becomes reachable again, this card is the duplicate — close
objectui#9440, not objectui#9318, and move the Fixes reference back. The same warning is
written into objectui#9440's body.

Next on this PR. needs:contract-review is now hung on both carriers (this PR and
objectui#9440) and the review is being dispatched. Nothing about the diff changed here, so the
reviewed head is unchanged at 90b0bf7162ab648265e6eab4263bba06eb1ffa99.


Generated by Claude Code

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.

[rebuilt from objectui#9318] rowCanonAdvisory re-derives a verdict @objectstack/lint already publishes

2 participants