fix(react): NavigationConfig.mode is optional — the type says what the hook does (#4550) - #4586
Merged
Merged
Conversation
…e hook does (#4550) `@object-ui/react` published a `NavigationConfig` that required `mode`, in front of a `useNavigationOverlay` that has always defaulted it. The alias `Omit`ted `mode` from the spec's authored config and re-added it as `NonNullable< … >`; ~140 lines below, the hook read `navigation?.mode ?? 'page'`. The type was strictly stricter than the implementation it fronts, and 'page' is meaningful behaviour, not a placeholder. The spec never asked for that: `NavigationConfigSchema` declares `mode: NavigationModeSchema.default('page')` (packages/spec/src/ui/view.zod.ts), and a `.default()` lands on the authoring side as `| undefined`. `@object-ui/types` already re-exported the spec's own `NavigationConfig` unchanged — so one monorepo shipped two published types of the same name that disagreed about whether `mode` could be omitted. The alias is now the spec's authored config verbatim, with no divergence of its own. `ListView` carried `schema.navigation as NavigationConfig | undefined` purely to get a valid spec-shaped value past the old declaration; that assertion is deleted rather than replaced. Nothing changes at runtime: `navigation?.mode ?? 'page'` is untouched. The default is now pinned as observable behaviour alongside every explicit mode, the `none` / `preventNavigation` short-circuits, the `onRowClick` priority and the Cmd/Ctrl/middle-click and `new_window` branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 13, 2026 14:21
This was referenced Aug 13, 2026
Merged
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 #4550
The mismatch, measured
@object-ui/reactpublished aNavigationConfigthat requiredmode, in front of auseNavigationOverlaythat has always defaulted it.The type was strictly stricter than the implementation it fronts, and
'page'is meaningful behaviour rather than a placeholder.The spec never asked for that.
packages/spec/src/ui/view.zod.ts:1210:A
.default()lands on the authoring side as| undefined, sonavigation: { view: 'summary_view' }is legal authored metadata that lets the mode default. The spec publishes exactly that as its own type (view.zod.ts:3466,z.input< typeof NavigationConfigSchema >).Corroboration:
@object-ui/types(packages/types/src/index.ts:1087) already re-exports the spec'sNavigationConfigunchanged. So this monorepo shipped two published types of the same name that disagreed about whethermodecould be omitted — and the react one was the odd one out.Writer / reader census (repo-wide)
Writers — 12 sites construct a value passed as
navigation. Only one carried an assertion caused by this defect:plugin-list/src/ListView.tsx:1738schema.navigation as NavigationConfig | undefinedplugin-grid/src/ObjectGrid.tsx:1032,plugin-list/src/ObjectGallery.tsx:236schema.navigationapp-shellInterfaceListPage:240, ObjectDataPage:283, ObjectView:1525ViewNavigationConfig, explicitmode(schema as any).navigationnavigationat all), not this aliasReaders — sites that destructure
.mode:useNavigationOverlayitself —navigation?.mode ?? 'page', already handlesundefined.ObjectGrid.tsx:3301— reads the hook's result (NavigationOverlayState.mode), always defined. Unaffected.navConfig.mode === 'drawer' | ...onany-typed values. Undefined-mode already yieldsfalse(non-overlay), which agrees with the hook's'page'default (also non-overlay).plugin-view/src/ObjectView.tsx:542+— a different alias (ViewNavigationConfigfrom@object-ui/types), guarded byif (navigationConfig).So ruling item 2's "fix any reader that inherits the default" is a measured no-op: no reader of this alias reads
.modeunguarded. Reported as a measurement rather than manufactured into edits.Red-first
A spec-authored config without
mode, assigned to the alias — verbatimtscoutput before the change:9 errors total across the two test files. After the change:
tsc --noEmit && tsc -p tsconfig.test.jsonboth clean.What changed
That, the doc comments, the
ListViewassertion removal, three inverted pins, and a new behaviour suite. The hook body is untouched.Three pins in
offline-nav-performance-spec-parity.test.tsencoded the defect and are inverted:_ModeIsRequiredbecomes_ModeIsOptional,_StillNarrowedbecomes_SpecShapedValueFits, and a new_IsExactlyTheSpecInputpins the collapse. That file's own comment predicted this ("if the spec ever makesmoderequired itself ... this alias should collapse toSpecAuthoredInput< typeof NavigationConfigSchema >") — the collapse arrived from the other direction, since the spec never moved and the alias was wrong all along.One pin needed care rather than inversion:
_ModeIsConfigModecomparedNavigationModetoNavigationConfig['mode'], which now carries| undefined. A bare equality would fail for a reason unrelated to the drift it guards, so it compares againstNonNullable< NavigationConfig['mode'] >— the membership of the seven modes stays pinned, which is what that test is for.Reverse verification — direction stated up front
This is a type-only change, so the honest direction is a tsc red, not a suite red. Both were run:
git checkout --, nevergit stash):tsc -p tsconfig.test.json→ 9 errors (exit 2).viteston the new suite → 16/16 still green, because the runtime is untouched. Reporting a vitest red here would have been a fabrication.?? 'page'to?? 'drawer'): 4 of 16 failed, withexpected 'drawer' to be 'page'andexpected [] to deeply equal [ [ 'r1', 'view' ] ]. The other 12 stayed green — correct, since they pin explicit modes that do not consult the default. Restored and sha256-verified again.Verification
pnpm --filter '@object-ui/react^...' --filter '@object-ui/react' build— green (dependency closure first).pnpm --filter @object-ui/react type-check— green, both passes (tsc --noEmitandtsc -p tsconfig.test.json).pnpm exec vitest run --maxWorkers=2 packages/react— 43 files, 598 tests passed; the new suite is 16 of them, confirmed running in thedomproject.pnpm exec vitest run --maxWorkers=2 packages/plugin-list packages/plugin-grid— 103 files, 1144 tests passed....@object-ui/react= downstream consumers, after a full 43-task build so no stale artifacts): 31 packages green, includingapps/console,app-shell, all nine hook-consuming plugins and three examples.3f5f87cc7in a comparison worktree: NET ZERO — react 350/350, plugin-list 417/417, 0 errors both sides.check:control-bytes,check:phantom-deps,changeset:check,check:spec-symbols,check-changeset-presence— all green.grep -naPcontrol-byte self-scan over all four touched files: clean.Changeset grade:
minorfor@object-ui/reactOptional-izing is looser for writers and narrower for readers, so the grade turns on which role the published surface plays. Measured from the
.d.ts:NavigationConfigoccurs only in input positions —useNavigationOverlay'snavigation?:option andresolveOverlayWidth's parameter — and never in a return type. The package consumes these values and never hands one back, so for consumers the change is purely permissive: everything that compiled still compiles, and spec-shaped configs that needed an assertion now compile without one. That gained input shape is a capability rather than an internal repair, which is more thanpatchdescribes. The reader-side narrowing is real but secondary, and the in-repo census found exactly one type-importer —ListView— which imported it only to write the assertion this PR deletes.The only type-level line in the whole
.d.tsdiff is the alias; everything else is doc comment.Surface
packages/react(hook + 2 test files),packages/plugin-list/src/ListView.tsx(the censused caller), one changeset. Untouched as instructed:SchemaRenderer.tsx/schema-input.ts(landed #4578),apps/console(#4563),types+ObjectGridexportOptions (#4535),SettingsView(#4570), plugin-report / previews (#4575),content/docs/releases/.Generated by Claude Code