Skip to content

feat(app-shell): add /:objectName/data parameterized bare data surface (#2251) - #2255

Merged
os-zhuang merged 2 commits into
mainfrom
claude/appnavinspector-nav-spec-ynx07n
Jul 5, 2026
Merged

feat(app-shell): add /:objectName/data parameterized bare data surface (#2251)#2255
os-zhuang merged 2 commits into
mainfrom
claude/appnavinspector-nav-spec-ynx07n

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Implements the objectui half of #2251 — the three-route model, purely additive:

RouteSaved-view tabsSemanticsStatus
/:objectNameWorkspace: default view + view switchinguntouched
/:objectName/view/:viewIdWorkspace, anchored to a named viewuntouched
/:objectName/data ± filter[...]/uf_*Parameterized bare data surface (new)added

What

  • ObjectDataPage (new, modeled on InterfaceListPage / ADR-0047 filters mode rather than carved out of the 1.9k-line ObjectView — zero regression surface on existing routes):
    • URL filter[<field>]=<value> conditions apply over everything row-level security permits — no saved-view filter is baked in; conditions render as visible, removable chips (deliberately unlike Odoo's invisible action domain)
    • no saved-view tab bar; "Save as view" (admin) materializes the current conditions via CreateViewDialog + createRuntimeMetadata and navigates to the new /view/:viewId — the one exit into the workspace
    • no write-back: the surface passes no persistence hooks to ListView
    • full toolbar (search / advanced filters / sort / group / hide fields / density) + auto-derived userFilters bar (ADR-0047 uf_* URL persistence, same wiring as InterfaceListPage)
    • visualization switcher (grid/kanban/calendar/gallery, bindings auto-derived) is ListView-internal, so switching presentation never rewrites the URL — filter state survives (新增 /:objectName/data 参数化裸数据路由:URL 过滤 + 不绑定视图,现有菜单/路由零破坏 #2251 acceptance)
    • read-permission route gate (403 empty state, data-testid="object-data-403"); auto-derived columns, filter-bar fields, and URL predicates are trimmed to readable fields via useFieldPermissions — client-side trims are UX only, the server remains the enforcement point
    • record drawer via ?recordId (shareable, refresh-safe — same convention as ObjectView)
  • Nav contract: NavigationItem.filters?: Record<string, string> (types + zod, mirrors recordId docs); resolveHref serializes object items with filters to /data?filter[...] with {current_user_id}/{current_org_id} template substitution and precedence recordId → filters → viewName. Items without filters produce byte-identical output.
  • Route: :objectName/data registered in AppContent.tsx (lazy, data joins new/view/record as reserved segments).
  • i18n: console.objectData.* (en, zh; other locales fall back to defaultValue).
  • Docs: both app-composition guides (skill + docs site) gain the rule — URL filters for one-off/parameterized slices, named views for curated ones; one-sentence generation rule updated.

Verification

  • pnpm turbo run build for types/layout/app-shell (+ deps): 29/29 pass (tsc typecheck included)
  • Tests: layout/types/i18n 303 passed, app-shell 955 passed, apps/console 80 passed; new resolveHref.test.ts covers 12 cases (bare/viewName/filters/empty-filters/precedence/template substitution/dropped unresolved entries/non-object targets)
  • ESLint on all touched files: 0 errors (the two pre-existing NavigationRendererstatic-components errors reproduce without this diff — verified via stash)
  • Browser smoke (Playwright + bundled Chromium against vite dev): app boots with the new lazy chunk registered, zero page errors. Driving the /data route with real data isn't possible in this environment (console requires an ObjectStack backend + auth — the same constraint that keeps the repo's deeper e2e specs .skipped), so runtime acceptance on seeded data still needs a manual pass.

Out of scope (tracked in #2251)

  • @objectstack/spec sync for filters (framework repo) — local @object-ui/types mirrors it here, flagged for spec alignment before publish
  • Server-side enforcement: dropping predicates on unreadable fields at the API layer
  • Rich operator syntax (filter[field][gte]=...)

Closes nothing on its own; primary implementation PR for #2251.

🤖 Generated with Claude Code

https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK


Generated by Claude Code

#2251)
Add a URL-addressable data surface that is NOT anchored to any saved
view: /apps/:app/:objectName/data renders everything row-level
permissions allow, with URL conditions (filter[<field>]=<value>)
applied on top and rendered as visible, removable chips. The existing
/:objectName (workspace, default view) and /view/:viewId routes are
untouched — this is purely additive.
- ObjectDataPage (new, modeled on InterfaceListPage / ADR-0047 filters
mode): no saved-view tab bar, no write-back to saved views,
auto-derived columns and userFilters bar trimmed by field-level
permissions, uf_* URL persistence, ListView-internal visualization
switcher so switching presentation preserves URL filter state,
read-permission route gate (403 empty state), record drawer via
?recordId, and an admin "Save as view" exit that materializes the
current conditions as a named view and navigates to /view/:id
- NavigationItem.filters (types + zod): object nav items can target the
surface declaratively; resolveHref serializes them to
/data?filter[...] with {current_user_id}/{current_org_id} template
substitution; precedence recordId -> filters -> viewName; covered by
new resolveHref unit tests (12 cases)
- i18n: console.objectData.* keys (en, zh)
- docs: app-composition guide (skills + content/docs) gains the /data
rule — URL filters for one-off/parameterized slices, named views for
curated ones
Client-side field trims are UX only; the server remains the enforcement
point for row-level filters and predicates on unreadable fields (#2251
security model).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
@vercel

vercelBot commented Jul 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJul 5, 2026 6:22am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)59.5 KB350 KB
Entry fileindex-B9qaXs0O.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)7.51KB2.72KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)18.34KB3.80KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)9.55KB3.36KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)26.07KB6.30KB
auth (createAuthenticatedFetch.js)3.93KB1.55KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.29KB0.82KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)437.03KB93.90KB
core (index.js)1.65KB0.59KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)109.66KB27.05KB
fields (index.js)185.40KB45.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)2.68KB0.98KB
layout (index.js)36.92KB10.17KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)5.09KB1.84KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.46KB1.03KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.50KB0.70KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.10KB12.33KB
plugin-charts (index.js)46.18KB13.02KB
plugin-chatbot (index.js)173.06KB41.28KB
plugin-dashboard (index.js)108.28KB26.86KB
plugin-designer (index.js)213.56KB42.96KB
plugin-detail (index.js)203.15KB48.93KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)102.41KB24.89KB
plugin-gantt (index.js)136.67KB33.88KB
plugin-grid (index.js)165.48KB43.77KB
plugin-kanban (index.js)48.15KB12.94KB
plugin-list (index.js)97.89KB23.09KB
plugin-map (index.js)16.78KB5.23KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)37.83KB9.97KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.21KB2.76KB
plugin-view (index.js)85.46KB20.80KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)3.19KB1.38KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.23KB5.97KB
react (index.js)0.76KB0.42KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)2.16KB0.94KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

- docs/adr/0055-parameterized-bare-data-surface.md: decision record for
the third list context (amends ADR-0053's two-mode table) — surface
contract, security model, rejected alternatives, follow-ups
- ADR-0053: add 'Amended by ADR-0055' header note
- content/docs/guide/console-architecture.md: add the /data row to the
console route table
- app-composition guide: link the ADR from the Related section
- ObjectDataPage / AppContent comments now cite ADR-0055 (stable ref)
instead of only the issue number
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Jul 5, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)59.5 KB350 KB
Entry fileindex-B9qaXs0O.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)7.51KB2.72KB
app-shell (runtime-config.js)4.72KB1.69KB
app-shell (types.js)0.01KB0.04KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)18.34KB3.80KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)4.79KB1.88KB
auth (LoginForm.js)9.55KB3.36KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)26.07KB6.30KB
auth (createAuthenticatedFetch.js)3.93KB1.55KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.29KB0.82KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)437.03KB93.90KB
core (index.js)1.65KB0.59KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)109.66KB27.05KB
fields (index.js)185.40KB45.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)2.68KB0.98KB
layout (index.js)36.92KB10.17KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)5.09KB1.84KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.46KB1.03KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.50KB0.70KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.10KB12.33KB
plugin-charts (index.js)46.18KB13.02KB
plugin-chatbot (index.js)173.06KB41.28KB
plugin-dashboard (index.js)108.28KB26.86KB
plugin-designer (index.js)213.56KB42.96KB
plugin-detail (index.js)203.15KB48.93KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)102.41KB24.89KB
plugin-gantt (index.js)136.67KB33.88KB
plugin-grid (index.js)165.48KB43.77KB
plugin-kanban (index.js)48.15KB12.94KB
plugin-list (index.js)97.89KB23.09KB
plugin-map (index.js)16.78KB5.23KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)37.83KB9.97KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.21KB2.76KB
plugin-view (index.js)85.46KB20.80KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)3.19KB1.38KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.23KB5.97KB
react (index.js)0.76KB0.42KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)2.16KB0.94KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-zhuang
os-zhuang marked this pull request as ready for review July 5, 2026 06:34
@os-zhuang
os-zhuang merged commit b8f158c into mainJul 5, 2026
10 checks passed
@os-zhuang
os-zhuang deleted the claude/appnavinspector-nav-spec-ynx07n branch July 5, 2026 06:34
os-zhuang added a commit that referenced this pull request Jul 5, 2026
…-up) (#2265)
Positive-path eval for the /data bare surface: dashboard drill-through
and an 'assigned to me' shareable link must be expressed as object nav
items with filters ({current_user_id} templating) rather than authored
views or pages. Completes the discovery chain shipped in #2255 /
framework#2626 (spec describe -> skill rules -> prompt one-liner ->
eval regression guard).
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 5, 2026
…deep-links (#2272) and AppNavInspector four-landing-mode rework (#2245) (#2273)
* feat(layout): resolveActiveNavItem — single-winner inverse of resolveHref (#2272)
The whole nav tree now elects exactly ONE active item per location:
resolveActiveNavItem parses the URL into a typed target and ranks
matches by specificity (record deep-link > filters slice > named view >
exact > object sub-route weak claim > boundary prefix), ties breaking
to tree order. NavigationRenderer computes the winner once and provides
it via context; per-item active state and group auto-expansion are id
comparisons. The old per-item computeIsActive prefix heuristics (and
its accreted special cases) are deleted.
Fixes the #2255 gap where a filters item never highlighted (its href
carries ?filter[...] which pathname matching can't see) while the bare
object sibling wrongly claimed /data. Behavior improvements: bare
object items weak-claim /view/* and /data when no more-specific sibling
is registered (orientation is kept instead of nothing highlighting),
and view matching tolerates qualified <object>.<key> ids.
resolveHref <-> resolveActiveNavItem are round-trip tested for every
leaf shape (11 new cases).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
* feat(app-shell): nav-id designer deep-links + edit-navigation affordance (#2272)
The designer's selected menu is now URL-addressable by the nav item's
spec-required snake_case id (?sel=nav:<id>) instead of being an
in-memory positional state:
- nav-selection.ts: id <-> positional-path translation at the designer
boundary (positions never leave component state; ids survive reorder)
- ResourceEditPage (metadata-admin app editor): applies ?sel=nav:<id>
on load (implies edit mode), mirrors nav selections back to the URL
(replace, ADR-0047 convention) so the selection is shareable and
reload-safe
- StudioDesignSurface (Interfaces pillar): same wiring for navSel
- AppSidebar: new 'Edit Navigation' app-switcher item that resolves the
CURRENTLY ACTIVE menu via resolveActiveNavItem (the resolveHref
inverse) and deep-links to /metadata/app/:name?sel=nav:<id> — runtime
-> designer jumps land on the menu you were looking at
- i18n: layout.appSwitcher.editNavigation (en, zh)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
* feat(app-shell): AppNavInspector type + four-landing-mode target editing (#2245)
Replace the off-spec path/kind free-text editing (runtime never read
either key) with contract-first target editing:
- type selector over the spec union (object/page/dashboard/report/url/
group); page/dashboard/report targets are metadata-backed comboboxes
(client.list), url is free text + target select
- object items expose the FOUR landing modes matching resolveHref
precedence (ADR-0055): default view / named view (picker scoped to
the object's <object>.<key> views) / record deep-link (template-var
hint + view|edit mode) / filters slice (key-value editor with
field picker per field-level readability)
- the mode is DERIVED from field presence, never persisted; switching
type or mode clears the other targets plus every legacy key
(path/kind/href/route/object/page/...), so editing a legacy item IS
its migration — a stale recordId can no longer hijack precedence
- every write ensures a snake_case id (ensureNavId, uniqued against
siblings) and an explicit type
- live 'Resolved link' preview renders the REAL runtime landing via
resolveHref with template vars kept visible — declared = enforced,
observable while editing
- pure logic extracted to nav-target.ts with 12 unit tests
- AppNavCanvas.addItem now creates {id, type:'object', label} instead
of the save-invalid {label, path:''} placeholder; AppPreview's empty
fallback root key is 'navigation' (the only spec'd write target),
not 'nav'
- i18n: engine.inspector.appNav.* reworked (en, zh); dead path/kind
keys removed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018m3GX7EMKNPDZuee152EKK
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationpackage: typestests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude