Skip to content

fix(app-shell,data-objectstack): system view override rows no longer masquerade as saved views - #4713

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4227-view-override-masquerade
Aug 15, 2026
Merged

fix(app-shell,data-objectstack): system view override rows no longer masquerade as saved views#4713
yinlianghui merged 2 commits into
mainfrom
claude/issue-4227-view-override-masquerade

Conversation

@yinlianghui

@yinlianghui yinlianghui commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4227

What was happening

Toggling any per-view personalization on a code-defined (system) view — density, sort, hidden columns, column widths, inline edit — persists a row under the same type='view' metadata namespace a genuinely saved view lives in, keyed by the same id (ObjectStackAdapter.updateViewConfig). listViews() returned that row indistinguishably from a real saved view, so ObjectView's savedViews.find(sv => viewRowId(sv) === view.id) matched it, isSystem = !saved flipped to false, and the tab gained Rename / Delete / Set-default / Pin / Edit-view-config against a view that lives in code — handleDeleteView would call dataSource.deleteView on it.

PR #4224 (#4211) pinned "a genuine system view stays readonly" as a control, but its fixture's savedViews was always [] — it never exercised an override row, so it passed straight through this bug.

The fix (per the 2026-08-11 maintainer ruling, paired with objectstack#7494)

Two layers, both needed:

  1. Write sideupdateViewConfig (the only production writer of personalization overlays — ObjectView's toolbar-driven toggle is its one caller) now stamps an explicit _isOverride: true discriminant on every row it saves, last in the spread so nothing in the merged config can shadow it.
  2. Read sidelistViews() excludes any row carrying that marker, and additionally excludes a best-effort legacy shape for rows written before this fix shipped: a flat body (no nested config) carrying a viewKind. Measured against the real write paths: a genuine saved view is always created with a nested config wrapper (createView / the ADR-0034 viewEnvelope seam), with viewKind living outside config; a personalization overlay is always flat. On a flat row, viewKind is therefore never something objectui itself authored — it can only be the platform's server-side identity inheritance (viewIdentityPatch) firing because the write's name resolved against a registry-backed (system) view, which a runtime-created saved view has no registry entry to trigger. So "flat body + a viewKind" reliably signals "override on a system view"; a flat row with no viewKind is left alone (this is the existing "legacy bare spec" pin's territory and stays a saved view).

This is a best-effort net for the realistic current-state case, not a guarantee for every conceivable legacy row — a pre-marker override that also copies the system view's full body (persistViewPatch spreads the whole active tab into the write) is structurally indistinguishable from an untouched saved view without the viewKind signal, and isn't caught. Those rows self-heal on their next write, which now carries the marker.

Because isSystem/readonly derive from savedViews.find(...), which is populated straight from listViews(), excluding the override row there is what makes the existing isSystem = !saved gate (and the same-predicate guard on all five mutating handlers) correct again — no change to that predicate itself was needed.

listViewOverrides() — the reader ObjectView uses to merge these settings back into the live view for display — is unchanged; it's a different, legitimate consumer of the same rows.

Follow-up fix (same PR, post-review — PM review found an unmeasured truth-table row)

persistViewPatch (ObjectView's debounced toolbar-toggle writer, the ONE call site of updateViewConfig) has no gate on which kind of tab is active — it fires identically for a system view AND an already-saved view. A toggle on a saved view's own toolbar therefore writes to that view's own (type='view', name=viewId) row (the same key its create path used), and the first commit's unconditional marker stamp flagged that row as an overlay too — listViews() then excluded it on the very next read: the user's own saved view vanished from the switcher the moment they adjusted its density.

Fix: updateViewConfig gains an optional opts.isSavedView (added to the DataSource interface in @object-ui/types too, for other implementations of the contract); when true the marker is withheld. persistViewPatch passes it from the exact same isSavedViewId classification the switcher's readonly gate and its five mutating handlers already use — not a new shape-inference, reusing the existing authoritative signal. (The five mutating handlers — rename/delete/pin/set-default/config-save — were already unaffected: they all route through dataSource.updateView, the pre-existing non-destructive read-merge-write, never through updateViewConfig.)

Implementation note: persistViewPatch is defined before the savedViews state exists further down the component, so referencing it directly in the useCallback dependency array would read it in its temporal dead zone. Mirrored into a savedViewsRef instead (same pattern as the file's existing identityPolicyRef), read only from inside the callback body.

New regression coverage in viewOverlayMarker.test.ts pins both directions against the real production shape (viewEnvelope's nested-config envelope, not the adapter's own flat createView, which the console's UI does not actually call): the fixed sequence (create → toggle on that same saved view → listViews() still returns it, fully manageable) and, side by side, the same sequence with the flag omitted (demonstrating the view disappearing), so the assertion is not vacuous.

Scope boundaries (per the paired ruling)

  • The write-side permission gate on this endpoint is a platform-side card under objectstack#7494 — not addressed here.
  • Per-user scope is a parked v18 platform direction; no client-side workaround was built for it. The overlay stays what it factually is today: org-wide shared view settings.

Prose sweep

Console/plugin prose describing this overlay as a "personal preference" was corrected to say it's org-wide shared:

  • packages/app-shell/src/views/ObjectView.tsxbuildViewTabs's override-wins-per-key comment, the density/sort/etc. hydration comment, and (added for consistency with those two) the viewOverrides state comment a few hundred lines earlier, which still said "toggle preferences".

Swept but found unrelated (left untouched, named here so the search doesn't need repeating): packages/react/src/hooks/useViewSharing.ts and packages/plugin-view/src/ViewTabBar.tsx's private/team/organization/public ViewVisibility — a distinct, legitimate feature (who can see a saved view), not the personalization-overlay contract this issue is about. packages/plugin-view/src/ManageViewsDialog.tsx's "per-user personalisation (objectui#1520)" note references a different, unrelated future-work issue. Checked all ten locale packs (packages/i18n/src/locales/*.ts) around systemViewReadonly and the density/sort/column-visibility toolbar strings — no "personal"/individual-scope framing found there.

Tests

  • packages/data-objectstack/src/listViews.test.ts — 5 cases: excludes a marked row, excludes a legacy unmarked row (flat + viewKind), excludes a minimal marked row with no legacy signal, does not exclude a genuine nested-config ViewItem record (even system-view-named), does not exclude a flat legacy saved view with no viewKind at all (pins the existing "legacy bare spec" boundary).
  • packages/data-objectstack/src/viewOverlayMarker.test.ts — write-side: every updateViewConfig save carries the marker by default; a caller-supplied _isOverride: false cannot un-mark the row; createView does not stamp it; opts.isSavedView: true withholds it. End-to-end round trips against a fake sys_metadata store: (a) a toolbar toggle on a system view is excluded from listViews() but still present in listViewOverrides(); (b) a genuinely created view survives the same round trip fully manageable; (c) new — create → toggle on that SAME saved view (viewEnvelope-shaped, nested config) → listViews() still returns it; (d) new, side by side — the same sequence with the flag omitted shows the view vanishing, pinning the regression this follow-up closes.
  • packages/app-shell/src/views/ObjectView.overrideMasquerade.test.ts — runs the real production pipeline (ObjectStackAdapter.listViews()buildViewTabs / isSavedViewId): a system view with a marked override row, and with a legacy unmarked override row, both stay readonly=true with the mutating-handler guard refusing; a positive control shows a genuinely created saved view (even reusing a system-view-shaped label) stays fully manageable; PR fix(app-shell): a stored id can no longer rename a view tab, so set-default writes again (#4211) #4224's own no-override-row control still holds.

Verification

Local, at 6ac6427ec (this PR's current head):

pnpm --filter '@object-ui/types^...' --filter '@object-ui/data-objectstack^...' --filter '@object-ui/app-shell^...' --workspace-concurrency=2 build   # exit 0
pnpm exec vitest run packages/data-objectstack/src/listViews.test.ts packages/data-objectstack/src/viewOverlayMarker.test.ts \
  packages/data-objectstack/src/onSaveAdvisory.test.ts packages/data-objectstack/src/viewCacheInvalidation.pin.test.ts \
  packages/data-objectstack/src/listViewOverrides.test.ts \
  packages/app-shell/src/views/ObjectView.overrideMasquerade.test.ts \
  packages/app-shell/src/views/ObjectView.setDefaultViewIdentity.test.tsx \
  packages/app-shell/src/views/ObjectView.defaultViewIdentity.test.tsx \
  packages/app-shell/src/views/ObjectView.viewOverrides.test.tsx
  # 9 files, 93 tests passed
pnpm --filter '@object-ui/types' --filter '@object-ui/data-objectstack' --filter '@object-ui/app-shell' type-check   # exit 0
pnpm --filter '@object-ui/types' --filter '@object-ui/data-objectstack' --filter '@object-ui/app-shell' lint         # exit 0, 0 errors
node scripts/check-control-bytes.mjs           # OK
node scripts/check-i18n-call-site-keys.mjs     # OK, every call site resolves
node scripts/check-changeset-presence.mjs      # OK, 1 changeset declared
node scripts/check-changeset-no-major.mjs      # OK

CI is not yet observed to converge — reporting at draft-PR time per this repo's dispatch contract; will be re-checked on any follow-up.


Generated by Claude Code

…masquerade as saved views

A system (code-defined) view's personalization overlay row (density/sort/
hidden columns/column widths/inline-edit, written by `updateViewConfig`)
was returned by `listViews()` indistinguishably from a genuine saved view.
`ObjectView`'s `isSystem = !saved` check then flipped to false, and the
tab gained Rename/Delete/Set-default/Pin against a view that lives in
code (objectui#4227).

Write side: `updateViewConfig` stamps an explicit `_isOverride: true`
discriminant on every row it saves — it has exactly one production
caller, the toolbar-driven toggle, so every row it writes is an overlay.

Read side: `listViews()` excludes any row carrying that marker, and (for
rows persisted before this fix) a best-effort legacy shape — a flat body
with a `viewKind` the platform can only have backfilled from a registry
(code-defined) baseline, which a genuine runtime-created saved view never
has. `listViewOverrides()` is unchanged.

Per objectstack#7494's ruling the overlay this stores is org-wide shared
view settings, not a per-user preference; prose describing it as
"personal" is corrected. Write-side permission gating and true per-user
scope are platform-side (out of scope here, per the paired ruling).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnQd8iMMUwXQEV1crFmQiQ
@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 15, 2026 3:52pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-BK0cuGfN.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
auth (index.js) 2.35KB 1.07KB
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.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 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.65KB 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) 489.91KB 108.67KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 164.73KB 45.23KB
fields (index.js) 230.37KB 57.17KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.86KB 12.91KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 121.90KB 31.76KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.86KB 59.99KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.72KB 27.70KB
plugin-gantt (index.js) 164.30KB 40.02KB
plugin-grid (index.js) 192.10KB 51.24KB
plugin-kanban (index.js) 52.74KB 14.53KB
plugin-list (index.js) 111.14KB 26.92KB
plugin-map (index.js) 18.16KB 5.81KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.64KB 9.44KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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

… view's own row as an overlay

PM review on PR #4713 identified an unmeasured truth-table row: `updateViewConfig`
has exactly one call site (`ObjectView`'s toolbar-driven toggle), but that site
fires for a toggle on EITHER a system view OR an already-saved view, and stamped
the `_isOverride` marker unconditionally. A toggle on a saved view's own toolbar
writes to that view's own `(type='view', name=viewId)` row -- the same key its
create path used -- so the unconditional marker flagged the user's own saved
view as an overlay, and `listViews()` then excluded it on the very next read:
the user's own view vanished from the switcher the moment they adjusted its
density.

`updateViewConfig` gains an optional `opts.isSavedView` (also added to the
`DataSource` interface in @object-ui/types); the marker is withheld when true.
`ObjectView`'s `persistViewPatch` passes it from the same `isSavedViewId`
classification its readonly gate and five mutating handlers already use --
mirrored into a ref (`savedViewsRef`) since the callback's `useCallback` deps
are evaluated before the `savedViews` state exists further down the component,
same pattern as the existing `identityPolicyRef`.

New regression coverage in viewOverlayMarker.test.ts pins both directions: the
fixed sequence (createView -> toggle on that same saved view -> listViews()
still returns it) and, side by side, the same sequence without the flag
(demonstrating the view disappearing) so the assertion is not vacuous.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-DHkQpXo4.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
auth (index.js) 2.35KB 1.07KB
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.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 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.65KB 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) 489.91KB 108.67KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 156.79KB 43.21KB
fields (index.js) 230.37KB 57.17KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.86KB 12.91KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 121.90KB 31.76KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.86KB 59.99KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.72KB 27.70KB
plugin-gantt (index.js) 164.30KB 40.02KB
plugin-grid (index.js) 192.10KB 51.24KB
plugin-kanban (index.js) 52.74KB 14.53KB
plugin-list (index.js) 111.14KB 26.92KB
plugin-map (index.js) 18.16KB 5.81KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.64KB 9.44KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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

Copy link
Copy Markdown
Collaborator Author

Review verdict (patch round): ACCEPT (reviewer of record, PM session session_01RnQd8iMMUwXQEV1crFmQiQ).

The review's danger sequence was confirmed real with line-level evidence and fixed the right way: the caller's existing isSavedViewId classification is threaded through updateViewConfig(..., {isSavedView}) — reusing the one signal that already gates the mutating handlers instead of inventing a second shape-inference — so a saved view's own row is never marked as an overlay and survives createView → toolbar toggle → listViews(). Both directions are pinned: the fixed sequence keeps the saved view visible, and the same sequence without the flag demonstrably reproduces the regression, so the new test cannot pass vacuously. Verified against the branch, not the report: the changeset frontmatter now covers all three touched published packages (app-shell, data-objectstack, types), the diff is the original six files plus packages/types/src/data.ts (the optional-param interface addition — non-breaking by construction), and the branch head is 6ac6427ec as reported.

Will flip ready + enable auto-merge once Lint, Type Check, and Test shards 1–4 conclude success on this head.


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 15, 2026 16:16
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 15, 2026
Merged via the queue into main with commit a0b9e91 Aug 15, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4227-view-override-masquerade branch August 15, 2026 16:17
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.

Personalising a system view makes it look user-created — its override row comes back from listViews() as a saved view

2 participants