Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .claude/skills/sidebar-nav-redesign/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: sidebar-nav-redesign
description: Use this skill when redesigning a Vue 3 app's navigation into a modern SaaS-style left sidebar layout, replacing a top nav bar, introducing CSS design tokens, or making an app's chrome (nav, header, sticky bars, profile menu) more consistent and professional-looking.
---

# Sidebar Nav Redesign

Turns a Vue 3 app's top-nav chrome into a modern SaaS-style left sidebar: collapsible, icon-led, with consistent spacing via extracted design tokens. This skill is a **procedure**, not a code generator — it never edits `.vue` files itself. It discovers the app's current chrome, designs the token/sidebar spec, then hands the actual file writes off to a Vue-capable agent (this repo's `vue-expert` subagent, if invoked from here; otherwise whatever frontend-editing agent/tool the host project designates).

Read the reference files as you reach each phase — don't front-load all of them.

## Phase 0 — Scope check

Confirm the target is a Vue 3 SPA: find `package.json` with a `vue` dependency, and a router (`vue-router` or a hand-rolled route table). If it isn't Vue 3, stop and say so — this skill assumes Single File Components and doesn't care whether they use the Options or Composition API.

## Phase 1 — Discover current chrome architecture

Read `references/discovery.md` and apply its heuristics to find: the shell/root component, the nav markup inside it, any companion pieces coupled to nav height/position (sticky bars, dropdowns), and the authoritative route list — then cross-check that list against existing nav links and i18n keys to catch gaps (a view that exists but has no nav entry, a nav label with no translation key, etc.).

## Phase 2 — Design tokens

Read `references/design-tokens.md`. **Extract, don't invent**: pull the app's existing hardcoded colors/spacing/radii into a `:root` CSS custom-property block so the redesign preserves the app's brand feel instead of replacing it wholesale.

## Phase 3 — Sidebar anatomy

Read `references/sidebar-anatomy.md`. Fixed structural slots: brand/logo top, nav item list (icon + label + active-state, icon-only when collapsed), a collapse/expand toggle, and a footer slot for the profile/account menu. Reuse whatever icon approach the app already has; never add a new icon dependency without asking the user first.

## Phase 4 — Responsive / mobile spec

Also in `references/sidebar-anatomy.md`. Desktop keeps the sidebar always visible (collapsible to an icon-only rail); below ~768px (or the app's existing breakpoint) it becomes an off-canvas drawer triggered by a slim top bar with a hamburger toggle.

## Phase 5 — Migrate companion pieces

Using Phase 1's findings: recalculate or remove any sticky/fixed offset that was hardcoded against the old nav's height; flip any dropdown that assumed a top-right, downward-opening position (a footer-anchored menu opens upward instead); fix the nav-link/i18n gaps found in Phase 1 in this same pass, not later.

## Phase 6 — Handoff

Do not write `.vue`, `.js`, or CSS files yourself. Compose one scoped delegation prompt containing:
- The concrete file list from Phase 1
- The exact token names/values from Phase 2
- The anatomy + responsive acceptance criteria from Phases 3–4
- The specific gaps to fix from Phase 5
- An explicit instruction: presentation-layer only, no changes to API calls, data logic, or business logic

Then delegate to the project's designated Vue-editing agent (in this repo, that's `vue-expert` — mandatory per this repo's root `CLAUDE.md`).

## Phase 7 — Verify

Start the dev server and drive the app in a browser (Playwright MCP in this repo, against `localhost:3000`): confirm the sidebar renders, active-route highlighting works, the collapse toggle works, the profile menu opens without clipping off-screen, the mobile drawer opens/closes at the breakpoint, and there are no new console errors.

## Common pitfalls

Read `references/pitfalls.md` before finishing — it's a short generalized do/don't list (sticky-offset coupling, dropdown-direction assumptions, nav/i18n parity drift, inventing tokens instead of extracting them) worth checking your output against regardless of which app you're redesigning.

## Templates

`templates/sidebar.vue.template` and `templates/tokens.css.template` are annotated skeletons, not copy-paste-ready components — adapt class names, exact values, and slot content to the target app before handing them to the editing agent.
61 changes: 61 additions & 0 deletions .claude/skills/sidebar-nav-redesign/references/design-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Design tokens: extract, don't invent

The fastest way to make a redesign feel disconnected from the app it came from is to replace its existing colors and spacing with a generic new palette. Instead, mine the app's current hardcoded values and formalize them into CSS custom properties. The redesign should feel like the same app, tidied up — not a different app wearing the old app's logo.

## Method

1. Grep the shell component and 2–3 representative components/views for hex colors, `rem`/`px`/`em` spacing values, and `border-radius` values:
```
grep -rno "#[0-9a-fA-F]\{3,6\}" src/ | sort | uniq -c | sort -rn
grep -rno "[0-9.]\+rem\|[0-9]\+px" src/ | sort | uniq -c | sort -rn
```
2. Cluster near-duplicates (`#2563eb` and `#2563EB` are the same color; `1.5rem` appearing 40 times is a real spacing unit, `1.37rem` appearing once probably isn't).
3. Name the survivors as tokens by **role**, not by raw value — a future edit to "the primary color" shouldn't require renaming a variable called `--blue-600`.

## Minimal token set to always produce

**Spacing scale** — a 4px or 8px multiple ladder, however many steps the app's actual usage supports (don't invent 10 steps if the app only really uses 4 distinct spacing values):
```css
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.5rem;
--space-6: 2rem;
```

**Color roles:**
```css
--color-bg: ...; /* page background */
--color-surface: ...; /* card/panel background */
--color-border: ...; /* dividers, card borders */
--color-text: ...; /* primary text */
--color-text-muted: ...; /* secondary/caption text */
--color-primary: ...; /* brand/action color */
--color-primary-hover: ...;
--color-accent: ...; /* if the app has a secondary brand color */
```
If the app has status colors (success/warning/danger badges, etc.), extract those too as `--color-success`, `--color-warning`, `--color-danger` — don't leave them as scattered literals.

**Radius scale:**
```css
--radius-sm: ...;
--radius-md: ...;
--radius-lg: ...;
```

**Shadow tokens** (only if the app already uses box-shadow anywhere):
```css
--shadow-sm: ...;
--shadow-md: ...;
```

## Where to put the block

Put the `:root { }` block in whichever file already holds global unscoped styles — usually the shell component's `<style>` block (found in Phase 1 discovery), or an existing `src/styles/`/`src/assets/` global stylesheet if one exists. Don't create a new global CSS file unless the app has zero existing convention for one; introducing a new file pattern is a bigger footprint than the redesign needs.

If the app already uses Tailwind, extend `tailwind.config.js`'s `theme.extend` with these same role names instead of hand-writing CSS custom properties — don't run two token systems side by side.

## Applying tokens

Once defined, the sidebar and its companion pieces (Phases 3–5 in `SKILL.md`) should reference tokens exclusively — no new hardcoded hex/px values introduced by this redesign. Existing components elsewhere in the app do not need to be swept and converted to tokens as part of this task unless the user asks for that separately; scope the token conversion to the chrome being touched.
54 changes: 54 additions & 0 deletions .claude/skills/sidebar-nav-redesign/references/discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Discovery: finding chrome in an unfamiliar Vue 3 app

Don't assume the shell component is called `App.vue`, or that nav classes are named `.nav`/`.header`. Find things by structural signal, not naming convention — the app you're pointed at may call things anything.

## 1. Find the shell component

Grep the whole `src/` tree for `router-view`:

```
grep -rl "router-view" src/
```

Whichever file contains `<router-view` (usually exactly one, sometimes wrapped in a `<transition>` or `<keep-alive>`) is the chrome/shell component, regardless of its filename. Everything outside that `<router-view>` in its template is persistent chrome — nav, header, global modals, footers.

## 2. Find the nav markup

Inside the shell component, look for:
- A `<nav>` or `<header>` element
- Containing multiple sibling `<router-link>` or `<a>` elements
- Or an element whose class name contains `nav`, `header`, `menu`, `tabs`, `sidebar` (the last one may mean a sidebar already partially exists — check before assuming a top-nav-only layout)

Note whether the nav is currently horizontal (top bar) or already vertical — this skill's job is to end at a left vertical sidebar either way, but a partial sidebar may already have components worth reusing (icons, active-state logic).

## 3. Find companion pieces coupled to nav position/height

Grep for layout coupling that will break once the nav's shape changes:

```
grep -rn "sticky\|position: *fixed\|top: *[0-9]" src/
```

Any hardcoded `top:` value that isn't `0` is a candidate for recalculation — it's very likely keyed to the current nav's height so that some other element (a filter bar, a secondary toolbar) stays visible just below it.

## 4. Find profile/account menus

Search component names and template content for `dropdown`, `menu`, `profile`, `account`, `avatar`. Note its current anchor position (usually top-right of the nav) and open-direction (`top: calc(100% + Xpx)` = opens downward). This will need to flip when relocated to a sidebar footer.

## 5. Enumerate the authoritative route list

Read the router config (wherever `createRouter`/`createWebHistory` is called, typically `src/main.js`, `src/router.js`, or `src/router/index.js`). The `routes` array is ground truth for "what pages exist." Cross-check:

- Does every route have a corresponding nav link in the shell component? (A view file existing in `src/views/` with no route or no nav link is a common gap — flag it.)
- If the app has i18n (look for `vue-i18n`, a custom `useI18n` composable, or a `locales/` directory), does every nav label have a translation key in every locale file? Flag any nav label that's hardcoded in one language while everything else goes through the translation function.

Carry these gaps forward into Phase 5 (migrate companion pieces) of `SKILL.md` — fix them in the same pass rather than leaving them for later, since you're already touching the nav.

## 6. Check for an existing design system

Before assuming there are no design tokens, check for:
- A `:root { }` block with `--`-prefixed custom properties anywhere in the codebase
- Tailwind config (`tailwind.config.js`) — if present, the app already has a token system (Tailwind's theme) and this skill's token work should map onto Tailwind theme extension, not a parallel CSS variable system
- A `src/styles/` or `src/assets/` directory with shared stylesheets

If Tailwind or an existing token system is present, adapt Phase 2 of `SKILL.md` to extend it rather than introducing a competing one.
19 changes: 19 additions & 0 deletions .claude/skills/sidebar-nav-redesign/references/pitfalls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Common pitfalls

Generalized lessons, not tied to any one app — check the redesign against these before calling it done.

- **Don't hardcode sticky offsets to another element's height.** A `top: 70px` on a filter bar because "that's the nav's height" breaks the moment the nav's shape changes — which is exactly what this redesign does. Prefer `top: 0` in the new sidebar+content layout, or let a flex/grid container handle the offset structurally instead of a magic number.

- **Don't assume a dropdown's open-direction is inherent to the component.** A menu opening downward from the top-right is a function of where its anchor currently sits in the viewport, not a property of "how dropdowns work" — always re-derive open-direction when relocating an anchor (e.g., sidebar footer → opens upward).

- **Don't let route/nav-link/i18n parity drift.** Every route should have a nav entry and a translation key in every locale, or an explicit, deliberate reason it's hidden (e.g., an admin-only route). Treat "does every route have a nav link and an i18n key" as a mandatory checklist item on any change that touches navigation — not just something to get right at initial creation and never revisit.

- **Don't invent a new visual language.** Extract design tokens from the app's existing hardcoded values first (see `design-tokens.md`); only introduce genuinely new values when there's a real gap (e.g., no existing "danger" color anywhere for a destructive action that needs one).

- **Don't add a new dependency silently.** No icon library in `package.json`? Use inline SVGs, or ask the user before adding one. The same applies to any other new package a "polish" pass might tempt you to reach for.

- **Do preserve all existing functionality.** Filters, language switchers, auth actions, notifications — everything reachable in the old nav must still be reachable after. A redesign relocates UI; it does not remove features.

- **Mobile sidebar is off-canvas, not a shrunken rail.** An icon-only collapsed rail is a desktop affordance for reclaiming horizontal space next to content that's already visible. On a phone-width viewport there's no spare width to reclaim from — hide the sidebar entirely and trigger it via a drawer instead of trying to keep a permanently-visible rail.

- **Don't skip verification because "it's just CSS."** Layout changes are exactly the kind of change that looks fine in the diff and breaks in the browser (overlapping elements, clipped dropdowns, a drawer that doesn't close). Always drive the app in a real browser at both desktop and mobile widths before calling the redesign done.
39 changes: 39 additions & 0 deletions .claude/skills/sidebar-nav-redesign/references/sidebar-anatomy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Sidebar anatomy and responsive spec

## Structural slots (top to bottom)

1. **Brand/logo slot** — reuse whatever markup the old top nav's logo used (company name, subtitle, mark/icon). Don't redesign the brand mark itself; this skill's job is the nav shell, not the logo.

2. **Nav item list** — one entry per route from the authoritative route list (Phase 1 discovery). Each item:
- Icon + label, both visible when expanded
- Icon only when the sidebar is collapsed (label available via `title` attribute or a hover tooltip so it isn't lost, just hidden)
- Active state driven by vue-router's own `router-link-active`/`router-link-exact-active` classes — style these, don't reimplement route-matching logic with manual `$route.path === '...'` comparisons
- Icons: check `package.json` for an existing icon library dependency (e.g. `lucide-vue-next`, `@heroicons/vue`, `vue-feather`) and reuse it. If none exists, fall back to inline SVGs copied from a neutral icon set — never add a new icon package dependency without asking the user first, since that's a footprint decision beyond "redesign the layout."

3. **Collapse/expand toggle** — a button, typically at the bottom of the nav list or top-right corner of the sidebar itself (a chevron that flips direction). Persist the collapsed/expanded boolean in a small composable (name it consistently with the app's existing composable naming convention — e.g. if the app has `useFilters.js`/`useAuth.js`, call this one `useLayout.js` or `useSidebar.js`). Keep the composable to just this one concern; don't fold unrelated layout state into it.

4. **Footer slot** — the profile/account menu relocates here. Since it's now pinned near the viewport bottom, its dropdown must open **upward**:
```css
/* before (top-right of a top nav): */
.dropdown { top: calc(100% + 0.5rem); right: 0; }

/* after (sidebar footer): */
.dropdown { bottom: calc(100% + 0.5rem); left: 0; }
```
Verify it doesn't clip off the top of the viewport when the sidebar is near the bottom of a short window.

## Collapsed state

Collapsing hides labels and shrinks the sidebar to an icon-only rail (roughly 64–72px wide vs. 220–260px expanded). This is a **desktop-only** affordance — see mobile behavior below, where a rail-width sidebar is still too cramped alongside real page content and the sidebar should hide entirely instead.

## Responsive / mobile behavior

Breakpoint: match whatever breakpoint the app already uses elsewhere (grep for existing `@media` queries first); default to ~768px if none exists.

- **Desktop (> breakpoint):** sidebar always visible, full-height, collapsible to the icon-only rail described above. Content area's left margin/grid column adjusts to the sidebar's current width.
- **Mobile (≤ breakpoint):** sidebar becomes an off-canvas drawer — `position: fixed`, translated fully off-screen (`transform: translateX(-100%)`) by default. A slim top bar (just a hamburger toggle + brand mark, not a full nav) triggers it open. An overlay/backdrop behind the open drawer closes it on click, and the drawer itself should close on route navigation (so picking a page doesn't leave the drawer hanging open).
- Prefer CSS `@media` queries for the visual show/hide and width changes; keep JS state to the minimum needed (an `isDrawerOpen` boolean), rather than tracking viewport width in JS if CSS alone can do it.

## What must not change

Filters, language switcher, auth/profile actions, and any other existing functionality in the old nav must all still be reachable after the redesign — this is a relocation of chrome, not a feature cut. If the old nav had a language switcher next to the profile menu, it moves to the sidebar footer alongside the profile menu; it doesn't disappear.
Loading