Repository navigation
feat(layout,app-shell): a nav entry reads its declared API dataSource, so its visible sees the selected value as data - #13095
Merged
Conversation
… visible sees the selected value as data The item guard hands the visible evaluator the entry it judges; the layout's default evaluator hides an entry that declares an API binding. The shell's one navigation visible evaluator (useNavVisibility) reads each binding through readElementApi and binds the selected value as data; the sidebar (through useNavMemberGates), nav:menu and the command palette all take it. Pending, failed or refused reads hide the entry. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
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
|
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 #13029
Clause-②: yes
What changes
A navigation entry may declare the spec's API
dataSource(BaseNavItemSchema.dataSource, objectstack-ai/objectstack#24427):{ provider: 'api', read: { url }, path? }. The entry'svisiblenow reads the value the binding selects, asdata(for exampledata.total == 1). The sidebar, thenav:menublock and the command palette all read it.@object-ui/layout(NavigationRenderer.tsx):VisibilityEvaluatortakes an optional second argument,entry, the entry whosevisibleit judges.passesNavItemGuardspasses it on every path it serves: the rows,hasVisibleNavigationItems(derived area visibility) and the Favorites walk. The layout's default evaluator reads no binding, so it hides an entry that declares one (fail closed). An entry without the key renders exactly as before.@object-ui/app-shell(hooks/useNavMemberGates.ts): one decision,navEntryVisible, and one hook,useNavVisibility(app), which every surface builds its evaluator from:useNavMemberGates(the expression context's app).UnifiedSidebaris unchanged: it already handsgates.evaluateVisibilitytoNavigationRenderer;nav:menu(views/nav-menu-renderer.tsx) uses it for the active app, and itsrenderItempasses the entry;chrome/CommandPalette.tsx) uses it for the active app, and its filter passes the entry.useNavVisibilityreads each distinct binding in the app'snavigationandareas[].navigation, at any group depth. It uses the page element's reader module in@object-ui/react:judgeElementApiDataSource(the spec's schema judges the binding),readElementApi(the same GET with the host'sapiFetch, which shares an in-flight request per URL) andselectElementApiPath. There is no second fetch implementation. The reads are keyed on the bindings' content, never on a tree's identity (AGENTS.md [WIP] Enhance every detail of the designer #10).@object-ui/types: thedataSourcedescribe (zod/app.zod.ts) and doc (app.ts) now say what the console does. The accept set is unchanged.How the binding is honoured
visibleis evaluated byevaluateNavigationVisibilityoverevaluator.withContext({ data }).datais the value atpath, or the whole answer when there is nopath.pathselects nothing, and when the spec refuses the binding.evaluateNavigationVisibilityas before, with no request and no wait on first paint.visiblereadsdata. The label and the target do not, and nothing is published tochildren.Published faces
VisibilityEvaluator(@object-ui/layout) gains an optional parameter. An existing evaluator that ignores it still type-checks and behaves as before, except for an entry that declaresdataSource. No other published export was added.NavigationVisibilityOptionsandNavigationRendererPropsare unchanged.navEntryVisible,useNavVisibilityandNavEntryDataReadare exported fromuseNavMemberGates.ts, an internal module that the package entry does not re-export..changeset/13029-nav-data-source.md: layoutminor, app-shellminor, typespatch.Tests
packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsxmounts each surface (describe.each) over the realExpressionProvider, the realMePermissionsProviderand a stubbedapiFetchonSchemaRendererContext:useNavMemberGates()to the realNavigationRenderer, asUnifiedSidebardoes;nav:menu: throughSchemaRenderer;CommandPaletteProviderplus?palette=1.Pins:
visible: 'data.total == 1'shows the entry fortotal: 1and hides it fortotal: 2.dataSourcedraws on first paint and no request is sent. Avisible: '2 > 1'on a bound entry still evaluates once the read answers.pathis honoured: the answer{ total: 2, summary: { total: 1 } }withpath: 'summary'shows the entry. Withoutpathit hides it.packages/layout/src/__tests__/NavigationRenderer.entryDataSource-13029.test.tsx: the guard passes the entry to a host evaluator, on rows and onhasVisibleNavigationItems. The default evaluator hides a bound entry and draws a plain one.Ablations, each run through
ablation-replace.mjs(anchor must hit; restore proved by blob hash693474f2afd7equal to HEAD and an emptygit diff HEAD). Both packages resolve through the root vitest alias tosrc, so nodistwas involved:evaluator.withContext({ data: read.data })replaced byevaluator): 6 failed out of 21. That is (a) "shown when total 1" and (d) "value at path" on all three surfaces. The first attempt was a no-op: the replacement text was already present 12 times, so the tool refused and ran nothing. It was rerun with a marked replacement.Local verification (head
ef2abeca8a)pnpm --filter '@object-ui/app-shell^...' buildpassed:VERDICT command-exit 0.type-checkpassed for@object-ui/types,@object-ui/layoutand@object-ui/app-shell:VERDICT command-exit 0. Both new test files are inside thetsconfig.test.jsonincludes.VERDICT command-exit 0. The family is every test that namesnav-menu-renderer,CommandPalette,useNavMemberGates,NavigationRenderer,evaluateNavigationVisibility,NavigationItemSchemaor the typesapp.zod. The full directories of the touched packages are left to CI.6058bf8b71) and at the head.check:phantom-deps,check:self-import,check:unreferenced-sources,check:doc-example-readers;check:handler-key-reads,check:side-effects-array,check:element-data-source-declaration,check:spec-symbols;check:action-forward-parity,check:designer-field-key-parity,check:icon-record-names;check:i18n-keys,check:i18n-drift,check:i18n-designer-parity;check:control-bytes,check:new-line-citations;check-hand-rolled-comment-mask.mjs,check-changeset-fixed.mjs,check-changeset-presence.mjs.Objectstack follow-up (not done here)
One
domain:specchange on objectstack after this lands:[PLANNED — not yet rendered]marker fromBaseNavItemSchema.dataSource's describe inpackages/spec/src/ui/app.zod.ts;packages/spec/liveness/app.json→navigation.children.dataSourcefromplannedtolive, and drop its author warning.Acceptance notes
@object-ui/layoutoutside the console (AppSchemaRendererwithout an evaluator, a third-party host) does not read bindings. With the default evaluator it hides a bound entry. A host evaluator that ignoresentryevaluatesvisiblewithoutdata, as before.AppRootLanding, throughnavEntriesShownToMember) treats a pending bound entry as hidden. If every entry of an app is bound, the no-access state can show until the reads answer. This is an observation, not measured in a browser.readElementApi), and no answer is cached, as for a page element.#13076(thenavItemVisibleDataNeedsDataSourcechain) is untouched and remains open.The session that wrote this PR is
https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB.Generated by Claude Code