Repository navigation
Admin navigation: ten items with section tabs - #514
Conversation
The mechanism for the admin navigation restructure. AdminNavItem gains `children` (section tabs) and `also` (extra prefixes that light the item), childrenOf and navFor(subject, items?) honour tab permissions, and SectionTabs draws the strip. ADMIN_NAV itself is unchanged. CLAUDE.md gets the Admin navigation rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The strip now draws its own container in the width and gutters the admin pages use (mx-auto w-full max-w-7xl px-4 md:px-6), so it lines up with the page header below it. The documented layout wrapper (px-4 pt-4 sm:px-6) hugged the left edge above 1280px, had an sm/md gutter mismatch between 640 and 768px, and left an empty box on detail pages where the strip renders nothing. Layouts now render <SectionTabs /> bare. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Replace the AI activity and Cost analysis sidebar items with one AI item that has three tabs. URLs do not change: the pages move into an (ai) route group whose layout draws the tab strip, and the item also lights on /admin/cost-analysis. AI settings is a new page at /admin/ai/settings. It holds the AI provider, conversation review and assistant sources sections, moved from the settings page's AI group with the same props. settingsSection() now maps those section ids to the new page, so the links to them follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Discord becomes one nav item with two tabs: Activity, the bot dashboard at /admin/discord, and Settings at /admin/discord/settings, which holds the Discord integration and the channel stats that moved out of the Settings page. Both pages sit under one layout that draws the tab strip, and the new page checks for an admin itself. settingsSection() sends the "discord" and "channel-stats" ids to the new page, the empty activity period links to it, and the bot's rejected-token message names the new place to save a token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…ngs tabs
Season, Playground and Share analytics become one Rewind nav item with
four tabs (Season, Playground, Shares, Settings), drawn by SectionTabs in
the layout of a new (rewind) route group. The routes keep their URLs:
/admin/rewind, /admin/rewind/playground and /admin/shares move into the
group, and `also` lights the Rewind item on /admin/shares.
The Rewind settings leave /admin/settings for /admin/rewind/settings, the
Settings tab. The page loads only what the section needs and checks for an
admin itself. settingsSection("rewind") now resolves through SETTINGS_HOMES,
the Season facts panel links to the new page, the runbook and the E2E specs
follow, and updateRewindSettings revalidates the page it is saved from.
mobileTabs() takes every `mobile: "tab"` item in nav order, so the Rewind
tab is a plain item instead of a special case. The zone note's "Change the
server time zone" is now a link to the Time zone section on the settings
page.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…ings page Rewind's "Change the server time zone" was the one place that opened a settings section from elsewhere on the page. It now links to the Time zone section from the Rewind settings page, so the hook has no caller (knip would fail on it). The settings-section test keeps the focus-on-edit coverage through the view's `edit`, the path the service, AI, Discord and Stripe sections use, and the Time zone test drops the Rewind link case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The Discord integration save, the channel stats save and "Update now" revalidated /admin/settings, a page that no longer renders those sections. They worked only because Next refreshes the calling page on any revalidatePath. Revalidate /admin/discord/settings instead, as the watchlist and inactivity actions do for their own pages. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The provider, spend caps, conversation review and assistant source saves still revalidated /admin/settings, a page that no longer renders those sections. Point them at /admin/ai/settings, as Watchlist and Inactivity do for their own pages. The Stripe saves in admin-config stay on /admin/settings. updateLlmCaps and the provider saves had no test of the path; each now has one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…s, Request limits and Queues tabs Watchlist sync, Inactive members and Job queues become one Automations nav item with four tabs (Watchlist sync, Inactive members, Request limits, Queues), drawn by SectionTabs in the layout of a new (automations) route group. The routes keep their URLs: /admin/watchlist, /admin/inactivity and /admin/queues move into the group with git mv, and `also` lights the item on the sibling paths. The failed-jobs badge moves from Job queues to Automations, and the item is a mobile tab, placed after the Rewind items. /admin/request-limits is the new Request limits tab. It renders RequestTiersSection with the props the settings page passed, checks for an admin itself and renders per request. Automations gets its own icon concept (Workflow); Layers stays for the queues page's empty state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The Members group of /admin/settings keeps Stripe only: the Request limits
section, its loader call and its nav entry leave the settings page for
/admin/request-limits. settingsSection("request-tiers") resolves there
through SETTINGS_HOMES, which every later move extends.
The settings save and Run now revalidated /admin/settings, a page that no
longer shows these fields; they revalidate /admin/request-limits instead,
as the watchlist and inactivity actions do for their own pages.
The axe scan covers the new page, the settings layout spec drops the
section, and the request limits and privacy docs name the new place.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The watchlist card gets Sync now in view mode, beside the last save, as Request limits and Inactive members have Run now. It calls the same action as the Queues page's "Sync watchlists" (triggerWatchlistSyncJob), which stays where it is, and toasts what the action returns. It is disabled, with a line saying why, while watchlist sync is off (the job would skip every member) or Redis is not set up (the action refuses). A Redis that does not answer shows in the action's error toast. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…ive members, Request limits and Queues tabs Resolves the shared nav, routes, settings page/groups and E2E spec conflicts as the union of both sides. The More-sheet E2E step keeps admin-nav-automations-mobile. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The three groups left on /admin/settings take their final names: Media becomes Connections (Plex server, Tautulli, Overseerr, Sonarr/Radarr/ Jellyfin), Members becomes Billing (Stripe billing), and System keeps Time zone, API access, Privacy and System information. Section ids, anchors and section test ids do not change; only the group ids and labels do, so the nav's group test ids are now settings-nav-group-connections and settings-nav-group-billing. The layout falls back to Connections when it has no items. The settings groups, nav and layout tests and the settings layout spec follow the new ids. Comments and docs/stripe.md that named the old groups name the new ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
… flag row "by <name>" in a settings footer now links to the audit event that recorded the save: LastSaved carries the newest audit row's id as eventId. The Season facts panel no longer lists Sound, since nothing in the UI can change rewindAudio (deferred in docs/rewind.md). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…tions SettingsSection now asks the browser to confirm before the tab closes or reloads while its form has unsaved changes and no SettingsEditProvider wraps it. The settings pages that moved onto feature pages (Rewind, Discord, AI, Request limits) render their sections stand-alone and had no guard, so a reload mid-edit silently discarded the draft. On the settings page nothing changes: the layout still owns the one listener, fed by reportDirty. The Watchlist sync and Automatic removal cards each carried a private copy of the guard. Both are deleted, and their leave-guard tests now exercise the shared one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…System Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Review nits from Task 6: the Stripe E2E comment named a group that no longer exists, and the service-form test header described Connections as read-only although its Sonarr, Radarr and Jellyfin section edits in a sheet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The admin nav loses its groups. The sidebar and the More sheet list every item in nav order as one list, with no headings or dividers, and the command menu no longer matches on or shows a group. AdminNavGroup, ADMIN_NAV_GROUPS, groupSlug, itemsIn and every item's group field are gone. The phone gets one menu. The top bar drops its hamburger and Sheet, and the navCounts prop that only fed it; the tab bar's More lists every item. The tab bar is Overview plus the next mobile "tab" items in nav order, four link tabs at most, then More, so Settings becomes a More item and Automations takes its tab. The failed-jobs dot follows the item's badge instead of the old Queues-in-More layout: a link tab shows admin-tab-<id>-failed while an item it covers has a destructive badge, and More shows its dot only for such an item that no link tab covers. With today's nav the dot sits on Automations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…dropped Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Review nit from Task 9: the component test asserted only the link, so a dropped space or a doubled "by" would have passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…s and sheets The settings layout, SettingsSection and SettingsSheet each need the same beforeunload backstop, so it moves into hooks/use-leave-guard.ts. SettingsSheet now guards the tab itself when no settings layout reports for it. AssistantSection's Sheets render stand-alone on the AI settings tab, so a reload with a Sheet open and edited used to discard the draft silently. On the settings page the Sheet still reports to the layout, which owns the one listener. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
A tab item past the four-tab cap opens from More, so its page must light More and none of the link tabs: the tab bar test now opens such a page and checks both. A guard in the nav test fails when the real nav gets a fifth mobile "tab" item, which the cap would otherwise demote to More without a word. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…ings sections useLeaveGuard is shared by the settings layout, sections and sheets, so the pages moved off Settings keep the reload and close prompt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
navFor opens a role's folded item on its first permitted tab, but the
client provider rebuilt items from ADMIN_NAV by id, so the rewritten href
never reached the sidebar, the tab bar or the More sheet.
The shell now sends nav: { id, href, childIds }[] in place of navIds: the
href as navFor returned it and the ids of the item's tabs the user may
open (childrenOf). AdminNavProvider rebuilds each item from ADMIN_NAV by
id, applies the server's href and keeps only those tabs in the item's
children, so every consumer of useAdminNav sees the permission-aware nav
the server resolved.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The "Go to" list now holds every page, then one entry per section tab
labelled "Page › Tab" (Automations › Request limits) that opens the tab's
own href, so a tab such as Request limits, Shares or Cost is a keystroke
away. A tab entry matches the search term against its combined label, so
"request" finds Request limits and "automations" finds the page and all of
its tabs.
Pages come before tabs so a page's name still selects the page itself
("set" opens Settings, not Discord › Settings). The menu lists each item's
children from the nav context, which keeps only the tabs the user may open;
Settings groups are not nav tabs and are not indexed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The Users, Invites and Affiliates nav items become one Members item, the third after Overview and Assistant, with three tabs (Users, Invites, Affiliates). It opens on Users at /admin/users, and `also` lights it on /admin/invites and /admin/affiliates, which sit beside it. It is the mobile tab after Overview, so the tab bar is Overview, Members, Rewind, Automations and More: four link tabs, at the cap. Its badge shows the affiliate applications waiting for a decision, else the member count; the active-invites badge is dropped. Invites carries no permission, so only an admin gets that tab, and Support gets Users and Affiliates, with Members opening on Users. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
/admin/users, /admin/invites and /admin/affiliates move into a (members)
route group with git mv, so their URLs are unchanged. The group's layout
draws SectionTabs from childrenOf("members"): every tab for an admin, Users
and Affiliates for Support, none without a session. A page below a tab (a
user, a user's Rewind preview, an invite) has no strip, and the Affiliates
page keeps it on its own ?tab= tabs, which change only the query.
The tests that import the moved pages by path, the page-guard list and the
rewind-design-system skill's admin preview path follow the move.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The Rewind, Discord, AI, Automations and Members layouts were the same body five times: getSession, childrenOf, SectionTabs, then the page. sectionLayout(id) in components/admin/section-layout.tsx returns that layout and reads the strip's label from the nav item, so the two cannot drift; each route group's layout.tsx is now one line. The shared behaviour tests move to the factory (all tabs for an admin, permitted tabs only for a role, no strip without a session or below a tab). The five near-identical layout tests give way to one table in app/admin/__tests__/section-layouts.test.tsx, which also fails when a nav item with tabs has no layout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
…nings
The beforeunload probe ("would the browser ask before the tab closes?")
was spelled six ways across the settings tests, one of them with the
opposite sense. Move it to __tests__/helpers/leave-guard.ts and import it
from all six.
The settings-section test that calls requestSubmit() on a blocked form
let the save settle outside act(), which printed three warnings. Await
the submit inside act().
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Feature settings moved to the feature's own page (Rewind, Discord, AI, Automations) and /admin/settings now holds only connections, billing and system, but the docs and a few strings still sent readers to "Admin > Settings" for all of it. Name the page each thing lives on now: - the docs and README (the conversation judge, assistant sources, the Discord bot and stat channels, the AI providers, Watchlist sync, Inactive members and Queues under Automations); - the two lines the admin assistant and the Discord /assistant reply read out to admins (the assistant is off; no knowledge base), and the Rewind generation tab's pointer to the Rewind settings tab, with tests that pin the new words; - comments that still described the one-page Settings. docs/roles.md says what a Support user sees in the sidebar (Assistant, Members and Audit log) without the dangling clause. CLAUDE.md's Admin navigation adds that nav.ts also feeds the command menu's "Page > Tab" entries and that a section's layout is sectionLayout(id). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The Affiliates page now carries two stacked tablists (the Members strip and its own tabs) and was the only Members tab the axe route table skipped. Add the route and its baseline row, kept in sorted order, so the accessibility, landmarks and CSP specs cover it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Docs and assistant copy name the new admin locations; the unread invites count and orphaned icons are gone; one sectionLayout factory replaces five identical layouts; one leaveIsGuarded test helper; an axe row for /admin/affiliates. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
recheckConnections() returns every check getSettingsHealth() reports, including Discord and Rewind (which inherits Tautulli's failure through dependsOn). Neither section renders on /admin/settings any more, so the toast could say "2 connections are not working" with one red section, or report a failure with every visible section green. The toast now counts only the checks behind the sections the page shows. SECTION_HEALTH_KEYS in settings-groups.ts names them, keyed by the nav's own section ids (a Record over SettingsNavStatuses, so adding or removing a section with a status fails the build until it is mapped), and the page reads the same constant for its "Sonarr, Radarr, Jellyfin" status. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The zone note in the open Rewind settings form linked to the Time zone section on /admin/settings. A client-side navigation fires no beforeunload, so clicking it mid-edit dropped the draft without asking (on main the same click kept it, because every section sat on one page). TimeZoneNote takes `editing`: the form passes it and the note reads "Change the server time zone in Settings, System." as plain text; the summary keeps the link, with its test id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The plan, the usability audit and CLAUDE.md list Rewind before Discord, but ADMIN_NAV had them the other way round. Rewind moves above Discord in the sidebar, the More sheet order and the command menu (all read ADMIN_NAV); mobileTabs() is unchanged, because Discord is not a tab. The nav test pins the order against ADMIN_NAV, the module's header diagram follows, and the E2E sidebar walk lists Rewind first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
On an item called Members, "2 pending" read as two pending members. The badge counts affiliate applications waiting for a decision, so it now says "2 to review". The nav and shell tests that pin the text follow; no E2E spec asserts it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
The tabs of a section share its route group's layout, so app/admin/loading.tsx no longer suspends again on a tab switch: a tab page without its own loading.tsx showed nothing until the next page was ready. Season, Playground, Shares and Settings (Rewind), Settings (Discord and AI), Request limits and Queues (Automations) get a loading.tsx that re-exports the generic admin loader. Next puts each loading.tsx in a Suspense boundary around its folder's page and nested segments, so the new one under settings/ is the narrower boundary and takes precedence over the Activity skeleton of ai/loading.tsx and discord/loading.tsx on those routes. tab-loading.test.ts walks the tabs in ADMIN_NAV and fails for a tab whose page has no loading.tsx beside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
On a phone the Automations strip is wider than the screen, and TabsList
scrolls inside itself, so on /admin/queues the selected tab started
off-screen. SectionTabs now keeps a ref on the selected tab and calls
scrollIntoView({ block: "nearest", inline: "nearest" }) on it after mount
and whenever the selection changes. The strip renders nothing on a page
below a tab, and then nothing scrolls.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
A bookmark or release note that links /admin/settings#rewind (or #ai, #discord, #request-tiers, #channel-stats, #conversation-review, #assistant) found no section there and landed on Connections without a word. The hash handler in SettingsLayout now replaces the URL with the page that section moved to, on load and on hashchange. lib/admin/routes.ts exports movedSettingsHome(id), the SETTINGS_HOMES entry or null (own keys only, so "constructor" is not a moved section), and settingsSection reads it too, so the layout does not parse a path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Controls that moved to AI, Discord and Rewind settings were still sent to "Settings" in a few places. The setup wizard's AI steps say Admin, AI, Settings; the summary's hint no longer names one page, because a skipped step may now live on Settings, AI or Discord. Season says the audience switch is by hand in Rewind settings, the preflight row names the switch that turned the copy off (AI features in AI settings, the mode in Rewind settings), and the season-open job logs the same. The get_admin_settings tool description, the JSDoc of the three loaders and the assistant's canConfigure prop name the pages that use them. The wizard's Discord step now revalidates /admin/discord/settings, where the integration is shown, instead of /admin/settings. Settings nav test: the Discord and AI rows that covered the Needs attention glyph left with their sections, so a test covers incomplete and degraded again, beside Not working (Tautulli was already unreachable). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
CLAUDE.md: the Admin navigation bullet says the tab strip is drawn by the layout of the item's route group or segment, because Discord's is a segment of its own. The File Organization tree shows what lives under app/admin: the route groups (overview, members, rewind, ai, automations) and the Discord segment, one line each, in nav order. docs/operations.md, Lost key: re-entering the secrets covers the AI provider keys too, which are on Admin, AI, Settings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Test all connections counts only the sections Settings shows; the time-zone note links only outside the open form; Rewind before Discord; Members badge says "to review"; loading skeletons for every section tab; the strip scrolls its selected tab into view; old Settings anchors open the section's new page; in-app copy and docs name the new locations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Brings in the assistant skill sources (#513). Their settings block renders on the AI Settings tab with the rest of the assistant sources, so the new action revalidates /admin/ai/settings, its E2E spec opens that page, and the executor copy and docs name the new location. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
|
Claude finished @mchestr's task in 42s —— View job Review of #514: Admin navigation, ten items with section tabsVerdict: no blocking issues found. I read the code only. I did not run tests or the build, so CI is the check for those. What I checked
Non-blocking notes
Test coverage looks thorough: section layouts, tab loading states for every tab, the command menu, the "every role lights every tab" nav test, and per-page tests for the four new pages. |
navFor opens a folded item at the role's first permitted tab, and isActive matches that href by prefix, so a role allowed only a later tab nested under the first tab's path would land on a page that never lights its item. No role has that shape; this fails the moment one does. Also drops "later the command menu" from the nav JSDoc, since it reads the nav now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
|
Checked the six points against the code:
Nits: the Generated by Claude Code |
|
Unit Tests is red on Fix: #519 makes the generator reject title sets where one title contains another (reproduced with the seed, passes with it, soaked for 2,000 runs). I'll re-run the Unit Tests job once the rest of the workflow finishes; a different seed should pass. Generated by Claude Code |
Element.scrollIntoView on the selected tab also moved Chromium's sequential-focus starting point, so on every page with a tab strip the first Tab after load landed past the skip link and e2e/landmarks.spec.ts failed for ten admin routes. The strip now moves its list's own scrollLeft by the overflow of the selected tab's box, which brings an off-screen tab into view on phones and leaves keyboard focus alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
|
E2E Tests was red for a reason that was this PR's, now fixed in The other failure in that run was the unrelated Generated by Claude Code |
|
On note 1 (health cache after moved-section saves): Generated by Claude Code |
Why
The admin area had grown to 17 sidebar items under four group headings, a hamburger sheet plus a tab bar on phones, and one
/admin/settingspage with 16 sections in six groups. Finding a feature's controls meant knowing whether they lived on the feature's page or somewhere on Settings. This PR implements the restructure from the usability audit: ten flat items, each feature's controls on its own page as a Settings tab, and/admin/settingskept for what is global.What changes
Sidebar — ten items, no group headings
lib/admin/nav.tsstays the one list: items carrychildren(the tab strip) andalso(sibling paths that light the item). The sidebar, the mobile tab bar, page titles, the command menu and each section's tab strip read it.CLAUDE.mdgains an "Admin navigation" subsection with the rules: a feature's controls live on the feature's page; the sidebar budget is ten items;settingsSection(id)is the only way to link to a settings section.app/admin/(members)/,(rewind)/,(ai)/,(automations)/, and thediscord/segment). Each group'slayout.tsxisexport default sectionLayout("<id>"), which drawsSectionTabsfrom the item's permitted children; detail routes (/admin/users/[userId], …) render no strip. Layouts are not authorisation boundaries — every page keeps its ownassertAdmin()/permission check.LastSavedfooters and test ids:/admin/rewind/settings,/admin/discord/settings(incl. channel stats),/admin/ai/settings(providers, caps, judge, assistant sources) and/admin/request-limits. The actions that save those sections revalidate the new pages. Old/admin/settings#<moved id>links redirect to the section's new page./admin/settingskeeps Connections (Plex, Tautulli, Overseerr, other), Billing (Stripe) and System (time zone, API access, privacy, system). "Test all connections" counts only the sections the page shows.loading.tsx, so switching tabs shows the skeleton instead of a frozen strip.LastSaved … by <admin>links to the audit event; the unused "Sound" flag row leaves the Season page; a shareduseLeaveGuardkeeps the reload/close prompt for sections that no longer sit inside the Settings layout (and gives it to settings sheets, which never had it); the time-zone note inside the open Rewind form no longer offers a link that would drop the draft;settingsSection()ignores keys inherited fromObject; the Watchlist page gets "Sync now"; dead icons and the unread invites count are removed; the Members badge reads "N to review" for pending applications; docs, setup-wizard and assistant copy name the new locations.Follow-ups (not in this PR)
beforeunloadcovers reload and close only. The old one-page Settings kept drafts while moving between sections.navForrewrites a gated item's href to the first permitted tab; a role that may open only a later, nested tab would not light the item (no such role exists today; a nav test fails the moment one does).Notes for review
.claude/skills/rewind-design-system/SKILL.mdchanges one path token (app/admin/users/…→app/admin/(members)/users/…) becausescripts/__tests__/project-skills.test.tschecks that named paths exist. CLAUDE.md asks for a/security-reviewof any.claude/skillschange: two reviews inspected it (prose only; no instructions, shell, links or frontmatter), but no separate/security-reviewhas run — that is yours to run or waive before merge.sectionLayout()returns the async server layout component; typecheck (incl.next typegen) passes and CI's Build job exercisesnext build.mainwas merged in after the skill-sources work (Assistant skills, phase 1 #513) landed: the new block renders on the AI Settings tab through the shared loader; its action, E2E spec, executor copy and docs now name/admin/ai/settings.--strict, knip,env:docs --check,mcp:docs --check; full jest — the only failures (get-share-safe"is null after the season closes", retention fallback "runs once per 24 hours") fail identically onmain. Playwright runs in CI.How it was built
Planned from the usability audit, then executed as thirteen tasks on separate worktrees by implementer agents with a task-scoped review each (spec compliance + code quality), fix rounds where a review found an Important issue, merges as the union of both sides, a whole-branch review at the end and one fix wave with a scoped re-review. Per-task commits are kept so pieces can be cherry-picked or reverted individually.
https://claude.ai/code/session_011TLtULGdZXTe6L61jNKqRF
Generated by Claude Code