feat(web): settings rework — category-list → detail-subpage for global and repo settings #198

Closed
opened 2026-07-22 02:08:39 +02:00 by dominik.polakovics · 1 comment

What to build

Replace the two single long settings pages (web/src/routes/Settings.tsx, web/src/routes/RepoSettings.tsx) with a category-list → detail-subpage structure (iOS Settings pattern), mobile-first, for both global and repo settings.

Routes & responsive behavior

  • New routes: /settings/:section and /repos/:id/settings/:section.
  • Bare routes (/settings, /repos/:id/settings):
    • Mobile (<1024px, same breakpoint as the shell): render the category index — tappable rows navigating to the section pages.
    • Desktop (≥1024px): redirect to the first category (general); /…/:section renders a slim (~180px) inline category list beside the section content (master-detail). The category list is visually lighter than the app SideNav rail (plain text links, active one accented) so it doesn't read as rail-next-to-rail.
    • Crossing the breakpoint keeps the route; growing to desktop while on the bare index triggers the redirect.

Categories

Global (/settings/…):

slug absorbs today's sections
general Git author
agents Spawn defaults, AFK defaults, Capacity
notifications Notifications (Web Push), Install app (both device-local)

Repo (/repos/:id/settings/…):

slug absorbs today's sections
general Identity, Incogni
integrations Credentials & tracker
branches Branches
agents Spawn defaults, AFK defaults, AFK (auto-spawn/budget/max instances)
autoland Autoland
secrets Secrets
danger Danger zone (delete repo)

Category metadata (slug, title, one-line description, icon, danger flag) lives in one array per settings area, driving routes, index rows, and the desktop category list from a single source.

Index rows & icons

  • Row = icon + title + one-line description + chevron (e.g. "Agents — spawn defaults, AFK behavior, capacity").
  • danger pinned last and styled in the app's danger red (index row and desktop list entry).
  • Icons come from lucide-solid (official Lucide package for SolidJS, ISC, tree-shakeable) — this issue adds the dependency. Render at 18–20px, inherit currentColor.

Navigation chrome

  • Mobile section pages get a header: back chevron + section title; back navigates to the category index (not browser-back).
  • Repo breadcrumbs gain a segment: Repos / <name> / Settings / <Section>, where "Settings" links to the index. Global settings stays breadcrumb-free.

Save model

  • Each section page is its own form with the existing dirty-fields-only PATCH semantics (same endpoints as today); Save enabled only when dirty.
  • One shared useSettingsForm primitive owns dirty tracking, PATCH submit, and the unsaved-changes guard: useBeforeLeave (@solidjs/router) + beforeunload, both via native confirm ("Discard unsaved changes?").
  • Device-local / immediate-action sections (Notifications, Install app, Secrets, Danger zone) keep their current act-immediately behavior and are guard-free.

Code layout

web/src/routes/settings/           # global: index, sections/General|Agents|Notifications
web/src/routes/repo-settings/      # repo: index, sections/General|Integrations|Branches|Agents|Autoland|Secrets|Danger
web/src/components/settings/       # shared: SettingsLayout (rail/back-header), index-row, useSettingsForm, category types

RepoSettings.tsx (currently 1215 lines) and Settings.tsx (759) are dissolved into these files; every resulting file must land under the 1000-line cap.

Acceptance criteria

  • /settings and /repos/:id/settings render the category index on mobile and redirect to general on desktop; /…/:section deep links work on both form factors
  • All existing fields/sections are reachable under the new categories with unchanged PATCH semantics (dirty-fields-only, per-section)
  • Unsaved-changes guard fires on in-app navigation and tab close for dirty form sections; native confirm; device-local sections unaffected
  • Mobile section pages show back-chevron + title header; repo breadcrumb gains the section segment
  • Index rows show lucide icon + title + description + chevron; danger last and red
  • Desktop shows the slim inline category list next to section content, visually distinct from the app rail
  • Category metadata defined once per area and drives routes, index, and desktop list
  • lucide-solid added; icons inherit currentColor in light and dark themes
  • Old monolith route files removed; all new files under the 1000-line cap; existing links to /repos/:id/settings keep working
  • Tests cover: category metadata↔route wiring, per-section dirty PATCH payloads, guard behavior

Blocked by

None - can start immediately

## What to build Replace the two single long settings pages (`web/src/routes/Settings.tsx`, `web/src/routes/RepoSettings.tsx`) with a category-list → detail-subpage structure (iOS Settings pattern), mobile-first, for both global and repo settings. ### Routes & responsive behavior - New routes: `/settings/:section` and `/repos/:id/settings/:section`. - Bare routes (`/settings`, `/repos/:id/settings`): - **Mobile (<1024px, same breakpoint as the shell):** render the category index — tappable rows navigating to the section pages. - **Desktop (≥1024px):** redirect to the first category (`general`); `/…/:section` renders a slim (~180px) inline category list beside the section content (master-detail). The category list is visually lighter than the app SideNav rail (plain text links, active one accented) so it doesn't read as rail-next-to-rail. - Crossing the breakpoint keeps the route; growing to desktop while on the bare index triggers the redirect. ### Categories Global (`/settings/…`): | slug | absorbs today's sections | |---|---| | `general` | Git author | | `agents` | Spawn defaults, AFK defaults, Capacity | | `notifications` | Notifications (Web Push), Install app (both device-local) | Repo (`/repos/:id/settings/…`): | slug | absorbs today's sections | |---|---| | `general` | Identity, Incogni | | `integrations` | Credentials & tracker | | `branches` | Branches | | `agents` | Spawn defaults, AFK defaults, AFK (auto-spawn/budget/max instances) | | `autoland` | Autoland | | `secrets` | Secrets | | `danger` | Danger zone (delete repo) | Category metadata (slug, title, one-line description, icon, danger flag) lives in **one array per settings area**, driving routes, index rows, and the desktop category list from a single source. ### Index rows & icons - Row = icon + title + one-line description + chevron (e.g. "Agents — spawn defaults, AFK behavior, capacity"). - `danger` pinned last and styled in the app's danger red (index row and desktop list entry). - Icons come from **`lucide-solid`** (official Lucide package for SolidJS, ISC, tree-shakeable) — this issue adds the dependency. Render at 18–20px, inherit `currentColor`. ### Navigation chrome - Mobile section pages get a header: back chevron + section title; back navigates to the category index (not browser-back). - Repo breadcrumbs gain a segment: `Repos / <name> / Settings / <Section>`, where "Settings" links to the index. Global settings stays breadcrumb-free. ### Save model - Each section page is its own form with the existing dirty-fields-only PATCH semantics (same endpoints as today); Save enabled only when dirty. - One shared `useSettingsForm` primitive owns dirty tracking, PATCH submit, and the unsaved-changes guard: `useBeforeLeave` (@solidjs/router) + `beforeunload`, both via native `confirm` ("Discard unsaved changes?"). - Device-local / immediate-action sections (Notifications, Install app, Secrets, Danger zone) keep their current act-immediately behavior and are guard-free. ### Code layout ``` web/src/routes/settings/ # global: index, sections/General|Agents|Notifications web/src/routes/repo-settings/ # repo: index, sections/General|Integrations|Branches|Agents|Autoland|Secrets|Danger web/src/components/settings/ # shared: SettingsLayout (rail/back-header), index-row, useSettingsForm, category types ``` `RepoSettings.tsx` (currently 1215 lines) and `Settings.tsx` (759) are dissolved into these files; every resulting file must land under the 1000-line cap. ## Acceptance criteria - [ ] `/settings` and `/repos/:id/settings` render the category index on mobile and redirect to `general` on desktop; `/…/:section` deep links work on both form factors - [ ] All existing fields/sections are reachable under the new categories with unchanged PATCH semantics (dirty-fields-only, per-section) - [ ] Unsaved-changes guard fires on in-app navigation and tab close for dirty form sections; native confirm; device-local sections unaffected - [ ] Mobile section pages show back-chevron + title header; repo breadcrumb gains the section segment - [ ] Index rows show lucide icon + title + description + chevron; `danger` last and red - [ ] Desktop shows the slim inline category list next to section content, visually distinct from the app rail - [ ] Category metadata defined once per area and drives routes, index, and desktop list - [ ] `lucide-solid` added; icons inherit `currentColor` in light and dark themes - [ ] Old monolith route files removed; all new files under the 1000-line cap; existing links to `/repos/:id/settings` keep working - [ ] Tests cover: category metadata↔route wiring, per-section dirty PATCH payloads, guard behavior ## Blocked by None - can start immediately
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Restructure global and repo settings from single long pages into a category-list → detail-subpage IA (mobile index, desktop master-detail), with per-section forms and an unsaved-changes guard.

Current behavior:
Global settings and repo settings each render as one long single-route page (/settings and /repos/:id/settings) containing every section stacked vertically. The repo settings component is over the 1000-line cap (currently ~1215 lines). Icons in the app are hand-inlined SVGs; there is no icon library dependency. Saves already use dirty-fields-only PATCH per section, but there is no navigation guard for unsaved edits.

Desired behavior:
The issue body is the authoritative spec — categories, routes, responsive behavior, save model, and code layout are all defined there. The behavioral contract in short:

  • /settings/:section and /repos/:id/settings/:section render one category each. The bare routes render a tappable category index below the app shell's desktop breakpoint (1024px) and redirect to the general section at or above it, where a slim inline category list (visually lighter than the app SideNav rail) sits beside the section content. Crossing the breakpoint keeps the current route; growing to desktop while on the bare index triggers the redirect.
  • Category metadata (slug, title, one-line description, icon, danger flag) is declared once per settings area and drives routes, index rows, and the desktop list. The danger category is pinned last and styled in the app's danger red.
  • Each section is its own form keeping today's endpoints and dirty-fields-only PATCH semantics; Save enables only when dirty. A single shared form primitive (useSettingsForm) owns dirty tracking, submit, and the unsaved-changes guard (useBeforeLeave from @solidjs/router plus beforeunload, both via native confirm). Device-local or act-immediately sections (Notifications, Install app, Secrets, Danger zone) stay guard-free.
  • Mobile section pages get a back-chevron + title header navigating to the index (not browser-back). Repo breadcrumbs gain a Settings / <Section> trail where "Settings" links to the index; global settings stays breadcrumb-free.
  • This issue introduces the lucide-solid dependency; index/list icons come from it, imported per-icon, rendered ~18–20px, inheriting currentColor in both themes.

