Skip to content

finding(react): NavigationConfig re-declares mode as required while useNavigationOverlay itself defaults it — every spec-typed caller has to assert past the alias #4550

Description

@yinlianghui

Observation-class finding, surfaced while fixing #4528. Nothing a user meets today; no fix proposed here.

The mismatch

packages/react/src/hooks/useNavigationOverlay.ts:48 derives its public alias from the spec schema and tightens one key:

export type NavigationConfig = Omit<
  SpecAuthoredInput< typeof NavigationConfigSchema >,
  'mode'
> & {
  mode: NonNullable< SpecAuthoredInput< typeof NavigationConfigSchema >['mode'] >;
};

So mode is REQUIRED on the alias. The hook's own body, 140 lines below, does not require it:

const mode: NavigationMode = navigation?.mode ?? 'page';

The implementation is strictly more permissive than the type that fronts it, and the default it applies ('page') is meaningful behaviour, not a placeholder.

Why it surfaces now

The spec-derived ListViewSchema['navigation'] leaves mode optional, which is correct — authored metadata may set navigation: { view: 'x' } and let the mode default. ListView passes schema.navigation straight into the hook, and that has been a type error the whole time; it just could not be seen, because ListViewProps carried a [key: string]: any that erased the props type and made schema resolve to any inside the render function (#4528).

With the erasure removed, the call needs an assertion to compile:

navigation: schema.navigation as NavigationConfig | undefined,

#4528's PR takes that assertion, because it is a type-only card and the runtime value is unchanged either way. It is a workaround at a consumer, which is the shape AGENTS.md #0.1 says to fix at the producer instead.

ObjectGrid makes the identical call (ObjectGrid.tsx:1029) and compiles without an assertion only because ObjectGridSchema['navigation'] happens to be shaped differently — so the two sibling renderers disagree about whether the same hook needs a cast.

The decision worth making

Either

  1. Relax the alias to match the implementation (mode?:, i.e. stop the Omit + re-add), so spec-shaped values are accepted as-is and the assertion in ListView (and any future one) disappears; or
  2. Keep the alias strict and make the requirement real — have the hook reject a missing mode rather than defaulting it, and have callers resolve the default where the metadata is normalized.

(1) is the smaller change and matches what the code already does. (2) is the contract-first reading, but it moves a default that has been live for a while and would need every caller to supply mode explicitly. Whoever picks should also decide whether 'page' is the right default to have been applying silently.

Refs #4528, #2578, #2942.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions