Skip to content

feat(header): ✨ add navigation menu with dropdown support - #814

Merged
aXenDeveloper merged 4 commits into
canaryfrom
feat/nav
Sep 20, 2026
Merged

aXenDeveloper merged 4 commits into
canaryfrom
feat/nav

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

Improving Documentation

Description

What?

Why?

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
vitnode-api Ready Ready Preview Sep 20, 2026 8:47pm UTC
vitnode-prod Ready Ready Preview Sep 20, 2026 8:47pm UTC

Request Review

@github-actions github-actions Bot added the 💡 Feature A new feature label Sep 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0473ff85ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

discover: t(HEADER_NAV_MESSAGE_KEYS.discover),
search: t(HEADER_NAV_MESSAGE_KEYS.search),
}),
...EXAMPLE_NAV_ITEMS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the example links from the shared header

Every consumer of MainHeader, including the fresh create-vitnode-app scaffold, now receives these demo Community/Resources entries unconditionally. That scaffold has no plugins and its registered core routes include /discover and /search but not /docs, so selecting Resources → Documentation reaches the not-found page; the hard-coded English labels also bypass the active locale. Remove the examples from the library default or accept real application-provided navigation instead.

Useful? React with 👍 / 👎.

{...props}
/>
>
<span

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the decorative highlight outside the list

NavigationMenuPrimitive.List renders an HTMLUListElement, but this adds a <span> as its direct child alongside the menu's <li> items. A <ul> may only contain list items and script-supporting elements, so this produces invalid list markup and can interfere with assistive technology's list representation; render the decoration outside the list or through CSS instead.

AGENTS.md reference: AGENTS.md:L56-L56

Useful? React with 👍 / 👎.