Key interfaces:

  • A per-area category metadata array (slug, title, description, icon component, danger flag) — the single source for route generation, index rows, and the desktop category list
  • useSettingsForm — shared primitive for dirty tracking, dirty-fields-only PATCH submit, and the leave/close guard
  • Existing settings PATCH endpoints — payload semantics must not change; only the UI is restructured
  • useBeforeLeave (@solidjs/router) and beforeunload for the guard

Acceptance criteria:
The checklist in the issue body is the acceptance contract. Highlights: every existing field reachable under the new categories with unchanged PATCH payloads; guard fires on in-app navigation and tab close only for dirty form sections; deep links work on both form factors; old monolith route files removed with every new file under the 1000-line repolint cap and existing /repos/:id/settings links still working; tests cover metadata↔route wiring, per-section dirty PATCH payloads, and guard behavior.

Out of scope:

  • Migrating the app's existing hand-inlined SVG icons elsewhere in the SPA to lucide-solid — that is #199 (blocked on this issue); only the new settings UI uses lucide icons here
  • Any backend/API changes — endpoints, payload shapes, and validation stay as they are
  • Redesigning or regrouping the fields within a section beyond moving them under their category
  • Changing save semantics (no autosave, no whole-page save)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Restructure global and repo settings from single long pages into a category-list → detail-subpage IA (mobile index, desktop master-detail), with per-section forms and an unsaved-changes guard. **Current behavior:** Global settings and repo settings each render as one long single-route page (`/settings` and `/repos/:id/settings`) containing every section stacked vertically. The repo settings component is over the 1000-line cap (currently ~1215 lines). Icons in the app are hand-inlined SVGs; there is no icon library dependency. Saves already use dirty-fields-only PATCH per section, but there is no navigation guard for unsaved edits. **Desired behavior:** The issue body is the authoritative spec — categories, routes, responsive behavior, save model, and code layout are all defined there. The behavioral contract in short: - `/settings/:section` and `/repos/:id/settings/:section` render one category each. The bare routes render a tappable category index below the app shell's desktop breakpoint (1024px) and redirect to the `general` section at or above it, where a slim inline category list (visually lighter than the app SideNav rail) sits beside the section content. Crossing the breakpoint keeps the current route; growing to desktop while on the bare index triggers the redirect. - Category metadata (slug, title, one-line description, icon, danger flag) is declared once per settings area and drives routes, index rows, and the desktop list. The `danger` category is pinned last and styled in the app's danger red. - Each section is its own form keeping today's endpoints and dirty-fields-only PATCH semantics; Save enables only when dirty. A single shared form primitive (`useSettingsForm`) owns dirty tracking, submit, and the unsaved-changes guard (`useBeforeLeave` from @solidjs/router plus `beforeunload`, both via native `confirm`). Device-local or act-immediately sections (Notifications, Install app, Secrets, Danger zone) stay guard-free. - Mobile section pages get a back-chevron + title header navigating to the index (not browser-back). Repo breadcrumbs gain a `Settings / <Section>` trail where "Settings" links to the index; global settings stays breadcrumb-free. - This issue introduces the `lucide-solid` dependency; index/list icons come from it, imported per-icon, rendered ~18–20px, inheriting `currentColor` in both themes. **Key interfaces:** - A per-area category metadata array (slug, title, description, icon component, danger flag) — the single source for route generation, index rows, and the desktop category list - `useSettingsForm` — shared primitive for dirty tracking, dirty-fields-only PATCH submit, and the leave/close guard - Existing settings PATCH endpoints — payload semantics must not change; only the UI is restructured - `useBeforeLeave` (@solidjs/router) and `beforeunload` for the guard **Acceptance criteria:** The checklist in the issue body is the acceptance contract. Highlights: every existing field reachable under the new categories with unchanged PATCH payloads; guard fires on in-app navigation and tab close only for dirty form sections; deep links work on both form factors; old monolith route files removed with every new file under the 1000-line repolint cap and existing `/repos/:id/settings` links still working; tests cover metadata↔route wiring, per-section dirty PATCH payloads, and guard behavior. **Out of scope:** - Migrating the app's existing hand-inlined SVG icons elsewhere in the SPA to lucide-solid — that is #199 (blocked on this issue); only the new settings UI uses lucide icons here - Any backend/API changes — endpoints, payload shapes, and validation stay as they are - Redesigning or regrouping the fields *within* a section beyond moving them under their category - Changing save semantics (no autosave, no whole-page save)
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.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#198
No description provided.