SAM-2: Add Settings entry to the avatar dropdown #801

Closed
opened 2026-05-03 19:44:56 +00:00 by code-lead · 0 comments
Collaborator

As an operator, I want a Settings entry in the avatar dropdown, so that the primary entry point to repos, labels, secrets, appearance, and agent-types lives next to my identity card and theme controls — the same place every other modern product puts it.

Acceptance criteria

Menu placement

  • In apps/web/src/components/avatar-menu.tsx, between the <ThemeSection /> block and the existing <Menu.Separator /> that precedes the external links, insert a new section separator and a Settings menu item.
  • The item is a <Menu.Item render={<Link to="/settings">…}/> using @tanstack/react-router's Link (matching the existing to="/settings/appearance" link inside ThemeSection) — not an <a href>, so client-side routing and the active-route machinery keep working.
  • The row uses the lucide Settings icon with size={14} and aria-hidden="true", matching MenuLink's visual rhythm (icon · label · trailing space).
  • Row className mirrors MenuLink: flex items-center gap-2 px-3 py-2 text-small text-text-primary transition-colors hover:bg-surface-high focus-visible:bg-surface-high focus-visible:outline-none.
  • data-testid="avatar-menu-settings".
  • Clicking the row navigates to /settings and closes the popup (Base UI's Menu.Item handles popup-close on activation by default — verify the wrapped <Link> doesn't preventDefault).

Ordering

  • Final popup section order, top → bottom:
    1. User card
    2. Theme section
    3. Settings (new)
    4. External links — Forgejo profile, Open docs
    5. Logout (when authEnabled)
  • A <Menu.Separator /> lives both above and below the new row so it visually anchors as its own group rather than dangling under the theme picker.

Tests

  • Vitest covers avatar-menu.tsx: opening the menu surfaces a getByTestId('avatar-menu-settings') element with role menuitem and an accessible name "Settings".
  • Activating the row via Enter (keyboard) and via click both navigate to /settings; assert through a memory-router test.
  • Playwright happy-path: from the desktop shell, click the avatar, click Settings, land on /settings/ (the index route).

Out of scope

  • Re-ordering the existing theme / profile / docs / logout entries.
  • Adding sub-items for /settings/repos, /settings/secrets, etc. — the dropdown links to the index, the existing in-page tab strip handles further navigation.
  • Replacing the Customize… link inside ThemeSection (it points to /settings/appearance and stays as-is).

References

  • Spec: specs/settings-into-avatar-menu.md § "Story SAM-2"
  • apps/web/src/components/avatar-menu.tsx — popup composition (lines 161–242)
  • apps/web/CLAUDE.md — foundation primitives, token discipline, a11y baseline

Dependencies

None. Per the spec's suggested implementation order, SAM-2 lands first so the entry point exists before SAM-1 drops it from the desktop nav.

As an operator, I want a Settings entry in the avatar dropdown, so that the primary entry point to repos, labels, secrets, appearance, and agent-types lives next to my identity card and theme controls — the same place every other modern product puts it. ## Acceptance criteria ### Menu placement - [ ] In `apps/web/src/components/avatar-menu.tsx`, between the `<ThemeSection />` block and the existing `<Menu.Separator />` that precedes the external links, insert a new section separator and a **Settings** menu item. - [ ] The item is a `<Menu.Item render={<Link to="/settings">…}/>` using `@tanstack/react-router`'s `Link` (matching the existing `to="/settings/appearance"` link inside `ThemeSection`) — **not** an `<a href>`, so client-side routing and the active-route machinery keep working. - [ ] The row uses the lucide `Settings` icon with `size={14}` and `aria-hidden="true"`, matching `MenuLink`'s visual rhythm (icon · label · trailing space). - [ ] Row className mirrors `MenuLink`: `flex items-center gap-2 px-3 py-2 text-small text-text-primary transition-colors hover:bg-surface-high focus-visible:bg-surface-high focus-visible:outline-none`. - [ ] `data-testid="avatar-menu-settings"`. - [ ] Clicking the row navigates to `/settings` and closes the popup (Base UI's `Menu.Item` handles popup-close on activation by default — verify the wrapped `<Link>` doesn't preventDefault). ### Ordering - [ ] Final popup section order, top → bottom: 1. User card 2. Theme section 3. **Settings** (new) 4. External links — Forgejo profile, Open docs 5. Logout (when `authEnabled`) - [ ] A `<Menu.Separator />` lives both above and below the new row so it visually anchors as its own group rather than dangling under the theme picker. ### Tests - [ ] Vitest covers `avatar-menu.tsx`: opening the menu surfaces a `getByTestId('avatar-menu-settings')` element with role `menuitem` and an accessible name "Settings". - [ ] Activating the row via Enter (keyboard) and via click both navigate to `/settings`; assert through a memory-router test. - [ ] Playwright happy-path: from the desktop shell, click the avatar, click Settings, land on `/settings/` (the index route). ## Out of scope - Re-ordering the existing theme / profile / docs / logout entries. - Adding sub-items for `/settings/repos`, `/settings/secrets`, etc. — the dropdown links to the index, the existing in-page tab strip handles further navigation. - Replacing the `Customize…` link inside `ThemeSection` (it points to `/settings/appearance` and stays as-is). ## References - Spec: `specs/settings-into-avatar-menu.md` § "Story SAM-2" - `apps/web/src/components/avatar-menu.tsx` — popup composition (lines 161–242) - `apps/web/CLAUDE.md` — foundation primitives, token discipline, a11y baseline ## Dependencies None. Per the spec's suggested implementation order, SAM-2 lands **first** so the entry point exists before SAM-1 drops it from the desktop nav.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#801
No description provided.