diff --git a/dev-docs/superpowers/plans/2026-05-16-gui-redesign-foundation.md b/dev-docs/superpowers/plans/2026-05-16-gui-redesign-foundation.md
new file mode 100644
index 00000000..8d611522
--- /dev/null
+++ b/dev-docs/superpowers/plans/2026-05-16-gui-redesign-foundation.md
@@ -0,0 +1,1186 @@
+# Crewli GUI Redesign — Foundation Plan 1 (RFC + bootable /v2/ slice)
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Land the new project RFC (superseding F4a–F4d) and the structural foundation so that `/v2/dashboard` boots through a new `OrganizerLayoutV2` + `AppShellV2` skeleton, with route-name collision prevention, boundary zones, layout-meta enforcement, and the v2 UI state layer — all green under lint/typecheck/test/build.
+
+**Architecture:** Parallel `/v2/*` route tree (own `routesFolder` with a `v2-` route-name prefix), a new `OrganizerLayoutV2` selected via `definePage({ meta: { layout: 'OrganizerLayoutV2' } })` (enforced by a custom ESLint rule), a Tailwind-grid `AppShellV2` *skeleton* with named slot regions (PrimeVue shell pieces arrive in Plan 2), a single `useShellUiStore` for sidebar/theme/density/right-drawer state, and a thin `useRightDrawer()` facade over it. No v1 code is touched except additive config.
+
+**Tech Stack:** Vue 3 `
+
+
+
v2 dashboard
+
+```
+
+- [ ] **Step 4: Typecheck the config change**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vue-tsc --noEmit -p tsconfig.json 2>&1 | head -20`
+Expected: no new errors referencing `vite.config.ts` or `v2RouteName`. (`routeNode.fullPath` is a documented `TreeNode` field in unplugin-vue-router 0.8.x.)
+
+- [ ] **Step 5: Verify the route is generated with the prefixed name**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vite build 2>&1 | tail -5`
+Expected: build succeeds. Then run: `grep -rn "v2-dashboard\|/v2/dashboard" "$(find /Users/berthausmans/Documents/Development/crewli/apps/app -name 'typed-router.d.ts' | head -1)"`
+Expected: an entry mapping path `/v2/dashboard` to route name `v2-dashboard`.
+
+**If the name is `dashboard` (no `v2-` prefix), the defensive path read
+returned empty** — the installed unplugin-vue-router exposes the node
+path differently. Remediate before continuing: temporarily add
+`console.log(JSON.stringify({ k: Object.keys(routeNode), fp: routeNode.fullPath, vp: routeNode.value?.path }))`
+inside `getRouteName`, run `pnpm exec vite build 2>&1 | grep v2`, read
+the actual field carrying `/v2/...`, update the `nodePath` expression to
+read that field, remove the log, rebuild, re-grep. Do not proceed to
+Task 4 until `typed-router.d.ts` shows `v2-dashboard`.
+
+- [ ] **Step 6: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/vite.config.ts apps/app/src/pages-v2/dashboard.vue
+git commit -m "feat(router): mount pages-v2 at /v2/* with v2- name prefix"
+```
+
+---
+
+## Task 4: eslint-plugin-boundaries — v2 zones + matrix
+
+**Files:**
+- Modify: `apps/app/.eslintrc.cjs` (`boundaries/elements` and `boundaries/element-types`)
+- Test: `apps/app/tests/unit/boundaries-v2.spec.ts`
+
+- [ ] **Step 1: Write the failing test (ESLint Node API on fixtures)**
+
+Create `apps/app/tests/unit/boundaries-v2.spec.ts`:
+
+```ts
+import { describe, expect, it } from 'vitest'
+import { ESLint } from 'eslint'
+
+const eslint = new ESLint({ cwd: `${process.cwd()}` })
+
+async function boundaryErrors(filePath: string, code: string) {
+ const [result] = await eslint.lintText(code, { filePath })
+
+ return result.messages.filter(m => m.ruleId === 'boundaries/element-types')
+}
+
+describe('boundaries — v2 zones', () => {
+ it('allows pages-v2 → components-v2', async () => {
+ const errs = await boundaryErrors(
+ 'src/pages-v2/dashboard.vue',
+ ``,
+ )
+ expect(errs).toHaveLength(0)
+ })
+
+ it('allows components-v2 → components-foundation (FormField bridge)', async () => {
+ const errs = await boundaryErrors(
+ 'src/components-v2/forms/Demo.vue',
+ ``,
+ )
+ expect(errs).toHaveLength(0)
+ })
+
+ it('forbids v1 components → components-v2 (no back-porting)', async () => {
+ const errs = await boundaryErrors(
+ 'src/components/organizer/Legacy.vue',
+ ``,
+ )
+ expect(errs.length).toBeGreaterThan(0)
+ })
+})
+```
+
+- [ ] **Step 2: Run it to verify it fails**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run tests/unit/boundaries-v2.spec.ts`
+Expected: FAIL — the "forbids v1 → components-v2" case currently produces 0 errors (no zone exists, so the import is unclassified and allowed), so `expect(errs.length).toBeGreaterThan(0)` fails.
+
+- [ ] **Step 3: Add the element zones**
+
+In `apps/app/.eslintrc.cjs`, find this exact line inside `'boundaries/elements'`:
+
+```js
+ { type: 'components', pattern: 'src/components/**' },
+```
+
+Insert **directly above** it (so the narrow zones win — first-match-wins, order matters):
+
+```js
+ // GUI-redesign v2 zones (RFC-WS-GUI-REDESIGN AD-G5). Declared
+ // before the generic `components` catch-all. `components-foundation`
+ // is the ONLY sanctioned v1→v2 bridge (FormField + Icon — audited
+ // to live in the generic `components` zone, not components-shared).
+ // eslint-plugin-boundaries 6.0.2 micromatch supports the brace
+ // form below; if a future bump breaks it, split into two entries
+ // with the same `type` (see RFC §14 fallback).
+ { type: 'components-foundation', pattern: 'src/components/{forms/**,Icon.vue}' },
+ { type: 'components-v2', pattern: 'src/components-v2/**' },
+```
+
+Then find this exact line:
+
+```js
+ { type: 'pages', pattern: 'src/pages/**' },
+```
+
+Insert **directly above** it:
+
+```js
+ { type: 'pages-v2', pattern: 'src/pages-v2/**' },
+```
+
+- [ ] **Step 4: Add the matrix rows**
+
+In `apps/app/.eslintrc.cjs`, find this exact line inside `'boundaries/element-types'` `rules`:
+
+```js
+ { from: 'components', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components', 'components-shared', 'components-organizer'] },
+```
+
+Insert **directly above** it:
+
+```js
+ // v2 zones. components-v2 may use the FormField/Icon bridge
+ // (components-foundation) but NOT any other v1 component zone.
+ // No v1 `from` rule lists components-v2/pages-v2 → back-porting
+ // is structurally impossible (RFC-WS-GUI-REDESIGN AD-G5).
+ { from: 'components-foundation', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components-foundation'] },
+ { from: 'components-v2', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'components-v2', 'components-foundation'] },
+ { from: 'pages-v2', allow: ['types', 'utils', 'lib', 'composables', 'composables-forms', 'stores', 'navigation', 'components-v2', 'components-foundation', 'layouts', 'plugins'] },
+```
+
+- [ ] **Step 5: Run the test to verify it passes**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run tests/unit/boundaries-v2.spec.ts`
+Expected: PASS — 3 tests. (If the brace-glob misbehaves on 6.0.2, replace the `components-foundation` element line with two lines — `pattern: 'src/components/forms/**'` and `pattern: 'src/components/Icon.vue'` — same `type`; re-run.)
+
+- [ ] **Step 6: Confirm no regression on existing zones**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec eslint 'src/components/organizer/**/*.vue' --rule '{}' 2>&1 | tail -3`
+Expected: no new `boundaries/element-types` errors introduced by the additions (exit status unchanged from baseline).
+
+- [ ] **Step 7: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/.eslintrc.cjs apps/app/tests/unit/boundaries-v2.spec.ts
+git commit -m "feat(lint): add components-v2/pages-v2 boundary zones (no back-port)"
+```
+
+---
+
+## Task 5: Custom ESLint rule — `require-v2-layout-meta`
+
+**Files:**
+- Create: `apps/app/eslint-rules/require-v2-layout-meta.cjs`
+- Create: `apps/app/eslint-local-rules.cjs`
+- Create: `apps/app/eslint-rules/__tests__/require-v2-layout-meta.spec.ts`
+- Modify: `apps/app/package.json` (add `eslint-plugin-local-rules` dev dep)
+- Modify: `apps/app/.eslintrc.cjs` (`plugins` + new `overrides` entry)
+
+- [ ] **Step 1: Add the local-rules plugin dependency**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm add -D eslint-plugin-local-rules@3.0.2`
+Expected: `package.json` devDependencies gains `"eslint-plugin-local-rules": "3.0.2"`.
+
+- [ ] **Step 2: Write the failing rule test (RuleTester)**
+
+Create `apps/app/eslint-rules/__tests__/require-v2-layout-meta.spec.ts`:
+
+```ts
+import { createRequire } from 'node:module'
+import { describe, it } from 'vitest'
+import { RuleTester } from 'eslint'
+
+// Project is ESLint 8.57 — RuleTester uses the legacy `parser` +
+// `parserOptions` shape (NOT ESLint-9 `languageOptions`). createRequire
+// gives us a CJS `require` inside this ESM test for the .cjs rule + the
+// parser path.
+const require = createRequire(import.meta.url)
+const rule = require('../require-v2-layout-meta.cjs')
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+})
+
+describe('require-v2-layout-meta', () => {
+ it('passes valid and rejects invalid', () => {
+ ruleTester.run('require-v2-layout-meta', rule, {
+ valid: [
+ {
+ filename: 'src/pages-v2/dashboard.vue',
+ code: ``,
+ },
+ {
+ // non-v2 file is ignored entirely
+ filename: 'src/pages/dashboard.vue',
+ code: ``,
+ },
+ ],
+ invalid: [
+ {
+ filename: 'src/pages-v2/dashboard.vue',
+ code: ``,
+ errors: [{ messageId: 'missing' }],
+ },
+ {
+ filename: 'src/pages-v2/events/index.vue',
+ code: ``,
+ errors: [{ messageId: 'wrongLayout' }],
+ },
+ ],
+ })
+ })
+})
+```
+
+- [ ] **Step 3: Run it to verify it fails**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run eslint-rules/__tests__/require-v2-layout-meta.spec.ts`
+Expected: FAIL — `Cannot find module '../require-v2-layout-meta.cjs'`.
+
+- [ ] **Step 4: Write the rule**
+
+Create `apps/app/eslint-rules/require-v2-layout-meta.cjs`:
+
+```js
+/**
+ * Enforces that every src/pages-v2/**.vue page declares
+ * definePage({ meta: { layout: 'OrganizerLayoutV2' } })
+ * (or 'PortalLayoutV2' for src/pages-v2/portal/**). Without this a v2
+ * page silently falls back to the `default` layout — a no-error
+ * wrong-shell bug. RFC-WS-GUI-REDESIGN AD-G2.
+ */
+'use strict'
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: { description: 'require definePage layout meta on pages-v2' },
+ messages: {
+ missing: 'pages-v2 page must call definePage({ meta: { layout: ... } }).',
+ wrongLayout: 'pages-v2 layout must be {{expected}} (got {{actual}}).',
+ },
+ schema: [],
+ },
+ create(context) {
+ const filename = (context.filename || context.getFilename() || '').replace(/\\/g, '/')
+ if (!filename.includes('src/pages-v2/'))
+ return {}
+
+ const expected = filename.includes('src/pages-v2/portal/')
+ ? 'PortalLayoutV2'
+ : 'OrganizerLayoutV2'
+
+ let sawDefinePage = false
+
+ return {
+ CallExpression(node) {
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'definePage')
+ return
+ sawDefinePage = true
+
+ const arg = node.arguments[0]
+ const metaProp = arg && arg.type === 'ObjectExpression'
+ ? arg.properties.find(p => p.key && p.key.name === 'meta')
+ : null
+ const layoutProp = metaProp && metaProp.value.type === 'ObjectExpression'
+ ? metaProp.value.properties.find(p => p.key && p.key.name === 'layout')
+ : null
+
+ if (!layoutProp || layoutProp.value.value !== expected) {
+ context.report({
+ node,
+ messageId: 'wrongLayout',
+ data: { expected, actual: layoutProp ? String(layoutProp.value.value) : 'none' },
+ })
+ }
+ },
+ 'Program:exit': function (node) {
+ if (!sawDefinePage)
+ context.report({ node, messageId: 'missing' })
+ },
+ }
+ },
+}
+```
+
+- [ ] **Step 5: Create the local-rules entry**
+
+Create `apps/app/eslint-local-rules.cjs`:
+
+```js
+'use strict'
+
+module.exports = {
+ 'require-v2-layout-meta': require('./eslint-rules/require-v2-layout-meta.cjs'),
+}
+```
+
+- [ ] **Step 6: Run the rule test to verify it passes**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run eslint-rules/__tests__/require-v2-layout-meta.spec.ts`
+Expected: PASS.
+
+- [ ] **Step 7: Wire the rule into .eslintrc.cjs**
+
+In `apps/app/.eslintrc.cjs`, find the top-level `plugins: [` array (≈ line 30) and add `'local-rules'` as an element. Then add this entry to the top-level `overrides:` array:
+
+```js
+ {
+ files: ['src/pages-v2/**/*.vue'],
+ rules: {
+ 'local-rules/require-v2-layout-meta': 'error',
+ },
+ },
+```
+
+- [ ] **Step 8: Verify the rule is active end-to-end**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && printf '%s' "" > /tmp/bad.vue && cp /tmp/bad.vue src/pages-v2/__rule_probe.vue && pnpm exec eslint src/pages-v2/__rule_probe.vue ; rm src/pages-v2/__rule_probe.vue`
+Expected: ESLint reports `local-rules/require-v2-layout-meta` `missing`. (The probe file is removed by the same command.)
+
+- [ ] **Step 9: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/eslint-rules apps/app/eslint-local-rules.cjs apps/app/.eslintrc.cjs apps/app/package.json apps/app/pnpm-lock.yaml
+git commit -m "feat(lint): enforce definePage layout meta on pages-v2"
+```
+
+---
+
+## Task 6: `useShellUiStore` (sidebar / theme / density / right-drawer)
+
+**Files:**
+- Create: `apps/app/src/stores/useShellUiStore.ts`
+- Test: `apps/app/src/stores/__tests__/useShellUiStore.spec.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `apps/app/src/stores/__tests__/useShellUiStore.spec.ts`:
+
+```ts
+import { beforeEach, describe, expect, it } from 'vitest'
+import { createPinia, setActivePinia } from 'pinia'
+import { useShellUiStore } from '@/stores/useShellUiStore'
+
+describe('useShellUiStore', () => {
+ beforeEach(() => {
+ setActivePinia(createPinia())
+ document.documentElement.removeAttribute('data-theme')
+ document.documentElement.removeAttribute('data-density')
+ document.documentElement.classList.remove('dark')
+ })
+
+ it('defaults: expanded sidebar, comfortable density, light theme, closed drawer', () => {
+ const s = useShellUiStore()
+ expect(s.sidebarCollapsed).toBe(false)
+ expect(s.density).toBe('comfortable')
+ expect(s.theme).toBe('light')
+ expect(s.drawer.isOpen).toBe(false)
+ })
+
+ it('toggleSidebar flips collapsed', () => {
+ const s = useShellUiStore()
+ s.toggleSidebar()
+ expect(s.sidebarCollapsed).toBe(true)
+ })
+
+ it('applyDomAttributes writes data-theme/data-density and .dark', () => {
+ const s = useShellUiStore()
+ s.setTheme('dark')
+ s.setDensity('compact')
+ s.applyDomAttributes()
+ expect(document.documentElement.getAttribute('data-theme')).toBe('dark')
+ expect(document.documentElement.getAttribute('data-density')).toBe('compact')
+ expect(document.documentElement.classList.contains('dark')).toBe(true)
+ })
+
+ it('openDrawer/closeDrawer mutate drawer state', () => {
+ const s = useShellUiStore()
+ s.openDrawer('PersonCard', { id: '01H' })
+ expect(s.drawer).toEqual({ isOpen: true, component: 'PersonCard', props: { id: '01H' } })
+ s.closeDrawer()
+ expect(s.drawer.isOpen).toBe(false)
+ })
+})
+```
+
+- [ ] **Step 2: Run it to verify it fails**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run src/stores/__tests__/useShellUiStore.spec.ts`
+Expected: FAIL — cannot resolve `@/stores/useShellUiStore`.
+
+- [ ] **Step 3: Write the store**
+
+Create `apps/app/src/stores/useShellUiStore.ts`:
+
+```ts
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+// v2 shell UI state ONLY (RFC-WS-GUI-REDESIGN AD-G4). No tenant/org
+// state — that stays in useAuthStore/useOrganisationStore. Owns the
+// writes to //.dark (composes with
+// Aura darkModeSelector '.dark'); v2 bypasses Vuexy useSkins.ts.
+
+export type ShellTheme = 'light' | 'dark'
+export type ShellDensity = 'comfortable' | 'compact'
+
+export interface ShellDrawerState {
+ isOpen: boolean
+ component: string | null
+ props: Record
+}
+
+export const useShellUiStore = defineStore('shellUi', () => {
+ const sidebarCollapsed = ref(false)
+ const density = ref('comfortable')
+ const theme = ref('light')
+ const drawer = ref({ isOpen: false, component: null, props: {} })
+
+ function toggleSidebar(): void {
+ sidebarCollapsed.value = !sidebarCollapsed.value
+ }
+
+ function setTheme(next: ShellTheme): void {
+ theme.value = next
+ }
+
+ function setDensity(next: ShellDensity): void {
+ density.value = next
+ }
+
+ function applyDomAttributes(): void {
+ const el = document.documentElement
+ el.setAttribute('data-theme', theme.value)
+ el.setAttribute('data-density', density.value)
+ el.classList.toggle('dark', theme.value === 'dark')
+ }
+
+ function openDrawer(component: string, props: Record = {}): void {
+ drawer.value = { isOpen: true, component, props }
+ }
+
+ function closeDrawer(): void {
+ drawer.value = { isOpen: false, component: null, props: {} }
+ }
+
+ return {
+ sidebarCollapsed,
+ density,
+ theme,
+ drawer,
+ toggleSidebar,
+ setTheme,
+ setDensity,
+ applyDomAttributes,
+ openDrawer,
+ closeDrawer,
+ }
+})
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run src/stores/__tests__/useShellUiStore.spec.ts`
+Expected: PASS — 5 tests.
+
+- [ ] **Step 5: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/src/stores/useShellUiStore.ts apps/app/src/stores/__tests__/useShellUiStore.spec.ts
+git commit -m "feat(stores): add useShellUiStore for v2 shell UI state"
+```
+
+---
+
+## Task 7: `useRightDrawer()` facade composable
+
+**Files:**
+- Create: `apps/app/src/composables/useRightDrawer.ts`
+- Test: `apps/app/src/composables/__tests__/useRightDrawer.spec.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `apps/app/src/composables/__tests__/useRightDrawer.spec.ts`:
+
+```ts
+import { beforeEach, describe, expect, it } from 'vitest'
+import { createPinia, setActivePinia } from 'pinia'
+import { useRightDrawer } from '@/composables/useRightDrawer'
+import { useShellUiStore } from '@/stores/useShellUiStore'
+
+describe('useRightDrawer', () => {
+ beforeEach(() => setActivePinia(createPinia()))
+
+ it('open() writes through to the store', () => {
+ const { open } = useRightDrawer()
+ open('ArtistCard', { id: '01H' })
+ const s = useShellUiStore()
+ expect(s.drawer).toEqual({ isOpen: true, component: 'ArtistCard', props: { id: '01H' } })
+ })
+
+ it('close() clears the store drawer', () => {
+ const { open, close, isOpen } = useRightDrawer()
+ open('ArtistCard')
+ close()
+ expect(isOpen.value).toBe(false)
+ expect(useShellUiStore().drawer.isOpen).toBe(false)
+ })
+})
+```
+
+- [ ] **Step 2: Run it to verify it fails**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run src/composables/__tests__/useRightDrawer.spec.ts`
+Expected: FAIL — cannot resolve `@/composables/useRightDrawer`.
+
+- [ ] **Step 3: Write the composable**
+
+Create `apps/app/src/composables/useRightDrawer.ts`:
+
+```ts
+import { storeToRefs } from 'pinia'
+import type { ComputedRef } from 'vue'
+import { computed } from 'vue'
+import { useShellUiStore } from '@/stores/useShellUiStore'
+
+// Thin facade over useShellUiStore.drawer (RFC-WS-GUI-REDESIGN AD-G4,
+// issue 5). NOT a module-level ref singleton: state lives in the Pinia
+// store so Playwright CT can drive the drawer via @pinia/testing
+// without rendering the shell, and tests don't leak state across cases.
+
+export interface UseRightDrawer {
+ isOpen: ComputedRef
+ component: ComputedRef
+ props: ComputedRef>
+ open: (component: string, props?: Record) => void
+ close: () => void
+}
+
+export function useRightDrawer(): UseRightDrawer {
+ const store = useShellUiStore()
+ const { drawer } = storeToRefs(store)
+
+ return {
+ isOpen: computed(() => drawer.value.isOpen),
+ component: computed(() => drawer.value.component),
+ props: computed(() => drawer.value.props),
+ open: (component, props = {}) => store.openDrawer(component, props),
+ close: () => store.closeDrawer(),
+ }
+}
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run src/composables/__tests__/useRightDrawer.spec.ts`
+Expected: PASS — 2 tests.
+
+- [ ] **Step 5: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/src/composables/useRightDrawer.ts apps/app/src/composables/__tests__/useRightDrawer.spec.ts
+git commit -m "feat(composables): add useRightDrawer facade over useShellUiStore"
+```
+
+---
+
+## Task 8: `OrganizerLayoutV2` + `AppShellV2` skeleton
+
+**Files:**
+- Create: `apps/app/src/layouts/components/AppShellV2.vue`
+- Create: `apps/app/src/layouts/OrganizerLayoutV2.vue`
+- Test: `apps/app/tests/component/layouts/AppShellV2.spec.ts`
+
+> Skeleton only: a Tailwind 12-col-ish grid with **named slot regions**
+> (`sidebar`, `topbar`, default=content, `drawer`) + `RouterView`. No
+> PrimeVue parts yet (spec: outer container is custom Tailwind grid;
+> PrimeVue shell pieces arrive in Plan 2). Per spec §13, the skeleton
+> gets a Vitest mount test now; Playwright-CT visual baselines are
+> captured in Plan 2 when the real shell pieces land.
+
+- [ ] **Step 1: Write the failing mount test**
+
+Create `apps/app/tests/component/layouts/AppShellV2.spec.ts`:
+
+```ts
+import { describe, expect, it } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { createPinia } from 'pinia'
+import AppShellV2 from '@/layouts/components/AppShellV2.vue'
+
+describe('AppShellV2 (skeleton)', () => {
+ it('renders the grid regions and default slot content', () => {
+ const wrapper = mount(AppShellV2, {
+ global: { plugins: [createPinia()] },
+ slots: {
+ sidebar: '',
+ topbar: 'TB',
+ default: 'CONTENT',
+ drawer: '',
+ },
+ })
+ expect(wrapper.find('[data-testid="appshell-v2"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="sb"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="tb"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="content"]').text()).toBe('CONTENT')
+ expect(wrapper.find('[data-testid="dr"]').exists()).toBe(true)
+ })
+
+ it('applies the collapsed modifier from useShellUiStore', async () => {
+ const pinia = createPinia()
+ const wrapper = mount(AppShellV2, { global: { plugins: [pinia] } })
+ const { useShellUiStore } = await import('@/stores/useShellUiStore')
+ useShellUiStore().toggleSidebar()
+ await wrapper.vm.$nextTick()
+ expect(wrapper.find('[data-testid="appshell-v2"]').classes()).toContain('is-collapsed')
+ })
+})
+```
+
+- [ ] **Step 2: Run it to verify it fails**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run --project component tests/component/layouts/AppShellV2.spec.ts`
+Expected: FAIL — cannot resolve `@/layouts/components/AppShellV2.vue`.
+
+- [ ] **Step 3: Write `AppShellV2.vue`**
+
+Create `apps/app/src/layouts/components/AppShellV2.vue`:
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vitest run --project component tests/component/layouts/AppShellV2.spec.ts`
+Expected: PASS — 2 tests.
+
+- [ ] **Step 5: Write `OrganizerLayoutV2.vue`**
+
+Create `apps/app/src/layouts/OrganizerLayoutV2.vue` (must live in `src/layouts/` — MetaLayouts `target`):
+
+```vue
+
+
+
+
+
+
+
+
+
+
+ v2 shell (skeleton)
+
+
+
+
+
+
+```
+
+- [ ] **Step 6: Typecheck**
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && pnpm exec vue-tsc --noEmit -p tsconfig.json 2>&1 | grep -E 'AppShellV2|OrganizerLayoutV2' | head`
+Expected: no output (no type errors in the new files).
+
+- [ ] **Step 7: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/src/layouts/OrganizerLayoutV2.vue apps/app/src/layouts/components/AppShellV2.vue apps/app/tests/component/layouts/AppShellV2.spec.ts
+git commit -m "feat(layouts): add OrganizerLayoutV2 + AppShellV2 skeleton"
+```
+
+---
+
+## Task 9: Boot proof — `/v2/dashboard` + full gate
+
+**Files:**
+- Modify: `apps/app/src/pages-v2/dashboard.vue` (flesh out from the Task 3 stub)
+- Test: `apps/app/tests/playwright-ct/v2/appshell-boot.spec.ts`
+
+- [ ] **Step 1: Flesh out the page**
+
+Replace the contents of `apps/app/src/pages-v2/dashboard.vue`:
+
+```vue
+
+
+
+
+
+ v2 foundation OK
+
+
+ AppShellV2 skeleton renders this route at /v2/dashboard.
+
+
+
+```
+
+- [ ] **Step 2: Write the failing CT smoke (mounts the skeleton + page body)**
+
+Create `apps/app/tests/playwright-ct/v2/appshell-boot.spec.ts`:
+
+```ts
+import { expect, test } from '@playwright/experimental-ct-vue'
+import { createTestingPinia } from '@pinia/testing'
+import AppShellV2 from '@/layouts/components/AppShellV2.vue'
+
+test('AppShellV2 mounts and renders content in the CT runner', async ({ mount }) => {
+ const component = await mount(AppShellV2, {
+ global: { plugins: [createTestingPinia({ stubActions: false })] },
+ slots: { default: 'v2 foundation OK' },
+ })
+
+ await expect(component.getByTestId('appshell-v2')).toBeVisible()
+ await expect(component.getByTestId('v2-dashboard')).toContainText('v2 foundation OK')
+})
+```
+
+- [ ] **Step 3: Run the smoke (integration test — depends on Task 8)**
+
+This is an integration smoke, not unit TDD: it proves the CT runner can
+mount the Plan-1 skeleton built in Task 8. There is no artificial
+red phase — the test file is the new artifact; if `AppShellV2` were
+absent the import would fail.
+
+Run: `cd /Users/berthausmans/Documents/Development/crewli/apps/app && grep '@pinia/testing' package.json && pnpm exec playwright test --config=playwright-ct.config.ts tests/playwright-ct/v2/appshell-boot.spec.ts 2>&1 | tail -6`
+Expected: `@pinia/testing` is present in package.json (it is, `^1.0.3`); result `1 passed`. If the first CT run reports the Chromium browser is missing, run `pnpm exec playwright install chromium` once and re-run.
+
+- [ ] **Step 4: Run the full foundation gate**
+
+Run each; all must pass:
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli/apps/app
+pnpm exec vue-tsc --noEmit -p tsconfig.json # typecheck: 0 new errors
+pnpm exec eslint src/pages-v2 src/components-v2 src/stores/useShellUiStore.ts src/composables/useRightDrawer.ts # boundaries + v2 layout rule clean
+pnpm exec vitest run src/stores/__tests__/useShellUiStore.spec.ts src/composables/__tests__/useRightDrawer.spec.ts src/plugins/1.router/__tests__/v2RouteName.spec.ts tests/unit/boundaries-v2.spec.ts
+pnpm exec vitest run --project component tests/component/layouts/AppShellV2.spec.ts
+pnpm exec vite build # production build succeeds
+```
+
+Expected: typecheck 0 new errors; eslint 0 errors; all vitest specs pass; build succeeds with a `/v2/dashboard` → `v2-dashboard` entry in `typed-router.d.ts`.
+
+- [ ] **Step 5: Commit**
+
+```bash
+cd /Users/berthausmans/Documents/Development/crewli
+git add apps/app/src/pages-v2/dashboard.vue apps/app/tests/playwright-ct/v2/appshell-boot.spec.ts
+git commit -m "feat(v2): boot /v2/dashboard through OrganizerLayoutV2 + AppShellV2"
+```
+
+---
+
+## Definition of Done (Plan 1)
+
+- New RFC committed; F4a–F4d banner + PRIMEVUE_COMPONENTS pointer added.
+- `/v2/dashboard` renders via `OrganizerLayoutV2` → `AppShellV2`; its
+ route name is `v2-dashboard` (no collision with v1 `dashboard`).
+- `useShellUiStore` + `useRightDrawer()` unit-tested; drawer state is
+ store-backed (CT-drivable).
+- New boundary zones active; back-port v1→`components-v2` is an error.
+- A `pages-v2/**` page missing the `OrganizerLayoutV2` layout meta is an
+ ESLint error.
+- `pnpm exec vue-tsc --noEmit`, `pnpm lint`, `pnpm test`, the CT smoke,
+ and `pnpm exec vite build` all pass. Existing test count not reduced.
+- No `src/components-v2/forms/` directory created (FormField reused via
+ the `components-foundation` bridge).
+
+---
+
+## Subsequent plans (authored after Plan 1 lands)
+
+- **Plan 2 — Shell pieces:** `AppSidebar`, `SidebarHeader`, `SidebarNav`,
+ `WorkspaceSwitcher` (PrimeVue `Popover` + computed over
+ `useAuthStore`/`useOrganisationStore`), `AppTopbar` (PrimeVue
+ `Breadcrumb`/`Button`/`Avatar`/`Menu`/`OverlayBadge`), `RightDrawer`
+ (PrimeVue `Drawer` + scaffold, driven by `useRightDrawer()`),
+ `AppDialog` (PrimeVue `Dialog` + scaffold). Each: Vitest mount + a
+ Playwright-CT `@visual` baseline captured after parity-check vs
+ crewli-starter. Fills the `OrganizerLayoutV2` slots.
+- **Plan 3 — Tier-1 primitives + DraggableBlock:** `StatusTag`
+ (+ `statusSeverity.ts` map seeded from `src/types/` enums), `StatCard`,
+ `StateBlock`, `PageHead`, `TagsInput`, `EnergyDots`, `EnergyPicker`,
+ and `DraggableBlock` (foundation despite Tier-4 deferring the
+ Timetable/Cue pages — spec §8/§9). Co-located `.stories.ts` each.
+- **Plan 4 — Template layer:** `ListTemplate`, `FormTemplate`,
+ `DetailTemplate`, `DashboardTemplate`, `StateBlock` integration.
+- **Plan 5 — Storybook catalog + toolbar:** global theme/density toolbar
+ decorators in `.storybook/preview.ts`; the ~80-component PrimeVue
+ standard catalog (grouped per crewli-starter `ComponentsPage.vue`);
+ Foundations stories. CT specs stay standalone (no
+ `@storybook/test-runner`), per spec §13.
+
+Then the Smart-Filter sub-sprint, then Page-1 (events list), then the
+remaining page trees, per spec §10.
+```