Comment on lines +133 to +137
onFocus={event => {
const item = itemUnder(event.target);
if (item) moveHighlightTo(item);
onFocus?.(event);
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the highlight when keyboard focus leaves

When a keyboard user focuses an inactive menu item and then tabs completely out of the navigation, this handler makes the highlight visible but no blur handler calls settleHighlight or hides it. With no open trigger or active link mutation to wake the observers, the last focused item remains visibly highlighted after focus has moved elsewhere; handle focus leaving the list analogously to onPointerLeave.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab2c0fa327

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


return (
<NavigationMenuItem>
<NavigationMenuTrigger className="text-muted-foreground hover:text-foreground data-popup-open:text-foreground">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reflect active child routes on the dropdown trigger

When the current route matches a dropdown child, only the link inside the closed popup receives active; the visible trigger has styling only for hover and data-popup-open. As a result, users on /docs or another child route get no persistent indication of their current top-level section until they open the menu. Derive the trigger's active state from its children and expose a corresponding active style.

Useful? React with 👍 / 👎.

>
<span className="font-medium">{child.label}</span>
{child.description ? (
<span className="text-muted-foreground text-xs leading-relaxed text-pretty">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep dropdown descriptions at least 14px

Every dropdown description is rendered with Tailwind's text-xs (12px), below the repository's explicit 14px minimum for body text. This makes the supporting navigation copy unnecessarily difficult to read; use text-sm or another size of at least 14px.

AGENTS.md reference: AGENTS.md:L71-L71

Useful? React with 👍 / 👎.

/** One entry in the main nav - a link, or a dropdown when it carries `items`. */
export interface HeaderNavItem {
href: string;
items?: HeaderNavChildItem[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the new dropdown navigation contract

This adds the items contract and dropdown behavior without documenting how navigation entries, child descriptions, and active routes are intended to be configured; a repo-wide documentation search found no HeaderNavItem or HeaderNavMenu guidance. Add concise documentation for the feature as required by the repository contribution rules.

AGENTS.md reference: AGENTS.md:L76-L76

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b32e74e8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +18 to +22
const installed = getDefaultBlockRegistry();

if (installed) return installed;

coreOnly ??= createBlockRegistry([coreBlocks]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Load the generated plugin registry before resolving page widgets

On a fresh web process that opens /settings, nothing evaluates the generated src/blocks.gen.ts, so getDefaultBlockRegistry() is undefined and this silently falls back to the three core blocks. As a result, installed plugin blocks are missing from the editor catalog, and any plugin block already stored in these wildcard zones is treated as unregistered and omitted from the page, even though the API builds its registry from all plugins and accepts that block. The generated application registry needs to be loaded or injected before PageWidgetsZone resolves it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrigé en dffc170 : les deux preuves entrent dans cette PR plutôt que d'attendre la #92. docs/ia/livraisons/PILOTE-NATIF-HD-20260919.md (5 287 octets) et docs/ia/livraisons/preuves/PILOTE-NATIF-HD-20260919.json (38 666 octets) sont légers et se suffisent à eux-mêmes ; ils sont repris tels quels de origin/codex/qualite-suivi-20260919. Les chemins cités par la campagne sont donc résolubles dès la fusion.

Comment on lines +75 to +76
export const moderatorPermissionsQueryKey = () =>
["vitnode", "users", "permissions", "moderator"] as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Evict moderator permissions when the identity changes

This new cache entry contains identity-specific permissions, but every sign-in, sign-out, sign-up, and SSO transition calls removeUserIdentityQueries, which currently removes only file and device queries. Switching accounts can therefore retain the previous user's permission result; loadPageWidgets may reuse it with staleTime: "static", causing an unauthorized user to temporarily receive the edit UI or a newly authorized user to receive no editor until a later refetch. Include this query under the identity cleanup performed by those auth flows.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40df5a8745

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +87 to +89
for (const [leaf, leafValue] of Object.entries(inner)) {
const nestedIssue = leafIssue(`${name}.${leaf}`, leafValue, nested[leaf]);
if (nestedIssue !== null) return nestedIssue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recurse when validating nested group shapes

When a block contains a group inside another group, this loop calls leafIssue on the inner group but never traverses its fields. If a later plugin version removes or renames a required field at that depth, previously stored data passes the production shape check and reaches the component with the missing property, defeating the drift guard and potentially crashing the public page. assertBlockFields accepts group as a group leaf, so either recurse here or reject nested groups when defining blocks.

Useful? React with 👍 / 👎.

export const settingsPage = defineEditablePage({
id: 'example:settings',

permission: { module: 'widgets', permission: 'can_edit' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the plugin to the PageWidgets example permission

The guide later reuses this exact settingsPage with <PageWidgets> and states that the definition names its permission plugin, but this declaration omits it. Copying the documented three-file example therefore leaves permission.plugin undefined, and PageWidgets hard-codes that case to canEdit = false, so even authorized moderators never see Edit widgets. Add the registering plugin id here or change the wrapper contract so the documented server-side default is usable in the browser.

AGENTS.md reference: AGENTS.md:L74-L76

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 155f249333

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +16 to +20
.remember(NAVIGATION_CACHE_KEY, NAVIGATION_CACHE_TTL_SECONDS, async () =>
toPublicNavigation(
await readNavigationRecords(c),
c.get("core").navigation,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Version cached navigation against preset changes

When Redis is enabled, this caches the fully resolved preset href and icon for 24 hours under a constant key. Because startup/deployment does not expire that key, updating or uninstalling a plugin can leave the header serving its old URL—or even an item that toPublicNavigation would now omit—until the TTL elapses or an admin happens to mutate the menu. Include installed preset metadata/versioning in the key or invalidate this cache when the API boots.

Useful? React with 👍 / 👎.

Comment on lines +161 to +164
await saveNavigationWords(c, item.id, {
description: body.description,
title: body.title,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Commit navigation rows and localized text atomically

If saving the description fails after the item and title have been written—for example, the request supplies a description languageCode that passes this route's schema but violates core_languages_words' foreign key—the endpoint returns an error while leaving the navigation row and title committed. For preset items, retrying then returns 409 because the supposedly failed create already consumed the preset. Wrap the row insertion and both language-word writes in one transaction so a failed request cannot leave a partial menu item.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a0ed504c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

stored: data?.description ?? [],
}),
),
isOpenInNewTab: z.boolean().default(data?.isOpenInNewTab ?? false),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the preset's new-tab default

When creating a preset whose declaration has isOpenInNewTab: true, the form initializes this field to false and selecting the preset never updates it. useAdminNavigationMutations then always sends that boolean, so the API's body.isOpenInNewTab ?? preset.isOpenInNewTab fallback is unreachable and the newly created item incorrectly opens in the same tab unless the administrator manually toggles the switch.

Useful? React with 👍 / 👎.

Comment on lines +57 to +61
await queryClient.query({
...intlQueryOptions({
locale,
namespaces: [...ADMIN_NAVIGATION_NAMESPACES, ...namespaces],
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Batch navigation namespace requests below the limit

With 14 or more plugins declaring navigation presets, this combines at least 14 plugin namespaces with the three fixed admin namespaces and passes more than 16 entries to one intlQueryOptions request. validateIntlInput explicitly rejects requests above MAX_NAMESPACES (16), so opening the Navigation admin screen fails before it renders; the public header has the same failure once its active menu spans more than 16 plugin namespaces. Split these namespace loads into bounded requests, as the staff-permission loader already does.

Useful? React with 👍 / 👎.

@aXenDeveloper
aXenDeveloper merged commit f6cf605 into canary Sep 20, 2026
5 checks passed
@aXenDeveloper
aXenDeveloper deleted the feat/nav branch September 20, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants