Skip to content

fix(app-shell): a refused metadata write on the view, report and draft surfaces is said, and the view-config panel stays dirty (objectui#11583) - #11593

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-11583-refused-writes-said
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-11583-refused-writes-said

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11583

Clause-②: yes
Why yes: ViewConfigPanel is exported from packages/app-shell/src/index.ts, and the return type of its onSave prop moves from void to unknown. The type admits any return, exactly what void admitted; the panel now awaits it, and false (returned, or the value a returned promise settles with) or a rejection means refused, while anything else, nothing included, is read as saved. An earlier head typed it void | Promise(boolean) (parentheses because the body sanitizer drops angle-bracket shapes) and called that caller-compatible; the at-tier contract review (5977112283, item ①.1) measured that false: the union refuses, with TS2322, an async host that returns nothing, a host resolving to a record and a sync non-void host. Remedy (a), the seat's call (5977119047): unknown, pinned at compile time below, and patch stands. No published schema, spec type or accept set moves. ReportConfigPanel's onSave is aligned to unknown in the same stroke; ReportConfigPanel is exported from neither packages/app-shell/src/index.ts nor packages/app-shell/src/views/index.ts, so it adds nothing to Clause-②: the yes is for ViewConfigPanel only. onCreate keeps its void type.

What this changes

This is the silent-refused-write family's closing card (direction: triage 5975148557). Every metadata write call under packages/app-shell/src/views/ now raises a refusal through the console's existing toast.error channel with formatMetadataError, the way objectui#11578 did for the two Create View doors. ⛔ No new notification surface.

Site (file › symbol) Write Before After
ObjectView.tsx › handleViewConfigSave persistRuntimeMetadata('view', …) console.error only, not awaited awaited; a refusal is a toast with the door's message, and the panel is told false
ViewConfigPanel.tsx › handleSave calls onSave cleared isDirty and bumped savedSignal before the save settled waits for the outcome; clears and bumps only when the save lands; Save is disabled while it is in flight; an edit made during the save keeps the panel dirty
ObjectView.tsx › handlePinView updateView(…, { isPinned }) console.error only toast with the door's message
ObjectView.tsx › handleReorderViews dispatchViewPatches, one updateView(…, { sortOrder }) per saved view console.error only toast with the door's message
ObjectView.tsx › persistViewPatch (triage's extra site) updateViewConfig a toast only for the client gate's ViewConfigPermissionDeniedError; every other refusal, a server 403 included, console.error only the gate's own message is unchanged; every other refusal is a toast with the door's message
ReportView.tsx › saveSchema persistRuntimeMetadata('report', …) console.warn only toast with the door's message; returns whether it saved
ReportConfigPanel.tsx › handleSave (patch round) calls onSave cleared dirty and shut the panel before the save settled; a refused save lost the edit waits for the outcome; clears and shuts only when the save lands; a refused save keeps it open with the edit and Save live; Save disabled and the inspector read-only while in flight
ObjectView.tsx › handleSetDefaultView (patch round) dispatchViewPatches, updateView(…, { isDefault }) a toast with the untranslated literal 'Failed to set default view' and no reason form.saveError with the door's message, like its siblings
RuntimeDraftBar.tsx › handlePublish, handleDiscard publishRuntimeMetadata, discardRuntimeDraft console.error only toast led by "Publish failed" / "Discard failed"; the draft indicator stays
  • Messages. The object page and the report reuse form.saveError, as objectui#11578 did. The draft bar takes its labels from the metadata-admin engine table (objectui#10202's claim, untouched here), which has no failure keys, so two keys are added to all ten packs: console.runtimeDraft.publishFailed and console.runtimeDraft.discardFailed. They are read through a module-level hook (useRefusalTitles) whose t is bound to the packs. An aliased translator was tried first and measured invisible to check:i18n-keys and check:i18n-dead-keys, which classify t / tt bindings only: the two keys read as dead-key candidates until the hook landed.
  • Report save cadence (PM hypothesis 5), measured. An inspector edit drives the live preview only (handleReportFieldChange); the write fires once per press of the editor's Save. So one refusal is one toast, no burst guard is added, and when the editor saves is unchanged. The pin counts the writes after three edits (none) and after Save (one).
  • Report editor (patch round, from the seat's review 5976535570). ReportView.handleReportConfigSave awaits saveSchema and re-seats the panel's config only on a save that landed, so a refused save keeps the open panel's draft. saveSchema's no-client branch returns false.
  • The published onSave type (contract-review remedy). ViewConfigPanelProps.onSave and ReportConfigPanelProps.onSave return unknown; the runtime read is unchanged ((await onSave?.(flat)) !== false), and the emitted JavaScript of both panels is byte-identical to the previous head's (TypeScript transpileModule without comments, same hash). The docblocks name false or a rejection as the only refusal signal. ViewConfigPanel.onSaveHosts-11583.test.ts, in the app-shell test project that type-check compiles, assigns the hosts the void prop admitted to the prop type.
  • A ratchet rebound to the handler (CI repair). view-filter-fold.ratchet.test.ts's explicit-save control asserted handleViewConfigSave[\s\S]{0,600}persistRuntimeMetadata('view', a fixed window after the name. This PR's docblock and early return moved the call 708 characters after the name, with the call still there, so CI's Test (shard 4/8) went red at e3c7e63. The control now finds the const handleViewConfigSave declaration with the TypeScript parser and requires a persistRuntimeMetadata('view', …) call inside it: the same meaning, no byte distance, and a call in another function cannot satisfy it. The old window still matches at b65aa5e and does not at e3c7e63 (measured with the regex itself).
  • Unchanged: objectui#11578's toasts, CreateViewDialog's await and objectui#11581's one builder; rename and delete, which already raised a toast with a pack key.

The census

packages/app-shell/src/views/writeRefusalCensus-11583.test.ts. The population is derived by the TypeScript parser over every non-test source under the directory, recursively (metadata-admin/** included; it holds no write today): calls to the ruling's five names (createRuntimeMetadata, persistRuntimeMetadata, publishRuntimeMetadata, discardRuntimeDraft, updateView) plus updateViewConfig (triage's extra site), deleteView (updateView's adapter sibling) and any symbol a relays row names (dispatchViewPatches). A site is keyed file › enclosing symbol › callee, never by line. Every ledger row carries a kind with mechanical evidence:

  • surfaces: a toast.error call sits inside a catch clause or a .catch(…) callback of the enclosing symbol, and the row names a pin file that exists and names the symbol.
  • relays: the write's promise leaves the symbol uncaught, and each call of the symbol is itself a census row.

The census is also red on a site with no row, a row whose declared call count differs from the tree, a stale row, a reason that says only "out of scope", an adapter write read off its object as a value, and a seam write imported under another name.

Evidence

Red first. At b65aa5e with only the new test files added: the panel's 403 and 422 cases saw toast.error called 0 times (the objectui#11578 dev's measurement, reproduced through the real ObjectView, the real panel and its real draft bar, and a real MetadataClient); pin, reorder, the toolbar toggle's server refusal, the report Save, Publish and Discard saw 0 toasts; the panel's false, rejection and in-flight cases were red; the census was red naming exactly the six log-only rows (handleViewConfigSave, handlePinView, handleReorderViews, saveSchema, handlePublish, handleDiscard). Every control was green: Tests 12 failed | 19 passed (31). After the change: Tests 31 passed (31).

Patch round, red first on the first round's head 69529b5 (the new pins only): Tests 5 failed | 23 passed (28). The set-as-default case: expected 'undefined' to contain 'Setting a default view requires the M…' (a toast, no description). The report editor through the real ReportView: Unable to find an element by: [data-testid="report-config-panel"] after a refused save. The panel's false and rejection cases: onClose called once; the in-flight case: Save not disabled. Controls green. After the change: Tests 41 passed (41) over the patched pins and the report panel's neighbours.

Ablations, from the committed head through ablation-replace.mjs (anchor hit once, blob changed on disk; restore proven by the HEAD blob hash and an empty git diff HEAD):

  1. handlePinView's toast swapped for console.info: exactly two cases went red, the pin case and the census's surfaces check naming ObjectView.tsx › handlePinView › updateView (Tests 2 failed | 18 passed (20)). Restored to blob e24f9c5, equal to HEAD.
  2. A scratch view file censusProbe11583Scratch.ts with one dataSource.updateView call in a catch-and-log handler: the census's "every write site has a ledger row" went red naming censusProbe11583Scratch.ts › renameQuietly › updateView (Tests 1 failed | 6 passed (7)). The file was removed by its trap; git status clean.
  3. The i18n gate reaches the new call: t('console.runtimeDraft.publishFailed') mutated to a missing key made check:i18n-keys exit 1 with [missing-key] on it. Restored to blob 405b57c, equal to HEAD.
  4. Patch round, from head e3c7e63: ReportConfigPanel.handleSave's if (!landed) return; swapped for void landed;, so the panel shuts whatever the outcome: exactly three cases went red, the panel's false and rejection cases and the report editor's refused case through ReportView (Tests 3 failed | 4 passed (7)). Restored to blob 2500984, equal to HEAD.
  5. Patch round: handleSetDefaultView's description: formatMetadataError(err) removed: exactly the set-as-default pin went red (Tests 1 failed | 20 passed (21)); the census stayed green, since the toast is still on the refusal path. Restored to blob ec24e59, equal to HEAD.
  6. CI repair, from head e075976: the persistRuntimeMetadata('view', …) call in handleViewConfigSave renamed to an ablated callee: the rebound ratchet control went red with handleViewConfigSave no longer persists through the metadata seam (Tests 1 failed | 8 passed (9)). Restored to blob ec24e59, equal to HEAD.
  7. Contract-review remedy, from head ba2a810: ViewConfigPanelProps.onSave typed back to void | Promise(boolean): pnpm --filter @object-ui/app-shell type-check exit 2 with TS2322 on exactly the three host rows of ViewConfigPanel.onSaveHosts-11583.test.ts (an async host returning nothing, a host resolving to a record, a sync host returning a number) and TS2344 on its return-type equation; the void host and the false host still compile. Restored to blob 0497429, equal to HEAD.

Verification at ba2a810

  • Since e075976, the changed sources are type and comment only: the emitted JavaScript of ViewConfigPanel.tsx and ReportConfigPanel.tsx hashes the same before and after (TypeScript transpileModule without comments), so no runtime test's verdict at e075976 can move. Run at ba2a810: every test that imports, mocks or names ViewConfigPanel or ReportConfigPanel, every app-shell test that reads sources from disk, every ObjectView* suite, the census and the new pin: Test Files 96 passed (96), Tests 1075 passed (1075). pnpm --filter @object-ui/app-shell type-check: exit 0, its test project lists the new pin (counted with --listFilesOnly). The readings below are from e075976.

Verification at e075976

  • Tests, the whole packages/app-shell suite, run under the verify lock in pieces that fit the foreground cap: --shard=1/4, then --shard=2/8 through --shard=8/8 (vitest's shard ranges over the 994 files: 1/4 is the first 249, 2/8 starts inside it, so together they cover every file). Each piece exit 0; summed: Test Files 1117 passed and 1 skipped across 1118 runs (124 files ran twice in the overlap), 0 failed (Tests summed: 10989 passed, 10 skipped). Plus every test outside app-shell that names ObjectView, ViewConfigPanel, ReportView, ReportConfigPanel or RuntimeDraftBar as text or reads sources from disk (35 files), and packages/i18n/: Test Files 113 passed (113), Tests 2727 passed | 13 skipped (2740).
  • The gap CI found: the earlier narrowed union selected tests by IMPORT, and the ratchet reads ObjectView.tsx from disk with readFileSync and imports nothing from it, so the union could not see it. Shards 1, 4, 5, 6 and 7 at 69529b5 were cancelled by the next push, so it never ran there either. The whole-package run above covers that gap for app-shell.
  • pnpm --filter @object-ui/app-shell type-check: exit 0 after the dependency closure was rebuilt at this head (turbo run build --filter=@object-ui/app-shell^..., 28 tasks). Its test project lists all six new files and the ratchet (counted with --listFilesOnly).
  • Gates, each exit 0: check:metadata-write-doors, check:control-bytes, check:new-line-citations (0 new citations), check:changeset-claims (report-only; the three pending changesets it names for touched files were re-read and still hold), check:pending-changeset-literals, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys (report), check:i18n-designer-parity, check-changeset-presence, check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots (the census's directory walk is classified as self-rooted), check:unreferenced-sources, check:handler-key-reads.
  • Lint, narrowed to the changed files and proven: the population is every .ts / .tsx file in the diff under the root eslint.config.js that each package's eslint . reads; --format json reports 23 files, 0 errors (the 23rd, the type pin, at ba2a810) (none of the 5 changed app-shell sources carries more warnings than at b65aa5e); the config enables no type-aware linting (no parserOptions.project, no projectService) and no local rule reads the filesystem, so this diff cannot move the verdict of an untouched file.
  • NOT MEASURED: the console bundle budget (performance-budget.yml); reason: it needs a full console build and is CI's. The eager en pack grows by two short strings.

Acceptance notes

  • ViewConfigPanelProps.onCreate is still typed as returning void while the object page passes handleViewCreate (which returns whether the view was saved). A void target admits that host, create mode does not gate Save on the dirty flag, and a refused create keeps the panel open, so nothing is stranded.
  • handleViewConfigSave's no-write branch (no metadata client or no view id) now returns false with its existing console.warn, so the panel stays dirty instead of reading as saved; ReportView.saveSchema's no-client branch returns false the same way. useMetadataClient always returns a client and the edit-mode draft carries the active view's id, so neither editor reaches them.
  • The census checks that a toast sits on the refusal path, not that every refusal reaches it: persistViewPatch passed that check before this change through its permission-denied branch. Its behaviour is pinned by ObjectView.viewWriteRefusal-11583.test.tsx.

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


Generated by Claude Code

claude added 4 commits October 4, 2026 03:40
…t surfaces is said, and the view-config panel stays dirty

Every metadata write under packages/app-shell/src/views now raises a refusal
through toast.error with formatMetadataError: the view-config panel's edit
Save, pin, reorder, the toolbar toggles' updateViewConfig, the report
editor's Save, and the draft bar's Publish and Discard. ViewConfigPanel
clears isDirty and bumps savedSignal only after the save lands. A census
over every write call under the directory keys each site to a ledger row
with mechanical evidence, so a new write site with no row is red.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
…ly close (objectui#11583)

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
…bound t the i18n gates can see (objectui#11583)

An aliased translator (`const { t: tConsole } = useObjectTranslation()`) is
invisible to check:i18n-keys and check:i18n-dead-keys, which classify `t` /
`tt` bindings only: the two new keys read as dead. A module-level hook binds
the packs' `t` in its own scope, so the bar's engine-table `t` is untouched.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
…ere check:test-path-roots can classify it (objectui#11583)

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3312.3 KB 3330.4 KB
Main entry chunk (gzip) 151.3 KB 350 KB
Entry file index-DWeEgwLr.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.69KB 64.78KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

…and set-as-default says the refusal's reason (objectui#11583)

Patch round from the seat's review: ReportConfigPanel.handleSave waits for
onSave's outcome (ReportView.saveSchema now resolves whether it saved), so a
refused report save leaves the editor open with the edit and Save live; Save
is disabled and the inspector read-only while the save is in flight.
handleSetDefaultView's untranslated literal toast becomes form.saveError with
formatMetadataError, like its siblings.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3312.3 KB 3330.4 KB
Main entry chunk (gzip) 151.3 KB 350 KB
Entry file index-DkL2z3QN.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.69KB 64.78KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

…leViewConfigSave's own body, not a 600-character window (objectui#11583)

The control asserted `handleViewConfigSave[\s\S]{0,600}persistRuntimeMetadata('view'`.
objectui#11583 added a docblock and an early return to the handler, moving the
call 708 characters after the name with the call still in place, so the
window went red (CI shard 4/8 at e3c7e63). The assertion now finds the
`const handleViewConfigSave` declaration with the TypeScript parser and
requires a `persistRuntimeMetadata('view', ...)` call inside it: same
meaning, no byte distance, and a call in another function cannot satisfy it.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3312.3 KB 3330.4 KB
Main entry chunk (gzip) 151.3 KB 350 KB
Entry file index-DpxZFUhJ.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.63KB 64.77KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e075976a934dfe70011e4e001145f743d397acba
Local-runs: probe — one tsc --noEmit over a scratch file outside the repository, asking whether the widened onSave type admits every handler the old void-returning prop admitted; the head's green Type Check answers that for in-repo hosts only, and the question is an out-of-repo host of the exported ViewConfigPanel

Written 2026-10-04T05:56Z by the reviewing subagent of session_01FjqrwXPfSMkSfkKYDSRkN2, read-only against GitHub and git show at the head. Inputs: card objectui#11583 (its body and its seven comments through the CI-repair report 5976952437), PR objectui#11593 (its body, its 23-file list, its three github-actions comments, and the net diff against merge-base b65aa5e), the check-runs on e075976, and packages/app-shell/src/index.ts, packages/app-shell/src/views/index.ts and packages/app-shell/package.json at the head. The superseded heads 69529b5 and e3c7e63 are not judged, except where the CI repair's cause is confirmed below.

Check-runs on e075976: 43 runs, all completed: 40 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard …/4)), 0 failure, 0 in progress. Among the successes: Test (shard 1/8) through Test (shard 8/8), the aggregate Test, Test (dist pins), Type Check, Lint, Build & E2E, Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Governed Surface Queue Guard, Line Citation Gate. First read 2026-10-04T05:45:59Z; re-read 2026-10-04T05:55:23Z with the same counts, the PR still at this head. The gates are green; the verdict below is not about them.

① Derived judgments

  1. ViewConfigPanelProps.onSave, void widened to void | Promise(boolean) — the one published surface this diff moves, and WRONG as claimed. ViewConfigPanel is exported from packages/app-shell/src/index.ts through views/index.ts (confirmed at the head). The runtime half is right: landed = (await onSave?.(flat)) !== false, so an absent handler, a handler returning nothing, and a handler resolving to anything but false are read as saved, and ViewConfigPanel.saveOutcome-11583.test.tsx pins it in a host that returns nothing is read as saved, as before (control). The type half is not caller-compatible: TypeScript ignores a source's return type only when the target's return type is exactly void; against the union void | Promise(boolean), a handler returning Promise(void) — every async host that does not return a boolean, the ordinary async (draft) { await save(draft); } — is refused, as is one returning Promise(Record) or a sync non-void value. The probe named in Local-runs confirms it: four handlers the old prop accepted fail against the new type with TS2322 Type 'Promise(void)' is not assignable to type 'void | Promise(boolean)'; a signature returning unknown admits all four, and void | boolean | Promise(void | boolean) admits the async-void host. So the PR body's every function a host could pass before still type-checks and its caller-compatible are false for the published prop. ReportConfigPanelProps.onSave carries the same shape but is unpublished (next item), so the finding is ViewConfigPanel alone. The remedy is the seat's, in ③.
  2. ReportConfigPanelProps.onSave, the same widening — unpublished, confirmed. ReportConfigPanel appears in neither packages/app-shell/src/index.ts nor packages/app-shell/src/views/index.ts, and the package's exports map has . and ./styles.css only, so no subpath reaches it. Its one in-repo host is ReportView, whose handleReportConfigSave returns Promise(boolean). Adds nothing to Clause-②. Right.
  3. The in-flight states — right; no stranding path found. ViewConfigPanel: Save is disabled={saving || (mode === 'edit' && !isDirty)}; handleSave captures savedDraft = draftRef.current before awaiting and clears isDirty only if (draftRef.current === savedDraft), so an edit made during the flight keeps the panel dirty; handleSave never calls onClose, so a refused save cannot close it. The draft re-seed keys on stableKey (activeView.id, not identity), so handleViewConfigSave's setViewDraft(draft) at the start of a save does not clear dirty under the save. ReportConfigPanel: if (!landed) return; precedes setDirty(false) and onClose(); readOnly={saving} and disabled={saving} hold while in flight. ReportView.reportConfig is memoised on configVersion, which handleReportConfigSave bumps only if (saved), so a refused save does not re-seed the open panel's draft. Noted, pre-existing and not moved by this diff: handleViewConfigSave applies setViewDraft(draft) and bumps refreshKey before the persist, so the page shows the refused configuration locally until the panel is discarded; and ReportConfigPanel's Cancel stays live during a save.
  4. The refusal paths — right. Eight sites (persistViewPatch, handleViewConfigSave, handlePinView, handleSetDefaultView, handleReorderViews, ReportView.saveSchema, RuntimeDraftBar.handlePublish, RuntimeDraftBar.handleDiscard) each raise toast.error(…, { description: formatMetadataError(err) }) inside the existing catch or .catch callback, with the prior console.error / console.warn line kept. toast is sonner's, which ObjectView already imported; ReportView and RuntimeDraftBar import the same, so no new notification surface. persistViewPatch's isViewConfigPermissionDeniedError branch is unchanged and returns before the new toast. handleReorderViews awaits one Promise.all, so a refused reorder is one toast. handleSetDefaultView drops the untranslated literal 'Failed to set default view' for t('form.saveError') with the reason.
  5. The two i18n keys — right. console.runtimeDraft.publishFailed and console.runtimeDraft.discardFailed sit under the console namespace in all ten packs (en, zh, ja, ko, de, fr, es, pt, ru, ar) and are read through useRefusalTitles, whose t is useObjectTranslation's. The head's Type Check job runs check:i18n-keys, check:i18n-drift and check:i18n-designer-parity, and is green.
  6. The census (writeRefusalCensus-11583.test.ts) — right in the ruling's terms, with edges it declares in part. The population is derived by ts.createSourceFile over every non-test .ts / .tsx under views/ recursively; a callee matches by name as a bare call, a method call, ?. or a cast; a site is keyed file › symbol › callee; a seam write imported under another name (an ImportSpecifier with a propertyName) and a detached or destructured adapter method are refused outright; each row's calls is compared with the tree; a relays row must hand the promise back uncaught and be a callee the census sees. A new write in any of those spellings goes red by name (the dev's scratch-file ablation shows it). What slips, by reading: an element-access call dataSource['updateView'](…), .call / .apply on the method, a re-export alias from a views barrel (export { persistRuntimeMetadata as x } is an ExportSpecifier, which the detached check does not read), a wrapper that moves the write outside views/, and a direct metadataClient.save(…) that bypasses the seam. A surfaces row is satisfied by any toast.error in any catch of the symbol, and its pin check is a text includes of the symbol's name. The body's acceptance note declares the first of these; the rest are a tightening lead, not a defect against the ruling.

② Semver level

Package set right: .changeset/11583-refused-writes-said.md names @object-ui/app-shell and @object-ui/i18n, the two packages whose published src/ moved; the fixed group carries the rest. Clause-②: yes is right: an exported component's prop type moves. Its reason is not accurate: caller-compatible and every function a host could pass before still type-checks fail for the hosts in ①.1. patch is right for the behaviour, and right for the type only once the type admits what void admitted; as written, the type refuses handlers it accepted, which check-changeset-no-major's own header classifies as a break that ships as minor with the break spelled out in the changeset body. origin/main at 94985a9 is five commits past merge-base b65aa5e (the release commit b493919 among them; both packages are 17.7.0 there); none touches this PR's files.

③ Boundary flags

  • Census population wider than the ruling's five names — right: updateViewConfig is triage's own extra site, deleteView is updateView's adapter sibling, and dispatchViewPatches is the in-file relay. A superset makes the ruling's every call … either surfaces a refusal or is ledgered stricter, never looser.
  • Two new keys — right: the draft bar's label table is objectui#10202's claim and covers two languages; the console's ten packs are the only channel for ten-language leads; see ①.5.
  • Narrowed local test run — answered by the head: Test (shard 1/8) through Test (shard 8/8) and the aggregate Test are success on e075976. The dev's gap report (5976952437), that an import-grep union cannot see a readFileSync ratchet, is accurate.
  • Reorder surfacing rather than ledgered — right: the order shows from this browser's copy either way, so a refused sortOrder write is exactly the family's looks-saved shape; ledgering would have needed a reason the card does not have.
  • ReportConfigPanel's read-only inspector, beyond the seat's bullets — right: without it an edit could land in the in-flight window, be absent from the save, and be dropped by onClose() on success; readOnly was already the inspector's prop (readOnly={false} before).
  • ViewConfigPanelProps.onCreate still typed void — harmless, confirmed: handleViewCreate returns Promise(boolean), which a void target accepts; create mode's Save is not gated on dirty, and the panel reads the create's outcome nowhere. If the seat takes remedy (a) below, the same stroke could align onCreate; not required.
  • The ratchet rebinding (view-filter-fold.ratchet.test.ts, commit e075976) — not a weakening. The CI red on e3c7e63 is confirmed from Test (shard 4/8)'s annotations: the EXPLICIT save path is untouched — this is the control failed with expected … to match /handleViewConfigSave[\s\S]{0,600}pers…/. That window matched any mention of the name, a comment included, within 600 characters of any persistRuntimeMetadata('view' in the file; the new control finds the const handleViewConfigSave declaration's initializer with the TypeScript parser and requires, inside it, a bare persistRuntimeMetadata call whose first argument is the literal 'view': tighter binding, no byte distance, and a comment cannot satisfy it. One asymmetry: a namespaced call (seam.persistRuntimeMetadata('view', …)) would go red where the regex went green — a false red, not a false green. The edit outside the claim's file surface was on the seat's direction, and no product code moved in that commit.
  • Escalated to the seat: the remedy for ①.1. Either (a) type onSave so it admits what void admitted and keep patch — unknown is the faithful shape, since the runtime reads only === false; void | boolean | Promise(void | boolean) is the narrower one that still admits the async-void host — with the docblock naming false as the only refusal signal, and the PR body's Clause-② reason and the changeset corrected to say what the type admits; or (b) keep the type, bump @object-ui/app-shell to minor, and spell the break out in the changeset. (a) keeps as before true and is what this review would PASS on a re-render at a new head; (b) is a policy call the seat owns.
  • Noted, not blocking: the perf-budget bot reads the eager closure at 3312.3 KB of 3330.4 KB on this head (PASS, 18 KB of headroom), and the census tightening leads in ①.6.

Implemented-by: claude/issue-11583-refused-writes-said
Reviewed-by: session_01FjqrwXPfSMkSfkKYDSRkN2

VERDICT: FAIL

…s every host the void prop admitted (objectui#11583)

Contract review 5977112283 (item 1.1), remedy (a) per the seat's answer
5977119047: the widened `void | Promise<boolean>` return refused, with TS2322,
an async host returning nothing, a host resolving to a record and a sync
non-void host, all of which the published `void` prop admitted. The prop now
returns `unknown`; the runtime keeps reading one signal, a `false` or a
rejection, and reads anything else as saved. ReportConfigPanel's unpublished
onSave is aligned in the same stroke. A compile-time pin in the app-shell test
project assigns those hosts to the prop. The emitted JS of both panels is
unchanged. The changeset says what the type admits.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3312.3 KB 3330.4 KB
Main entry chunk (gzip) 151.3 KB 350 KB
Entry file index-DpxZFUhJ.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.63KB 64.77KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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.81KB 3.58KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ba2a810bf04a2ead89cfc50e11fe1e94dc9f30aa
Local-runs: probe — one tsc --noEmit over two scratch files outside the repository, reproducing the compile-time pin's type assertions against a stub of each prop shape (unknown at this head, and the superseded union), to confirm that the pin reds on the old union and holds on the head's; the head's green Type Check shows only the holding half

Written 2026-10-04T06:28Z by the reviewing subagent of session_01FjqrwXPfSMkSfkKYDSRkN2, read-only against GitHub and git show at the head. This record re-renders the FAIL of record 5977112283 (head e075976) on the remedy head; that record stands for its head. Inputs: card objectui#11583 (its body and its nine comments through the seat's answer 5977119047 and the dev's remedy report 5977194407), PR objectui#11593 (its body, its 24-file list, its five comments, and the net diff against merge-base b65aa5e), the check-runs on ba2a810, and packages/app-shell/src/index.ts, packages/app-shell/src/views/index.ts, packages/app-shell/package.json and packages/app-shell/tsconfig.test.json at the head.

Check-runs on ba2a810: 43 runs, all completed: 40 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard …/4)), 0 failure, 0 in progress. Among the successes: Test (shard 1/8) through Test (shard 8/8), the aggregate Test, Test (dist pins), Type Check, Lint, Build & E2E, Changeset Declaration, Changeset Bump Policy, Changeset Claim Re-read, Governed Surface Queue Guard, Line Citation Gate. First read 2026-10-04T06:24:33Z; re-read 2026-10-04T06:27:00Z with the same counts, the PR still at this head.

The delta since e075976 (commit ba2a810, four files, +109 / −15): ViewConfigPanel.tsx and ReportConfigPanel.tsx change one interface member's type and its docblock each, and no statement; ViewConfigPanel.onSaveHosts-11583.test.ts is new; the changeset's onSave sentence is rewritten. The 20 other files of the PR are byte-equal to the ones record 5977112283 judged, so ①.3 to ①.6 and the earlier ③ flags are restated below as they stood, re-read at this head.

① Derived judgments

  1. ViewConfigPanelProps.onSave, now (draft: Record(string, any)) returning unknown — the one published surface this diff moves, and RIGHT. ViewConfigPanel is still exported from packages/app-shell/src/index.ts through views/index.ts. The record's remedy (a) is taken as specified: the probe named in Local-runs assigns the three hosts the old union refused (an async host returning nothing, a host resolving to a record, a sync host returning a number) and the two it admitted (a void host, a false-resolving host) to the new type with zero errors, and the pin's ReturnType equation reads unknown, not any. Every function the void prop admitted is admitted again; the one read-side residual is that a consumer storing the call's result in a void-annotated binding would see unknown, which no realistic host does. The runtime is unchanged, landed = (await onSave?.(flat)) !== false, so a host returning nothing is read as saved (pinned in ViewConfigPanel.saveOutcome-11583.test.tsx), and false, returned or resolved, or a rejection is the only refusal signal — which the docblock now says in those words. Clause-②: yes is right (an exported component's prop type moves, void to unknown), and its reason is now accurate: it states what the type admits, names the earlier union's defect, and cites the review and the seat's answer.
  2. ReportConfigPanelProps.onSave, aligned to unknown — unpublished, confirmed again. ReportConfigPanel appears in neither barrel and the package's exports map still has . and ./styles.css only; none of those three files moved since e075976. Its one host, ReportView.handleReportConfigSave, returns Promise(boolean). Adds nothing to Clause-②. Right.
  3. The in-flight states — right; no stranding path. ViewConfigPanel: Save is disabled={saving || (mode === 'edit' && !isDirty)}; handleSave captures savedDraft = draftRef.current before awaiting and clears isDirty only if (draftRef.current === savedDraft), so an edit during the flight keeps the panel dirty; it never calls onClose. The draft re-seed keys on stableKey (activeView.id), so handleViewConfigSave's setViewDraft(draft) at the start of a save does not clear dirty under it. ReportConfigPanel: if (!landed) return; precedes setDirty(false) and onClose(); readOnly={saving} and disabled={saving} hold in flight. ReportView.reportConfig is memoised on configVersion, bumped only if (saved), so a refused save does not re-seed the open panel's draft. Pre-existing, not moved: the page applies the draft locally before the persist, and the report panel's Cancel stays live during a save.
  4. The refusal paths — right. Eight sites (persistViewPatch, handleViewConfigSave, handlePinView, handleSetDefaultView, handleReorderViews, ReportView.saveSchema, RuntimeDraftBar.handlePublish, RuntimeDraftBar.handleDiscard) each raise toast.error(…, { description: formatMetadataError(err) }) inside the existing catch or .catch callback, the prior console.error / console.warn line kept; toast is sonner's, the console's existing channel; persistViewPatch's isViewConfigPermissionDeniedError branch is unchanged; one Promise.all makes a refused reorder one toast; handleSetDefaultView drops the untranslated literal for t('form.saveError') with the reason. No new notification surface.
  5. The two i18n keys — right. console.runtimeDraft.publishFailed and console.runtimeDraft.discardFailed under console in all ten packs, read through useRefusalTitles; the head's Type Check job runs check:i18n-keys, check:i18n-drift and check:i18n-designer-parity, green.
  6. The census (writeRefusalCensus-11583.test.ts) — right in the ruling's terms. Population derived by ts.createSourceFile over every non-test source under views/, keyed file › symbol › callee; an aliased seam import and a detached or destructured adapter method are refused; row counts are compared with the tree; a relays row must hand the write back uncaught. What slips, as record 5977112283 listed (element-access calls, .call / .apply, an ExportSpecifier re-export alias, a wrapper outside views/, a direct metadataClient.save, and the surfaces row's text-includes pin check), remains a tightening lead the dev has recorded as a carried finding, not a defect against the ruling.

② Semver level

Package set right: the changeset names @object-ui/app-shell and @object-ui/i18n, the two packages whose published src/ moved; the fixed group carries the rest. patch is right: the behaviour is a fix, and the one published type move is a widening that admits everything void admitted, with the refusal signal read by the panel and required of no host — nothing a consumer compiled against the old prop stops compiling. The changeset's onSave sentence now says exactly that. origin/main at 973fc20 is six commits past merge-base b65aa5e; on this PR's paths the only drift is the release commit b493919's version line in packages/app-shell/package.json (17.6.0 to 17.7.0), which this PR does not touch.

③ Boundary flags

  • The compile-time pin (ViewConfigPanel.onSaveHosts-11583.test.ts) — holds the contract, and reds on the old union. It is in the compiled set: packages/app-shell/tsconfig.test.json includes src/**/*.test.ts, and the package's type-check script is tsc --noEmit && tsc -p tsconfig.test.json, which CI's Type Check runs (green here). The probe reproduces its type machinery against a stub of each shape: under unknown, zero errors; under void | Promise(boolean), TS2344 (Type 'false' does not satisfy the constraint 'true') on each ReturnType-equals-unknown equation and TS2322 on each of the three host rows per panel — eight in the probe, which flips both panels; the dev's ablation flipped ViewConfigPanel alone and reports TS2322 ×3 plus TS2344, consistent. The IsAny guards close the one way the rows could pass vacuously. The one it body keeps vitest from refusing a suite-less file and asserts each host is callable; it proves nothing about types and claims nothing. Right.
  • Census population wider than the ruling's five names — right: updateViewConfig is triage's own extra site, deleteView the adapter sibling, dispatchViewPatches the in-file relay; a superset is stricter.
  • Two new keys — right: the draft bar's label table is objectui#10202's and two-language; see ①.5.
  • Narrowed local test run — answered by the head: Test (shard 1/8) through Test (shard 8/8) and the aggregate Test are success on ba2a810; the delta since e075976 moved no statement in any runtime file, so the dev's narrowing by emit identity is consistent with the diff (the emit hash itself is the dev's measurement, not re-run here).
  • Reorder surfacing rather than ledgered — right: a refused sortOrder write with the order showing locally is the family's looks-saved shape.
  • ReportConfigPanel's read-only inspector, beyond the seat's bullets — right: no edit can land in the window the save does not carry and be dropped by onClose() on success.
  • ViewConfigPanelProps.onCreate still void — harmless, as before: handleViewCreate returns Promise(boolean), which a void target admits; create mode's Save is not gated on dirty. Left as the seat allowed.
  • The ratchet rebinding (view-filter-fold.ratchet.test.ts, commit e075976) — unchanged since the previous record and not a weakening: the control finds the const handleViewConfigSave declaration's initializer with the TypeScript parser and requires a bare persistRuntimeMetadata call whose first argument is the literal 'view' inside it; a comment cannot satisfy it and a byte distance cannot break it.
  • PR body against the diff — the stored body equals the dev's pr_body_update byte for byte; its Clause-② paragraph, the new The published onSave type bullet, ablation 7 and the Verification at ba2a810 section describe this head's diff accurately, and every row of its site table matches the sources. The perf-budget bot on this head reads the eager closure at 3312.3 KB of 3330.4 KB (PASS).
  • Escalated to the seat: nothing blocking. Carried leads, the seat's call whether to card them: the census tightening in ①.6, and aligning onCreate with onSave's shape.

Implemented-by: claude/issue-11583-refused-writes-said
Reviewed-by: session_01FjqrwXPfSMkSfkKYDSRkN2

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

needs:contract-review removed: an at-tier PASS is on record at this head · domain:ui seat 1 · session_01FjqrwXPfSMkSfkKYDSRkN2 · 2026-10-04T06:29Z.

  • Record: 5977315779 on this PR: ## Contract review, Served-tier: CONTRACT_REVIEW_TIER, Head-sha: ba2a810bf04a2ead89cfc50e11fe1e94dc9f30aa, Local-runs: probe (one tsc --noEmit over scratch files outside the repository, declared), Implemented-by: claude/issue-11583-refused-writes-said / Reviewed-by: session_01FjqrwXPfSMkSfkKYDSRkN2, PASS.
  • Its history:
    • The same reviewer's FAIL 5977112283 on e075976 stands for that head. Its item ①.1: ViewConfigPanelProps.onSave, widened to void | Promise(boolean), refused hosts the published void prop admitted.
    • The seat took remedy (a) in 5977119047 on objectui#11583: the prop returns unknown, the changeset stays patch, and a compile-time pin holds the old hosts.
    • The dev's commit ba2a810 applied it. The PASS re-renders on that head, with the probe confirming the pin turns red on the old union and holds on unknown.
    • Earlier on this PR: a CI repair (e075976, the Console: opening a list filter panel persists a view overlay that overrides the source-defined view filter (empties the list) #4155 ratchet rebound to the handler's own declaration; judged "not a weakening") and the seat's patch round (e3c7e63: ReportConfigPanel and set-as-default).
  • The reviewer's writes: exactly two comments on this PR, the FAIL and the PASS. It made no other GitHub write: no label, no review, no ready flip. The reviewer was held once on the red head e3c7e63, with no write.
  • Pre-landing checks on ba2a810:
    • every check is success or an expected skip (40 success; dependabot, Test (coverage) and its shard matrix skipped);
    • mergeable_state: clean;
    • check-governed-merges --pr objectstack-ai/objectui#11593: NOT governed, with 1,917 changed lines, under the human-merge threshold.
  • The PR body is the dev's pr_body_update from report 5977194407, written by the seat and read back byte-identical (16,310 bytes). The record judged it accurate against the diff.
  • Carried leads, dropped with reasons:
    • The census-tightening spellings (record ①.6): element access, .call / .apply, a barrel re-export alias, a wrapper outside views/, a direct metadataClient.save. The record calls them "a tightening lead, not a defect against the ruling", and none has a measured writer, so there is no reach:.
    • onCreate alignment: harmless, as the record confirms.
  • Landing in this act: ready, then auto-merge, so the PR enters the merge queue. Fixes #11583 closes the family's closing card on merge.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 06:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 06:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit e8c0b96 Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11583-refused-writes-said branch October 4, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants