diff --git a/docs/superpowers/plans/2026-06-17-plans-library.md b/docs/superpowers/plans/2026-06-17-plans-library.md
new file mode 100644
index 0000000..01c20ab
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-17-plans-library.md
@@ -0,0 +1,846 @@
+# Plans Library Implementation Plan
+
+> **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:** Let the app hold several named Macroplans in localStorage — switch between them, create and delete them, and download any plan as a portable `.toml` file.
+
+**Architecture:** `useMacroplan` becomes the owner of a `Library` (a `{version, activeId, plans[]}` object in localStorage), exposing the active plan's authoring state plus switch/create/delete operations. A new presentational `PlanSwitcher.vue` renders the header dropdown. A small `useSourceExport` util downloads the active plan's TOML, reusing a `slugify` helper extracted from the existing PNG exporter.
+
+**Tech Stack:** Vue 3 (`
+
+
+
+
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `pnpm exec vitest run src/components/PlanSwitcher.test.ts`
+Expected: PASS (3 tests).
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/components/PlanSwitcher.vue src/components/PlanSwitcher.test.ts
+git commit -m "feat(ui): add the plan switcher dropdown"
+```
+
+---
+
+### Task 3: `useMacroplan` library refactor
+
+Turn the single-source composable into the Library owner: storage + migration + active-plan binding + autosave, plus the switch/create/delete operations. Keep `resetToSample` exported for now so `App.vue` still compiles — Task 4 removes it.
+
+**Files:**
+- Modify (rewrite): `src/composables/useMacroplan.ts`
+- Test: `src/composables/useMacroplan.test.ts`
+
+**Interfaces:**
+- Consumes: `parseMacroplan` (throws `PlanParseError`; defaults `title` to `'Untitled Macroplan'`), `buildPlan`, `SAMPLE_PLAN` (its title is `"Q3 — Checkout revamp"`).
+- Produces (the composable's return shape, relied on by Task 4):
+ - `source: WritableComputedRef` — reads/writes the active plan's source
+ - `plan: ComputedRef`
+ - `error: ComputedRef`
+ - `plans: ComputedRef<{ id: string; name: string }[]>`
+ - `activeId: ComputedRef`
+ - `selectPlan(id: string): void`
+ - `newPlan(): void`
+ - `deletePlan(id: string): void`
+ - `resetToSample(): void` *(temporary — removed in Task 4)*
+
+- [ ] **Step 1: Write the failing tests (storage, binding, CRUD)**
+
+Create `src/composables/useMacroplan.test.ts`:
+
+```ts
+// @vitest-environment happy-dom
+import { describe, it, expect, beforeEach } from 'vitest'
+import { nextTick } from 'vue'
+import { useMacroplan } from './useMacroplan'
+import { SAMPLE_PLAN } from '../data/sample'
+
+const LIB_KEY = 'macroplan:library'
+const LEGACY_KEY = 'macroplan:source'
+
+beforeEach(() => localStorage.clear())
+
+describe('useMacroplan — load & migration', () => {
+ it('seeds one sample plan when storage is empty, and persists it immediately', () => {
+ const m = useMacroplan()
+ expect(m.plans.value).toHaveLength(1)
+ expect(m.activeId.value).toBe(m.plans.value[0].id)
+ expect(m.source.value).toBe(SAMPLE_PLAN)
+ expect(localStorage.getItem(LIB_KEY)).toBeTruthy() // survives a reload
+ })
+
+ it('migrates a legacy single-source store into a one-plan library and drops the legacy key', () => {
+ localStorage.setItem(LEGACY_KEY, SAMPLE_PLAN)
+ const m = useMacroplan()
+ expect(m.plans.value).toHaveLength(1)
+ expect(m.source.value).toBe(SAMPLE_PLAN)
+ expect(m.plans.value[0].name).toBe('Q3 — Checkout revamp')
+ expect(localStorage.getItem(LEGACY_KEY)).toBeNull()
+ })
+
+ it('falls back to a fresh sample when the library JSON is corrupt', () => {
+ localStorage.setItem(LIB_KEY, '{ not valid json')
+ const m = useMacroplan()
+ expect(m.plans.value).toHaveLength(1)
+ expect(m.source.value).toBe(SAMPLE_PLAN)
+ })
+
+ it('repairs a stale activeId instead of discarding the stored plans', () => {
+ localStorage.setItem(
+ LIB_KEY,
+ JSON.stringify({
+ version: 1,
+ activeId: 'gone',
+ plans: [{ id: 'x', name: 'Kept', source: SAMPLE_PLAN }],
+ }),
+ )
+ const m = useMacroplan()
+ expect(m.activeId.value).toBe('x')
+ expect(m.plans.value[0].name).toBe('Kept')
+ })
+})
+
+describe('useMacroplan — active plan binding', () => {
+ it('refreshes the cached name when the active source parses to a title, and autosaves', async () => {
+ const m = useMacroplan()
+ m.source.value = 'title = "Renamed"\n'
+ await nextTick()
+ expect(m.plans.value[0].name).toBe('Renamed')
+ expect(localStorage.getItem(LIB_KEY)).toContain('Renamed')
+ })
+
+ it('keeps the last-good name and render when the source is mid-edit/broken', async () => {
+ const m = useMacroplan()
+ const goodPlan = m.plan.value
+ m.source.value = 'title = "Renamed"\n[[feature]]\n' // feature missing name → parse error
+ await nextTick()
+ expect(m.error.value).toBeTruthy()
+ expect(m.plan.value).toBe(goodPlan) // last-good render retained
+ expect(m.plans.value[0].name).toBe('Q3 — Checkout revamp') // name unchanged
+ })
+})
+
+describe('useMacroplan — CRUD', () => {
+ it('newPlan appends a sample plan and activates it', () => {
+ const m = useMacroplan()
+ const firstId = m.activeId.value
+ m.newPlan()
+ expect(m.plans.value).toHaveLength(2)
+ expect(m.activeId.value).not.toBe(firstId)
+ expect(m.source.value).toBe(SAMPLE_PLAN)
+ })
+
+ it('deletePlan removes the active plan and re-points to the preceding one', () => {
+ const m = useMacroplan()
+ m.newPlan() // 2 plans; second is active
+ const [first, second] = m.plans.value
+ m.deletePlan(second.id)
+ expect(m.plans.value).toHaveLength(1)
+ expect(m.activeId.value).toBe(first.id)
+ })
+
+ it('deleting the last plan re-seeds a fresh sample (never empty)', () => {
+ const m = useMacroplan()
+ m.deletePlan(m.activeId.value)
+ expect(m.plans.value).toHaveLength(1)
+ expect(m.source.value).toBe(SAMPLE_PLAN)
+ })
+
+ it('switching to a broken plan shows its own empty state, not the previous render', async () => {
+ const m = useMacroplan()
+ const aId = m.activeId.value
+ m.newPlan()
+ const bId = m.activeId.value
+ m.source.value = 'title = "B"\n[[feature]]\n' // B broken
+ await nextTick()
+ m.selectPlan(aId)
+ await nextTick()
+ expect(m.plan.value).toBeTruthy() // A renders
+ m.selectPlan(bId)
+ await nextTick()
+ expect(m.error.value).toBeTruthy() // B's parse error surfaces
+ expect(m.plan.value).toBeNull() // no stale A render leaks through
+ })
+})
+```
+
+- [ ] **Step 2: Run the tests to verify they fail**
+
+Run: `pnpm exec vitest run src/composables/useMacroplan.test.ts`
+Expected: FAIL — the new API (`plans`, `activeId`, `newPlan`, etc.) does not exist yet.
+
+- [ ] **Step 3: Rewrite `useMacroplan.ts`**
+
+Replace the entire file with:
+
+```ts
+import { ref, computed, watch } from 'vue'
+import { parseMacroplan } from '../model/parse'
+import { buildPlan } from '../model/plan'
+import type { Plan } from '../model/types'
+import { SAMPLE_PLAN } from '../data/sample'
+
+const STORAGE_KEY = 'macroplan:library'
+const LEGACY_KEY = 'macroplan:source'
+
+export interface StoredPlan {
+ id: string
+ name: string
+ source: string
+}
+
+interface Library {
+ version: 1
+ activeId: string
+ plans: StoredPlan[]
+}
+
+/** The source's title if it fully parses, else null (so the cached name only
+ * ever updates on a valid parse). */
+function titleOf(source: string): string | null {
+ try {
+ return parseMacroplan(source).title
+ } catch {
+ return null
+ }
+}
+
+function newStoredPlan(source: string): StoredPlan {
+ return { id: crypto.randomUUID(), name: titleOf(source) ?? 'Untitled', source }
+}
+
+function save(lib: Library): void {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(lib))
+ } catch {
+ /* localStorage may be full or blocked — autosave is best-effort */
+ }
+}
+
+/** Resolve the initial library: existing store → legacy migration → fresh seed.
+ * Always persisted before returning so a migrated/seeded library survives a
+ * reload even if the user never edits. */
+function loadLibrary(): Library {
+ let result: Library | null = null
+
+ const raw = localStorage.getItem(STORAGE_KEY)
+ if (raw) {
+ try {
+ const lib = JSON.parse(raw) as Library
+ if (lib && Array.isArray(lib.plans) && lib.plans.length > 0) {
+ if (!lib.plans.some((p) => p.id === lib.activeId)) lib.activeId = lib.plans[0].id
+ result = { version: 1, activeId: lib.activeId, plans: lib.plans }
+ }
+ } catch {
+ /* corrupt JSON → fall through to migration / seed */
+ }
+ }
+
+ if (!result) {
+ const legacy = localStorage.getItem(LEGACY_KEY)
+ if (legacy != null) {
+ const p = newStoredPlan(legacy)
+ localStorage.removeItem(LEGACY_KEY)
+ result = { version: 1, activeId: p.id, plans: [p] }
+ } else {
+ const seed = newStoredPlan(SAMPLE_PLAN)
+ result = { version: 1, activeId: seed.id, plans: [seed] }
+ }
+ }
+
+ save(result)
+ return result
+}
+
+/**
+ * Owns the Library — the collection of named Macroplans in localStorage
+ * (ADR-0002). Exposes the active plan's authoring state (source / parsed plan /
+ * error, with the last good render kept through transient typos, F3) plus the
+ * switch / create / delete operations over the library.
+ */
+export function useMacroplan() {
+ const lib = ref(loadLibrary())
+ const lastGood = ref(null)
+
+ const active = computed(
+ () => lib.value.plans.find((p) => p.id === lib.value.activeId) ?? lib.value.plans[0],
+ )
+
+ const source = computed({
+ get: () => active.value.source,
+ set: (v) => {
+ const p = active.value
+ p.source = v
+ const t = titleOf(v)
+ if (t) p.name = t // refresh the cached label only on a valid parse
+ },
+ })
+
+ const parsed = computed<{ plan: Plan | null; error: string | null }>(() => {
+ try {
+ return { plan: buildPlan(parseMacroplan(source.value)), error: null }
+ } catch (e) {
+ return { plan: null, error: e instanceof Error ? e.message : String(e) }
+ }
+ })
+
+ watch(
+ parsed,
+ (p) => {
+ if (p.plan) lastGood.value = p.plan
+ },
+ { immediate: true },
+ )
+
+ // Persist the whole library on any change (best-effort, like the old autosave).
+ watch(lib, (l) => save(l), { deep: true })
+
+ // Switching plans drops the prior render so a broken target shows its own
+ // error/empty state, not the previous plan's grid.
+ function switchTo(id: string): void {
+ lib.value.activeId = id
+ lastGood.value = parsed.value.plan
+ }
+
+ return {
+ source,
+ plan: computed(() => parsed.value.plan ?? lastGood.value),
+ error: computed(() => parsed.value.error),
+ plans: computed(() => lib.value.plans.map((p) => ({ id: p.id, name: p.name }))),
+ activeId: computed(() => lib.value.activeId),
+ selectPlan: (id: string) => {
+ if (lib.value.plans.some((p) => p.id === id)) switchTo(id)
+ },
+ newPlan: () => {
+ const p = newStoredPlan(SAMPLE_PLAN)
+ lib.value.plans.push(p)
+ switchTo(p.id)
+ },
+ deletePlan: (id: string) => {
+ const idx = lib.value.plans.findIndex((p) => p.id === id)
+ if (idx === -1) return
+ const wasActive = lib.value.activeId === id
+ lib.value.plans.splice(idx, 1)
+ if (lib.value.plans.length === 0) {
+ const seed = newStoredPlan(SAMPLE_PLAN)
+ lib.value.plans.push(seed)
+ switchTo(seed.id)
+ } else if (wasActive) {
+ switchTo(lib.value.plans[Math.max(0, idx - 1)].id)
+ }
+ },
+ // TODO(task 4): remove once App.vue drops the "Reset to sample" button.
+ resetToSample: () => {
+ source.value = SAMPLE_PLAN
+ },
+ }
+}
+```
+
+- [ ] **Step 4: Run the tests to verify they pass**
+
+Run: `pnpm exec vitest run src/composables/useMacroplan.test.ts`
+Expected: PASS (all groups).
+
+- [ ] **Step 5: Confirm the app still typechecks (App.vue unchanged, `resetToSample` retained)**
+
+Run: `pnpm build`
+Expected: build succeeds (no type errors).
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/composables/useMacroplan.ts src/composables/useMacroplan.test.ts
+git commit -m "feat(model): make useMacroplan own a library of named plans"
+```
+
+---
+
+### Task 4: Wire `App.vue` (switcher, delete confirm, .toml download)
+
+Replace the static title block with the switcher, add a trash button + confirm modal that deletes the active plan, wire the `.toml` download, and remove the now-retired `resetToSample`.
+
+**Files:**
+- Modify (rewrite): `src/App.vue`
+- Modify: `src/composables/useMacroplan.ts` (delete the temporary `resetToSample`)
+
+**Interfaces:**
+- Consumes: `useMacroplan` (`source`, `plan`, `error`, `plans`, `activeId`, `selectPlan`, `newPlan`, `deletePlan`), `PlanSwitcher`, `sourceFilename` + `downloadSource`, `exportFilename` + `usePngExport`.
+
+- [ ] **Step 1: Remove the temporary `resetToSample` from `useMacroplan.ts`**
+
+Delete these lines from the returned object:
+
+```ts
+ // TODO(task 4): remove once App.vue drops the "Reset to sample" button.
+ resetToSample: () => {
+ source.value = SAMPLE_PLAN
+ },
+```
+
+- [ ] **Step 2: Rewrite `App.vue`**
+
+Replace the entire file with:
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source (TOML)
+
+
+
+
+
+
+
{{ plan.title }}
+
+
+
+ Nothing to render yet — fix the source on the left.
+
+
+
+
+
+
+
+
+ {{ toast.text }}
+
+
+
+
+
+
+```
+
+- [ ] **Step 3: Typecheck + build**
+
+Run: `pnpm build`
+Expected: build succeeds — no reference to `resetToSample` remains, all props/events typecheck.
+
+- [ ] **Step 4: Run the full test suite**
+
+Run: `pnpm test`
+Expected: PASS — all suites (existing + the new filename, switcher, and library suites).
+
+- [ ] **Step 5: Manual smoke check in the dev server**
+
+Run: `pnpm dev` and open the app, then verify each:
+1. The header shows the current plan name in a dropdown (no more static title).
+2. Open the dropdown → **+ New plan** adds a plan and switches to it (editor shows the sample again).
+3. Edit the `title` in the source → the dropdown label updates to the new title.
+4. Switch back to the first plan via the dropdown → its source returns.
+5. **⤓ Download .toml** downloads a `macroplan-.toml` file containing the current source.
+6. The 🗑 button opens a confirm modal naming the active plan; **Cancel** dismisses, **Delete** removes it and switches to the preceding plan.
+7. Delete every plan in turn → the last delete leaves a fresh sample plan (never an empty app).
+8. Reload the page → the library and active plan persist.
+
+Expected: all eight behave as described.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/App.vue src/composables/useMacroplan.ts
+git commit -m "feat(ui): wire the plan switcher, delete confirm and .toml download into the app"
+```
+
+---
+
+### Task 5: Documentation
+
+Record the new `Library` term in the ubiquitous language and flip the README now that the library exists.
+
+**Files:**
+- Modify: `CONTEXT.md`
+- Modify: `README.md`
+
+- [ ] **Step 1: Add the `Library` term to `CONTEXT.md`**
+
+In the `## Language` section, immediately after the **Now line** entry (the block ending `_Avoid_: today marker, cursor`) and before `## Symbols`, insert:
+
+```markdown
+
+**Library**:
+The collection of saved **Macroplans** held in the browser's localStorage — the live store. Always holds at least one Macroplan; durability rests on exporting a Macroplan's `.toml` (per ADR-0002), not on the Library itself. Carries no status of its own.
+_Avoid_: workspace, project, file list
+```
+
+- [ ] **Step 2: Add a Library relationship line to `CONTEXT.md`**
+
+In `## Relationships`, after the first bullet (the one starting `A **Macroplan** contains a flat, author-ordered list...`), add:
+
+```markdown
+- The **Library** holds many **Macroplans**, exactly one of which is active (shown in the editor and grid). Each is identified internally by a stable id and labelled by its **title**.
+```
+
+- [ ] **Step 3: Update the README Status paragraph**
+
+Replace:
+
+```markdown
+**Feature-complete** against the [design](DESIGN.md) and covered by tests — TOML authoring with live reload, the full week × feature grid render, derived on-time/late classification, milestones, and PNG export all work client-side.
+
+Not yet built: a **library** of multiple named plans. Today a single source autosaves to localStorage.
+```
+
+with:
+
+```markdown
+**Feature-complete** against the [design](DESIGN.md) and covered by tests — TOML authoring with live reload, a **library** of named plans, the full week × feature grid render, derived on-time/late classification, milestones, and PNG + `.toml` export all work client-side.
+```
+
+- [ ] **Step 4: Update the README "How it works" persistence bullet**
+
+Replace:
+
+```markdown
+- Your source **autosaves to localStorage**; **export a PNG** to share into Slack or a deck.
+```
+
+with:
+
+```markdown
+- Keep a **library** of named plans in localStorage and switch between them; **export** any plan as a `.toml` file, or the rendered view as a **PNG** to share into Slack or a deck.
+```
+
+- [ ] **Step 5: Verify the docs build is unaffected and commit**
+
+Run: `pnpm test`
+Expected: PASS (docs-only change; suites still green).
+
+```bash
+git add CONTEXT.md README.md
+git commit -m "docs: record the Library term and mark the plans library as built"
+```
+
+---
+
+## Self-Review
+
+**1. Spec coverage**
+- Multiple named plans in localStorage → Task 3 (`Library` model).
+- Header dropdown switcher → Task 2 (`PlanSwitcher`) + Task 4 (wiring).
+- New plan from sample → Task 3 (`newPlan`) + Task 4.
+- Delete active plan with confirm → Task 3 (`deletePlan`) + Task 4 (modal).
+- `.toml` download → Task 1 (`sourceFilename`/`downloadSource`) + Task 4.
+- Migration from legacy key + always-≥1 invariant + corrupt fallback + stale-activeId repair → Task 3 (tested).
+- Name = cached last-good title → Task 3 (`titleOf` in the `source` setter, tested).
+- `slugify` extracted, `exportFilename` output unchanged → Task 1 (existing test still passes).
+- Switch resets last-good render → Task 3 (`switchTo`, tested).
+- `Library` glossary term + README flip → Task 5.
+- All spec requirements map to a task. No gaps.
+
+**2. Placeholder scan:** No TBD/TODO-as-deliverable, no "add error handling" hand-waves; every code step shows complete code. The one `TODO(task 4)` comment is a deliberate, scheduled removal (Task 4, Step 1), not an unfinished placeholder.
+
+**3. Type consistency:** `slugify` (Task 1) is consumed by `sourceFilename` (Task 1) and unchanged `exportFilename`. `useMacroplan`'s return shape (Task 3) — `source`, `plan`, `error`, `plans`, `activeId`, `selectPlan`, `newPlan`, `deletePlan` — matches exactly what `App.vue` destructures (Task 4). `PlanSwitcher` props (`plans: {id,name}[]`, `activeId: string`) and emits (`select`, `new`, `download`) match the App template bindings. `StoredPlan`/`Library` are internal to `useMacroplan`. Consistent throughout.