356 Commits

Author SHA1 Message Date
1ba5d5bb9b docs(plan-2.5): closure — RFC supersession, BACKLOG landing, applyDomAttributes dedupe
Plan 2.5 final phase (P8). Closes the PrimeVue shell-migration workstream.

- RFC-WS-PRIMEVUE-PLAN-2-5: added Supersessions section recording the
  governing-RFC divergences (§4 dark mode `<body>`→`<html>` per AD-2.5-D1;
  §7.4 workspace sub option A reversed to placeholder after visual
  review). Added closure summary (phases, ADs, brand-square recipe,
  suite delta, lessons). Status → COMPLETE.
- BACKLOG: landed 8 items surfaced during Plan 2.5 (MOBILE-SHELL-PARITY,
  WORKSPACE-DROPDOWN-SUB-CONTENT, DENSITY-AWARE-SPACING, TOPBAR-H-VAR-
  DECLARE, CSP-FONT-SRC-LOCKDOWN, AUTO-IMPORTS-V2-SCAN, PNPM-RESOLUTIONS-
  ROOT, SHELLUI-STALE-DATA-THEME-CLEANUP). Marked GRADIENT-BRAND-
  ALIGNMENT as resolved.
- useShellUiStore.toggleDensity: removed redundant applyDomAttributes()
  call (the AppShellV2 watch already covers density changes). Moved the
  DOM-write assertion to AppShellV2 watcher-coverage specs.

Plan 2.5 status: COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:23:39 +02:00
30da66456a style(layout): unify workspace avatar with header logo (size/offset/centering parity)
Holistic parity pass after repeated piecemeal switcher tweaks kept
diverging from the header logo. Side-by-side audit found size, offset
and collapsed-centering already matched (both 32px at 16px); the real
divergences were (a) the inset-shadow opacity (logo 8% vs avatar 10%)
sourced from TWO separate scoped-CSS blocks (drift risk), and (b) the
hover-bg inset was only 8px ("stuck against the edge").

- Shared brand-square recipe: both the SidebarHeader logo and the
  WorkspaceSwitcher avatar now use the SAME Tailwind utilities
  `w-8 h-8 rounded-lg shadow-[inset_0_-2px_0_#00000014]`. Single
  source (the utility classes) so size/radius/shadow can't drift
  again. The two per-component scoped `.mark` / `.ws-logo-square`
  box-shadow rules are deleted (the dropdown's larger
  `.ws-logo-square-lg` stays scoped — out of scope). Only the gradient
  differs by design (brand teal vs per-org).

- Breathing room: the avatar's horizontal offset is pinned at 16px by
  the collapsed rail (64px = 32px square + 2×16px → the only offset
  that centres the square AND matches the logo's px-4). Within that
  fixed 16px, the budget is split inset + internal padding: wrapper
  px-3 (12px hover-bg inset for breathing room, was 8px) + trigger
  px-1 (4px internal). Vertical is unconstrained → py-2 both for a
  generous hover-bg height. The offset stays 16px so logo↔avatar
  parity and the no-jump invariant are preserved; only the hover-bg
  inset grew from 8px to 12px.

Note: the prompt's 20px-offset option is incompatible with the fixed
64px collapsed rail (20+32+20≠64 → breaks centring + reintroduces a
jump), so the 16px-offset / 12px-inset path was taken per the brief's
stated alternative.

Specs: new cross-component parity spec mounts BOTH components and
asserts the avatar + logo share the exact w-8/h-8/rounded-lg/shadow
utilities; padding spec updated to px-3 wrapper + px-1 trigger.
Borderless + hover/open-bg + sub specs retained.

Suite delta: 571 → 572 (+1). vue-tsc clean. Scoped ESLint clean
(0 errors). Desktop only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:12:06 +02:00
9f56fb1112 style(layout): borderless WorkspaceSwitcher with hover/open background; fix avatar jump
Matches the crewli-starter SoT and fixes the recurring collapse jump at
its root cause. The prior structures left a residual avatar shift:
- the original split put the avatar at 24px expanded (wrapper px-4 +
  card p-2) vs 16px collapsed (bare square) — an 8px horizontal jump;
- the interim single-trigger variant used wrapper p-[10px] + trigger
  px-[10px] expanded (~20px) vs justify-center collapsed (16px) — a
  ~4px residual horizontal shift.

Unified both states to a single symmetric structure:

    avatar offset = wrapper px-2 (8px) + trigger p-2 (8px) = 16px

16px from the rail's left edge in BOTH states — identical to the
SidebarHeader brand logo. Because the padding is symmetric (8 + 8 each
side) and the collapsed rail is 64px = 16 + 32 + 16, the left-aligned
avatar is also visually centred when collapsed — no justify-center,
no px swap, no horizontal shift; constant vertical padding, no vertical
shift. The jump is gone at the root.

Borderless: the trigger has NO border in any state (the prior is-open
border is dropped per the starter screenshots). The only divider is the
wrapper's border-t between the switcher and the nav. The grey
background is the sole fill — transparent at rest, grey on hover, and
grey while the popover is open (isOpen wired to Popover @show/@hide).
The trigger's p-2 gives the grey background generous padding around the
avatar+text, matching the starter's hover treatment, and since it is
the button's own background it never moves the content.

Specs reworked: trigger p-2 identical across states (no px swap / no
justify-center — the no-jump lock), wrapper carries p-2, trigger is
borderless at rest AND while open, open-state grey background applies
on @show and clears on @hide. Single-.trigger / rounded-lg / collapsed-
hides-meta+chev / sub-line specs retained.

Suite delta: 571 → 571 (specs reworked, count unchanged). vue-tsc
clean. Scoped ESLint clean (0 errors). Desktop only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:49:08 +02:00
9f26215e54 fix(layout): WorkspaceSwitcher crewli-starter parity — no avatar jump, is-open bg, no resting border
Diagnosed against the crewli-starter SoT
(crewli-starter/src/components/layout/WorkspaceSwitcher.vue +
main.css .ws-switcher rules). Three issues Bert flagged in manual
smoke, one root cause:

ROOT CAUSE — the prior version swapped between TWO separate <button>
elements on collapse (a bare collapsed button vs a padded expanded
trigger) with different box models. Their vertical padding differed,
so the avatar's distance from the rail bottom changed by ~6px and
visibly jumped on collapse. crewli-starter instead renders ONE
`.trigger` button in both states and only hides `.meta` + `.chev`
(+ recentres) on collapse.

Fixes (all by adopting the single-trigger structure):
- Avatar no longer jumps: one `.trigger` button always; `py-2`
  vertical padding constant across states; only horizontal
  padding/justify changes on collapse. Avatar's vertical box is now
  identical collapsed vs expanded.
- No resting border: trigger is `border-transparent` at rest and on
  hover (hover only adds the grey bg), matching crewli-starter
  `.trigger:hover { background }`. The wrapper has only the `border-t`
  separator, no box border.
- is-open persistence: new `isOpen` ref synced from the PrimeVue
  Popover `@show`/`@hide` events. While the dropdown is open the
  trigger keeps the grey bg AND shows a visible border, matching
  crewli-starter `.ws-switcher.is-open .trigger { background;
  border-color }`. Persists until the popover closes (covers
  programmatic hide via selectOrg + outside-click dismissal).
- Hover padding: trigger `px-[10px] py-2` inside a `p-[10px]` wrapper
  reproduces crewli-starter's generous hover-card inset
  (`.ws-switcher { padding:10px } .trigger { padding:8px 10px }`).

Collapsed alignment preserved: wrapper p-[10px] + trigger
`justify-center px-0` centres the 32px avatar at 16px from the rail's
left edge — still aligned with the SidebarHeader brand logo (px-4).
At rest the collapsed trigger is transparent, so it still reads as a
bare square mirroring the header logo; hover/open add the card.

Specs: replaced the now-obsolete "bare avatar button / no .trigger"
+ "collapsed wrapper px-4" locks with crewli-starter-structure specs:
single .trigger in both states, collapsed centres the lone avatar +
hides .meta/.chev, py-2 constant (the no-jump lock), and is-open
keeps bg+border. Sub-line + dropdown specs unchanged.

Suite delta: 569 → 571 (+2). vue-tsc clean. Scoped ESLint clean
(0 errors). Desktop only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:31:44 +02:00
2fd3c9ea66 style(layout): re-add placeholder workspace sub line + unify avatar across states
Reverses AD-2.5-W1 option A (no-sub) after visual review against the
crewli-starter SoT — the two-line layout reads better. Adds a
light-grey placeholder sub line under the workspace name (expanded
trigger + dropdown items); collapsed stays bare-avatar-only. No
backend: the placeholder is a neutral static string ('Organisatie'),
real org type + metrics still deferred under
WORKSPACE-DROPDOWN-SUB-CONTENT. The org object exposes no field that
reads well as a subtitle today (id/name/slug/role only; role is an
access identifier, not a description), so a neutral string is used
rather than fabricated metrics or the role string P4 originally
removed.

Fix A — avatar unified across collapsed/expanded. The collapsed
avatar styling previously lived directly on the <button>, letting
user-agent button rendering diverge subtly from the expanded <span>
avatar. The collapsed render now wraps the EXACT SAME avatar span
markup (same classes, gradient, .ws-logo-square inset-shadow) in a
bare transparent p-0 button — the 32px square is byte-identical
across states; only the surrounding context differs.

Fix B — sub line re-added to WorkspaceDisplay (cleanly typed as
`sub: string`, sourced from a SUB_PLACEHOLDER const with a TODO
pointing at the deferred backend). Rendered light-grey
(text-[var(--p-text-muted-color)], matching the in-component muted
text) at text-[11.5px] in the trigger and text-[12.5px] in dropdown
rows, mirroring the pre-P4 sizes. Collapsed renders no sub.

Specs: reversed the P4/P5 no-sub locks to sub-present assertions
(trigger sub present + honest-placeholder/not-role; dropdown sub on
every row + no role leak; collapsed-no-sub via `.meta .sub`).
Updated the collapsed-bare-avatar spec for the new span-in-button
structure (.ws-logo moved from button to inner span).

Suite delta: 566 → 569 (+3). vue-tsc clean. Scoped ESLint clean
(0 errors). No backend, no fabricated data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:43:14 +02:00
cd118bd165 fix(theme): align avatar gradients to Crewli brand teal + diverse palette
P7 token audit (8330e93f §10) found two off-brand color bugs:

- utils/v2/gradient.ts GRADIENT_PALETTE used Tailwind blue-green
  anchors (teal-500/600 #0d9488, cyan, emerald, sky) clustered in a
  single hue family. Two problems: the brand-anchor slot used Tailwind
  teal #0d9488, NOT Crewli's #0D9394, AND orgs in multi-workspace
  views all rendered as similar teal/green squares (poor
  distinguishability). Replaced with the crewli-starter SoT palette:
    [0] #0D9394 → #075F60  (Crewli teal — brand anchor)
    [1] #7C3AED → #4C1D95  (purple)
    [2] #EA580C → #9A3412  (orange)
    [3] #16A34A → #14532D  (green)
    [4] #F59E0B → #92400E  (amber)
    [5] #EC4899 → #9D174D  (pink)
    [6] #4F46E5 → #312E81  (indigo — added for org-distinguishability)
    [7] #E11D48 → #881337  (rose — added for org-distinguishability)
  Palette stays 8-entry; only the values change. Indexing logic
  (djb2 hash % 8) unchanged. Per-org avatar colors are not persisted
  pre-launch, so the slot reshuffle is safe.

- AppTopbar.vue user-avatar gradient (two sites: the trigger Avatar +
  the user-menu header Avatar). Fallback in the CSS var was #0d9488
  (Tailwind teal-600), NOT Crewli #0D9394 — if the var ever fails to
  resolve, the chrome would render off-brand. Fixed to #0D9394.
  The hardcoded pink #f472b6 in the gradient's from-color was kept
  intentionally: it matches the crewli-starter SoT (user avatars are
  a pink/purple gradient distinct from workspace chrome's teal — the
  visual contrast between "your account" and "your workspace" is
  by design).

Regression locks:
- gradient.spec.ts +2 specs: brand-anchor slot is #0D9394 (and
  defensively, #0d9488 must not appear anywhere in the palette);
  palette spans diverse hue families (purple + orange present
  beyond the teal anchor).

Suite delta: 564 → 566 (+2). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 02:38:27 +02:00
5e3335bf6d style(layout): give expanded WorkspaceSwitcher breathing room from rail edges
Manual smoke showed the expanded trigger flush against the rail edges:
the wrapper used px-4 when collapsed but only p-2 when expanded.
Unified the expanded wrapper to `px-4 py-2`, matching the collapsed
state and the header brand row, so the rounded trigger card is inset
16px from both rail edges with symmetric breathing room consistent
with the header's px-4 rhythm.

Trade-off documented in the wrapper comment: with px-4 wrapper + p-2
internal trigger padding, the expanded avatar sits ~24px from the
rail edge — ~8px deeper than the bare header logo at 16px. Accepted
as deliberate (the expanded trigger reads as a distinct rounded
button card). The bare-square / logo mirror lives in the collapsed
state, which is unchanged.

No spec changes — the prior assertions cover the collapsed wrapper
(px-4 + h-[56px]) and the expanded trigger's rounded-lg shape, both
still hold. Suite delta: 564 -> 564.

Desktop only. vue-tsc clean. Scoped ESLint clean (0 errors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 02:08:19 +02:00
a9c5746e12 style(layout): align WorkspaceSwitcher avatar to the header logo recipe
Manual smoke after the logo-anchor fix (63300e5f) showed the footer
WorkspaceSwitcher used different spacing and a boxed avatar vs the
header logo. Applied the identical alignment recipe so the workspace
avatar mirrors the brand logo in both states (Option B from the
brief — bare square when collapsed, padded trigger only when
expanded).

Changes (WorkspaceSwitcher.vue only):
- Wrapper toggles `h-[56px] flex items-center px-4` when collapsed
  (mirrors the SidebarHeader brand row) and stays `p-2` when
  expanded (room for the padded trigger).
- Collapsed: a BARE rounded-lg avatar button at the same 16px left
  offset as the header logo. No `.trigger` container, no rounded
  hover-bg box wider than the avatar — the button IS the visible
  square (`.ws-logo .ws-logo-square w-8 h-8 rounded-lg`). True
  top/bottom mirror of the brand square.
- Expanded: unchanged padded `.trigger` button with avatar + name +
  chevron + hover bg. Avatar's left offset stays at 16px from the
  rail (wrapper p-2 + trigger p-2) so the expanded avatar also
  lines up vertically with the header logo.

Same alignment equation as the header recipe:

    rail_collapsed (64px) = square (32px) + 2 × px-4 (2 × 16px)

In both states the avatar's left edge sits at x=16px from the
rail's left — identical to the brand logo above. Vertical line
down the left side now reads as a single column of squares.

Desktop only. Mobile drawer chrome stays as MOBILE-SHELL-PARITY.

Tests adapted:
- `expanded trigger uses rounded-lg` (was tested in both states; the
  collapsed render no longer has a `.trigger` container).
- `expanded trigger has no justify-center` (split from the
  prior two-state assertion).
- New: `collapsed renders a bare avatar button (no .trigger
  container, just .ws-logo)` — locks the bare-square contract.
- New: `collapsed wrapper uses px-4` — locks the
  centring-equation invariant (rail=square+2×px-4) against
  accidental wrapper-padding regressions.

Suite delta: 563 → 564 (+1 net: +2 new collapsed-shape asserts,
−1 redundant two-state assert).

vue-tsc clean. Scoped ESLint clean (0 errors, pre-existing
warnings only). Manual smoke pending Bert — draw a vertical line
down the rail's left edge and verify the brand square and the
workspace square left edges sit on it in both states; in collapsed
mode verify the avatar is a bare square (no boxed button), same
visual treatment as the bare logo above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:08:41 +02:00
63300e5fc9 style(layout): logo stays anchored on collapse + squarer corners
Refines the prior sidebar styling commit (8e166512) after manual smoke:

- Brand logo + workspace avatar: rounded-xl -> rounded-lg (crisper
  square per design review; both stay unified at the same radius).
- Logo no longer jumps on collapse. The previous code toggled
  `justify-between` ⇄ `justify-center` on the header row, which
  re-centred the logo against the parent's width — and the parent
  width animates from 256px to 64px over 200ms, so the logo slid
  from x≈112px (centred in the expanded rail) to x=16px (centred in
  the collapsed rail). Visible jump.

  Fix: the brand row is now ALWAYS `px-4` and left-aligned. The
  logo's horizontal offset (16px from the rail's left edge) is
  identical in expanded and collapsed states. Why this still looks
  centred when the rail collapses:

      rail_collapsed (64px) = logo (32px) + 2 × px-4 (2 × 16px)

  With those numbers aligned, a stationary left-aligned logo IS
  visually centred in the 64px-wide collapsed rail. The width
  transition then "slides the rail closed around" the anchored
  logo. Wordmark + Beta badge sit to the RIGHT of the logo and
  v-if-disappear on collapse; their absence doesn't shift the logo
  because they were never to its left.
- Toggle chevron placement:
  - Expanded: collapse chevron (◀) inline at the right of the brand
    row, pushed by `ms-auto` (NOT by justify-between forcing the
    layout to recentre the logo).
  - Collapsed: a SECOND row below the brand row holds a centred
    expand chevron (▶) button. Replaces the prior tucked-chip that
    overlapped the logo. No overlap, no overhang needed against the
    aside's `overflow-hidden`.
- WorkspaceSwitcher trigger: same anchor-on-the-left treatment
  applied. The `justify-center` switch on collapse is gone (it
  caused an identical avatar slide). Wrapper padding `p-[10px]` ->
  `p-2` so the avatar's left offset (wrapper 8 + trigger 8 = 16px)
  matches the SidebarHeader logo (px-4 = 16px) — the brand square
  and the workspace square are now vertically aligned in the
  collapsed rail.

Desktop only. Mobile drawer chrome tracked separately as
MOBILE-SHELL-PARITY.

Tests adapted:
- WorkspaceSwitcher.spec.ts: trigger-rounded assertion bumped
  rounded-xl -> rounded-lg; +1 spec locks "trigger never carries
  justify-center" (avatar-anchored invariant).
- SidebarHeader.spec.ts: collapsed-behaviour spec rewritten — was
  asserting `justify-center`, now asserts the row carries `px-4`
  WITHOUT either justify-center OR justify-between (the actual
  anchor contract); +1 spec confirms the expand chevron lives in a
  SIBLING row of the brand mark (no overlap).

Suite delta: 561 -> 563 (+2). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).

Manual smoke pending Bert: collapse/expand slowly and watch the
logo — must NOT move horizontally. Confirm rounded-lg looks crisp
(not pebble-soft). Confirm expand chevron sits in its own row
below the logo, no overlap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 00:45:08 +02:00
8e16651232 style(layout): sidebar brand-square symmetry + WorkspaceSwitcher trigger polish
Plan 2.5 P6 follow-up. Closes two desktop shell-parity gaps from P6
manual smoke against crewli-starter SoT:

- Sidebar logo decoupled from the collapse toggle. Expanded layout is
  now justify-between (brand group left, collapse chevron right) and
  collapsed layout is justify-center with the logo alone. The expand
  affordance becomes a small absolute-positioned circular button at
  the rail's right edge — solid background + border so the slight
  overlap with the centred logo reads as a tucked-in chip rather than
  a collision. Toggling collapsed no longer shifts the logo.
- Brand square (SidebarHeader logo) and workspace avatar
  (WorkspaceSwitcher trigger) unified to the same rounded square
  (h-8 w-8 rounded-xl). Existing sizes were already consistent at
  32px — radius bumped from rounded-lg (8px) / var(--p-border-radius)
  (~6px) to rounded-xl (12px) per the design direction. Collapsed
  rail now reads as a vertical mirror: brand square at the top,
  avatar square at the bottom, bracketing the nav icons.
- WorkspaceSwitcher trigger restyled: rounded-xl (was the sharper
  var-radius), p-2 (was px-[10px] py-[8px]), hover background. The
  collapsed-variant gating of name + chevron is unchanged from P5.

Edge-mounted overhang past the rail edge was not possible: the aside
carries `overflow-hidden` (intentional, for the w-64 ⇄ w-16 width
transition) which clips anything past the rail edge. The tucked-chip
pattern (24px circle at end-0, solid bg) is the visual compromise —
the affordance stays inside the rail, discoverable, and visually
decoupled from the logo.

Desktop only. Mobile drawer chrome (logo placement, drawer X button,
missing switcher) tracked separately as MOBILE-SHELL-PARITY.

Tests:
- +2 WorkspaceSwitcher.spec.ts: trigger uses rounded-xl; collapsed
  trigger renders avatar only (hides .meta).
- +1 SidebarHeader.spec.ts: collapsed row hides the .brand-name
  wordmark and toggles to justify-center (logo-stays-centred lock).

Suite delta: 558 → 561 (+3). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).

Manual smoke pending Bert: collapse the rail, verify logo stays put
and the expand chip appears at the right edge; verify the trigger
shows rounded corners + hover bg; verify the collapsed avatar
mirrors the brand square size/radius.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 00:15:34 +02:00
59439c924b feat(layout): Plan 2.5 P6 — final shell parity (Fix 7, 9, 10)
Per RFC-WS-PRIMEVUE-PLAN-2-5 §5.6–§5.10. Final code phase of Plan 2.5
before closure docs (P7 tokens, P8 closure).

Changes:
- Fix 9: sidebar full-height. The desktop <aside> now carries
  `h-screen sticky top-0` so it fills the viewport vertically and
  pins to top on body scroll. Without this the aside sized to its
  children's intrinsic heights (~250-400px) and ended mid-viewport
  even though the surrounding grid row stretched to 100vh. With
  h-screen, SidebarNav's `flex-1` claims the remaining column space
  and WorkspaceSwitcher anchors to the true viewport bottom — its
  `border-t` (existing from P5) is now the divider above the
  switcher per crewli-starter. Mobile Drawer untouched (PrimeVue's
  internal pt classes already give it 100% panel height).
- Fix 10: density toggle promoted to the store. New
  useShellUiStore.toggleDensity() flips comfortable ⇔ compact and
  calls applyDomAttributes() synchronously. AppTopbar's local
  toggleDensity wrapper deleted; the button now invokes
  shell.toggleDensity() directly and carries a stable
  data-testid="density-toggle" plus a `title` matching its
  aria-label. Density icons swapped from generic flex-alignment
  glyphs (tabler-layout-distribute-{vertical,horizontal}) to the
  literal density metaphor (tabler-baseline-density-{small,medium}).
  Both new icons verified present in the loaded
  @iconify-json/tabler set. Topbar right-side order
  (search → density → dark → notifications → user) was already
  correct from P5; locked with a new ordering spec.

Verified (no code change):
- Fix 6 (§5.6): dark mode `.dark` on <html> confirmed in
  useShellUiStore.applyDomAttributes (AD-2.5-D1, P3 complete).
  Component-level dark coverage remains a separate backlog item
  (DARKMODE-V2-COVERAGE).
- Fix 8 (§5.8): the ▼ arrow is the Vue DevTools v8.0.2 dev-only
  toggle button injected by the devtools vite plugin, not Crewli
  code — diagnosed, no action.
- Fix 7 (§5.7): non-reproducible at code level. Topbar is
  `sticky top-0` and is a SIBLING flex item of <main> inside the
  shell's flex-col right column; normal flow stacks <main> below
  the topbar at first paint, so the title cannot fall behind a
  sticky topbar in this composition. Documented as no-op; if
  Bert reproduces it after Fix 9 lands, the symptom is something
  else (likely a per-page negative margin or a separate scroll-
  container interaction worth its own ticket).

Density enum corrected against runtime data-density: 'comfortable'
(not 'comfy' — the earlier RFC assumption is wrong; the store has
always typed `'comfortable' | 'compact'`).

Tests:
- +2 useShellUiStore.spec.ts: toggleDensity flips comfortable ⇔
  compact AND writes data-density via applyDomAttributes;
  toggleDensity from compact returns to comfortable on call 2.
- +2 AppTopbar.spec.ts: density button reachable by
  data-testid="density-toggle"; topbar right-side order locked
  via HTML index comparison (search → density → dark → notif →
  user). Existing density-flip specs adapted to spy on
  toggleDensity (the new direct call site).

Suite delta: 554 → 558 (+4). vue-tsc clean. Scoped ESLint clean
(0 errors, pre-existing warnings only).

Manual smoke pending Bert:
  1. Sidebar full-height, switcher pinned to viewport bottom (Fix 9)
  2. Page title clears topbar (Fix 7 — expected no change needed)
  3. Density toggle visible between search and dark with the
     density icon (Fix 10)
  4. Click density toggle → spacing visibly changes, <html
     data-density> flips between comfortable and compact (Fix 10)
  5. Topbar order: search → density → dark → notifications →
     avatar (Fix 10)
  6. Dark mode still toggles (Fix 6 regression)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:06:28 +02:00
641ca5131d fix(theme): remove webfontloader Public Sans path (AD-2.5-T1 completion)
P2 reverted CSS font-family to Inter but missed the JS font-loading
path: src/plugins/webfontloader.ts loaded Public Sans from Google
Fonts via WebFont.load(). The wf-publicsans-n4-active class on <html>
(found during P5 manual smoke) proved Public Sans was still loaded at
runtime, plus an external Google Fonts CDN request — both contrary to
AD-2.5-T1 (local @fontsource/inter, no CDN).

Audit context: the plugin was auto-registered via the Vuexy
registerPlugins() glob (src/@core/utils/plugins.ts walks
plugins/*.{ts,js}). No explicit import / call site to delete — file
removal is enough. The plugin only ever loaded Public Sans (no other
families), so full deletion is correct.

Changes:
- Removed src/plugins/webfontloader.ts (auto-registration falls away
  with the file itself; no manual unregister needed).
- Removed webfontloader (1.6.28) + @types/webfontloader (1.6.38) from
  package.json / pnpm-lock.yaml.
- Strengthened tests/unit/styles/typography.spec.ts with a new
  describe block that scans every src/plugins/*.ts for: any
  webfontloader reference, any WebFont.load call, any "Public Sans"
  spelling, any fonts.googleapis.com URL. Plus a regression-lock
  spec asserting webfontloader.ts itself stays deleted.

Suite delta: 552 → 554 (+2 new JS-path specs). vue-tsc clean.
Scoped ESLint clean (0 errors).

Manual smoke pending (Bert): hard-reload /v2/dashboard, confirm
- wf-publicsans-* and wf-active classes are gone from <html>
- computed font-family on body text starts with "Inter"
- Network tab has no fonts.googleapis.com request

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:25:28 +02:00
30be9aa331 fix(layout): explicit import of AppBreadcrumb in AppTopbar
Follow-up to P5 (commit ac36dfe9). Vue warned about unresolved
AppBreadcrumb component in AppTopbar's #start slot — auto-import via
unplugin-vue-components did not register it because
components-v2/ is outside the scan path (Components({ dirs: [...] })
covers src/@core/components, src/views/demos, src/components only).

The original P5 edit did include this import line, but a formatter
pass appears to have pruned it as "unused" before runtime parsed the
template; the symbol was unresolved at render and the warning
surfaced. Restored explicitly so any future formatter pass keeps it.

Fix 2 (AppTopbar #start = AppBreadcrumb) now functions visually,
not just structurally. Manual smoke pending (Bert).

Follow-up backlog: AUTO-IMPORTS-V2-SCAN — extend Components({ dirs })
to include src/components-v2/ so the v2 chrome can rely on the same
auto-import ergonomics as v1. Not done here to keep the fix surgical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:29:56 +02:00
ac36dfe9b7 feat(layout): Plan 2.5 P5 — shell parity fixes 1–5 + useBreadcrumb retire
Per RFC-WS-PRIMEVUE-PLAN-2-5 §5.1–§5.5 plus the AD-2.5-W1 option-A
supersession (no sub on dropdown items either, accepted divergence).

Atomic changes:
- AppTopbar: brand block (gradient "C" mark + Crewli wordmark) removed
  per Fix 1; the #start slot now renders <AppBreadcrumb /> per Fix 2.
  Legacy meta-based useBreadcrumb consumption (breadcrumbModel computed,
  vue-router useRouter import, command-based PrimeVue Breadcrumb model)
  is gone; AppBreadcrumb owns the registry-driven path. Dead
  topbar-mark-shadow scoped CSS rule deleted.
- AppBreadcrumb: import updated to the renamed useBreadcrumb.
- AppSidebar: docstring updated to make the Fix 3 vertical order
  (Header → Nav → Switcher, switcher bottom-anchored) explicit. No
  template change needed: SidebarNav's root <nav class="flex-1"> already
  fills available column space, naturally pushing WorkspaceSwitcher to
  the bottom (two flex-1 siblings would split the column 50/50 and
  compress the nav — a separate spacer element is structurally wrong).
- WorkspaceSwitcher: dropdown panel restructured per crewli-starter
  reference. Semantic class markers (.popover-head/.title/.link/.list/
  .opt/.is-current/.ws-logo/.name/.check-mark/.foot) added alongside
  Tailwind utilities so specs assert structure with stable selectors.
  Footer buttons wired to placeholder createWorkspace / inviteUser
  handlers (console.warn + TODO) until the flows ship. Manage link
  stays a non-navigating label (no v2-workspaces-manage route yet).
  No sub line on any dropdown row (AD-2.5-W1 option A).

Atomic legacy useBreadcrumb retirement (planned since P1):
- Legacy route-meta-driven useBreadcrumb + toBreadcrumbItems +
  BreadcrumbRouteRecord types deleted entirely (only AppTopbar
  consumed it, and that consumption is gone after Fix 2).
- useNavBreadcrumb → useBreadcrumb (single SoT for breadcrumb chain).
- NavBreadcrumbItem → BreadcrumbItem.
- AppBreadcrumb.vue import updated to the new name.
- SidebarNav.vue docstring reference scrubbed to the new name.
- useBreadcrumb.spec.ts: 10 legacy toBreadcrumbItems specs removed;
  4 walkNavTree specs retained.

AppTopbar.spec.ts:
- vue-router mock simplified (route.matched no longer relevant).
- AppBreadcrumb stubbed in #start; legacy command-vs-route assertion
  removed; new spec verifies AppBreadcrumb is rendered.

WorkspaceSwitcher.spec.ts: 5 new dropdown specs (header / row count /
current-row checkmark / footer buttons / no-sub on rows).

Suite delta: 557 → 552 (−5 net: −10 legacy toBreadcrumbItems specs,
+5 Fix 5 dropdown specs, −1 obsolete AppTopbar breadcrumb-model spec,
+1 new AppTopbar AppBreadcrumb-presence spec).

vue-tsc clean. Scoped ESLint clean (0 errors). All 3 re-grep checks
returned 0 hits (useNavBreadcrumb/NavBreadcrumbItem, topbar brand
selectors, standalone "sub" identifier in WorkspaceSwitcher — only
documentation comments referencing the no-sub state remain, which
describe absence by design).

Manual smoke skipped (Auto Mode); coverage from the post-edit specs
includes AppBreadcrumb-in-#start, dropdown structure, and trigger
no-sub. Recommend Bert run `pnpm --filter crewli-app dev` and verify
the 6 checks listed in the prompt before merging.

Known divergence from crewli-starter (accepted):
- Dropdown rows are ~16px shorter than crewli-starter (no sub line).
  Tracked as WORKSPACE-DROPDOWN-SUB-CONTENT for a future RFC with
  the required backend scope (organisations.type enum + metrics).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:22:33 +02:00
967f1a93bb chore(layout): remove v2 nav-folding orphans surfaced by P4 refactor
P4 (Plan 2.5, AD-2.5-W1 + AD-2.5-B1) refactored SidebarNav to read
APP_NAVIGATION directly, retiring the OrganizerLayoutV2 → useV2Nav →
AppSidebar :groups → SidebarNav :groups props chain. Five artifacts
were deliberately left in place to keep the P4 diff focused — this
commit removes them.

Deleted:
- src/composables/useV2Nav.ts (+ spec) — v1→v2 nav fold adapter, no
  production consumer post-P4
- src/types/v2/nav.ts — V2NavGroup / V2NavItem types, only consumed
  by the deleted composables above. types/v2/ directory removed (empty)
- src/components-v2/layout/sidebarNavActive.ts (+ spec) — pure helper,
  SidebarNav now uses inlined active check against NavItem.routeName
- navFixture export + V2NavGroup import from stories/v2/_helpers.ts

Also: stale "useV2Nav(orgNavItems)" reference scrubbed from
OrganizerLayoutV2.vue docstring (the function no longer exists; the
comment now describes the retired plumbing generically).

Suite delta: 575 → 557 (−18 specs). The drop is correct — the removed
specs tested deleted dead code (sidebarNavActive: 8 specs, useV2Nav:
10 specs), not contract behaviour.

vue-tsc clean. Scoped ESLint clean (0 errors). Final re-grep on all
deleted symbols (useV2Nav, V2NavGroup, V2NavItem, sidebarNavActive,
navFixture) returns zero hits across apps/app/src/.

Per zero-compromise gap 5 (delete > adapt): orphans don't stay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:32:46 +02:00
864cc558e2 feat(layout): Plan 2.5 P4 — WorkspaceSwitcher no-sub + SidebarNav APP_NAVIGATION
Per RFC-WS-PRIMEVUE-PLAN-2-5 §4 AD-2.5-W1 and AD-2.5-B1, §5.4 Fix 4.

Changes:
- WorkspaceSwitcher: sub field removed from template, WorkspaceDisplay
  type, and buildDisplay derivation. Stories did not carry sub args
  (auto-derived from seeded org.role); no WithSub story existed. New
  regression spec (WorkspaceSwitcher.spec.ts) locks the no-sub render.
- SidebarNav: now consumes APP_NAVIGATION from src/config/navigation.ts
  as the single source of truth (shared with breadcrumb derivation in
  useNavBreadcrumb). The groups: V2NavGroup[] prop is removed; render
  walks top-level NavItems (branch nodes render label-heading + children;
  leaf nodes render as rows; items without routeName render as
  non-clickable dormant placeholders). Previous nav data source:
  groups prop fed by useV2Nav(orgNavItems) in OrganizerLayoutV2.
- APP_NAVIGATION expanded with 7 entries to preserve visual sidebar
  continuity (Evenementen at top-level + Beheer branch with 5 children).
  All new entries use routeName: undefined until the corresponding v2
  page lands (TODOs noted per entry); only Dashboard maps to v2-dashboard.
- AppSidebar: groups prop removed; passes only :collapsed to SidebarNav.
- OrganizerLayoutV2: useV2Nav(orgNavItems) plumbing retired; the layout
  now renders <AppSidebar /> with no nav-data wiring.
- Tests: AppSidebar.spec drops the "passes groups prop to SidebarNav"
  assertion; OrganizerLayoutV2.spec drops the "forwards orgNavItems"
  assertion. New WorkspaceSwitcher no-sub regression spec (+2 tests).
- Storybook: SidebarNav.stories and AppSidebar.stories updated to no
  longer thread navFixture/groups; WithActiveItem pushes v2-dashboard.

Position of WorkspaceSwitcher (Fix 3), workspace dropdown panel (Fix 5),
and AppBreadcrumb wiring (Fix 2) remain unchanged in P4 — both lands in
P5. The legacy useBreadcrumb composable also remains untouched until P5
(atomic with AppTopbar refactor).

Orphans flagged for follow-up cleanup (intentionally not deleted in P4):
useV2Nav composable + spec, V2NavGroup/V2NavItem types, sidebarNavActive
helper + spec, navFixture in stories/v2/_helpers.ts.

Suite delta: 575 → 575 (+2 WorkspaceSwitcher no-sub spec, -1 AppSidebar
groups-prop assertion, -1 OrganizerLayoutV2 groups-forward assertion).
vue-tsc clean. Scoped ESLint clean (0 errors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:14:31 +02:00
d0dd45c03a refactor(theme): Plan 2.5 P3 — dark mode class on <html> (AD-2.5-D1 + Fix 6)
Per RFC-WS-PRIMEVUE-PLAN-2-5 §4 AD-2.5-D1 and §5.6 Fix 6. Single class
on <html> drives both PrimeVue darkModeSelector and Tailwind v4
@custom-variant dark — one toggle, two ecosystems react.

Audit findings (pre-change):
- applyDomAttributes was writing BOTH data-theme="dark" AND .dark on
  documentElement. The historic data-theme write is the design-doc §4
  mechanism that AD-2.5-D1 supersedes; the .dark toggle was already
  correct (and is already paired with PrimeVue darkModeSelector: '.dark'
  in plugins/primevue/index.ts:31, verified in P1).
- tailwind.css had NO @custom-variant dark directive — Tailwind v4
  default is `prefers-color-scheme` (OS-controlled), so utility
  `dark:` variants would have ignored the topbar toggle entirely.
- One stray .dark subtree wrapper in AppTopbar.stories.ts:56
  (DarkTheme story) — deliberate Storybook isolation per its comment,
  but in violation of AD-2.5-D1's single-source-of-truth rule.

Changes:
- useShellUiStore.applyDomAttributes(): removed data-theme write,
  kept .dark class toggle on document.documentElement, kept
  data-density (P6 wires density-toggle UI; density is an
  orthogonal axis and unaffected). File-header comment updated to
  cite AD-2.5-D1 + reference the Tailwind & PrimeVue mirror sites.
- assets/styles/tailwind.css: added
  `@custom-variant dark (&:where(.dark, .dark *))` so utility
  `dark:` classes resolve via the same .dark trigger.
- components-v2/layout/AppTopbar.stories.ts: stripped class="dark"
  from the DarkTheme story's render wrapper. Story comment updated
  to flag that visual confirmation now comes via parity-batch
  Playwright (after Plan 2.5 closes), not Storybook autodocs. A
  proper documentElement-mutating decorator is a backlog item.
- stores/__tests__/useShellUiStore.spec.ts: updated the existing
  applyDomAttributes assertion to drop the data-theme expectation
  (the write is gone); added a new `describe('applyDomAttributes
  — dark mode (AD-2.5-D1)', …)` block with 2 specs (class toggle
  reactive, no data-theme attribute written).

Re-grep verification — all three return 0 hits:
- stray .dark in v2 (excluding `dark:` utility prefixes)
- data-theme setAttribute calls in stores/
- [data-theme=…] CSS selectors anywhere

Suite delta: 573 → 575 (+2). vue-tsc clean. Scoped ESLint clean.

Note: darkModeSelector: '.dark' was already set in
plugins/primevue/index.ts:31 (verified in P1 audit) — the config
dimension of AD-2.5-D1 was satisfied before this commit; P3 closes
the store-side, Tailwind-side, and stray-class dimensions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:31:30 +02:00
41af180168 feat(theme): Plan 2.5 P2 — Inter typography (AD-2.5-T1)
Per RFC-WS-PRIMEVUE-PLAN-2-5 §4 AD-2.5-T1. Establishes Inter as the
canonical Crewli body font via @fontsource/inter (local package, no
Google Fonts CDN).

Audit findings (pre-change):
- No @fontsource/public-sans package was installed.
- No <link> tag in index.html loaded Public Sans.
- Only one Public Sans reference existed in source: the vendored
  Vuexy SCSS variable $font-family-custom at
  src/@core/scss/template/libs/vuetify/_variables.scss, which drives
  Vuetify's $body-font-family on legacy surfaces during F4.
- No src/main.css exists; the Tailwind v4 entry lives at
  src/assets/styles/tailwind.css with no @theme block yet.

Changes:
- @fontsource/inter@^5.2.8 added to dependencies; weights
  400/500/600/700 imported at main.ts ahead of tailwind.css.
- src/assets/styles/tailwind.css: new @theme block declaring
  --font-sans Inter-first, plus :root --crewli-font-family and
  html/body font-family applying that variable cascade-wide.
- src/@core/scss/template/libs/vuetify/_variables.scss:
  $font-family-custom switched from the historical body font to
  Inter (vendored edit, narrowly scoped, F6 removes @core/ entirely).
- tests/unit/styles/typography.spec.ts: 3-spec regression lock
  (Tailwind direct stacks, Vuexy SCSS variable, zero historical
  references in either file). File-content inspection — jsdom does
  not cascade from imported stylesheets, so getComputedStyle would
  always pass.

Suite delta: 570 → 573 (+3; the prompt's template was +2 but the
audit revealed two distinct font-config files, so each gets its own
assertion per the prompt's "cover all sites" rule). vue-tsc clean.
Scoped ESLint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 08:36:11 +02:00
59007e60e0 feat(layout): Plan 2.5 P1 foundation — APP_NAVIGATION + walkNavTree + AppBreadcrumb
Per RFC-WS-PRIMEVUE-PLAN-2-5 §8 step 1. Foundation scaffolding only —
no shell fixes, no Public Sans removal, no useShellUiStore changes
(P2–P6 scope).

Implements:
- theme darkModeSelector verified at '.dark' (already correct in
  plugins/primevue/index.ts — config site is here, not theme.ts).
- src/config/navigation.ts: APP_NAVIGATION registry per AD-2.5-B1
  (Dashboard entry only — v2-dashboard is the only v2 route today).
- src/composables/useBreadcrumb.ts: walkNavTree pure helper +
  useNavBreadcrumb composable per AD-2.5-B1. The legacy meta-based
  useBreadcrumb is preserved (consumed by AppTopbar, P1 may not
  touch AppTopbar); P4 retires it and renames useNavBreadcrumb.
- src/components-v2/layout/AppBreadcrumb.vue: layout primitive
  wrapping PrimeVue Breadcrumb, consuming useNavBreadcrumb.
- Tests: walkNavTree (4 specs, co-located), AppBreadcrumb mount
  (2 specs, tests/component/layouts/).

Suite 564 → 570 (+6, all new specs green). vue-tsc clean. Scoped
ESLint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 07:23:16 +02:00
637d77b327 docs(plan-3): close out Plan 3 — BACKLOG entries, RFC status, primitives registry, tooling conventions
- BACKLOG: add 3 spawned follow-ups (EnergyDots NaN, DraggableBlock pointercancel, AD-3 Menubar a11y)
- RFC-WS-GUI-REDESIGN-CREWLI-STARTER: mark Plan 3 complete with commit refs + DoD ledger
- PRIMEVUE_COMPONENTS: v2 primitives registry (8 components), statusSeverity SoT, Menubar-wrap pattern
- ARCH-TESTING: mount-helper type convention (Plan 3 codified, Plan 4 carry-over)
- FRONTEND-TOOLING: scoped lint invocation note (DoD #13 root cause)
- AppDialog.stories.ts: rename title to 'Shared/AppDialog' for sibling consistency
2026-05-19 01:41:19 +02:00
0b19e7856b style(gui-v2): resolve 7 ESLint errors in Plan 3 components/specs (behavior-neutral, DoD #13)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:23:30 +02:00
1a66ac6e64 refactor(gui-v2): delete X.vue stub, repoint 2 boundary refs to StatusTag, add shared/* regression locks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:48:21 +02:00
237afc89e6 fix(gui-v2): cleanup(b) — keep mobile workspace btn a free #end sibling (Plan-2 flex parity) + lock data-tb=search 2026-05-18 13:36:12 +02:00
f03a3f16c6 refactor(gui-v2): cleanup(b) — AppTopbar wraps PrimeVue Menubar per RFC AD-3 2026-05-18 12:55:18 +02:00
183218effa refactor(gui-v2): cleanup(a) — co-locate Plan 2's 6 stories per amended §6 (_helpers stays)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:00:58 +02:00
814d11c8db feat(gui-v2): DraggableBlock §7.1 abstraction (PointerEvent drag, A2-reconciled) + CT + stories 2026-05-18 11:48:59 +02:00
91d20d0dd2 feat(gui-v2): EnergyPicker interactive 5-step (crewli-starter port) + story 2026-05-18 11:25:27 +02:00
79650d0b72 feat(gui-v2): EnergyDots 5-dot meter (scoped CSS justified per §8) + story 2026-05-18 11:16:57 +02:00
b64b024166 feat(gui-v2): TagsInput re-impl on PrimeVue AutoComplete (5 behavioural rules) + story
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:08:45 +02:00
284fdcc437 feat(gui-v2): StateBlock 3-state wrapper (exhaustive Vitest, no @visual per constraint #5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:00:20 +02:00
b0d5e9611f feat(gui-v2): PageHead (Tailwind flex title/sub/#actions) + story 2026-05-18 10:52:09 +02:00
12cff8c03a feat(gui-v2): StatCard (PrimeVue Card KPI tile, replaces AppKpiCard) + story
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:41:34 +02:00
9d1fd16f0f feat(gui-v2): StatusTag (PrimeVue Tag + statusSeverity map) + story
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:34:21 +02:00
20af2ebd32 feat(gui-v2): statusSeverity SoT map + bidirectional §8.X consistency test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:23:44 +02:00
aa4b651870 refactor(gui-v2): imports-first in shell specs, drop eslint-disable
Vitest hoists vi.mock()/vi.hoisted() above all imports, so the
component import can sit with the other imports (import/first satisfied)
without the eslint-disable-next-line directives — the mock factories
only deref their refs at mount time. Honors the no-eslint-disable rule.
28/28 affected specs green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 13:29:58 +02:00
7d326720ab fix(gui-v2): rename unused _bp mock arg to _ (no-unused-vars gate)
The project's no-unused-vars only ignores all-underscore names (/^_+$/u);
`_bp` in the @vueuse/core useBreakpoints mock failed it. Latent since
Task 3 — masked because the whole-codebase `pnpm lint` stylish formatter
OOMs (RangeError on the legacy-code message volume) and emitted no
results. Scoped errors-only lint surfaced it. 21/21 specs still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 03:24:19 +02:00
92d8051903 fix(gui-v2): scope AppTopbar dark story + DRY shell story renders
Code-review follow-up. AppTopbar DarkTheme mutated <html>.dark which
leaked into Default/CompactDensity stacked on the same autodocs page;
scope dark to the story subtree via a `.dark` wrapper (Aura
darkModeSelector is the `.dark` class) — verified isolated on the docs
page. Also factor the duplicated render scaffolds in AppDialog (shared
dialogStory factory) and WorkspaceSwitcher (meta-level render).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 02:29:03 +02:00
e356bc8a95 feat(gui-v2): Storybook stories for the v2 shell components 2026-05-17 02:18:37 +02:00
41b4017bd1 feat(gui-v2): wire OrganizerLayoutV2 to compose the real shell components
Replaces the Plan-1 skeleton stubs: OrganizerLayoutV2 now fills
AppShellV2's #sidebar/#topbar/#drawer slots with the ported AppSidebar /
AppTopbar / RightDrawer and sources orgNavItems via useV2Nav() (legal
now that OrganizerLayoutV2.vue is the layouts-v2 zone). AppShellV2 is
unchanged; its contract test stays green. New component test locks the
composition (right component per slot, :groups forwarded, no skeleton).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 02:04:07 +02:00
a341a60412 feat(boundaries): add layouts-v2 zone so v2 shell layout can use components-v2
AD-G2 ("OrganizerLayoutV2 wraps AppShellV2") was in tension with AD-G5:
src/layouts/OrganizerLayoutV2.vue classified as the v1 `layouts` zone,
which is deliberately barred from components-v2. New `layouts-v2` zone
(src/layouts/*V2*.vue, mode:file) gets pages-v2-equivalent v2 capability;
the v1 `layouts` zone is unchanged so v2 isolation is preserved. RFC
AD-G5 amended; locked by 3 boundaries-v2.spec.ts regression tests (7/7).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 02:03:00 +02:00
6e5c5bbec3 fix(gui-v2): rename AppDialog test stub off reserved HTML name "Dialog"
vue/no-reserved-component-names is error-level; <dialog> is native HTML.
Matching is via the stubs key + findComponent reference, not the stub's
own name, so the rename is behaviour-neutral (14/14 tests still pass).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:55:14 +02:00
3685797e18 fix(gui-v2): wire AppDialog accessible name + cover close/width in tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:53:04 +02:00
c26b281fa7 feat(gui-v2): port AppModal -> AppDialog (PrimeVue Dialog) to TypeScript
Replaces crewli-starter's hand-rolled Teleport/scrim/keydown Escape
pattern with a typed PrimeVue Dialog wrapper. v-model:open via writable
computed; slots #tabs/#footer gated; 12 unit tests all passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:37:46 +02:00
ca0332d17a fix(gui-v2): :key on RightDrawer dynamic body to remount on switch
Adds :key="component" to <component :is="Body"> so opening drawer B
while A is open fully remounts (A's instance/state can't leak into B) —
a real defect for a primary shell overlay. Same-name reopen still
relies on the body reacting to prop changes (documented inline).
Companion test asserts the cross-component switch swaps the body
cleanly (A unmounts, B mounts). Addresses the Task 5 code-review
Important finding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:30:43 +02:00
2d7d4b49d8 chore(types): sync auto-imports.d.ts for drawerRegistry
unplugin-auto-import scans src/composables/; the new drawerRegistry
exports added global + vue-module declarations. auto-imports.d.ts is
tracked — keep it in sync (same precedent as prior composable syncs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:22:55 +02:00
4ba927623b feat(gui-v2): drawer registry + port RightDrawer to TypeScript
Adds composables/drawerRegistry.ts (boundary-safe register-by-call map:
register/resolve, zero static component imports — composables zone may
not import components, RFC-WS-GUI-REDESIGN AD-G5). Extends useRightDrawer
with resolveDrawerComponent (thin facade, prior API/tests preserved).
RightDrawer.vue: PrimeVue <Drawer position=right>, v-model:visible via a
writable computed ↔ useRightDrawer isOpen/close; title/flush read from the
open() props object (A4); dynamic <component :is> via resolveDrawerComponent
with a graceful empty state on null; #actions header slot retained. 18
unit/component tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:22:42 +02:00
615a114f33 fix(gui-v2): breadcrumb navigation via router.push + button type + void logout
- FIX A (IMPORTANT): PrimeVue Breadcrumb ignores `route` key; map non-last
  items with `command: () => router.push(item.to)` for real client-side nav
- FIX B: add type="button" to all 6 native <button> chrome elements
- FIX C: authStore.logout() bare call matches project no-void pattern
- FIX D: document param-route edge case in toBreadcrumbItems
- FIX E: regression test asserts command+push on non-last, no command on last,
  no `route` key on any item

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:07:57 +02:00
4f1fb7385b chore(types): sync auto-imports.d.ts for useBreadcrumb
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:51:53 +02:00
7489301195 feat(gui-v2): port AppTopbar + useBreadcrumb to TypeScript
- useBreadcrumb composable: pure toBreadcrumbItems() helper + thin
  useRoute() wrapper; route-driven, no prop coupling
- AppTopbar: hamburger→setMobileOpen, theme/density toggles→shell store,
  PrimeVue Breadcrumb/OverlayBadge/Popover/Avatar/Menu; replaces all
  manual document.mousedown listeners with PrimeVue built-in dismissal;
  notifications stubbed (useNotificationStore is a toast queue, not a
  feed — TODO TECH-WS-GUI-REDESIGN); sign-out→authStore.logout()
- Unit tests: 10 breadcrumb + 6 AppTopbar assertions (16 total, all pass)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:51:48 +02:00
23e1262f9c fix(gui-v2): mount Drawer only on mobile (v-if) + shared Tailwind breakpoint
CRITICAL: replace `lg:hidden` on PrimeVue Drawer with `v-if="isMobile"` so the
teleported portal/overlay is never created on desktop viewports regardless of
mobileOpen state. Replace useMediaQuery raw string in SidebarHeader with
useBreakpoints(breakpointsTailwind).smaller('lg') shared by both components.
Add desktop/mobile comments; adapt tests to useBreakpoints mock; add
Drawer-absent-on-desktop and aside w-16/w-64 width-class assertions (21 tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:41:50 +02:00