Repository navigation
fix(app-shell): Studio's Interfaces rail keys a nav entry by its id, so slices and named views of one object preview and deep-link as themselves (objectui#11774) - #11833
Merged
objectstack-fleet[bot] merged 4 commits intoOct 7, 2026
Conversation
…d and previews its slice or view
The Interfaces rail identified a nav entry by {type,name} alone, so entries
that open one object with different `filters` or `viewName` were one surface:
all highlighted at once, one unfiltered preview, and a reload landing on the
first. The resolved Surface now carries the entry's `id`, `filters` and
`viewName`; the rail and the deep-link restore compare by id when both sides
carry one; `?surface=` gains a `nav` key beside it; and the default object
canvas reads the entry the way the runtime's nav does (resolveHref, then the
landing route's own reading).
Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
… and its preview One object, five entries (the showcase shape): each click makes exactly that row active and hands the default canvas that entry's slice or view; the `?surface=` + `nav` link round-trips; a link with no entry id, or a stale one, resolves as a `<type>:<name>` link did; distinct-object apps are the control. Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z Co-authored-by: Claude <noreply@anthropic.com>
…ues, dependency-complete The memo read `entry.filters` while keyed on its serialisation, which needed an inline lint suppression the lint script does not honour (`--no-inline-config`). It now reads the filters back from the key it is keyed on. Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-11774-nav-surface-identity
branch
October 7, 2026 21:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11774
Clause-②: no
Studio's Interfaces rail identified a nav entry by its target,
{type, name}, alone. An app whose nav holds several entries on one object (the plain list, data slices viafilters, a named view viaviewName) therefore had one surface for all of them: clicking Urgent Tasks highlighted all fiveshowcase_taskrows, previewed every task unfiltered, and a reload landed on Tasks. The surface is now the nav entry.What changed
All inside
packages/app-shell/src/views/studio-design/, the claim's file surface. No package-entry export, no@object-ui/typesmember and no locale key is added.navSurface.ts:resolveSurfacenow carries the entry'sidasnavIdon every variant, plus anobjectentry'sfilters/viewNameas authored. The binding itself is unchanged: all five showcase entries still bindobject:showcase_task.isSameSurfaceis the one identity rule. It compares by nav id when both sides carry one, else by{type, name}.findSurfaceInTreeopens the entry whose id a deep link names when that entry still exists. Otherwise it takes the first{type, name}match in tree order, as before.StudioDesignSurface.tsx(Interfaces pillar only):NavTree'sisActivereadsisSameSurface. The canvas hands the open entry to the studio canvas throughStudioCanvasNavEntryContext, which sits besideStudioCanvasPreviewPropsand not in them, because those props are on the package entry.useSurfaceDeepLink.ts: the entry id travels in a key of its own,nav, beside?surface=, for example?surface=object:showcase_task&nav=nav_slice_urgent. It is a separate key and not anav:IDvalue of?surface=, because every?surface=reader parsesTYPE:NAME: each pillar's restore, the app-to-Studio bridge inutils/appRoute.ts, and the copilot context inStudioAiCopilot. A second value grammar would hand each of them anav"type". All of those readers stay unedited. A link withoutnavresolves exactly as before. The mirror clearsnavwhen the open surface has no entry id.studio-canvas-preview.tsx: the default object canvas does not interpretfilters/viewNameitself.navEntryListTargetasksresolveHref(@object-ui/layout, the shell's nav-to-URL source) where the running app takes the entry, then reads that landing the way its route does:/datalanding throughparseUrlFilterTriples, the parserObjectDataPageuses;/view/VIEWlanding matched withresolveViewIdagainst the object's mergedlistViews, the matcherObjectViewanduseNavTargetLabeluse.A plain entry renders the schema it rendered before.
Changeset:
.changeset/11774-nav-surface-identity.md,@object-ui/app-shellpatch.Evidence (local head
1a301c4)H1, measured on the base (a scratch run of
resolveSurface, not committed): the five showcase entriesnav_tasks,nav_slice_in_progress,nav_slice_urgent,nav_slice_reviewandnav_report_tabulareach returned exactly{type: object, name: showcase_task, label}. No id, nofilters, noviewName.findSurfaceInTree(object:showcase_task)returned Tasks.Real browser (Chromium, through a dev-only harness that was never committed: the real
InterfacesPillarover an in-memory metadata backend and data source, the showcase nav abridged to itsshowcase_taskentries):d53fd02)?surface=object:showcase_task[[priority, =, urgent]]; 3 rows, all Urgent; URL?surface=object:showcase_task&nav=nav_slice_urgenttabularview's columnsPins:
StudioDesignSurface.navEntryIdentity-11774.test.tsxmounts the pillar with the default canvas and a recorder forobject-view. It pins five things: each click makes exactly that row active and hands the canvas that entry's slice or view; the deep link round-trips (Urgent Tasks, read back from the URL, remounts active);?surface=object:showcase_taskwith no id opens Tasks; a stale id falls back as the same link without it does; distinct-object apps are the control.navSurface.test.ts,useSurfaceDeepLink.test.tsandstudio-canvas-preview.test.tsxpin the pure halves.Reverse check: the four source files were checked out from
d53fd02with the pins kept, restored fromHEADafter the run, andgit diff HEADwas empty afterwards. The pillar pin file went4 failed | 2 passed. The first pin failed in the predicted direction,["Tasks"]expected and the five labels received. The round-trip failed withnavexpected asnav_slice_urgentand received asnull. The control pin and the fixture-parse pin passed on the base as on the fix.Gates, run on
1a301c4unless noted, exit code then verdict:vitest runoverpackages/app-shell/src/views/studio-design/plusapps/console/src/components/StudioRoute*.test.tsx: 0,Test Files 97 passed (97),Tests 604 passed (604).pnpm --filter @object-ui/app-shell type-check: 0. Bothtsclegs ran, and--listFilesOnlyshows all four touched test files in the test project.pnpm --filter '@object-ui/app-shell^...' build: 0. Run once, before the first commit. The closure excludes app-shell, the only package this diff touches.check:control-bytes,check:new-line-citations,check:changeset-claims,check:pending-changeset-literals: all 0.vi.mocktest):check:phantom-deps,check:esm-specifiers,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check-changeset-presence,check-changeset-no-major,check-governed-queue-guard --test(NOT GOVERNED): all 0.Lint: the repo-wide
pnpm lintwas not run here; CI owns it. A targetedeslint --no-inline-configover the eight changed TS files was compared per file againstd53fd02. The only new findings are 2react-refresh/only-export-componentswarnings instudio-canvas-preview.tsx, which already carried 3 of that rule. The 1 error the run reports isreact-hooks/static-componentsin the Data pillar, in the same count on the base.Acceptance notes
registerStudioCanvasPreviewdoes not receive the entry. Carrying it there means a new member on the publishedStudioCanvasPreviewProps, which is a Clause-② question and is left out of this patch.object-viewrenderer throughlistViews, the route the Studio's view preview already takes (the named-view member coverage of that route is the objectui#10885 family).ObjectDataPage's field-level trimming and its removable filter chips are not reproduced.sys_viewrow is not in the metadata cache'slistViews, so such an entry previews the plain list.recordIdentry previews the list, as before.idis required on every nav item (SnakeCaseIdentifierSchema), so served nav carries one. An id-less entry in an unsaved draft buffer is compared by{type, name}, and the mirror writes nonavfor it. No id is invented. The installed spec states ids are unique but enforces no uniqueness; two entries sharing an id would both highlight. This is not measured through a public door and is noted only.{type, name}reader isleafKeyOf, the identity of the draft buffer, selection stamp and autosave target. It stays on{type, name}on purpose: two entries on one page edit the same page.PackagesPage.tsxandmetadata-admin/i18n.tsbelong to the in-flight objectui#11784. The other pillars'?surface=handlers are unedited. The shared hook now also clears a straynavon their URLs, which is inert because no producer writes one there.Implemented in session
https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z(seatdomain:ui#1).Generated by Claude Code