- No improvement ideas yet. Start by investigating findings in the Investigation
- workspace, then add improvement ideas to your questions. Answered questions with ideas
- will appear here.
-
);
diff --git a/apps/pwa/src/persistence/PwaHubRepository.ts b/apps/pwa/src/persistence/PwaHubRepository.ts
index 5eafa1744..6f746b699 100644
--- a/apps/pwa/src/persistence/PwaHubRepository.ts
+++ b/apps/pwa/src/persistence/PwaHubRepository.ts
@@ -52,6 +52,7 @@ import type { HubAction } from '@variscout/core/actions';
import type { ProcessHub } from '@variscout/core/processHub';
import type { ProcessMap } from '@variscout/core/frame';
import type { ActionItem } from '@variscout/core/findings';
+import { migrateImprovementProjectMetadata } from '@variscout/core/improvementProject';
import { db, type HubRow } from '../db/schema';
import { applyAction } from './applyAction';
@@ -95,8 +96,11 @@ export class PwaHubRepository implements HubRepository {
this.sustainmentReviews.listByHub(hubMeta.id),
this.controlHandoffs.listByHub(hubMeta.id),
]);
+ const hydrateAt = Date.now();
const liveOutcomes = outcomes.filter(o => o.deletedAt === null);
- const liveProjects = improvementProjects.filter(p => p.deletedAt === null);
+ const liveProjects = improvementProjects
+ .filter(p => p.deletedAt === null)
+ .map(p => migrateImprovementProjectMetadata(p, hydrateAt));
const canonicalProcessMap = canvasRow ? stripHubId(canvasRow) : undefined;
return {
...hubMeta,
diff --git a/docs/decision-log.md b/docs/decision-log.md
index a8dff8f59..bd3f31445 100644
--- a/docs/decision-log.md
+++ b/docs/decision-log.md
@@ -30,6 +30,14 @@ Decisions we keep relitigating. Each entry: short statement, rationale, closing
**Amendment 2026-05-16 — PR-WV1-1 shipped (project membership foundation).** 17 commits on `feat/wedge-pr-wv1-1-project-membership` deliver the wedge §4 membership data model + ACL pattern: `ProjectRole` (`lead | member | sponsor`), `ProjectMember` + `Invitation` types extending `EntityBase`, `MembershipAction` reducer (ADD/UPDATE/REMOVE — patches use `Omit` per `feedback_action_patch_omit_lifecycle`), pure `canAccess(userId, members, action)` ACL function, Annotation-per-user `useProjectMembershipStore`, `InviteModal` + `MemberList` UI primitives with role-gated remove + per-row `aria-label`, Charter stage integration via additive `members[]` on `ImprovementProjectMetadata`, IPDetailPage ACL guard (non-member → `NoAccessRedirect`, Sponsor → `sponsor-report-panel` placeholder pointing to top-level Report nav), legacy `team[]` → wedge `members[]` `migrateTeamToMembers` function, Azure `useInvitationSync` Graph API stub, and PWA/Azure `ProjectsTabView` wired with `currentUserId` (PWA: `'analyst@local'` constant for single-user mode; Azure: `currentUser?.email` from EasyAuth) + `onMembersChange` callback mirroring the legacy `onTeamChange` `applyProjectPatch` pattern. Architecture review (system-architect, Opus) confirmed patterns generalize to PR-WV1-3's `MeasurementPlan` (reducer-over-domain-slice + pure ACL lookup table + sibling field on parent + migration helper). Three architectural decisions deferred to PR-WV1-2 with explicit ownership: (a) **`INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds** — `Invitation.status` + `acceptedAt` + `revokedAt` fields ship but no code mutates them yet; lifecycle action kinds land in PR-WV1-2 alongside Inbox simplification (Inbox surfaces pending invites; acceptance is the user action that transitions status → emits a `PROJECT_MEMBER_ADD` via composite reducer). (b) **`team[]` deprecation cutover** — `migrateTeamToMembers` exists in `packages/core/src/improvementProject/migration.ts` but is invoked nowhere; legacy `team[]` and wedge `members[]` coexist on the same `ImprovementProjectMetadata` for the migration window. PR-WV1-2 (stage rename + Improve fold-in) owns eager one-shot migration during `.vrs`/Dexie round-trip so legacy Sponsors (`team[].role === 'sponsor'`) don't get caught by the wedge ACL guard's `isExplicitlyExcluded` branch. (c) **Per-user persistence key on `useProjectMembershipStore`** — currently uses static `'variscout:projectMembership'` localStorage key (Zustand `persist` middleware). Shared-workstation users (rare in Azure ICP but possible) would bleed pending invites across sessions. Refactor to `useActiveIPStore`-style dynamic-key pattern (no `persist` middleware, manual `localStorage` with `variscout:projectMembership:{userId}` key + scope-keyed `Record` state) lands in PR-WV1-2 alongside auth-wiring refinement. (d) **Wire `canAccess` at consumer call sites** — final Opus code review noted that `IPDetailPage.tsx:126-134` inlines role lookup (`members.find(m => m.userId === currentUserId)?.role`) instead of calling `canAccess`, and `CharterOverview.tsx:129` gates the Invite button on `onInvite` prop presence rather than `canAccess(currentUserId, members, 'manage-membership')`. Today's behavior is correct because the only consumer is `IPDetailPage` itself, but PR-WV1-2's stage editors will need `canAccess('edit-charter')` / `canAccess('edit-improve')` per surface; converging the ACL truth table on one entry point at that point prevents drift. First commit of PR-WV1-2 should add §"Wire `canAccess` at consumer call sites" before stage editors land. Verification: 8/9 packages green via Turbo; `@variscout/ui` full suite stalls on the known unchanged-Canvas hang (per existing memory entries) — touched suites (`IPDetail`, `projects`, `InviteModal`, `MemberList`, `CharterOverview`) run green in isolation. `bash scripts/pr-ready-check.sh` deferred until pre-PR. _Amendment pinned 2026-05-16._
+ **Amendment 2026-05-16 — PR-WV1-2 shipped (Improve restored as top-level tab + Project singular).** Mid-execution of PR-WV1-2 (Improve workspace migration), user surfaced the asymmetry: wedge §3.1 kept Analyze + Investigation as top-level verbs but removed Improve. That collapsed the pre-wedge verb/noun split too aggressively. The amendment **restores Improve as a top-level verb tab** (active-IP-scoped via the existing `useActiveIPContext(sessionHub)` cascade from PR-PT-7), **renames Projects → Project (singular)** to match the active-IP-centric pattern of every cascading verb tab, **trims IP detail from 4 stages to 3** (`Charter / Approach / Sustainment` — the `'improve'` stage retires; Sustainment still absorbs Handoff close-logic per Task 5), and **reuses the production ``** as the Advanced toggle target inside `` (retiring the PR-WV1-2 Task 3 `` skeleton — production primitives over parallel-build). Improve tab empty state when no active IP: `` with "Go to Home" button — mirrors how PR-WV1-1's `NoAccessRedirect` handles the ACL empty state. Preserves wedge §(3) "idea board / action conversion retire" (Improve tab is single-IP-scoped, not free-roaming).
+
+ Engineering shape: 17 commits on `feat/wedge-pr-wv1-2-improve-workspace`. Tasks 0-5 from the original sub-plan delivered (canAccess wiring, stage rename, migrateImprovementProjectMetadata, ImproveStage, ImproveStageAdvanced [now retired], Advanced toggle, Handoff→Sustainment fold). Amendment Tasks A-E delivered the wedge-spec amendment: StageName trim to 3 values, ImproveStage routing removed from IPDetailPage, NoActiveProjectGuidance + ImproveTabRoot built, Improve components moved to `packages/ui/src/components/Improve/`, PWA + Azure shells wired through `` in `` body, `workspace.projects` i18n key renamed to `workspace.project` across 32 locales.
+
+ PR-WV1-1 deferred items disposition: (b) `team[] → members[]` eager cutover via `migrateImprovementProjectMetadata` at .vrs/Dexie hydration — **closed by this PR**. (d) `canAccess` wired at consumer call sites (IPDetailPage + CharterOverview + ImproveStage) — **closed by this PR**. (a) `INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds — **still owed to PR-WV1-3**. (c) Per-user persistence key on `useProjectMembershipStore` — **still owed to PR-WV1-5**.
+
+ Canonical artifacts: spec [`docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md`](superpowers/specs/2026-05-16-improve-tab-amendment-design.md); original sub-plan [`docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md`](superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md); amendment plan [`docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md`](superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md). _Amendment pinned 2026-05-16._
+
- **2026-05-14 — Projects tab + IP detail (Lifecycle) page + Home active-IP launchpad locked.** Brainstorm session resolved coherence spec §15's "IP detail page details" carry-forward and reframed the top-nav structure. Six locks: (1) **7-tab nav split** — coherence's locked 6-tab Path A becomes 7 tabs (`[Home] [Process] [Analyze] [Investigation] [Improve] [Projects] [Report]`), splitting the previous single "Improvement" tab into a verb tab (Improve = legacy `ImprovementView` / PDCA workbench, zero migration) and a noun tab (Projects = new IP lifecycle + detail page); deliberate verb/noun split because the two surfaces serve genuinely different jobs (casual ideation across IPs vs. tracked-project authoring). (2) **Home becomes the active-IP launchpad** for the Project Lead persona; picking an active IP at Home cascades scope to Process / Analyze / Investigation / Improve / Report tabs via the IP-context chip (Coherence §11 pattern #5). Process Owner doesn't pick (hub-level cadence work); SME auto-scopes when accepting a consult; Frontline lives in action cards. State persists per-Hub-per-user via `localStorage` (new `useActiveIPStore` Annotation-layer Zustand store). (3) **IP detail (Lifecycle) page anatomy** — header (back-link / status pill / title / goal summary / team avatars + Invite) + stage tabs (`Charter / Approach / Sustainment / Handoff`) + Overview/Sections segmented toggle (Coherence §11 pattern #2) + 280px right rail team workspace. (4) **Approach stage is SuspectedCause-anchored** — per-cause hierarchy (Hypothesis → ImprovementIdea → ActionItem) as the visual unit in both Overview and Sections modes. Sections mode shows read-only summary with per-cause "Open in Improve workbench" jump-out (avoids duplicating the legacy PDCA tooling). (5) **Report tab IP-scoped with Overview/Technical audience toggle** (fractal pattern with IP detail's mode toggle) — Overview renders a 7-section QC-Story-shaped narrative arc with plain-English UI copy ("Where we started" / "What we aimed for" / "What we found + what we did" / "Did it work?" / "What we standardized + learned" / "What's next"); methodology lineage stays internal per RPS V1 D2 (`feedback_drop_methodology_bridges`). Technical layer renders the full analytical chart suite. Free-roaming Report = Hub-level portfolio. (6) **Single data-model addition** — optional `ImprovementProject.reflection?: string` field (sibling of `signoff`) for analyst lessons-learned narrative; backward-compatible, no `.vrs` format version bump. Spec [`docs/superpowers/specs/2026-05-14-projects-tab-design.md`](superpowers/specs/2026-05-14-projects-tab-design.md) written + committed as `2f4cff53`. Coherence spec amendments applied: §4 (nav: 6 → 7 tabs + Improve/Projects rows in table + amendment block), §15 (carry-forward marked resolved), §16 (new row in spec contributions matrix). Implementation plans next (4 sub-plans recommended: Projects tab + IP detail / Home + cascade / Team rail / Report). Tier behavior: V1 features in PWA free + Azure paid; V2 collaboration features (threaded comments, @-mentions, signoff queue UI, RACI per-section, change notifications) stay deferred + tier-gated when shipped per `feedback_tier_gate_inside_surface`. _Pinned 2026-05-14._
**Amendment 2026-05-15 — Plan 2 PR-PT-6 shipped (PR #177).** Squash-merged as `e7794a21`, first PR of Plan 2 (`docs/superpowers/plans/2026-05-14-projects-tab-launchpad-cascade.md`). Delivered the ADR-078 Annotation-layer `useActiveIPStore` in `@variscout/stores` with per-hub/per-user `localStorage` key `variscout:activeIP:${encodeURIComponent(hubId)}:${encodeURIComponent(userId)}`; no Document-layer persistence, HubAction dispatch, Dexie table, or migration. Added shared `@variscout/ui` Active-IP primitives: Mira's Home launchpad card, presentation helpers, and the shared IP-context chip with the spec's indigo inline style. Wired PWA and Azure Home/Projects/header flows: Project Lead can start/select/switch/exit active IP, Projects list selection sets active IP before opening detail, one-IP session auto-activation honors §4.3, and Exit preserves free-roaming. Added component and app integration tests for store behavior, Home card branches, chip text/actions/style, and PWA/Azure header consumption. Verification: touched-surface Vitest suites + `pnpm docs:check` + `pnpm build` passed; `bash scripts/pr-ready-check.sh` docs failure was fixed by linking the Plan 2 plan from the Projects Tab spec. Full `@variscout/ui` suite still stalls on unchanged `Canvas.test.tsx` in this environment; isolated branch and temp-main repros point to a pre-existing Canvas/Vitest runner issue, not the PR-PT-6 diff. PR final review subagent approved after mandatory checkout STEP 0. _Amendment pinned 2026-05-15._
diff --git a/docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md b/docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md
new file mode 100644
index 000000000..2efd7c49a
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md
@@ -0,0 +1,1124 @@
+---
+title: 'PR-WV1-2 amendment — restore Improve as top-level verb tab + Project singular (bite-sized plan)'
+status: draft
+last-reviewed: 2026-05-16
+related:
+ - docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md
+ - docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md
+ - docs/superpowers/plans/2026-05-16-wedge-implementation.md
+ - docs/07-decisions/adr-082-wedge-architecture.md
+---
+
+# PR-WV1-2 amendment — restore Improve as top-level verb tab + Project singular 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. **TDD IS NON-NEGOTIABLE** per user's explicit reminder: every code-touching step follows red→green→commit.
+
+**Goal:** Amend the in-flight PR-WV1-2 branch to (a) trim `StageName` from 4 values to 3 (drop `'improve'`), (b) remove `` routing from `IPDetailPage`, (c) wire the existing top-level Improve tab to render `` which mounts `` (with active IP) or `` (without), (d) rename the `workspace.projects` i18n key to `workspace.project` across 32 locale files, (e) amend the decision-log to reflect the restored 7-tab nav.
+
+**Architecture:** The existing `` in PWA + the Azure equivalent currently render `` as the legacy Improve panel. The amendment replaces ``'s body with ``, which uses active-IP context (via `useActiveIPContext(sessionHub)`) to choose between the simple tracker (`` from Task 2) and the guidance state. `` continues to back the Advanced toggle inside `` (replacing the Task 3 `` skeleton, which retires). The `'improve'` stage is removed from IP detail per the amendment spec; consumers in `IPDetailPage` are cleaned up.
+
+**Tech Stack:** TypeScript + React 18 + Zustand + Vitest + React Testing Library.
+
+**Canonical spec:** [`docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md`](../specs/2026-05-16-improve-tab-amendment-design.md).
+
+**Parent sub-plan (historical):** `docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md` — Tasks 0-5 stand verbatim per the amendment spec; this plan covers the rework needed on top.
+
+---
+
+## Branch setup
+
+The branch `feat/wedge-pr-wv1-2-improve-workspace` already exists in the worktree at `/Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace`, 16 commits ahead of PR-WV1-1's HEAD (`7f7d21ea`), top commit `01e65326` (the spec amendment).
+
+Verify:
+
+```bash
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace branch --show-current
+# expect: feat/wedge-pr-wv1-2-improve-workspace
+
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace log --oneline 7f7d21ea..HEAD | head -20
+# expect (top to bottom): 01e65326 spec amendment, 6de849c3 Task 5 Sustainment fold, 80876352 Task 4 toggle, 09e03a37 Task 3 ImproveStageAdvanced, 7b391a9a Task 2 ImproveStage, c6e5872c + 6dba97c4 Task 1 stage rename, d16b7655 + b45a24a2 Task 0 canAccess.
+```
+
+---
+
+## Foundation in place
+
+Already shipped on this branch:
+
+- PR-WV1-2 Task 0 — `canAccess` wired at `IPDetailPage` + `CharterOverview` Invite gate.
+- PR-WV1-2 Task 1 — stage rename to 4 stages (`StageName = 'charter' | 'approach' | 'improve' | 'sustainment'`) + `migrateImprovementProjectMetadata` helper. The amendment trims this to 3 stages.
+- PR-WV1-2 Task 2 — `` component at `packages/ui/src/components/IPDetail/stages/ImproveStage.tsx`. Currently routed from `IPDetailPage`. The amendment moves it.
+- PR-WV1-2 Task 3 — `` skeleton at `packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx`. Mounts existing PDCA primitives. **The amendment retires this** — the production Advanced surface is `` (already wired in ``), not this skeleton.
+- PR-WV1-2 Task 4 — Advanced toggle on `` that swaps in ``. The amendment re-points the toggle target.
+- PR-WV1-2 Task 5 — Handoff close-logic folded into Sustainment closure; `HandoffOverview`/`HandoffSections` deleted.
+
+Scout findings to anchor file paths:
+
+- **PWA Improve tab handler:** `apps/pwa/src/features/panels/panelsStore.ts:113` — `showImprovement: () => set({ activeView: 'improvement' })`. PWA `App.tsx` lines 1330-1344 render `` when `activeView === 'improvement'`.
+- **Azure Improve tab handler:** `apps/azure/src/pages/Editor.tsx:561` — `ps.showImprovement()`. Equivalent panel-store-driven render path in Editor.
+- **Active-IP cascade hook:** `useActiveIPContext(sessionHub)` — returns `{ activeIP, activeIPScope, setActiveIP, clearActiveIP, ... }`. Canonical consumer pattern: `const { activeIP } = useActiveIPContext(sessionHub)`.
+- **ActionItem fetch pattern:** `pwaHubRepository.actionItems.listByHub(activeHubId)` returns `ActionItem[]`. Used in `FrameView.tsx:150` and similar.
+- **i18n locale count:** 32 files in `packages/core/src/i18n/messages/`. The `workspace.projects` key exists in every locale with literal value `'Projects'` (singular semantics in some languages, but English value is plural).
+- **i18n test:** `packages/core/src/i18n/__tests__/index.test.ts:228` — "every locale defines every wall.\* key" pattern. The amendment adds `workspace.project` to every locale; the key-coverage test will fail if any locale is missed.
+- **Nav consumer:** `apps/pwa/src/components/layout/AppHeader.tsx:101-102` — tab config has `{ id: 'projects', labelKey: 'workspace.projects' }`. Azure equivalent in the analogous header file.
+- **`ImproveStage` callers:** Only `packages/ui/src/components/IPDetail/IPDetailPage.tsx:22` imports `ImproveStage`. `ImproveStage.tsx:4` imports `ImproveStageAdvanced` internally.
+
+---
+
+## File structure (final state after amendment)
+
+**Created:**
+
+- `packages/ui/src/components/Improve/NoActiveProjectGuidance.tsx` — guidance panel with role="alert" + heading + body + "Go to Home" button.
+- `packages/ui/src/components/Improve/NoActiveProjectGuidance.test.tsx` — RTL tests.
+- `packages/ui/src/components/Improve/ImproveTabRoot.tsx` — switches between `` (active IP) and `` (no active IP).
+- `packages/ui/src/components/Improve/ImproveTabRoot.test.tsx` — RTL tests covering both branches.
+- `packages/ui/src/components/Improve/index.ts` — barrel exporting both components.
+
+**Moved (via `git mv` to preserve history):**
+
+- `packages/ui/src/components/IPDetail/stages/ImproveStage.tsx` → `packages/ui/src/components/Improve/ImproveStage.tsx`
+- `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx` → `packages/ui/src/components/Improve/ImproveStage.test.tsx`
+
+**Deleted:**
+
+- `packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx` — the Task 3 skeleton retires; `` is the production Advanced surface.
+- `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx`
+
+**Modified:**
+
+- `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx` — `StageName` becomes `'charter' | 'approach' | 'sustainment'`; `STAGE_ORDER` becomes 3 values; `LABEL` drops `'improve'` entry.
+- `packages/ui/src/components/IPDetail/stageState.ts` — `StageStateInputs.improveComplete` retires; `deriveStageState` simplifies per amendment spec table.
+- `packages/ui/src/components/IPDetail/IPDetailPage.tsx` — remove `import { ImproveStage } from './stages/ImproveStage'`; remove `'improve'` case from stage router; remove `onActionAdd`/`Update`/`Remove` from `IPDetailPageProps`.
+- `packages/ui/src/components/IPDetail/__tests__/IPDetailStageTabs.test.tsx` + `stageState.test.ts` + `IPDetailPage.test.tsx` — trim assertions to 3 stages.
+- `packages/ui/src/components/Improve/ImproveStage.tsx` (after move) — Advanced toggle's `` import switches to `` from `@variscout/ui` (verify the actual import path during implementation).
+- `packages/ui/src/components/Improve/ImproveStage.test.tsx` (after move) — Advanced-toggle test updated to assert the production Advanced surface renders (not the deleted skeleton).
+- `packages/ui/src/index.ts` — export new `Improve/` barrel.
+- `packages/core/src/i18n/messages/*.ts` (32 files) — drop `'workspace.projects'` line, add `'workspace.project'` line.
+- `apps/pwa/src/components/layout/AppHeader.tsx` — update tab config: `labelKey: 'workspace.project'`.
+- `apps/azure/src/components/layout/AppHeader.tsx` (or equivalent) — same update.
+- `apps/pwa/src/App.tsx` — `` body replaced by `` wiring (or `` itself updated to compose `` internally).
+- `apps/azure/src/pages/Editor.tsx` — same.
+- `docs/decision-log.md` — amendment under the existing 2026-05-16 wedge entry.
+
+**Sub-path exports:** No new sub-path needed. `Improve/` directory exports flow through the existing `packages/ui/src/index.ts` barrel. Sub-path-export pair (`package.json#exports` + `tsconfig.json#paths`) does NOT need updating.
+
+---
+
+## Task A — Trim `StageName` to 3 values
+
+**Goal:** Remove `'improve'` from `StageName` and downstream. After this, IP detail has 3 stage tabs.
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx`
+- Modify: `packages/ui/src/components/IPDetail/stageState.ts`
+- Test: `packages/ui/src/components/IPDetail/__tests__/IPDetailStageTabs.test.tsx`
+- Test: `packages/ui/src/components/IPDetail/__tests__/stageState.test.ts`
+
+- [ ] **Step 1: Write the failing test asserting 3 stages**
+
+Add to `packages/ui/src/components/IPDetail/__tests__/IPDetailStageTabs.test.tsx`:
+
+```typescript
+describe('STAGE_ORDER (amendment — 3 stages)', () => {
+ it('contains exactly charter, approach, sustainment', () => {
+ // STAGE_ORDER is module-internal; assert via the rendered tab list.
+ const ip: ImprovementProject = {
+ id: 'ip-1',
+ hubId: 'hub-1',
+ createdAt: 0,
+ updatedAt: 0,
+ deletedAt: null,
+ status: 'active',
+ metadata: { title: 'Test' },
+ goal: { outcomeGoal: { outcomeSpecId: 'o-1', baseline: 0.5, target: 1.33 } },
+ sections: { background: {}, investigationLineage: {}, approach: {}, outcomeReference: {} },
+ };
+ render(
+ {}}
+ />
+ );
+ expect(screen.getByTestId('stage-tab-charter')).toBeInTheDocument();
+ expect(screen.getByTestId('stage-tab-approach')).toBeInTheDocument();
+ expect(screen.getByTestId('stage-tab-sustainment')).toBeInTheDocument();
+ expect(screen.queryByTestId('stage-tab-improve')).not.toBeInTheDocument();
+ });
+});
+```
+
+- [ ] **Step 2: Run to verify FAIL**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetailStageTabs
+```
+
+Expected: FAIL — `stage-tab-improve` still present (Task 1 added it).
+
+- [ ] **Step 3: Trim `StageName` and `STAGE_ORDER` in `IPDetailStageTabs.tsx`**
+
+Replace the existing declarations:
+
+```typescript
+export type StageName = 'charter' | 'approach' | 'sustainment';
+
+const STAGE_ORDER: StageName[] = ['charter', 'approach', 'sustainment'];
+```
+
+Update the `LABEL` map: drop the `'improve'` entry entirely. Keep the existing `'charter'`, `'approach'`, `'sustainment'` entries verbatim.
+
+- [ ] **Step 4: Trim `deriveStageState` in `stageState.ts`**
+
+Open `packages/ui/src/components/IPDetail/stageState.ts`. Remove the `improveComplete` field from `StageStateInputs`. Update `deriveStageState` to return the new 3-key shape per the amendment spec table:
+
+```typescript
+export interface StageStateInputs {
+ status: ImprovementProject['status'];
+ sustainmentConfirmed: boolean;
+}
+
+export interface StageStateMap {
+ charter: StageState;
+ approach: StageState;
+ sustainment: StageState;
+}
+
+export function deriveStageState({
+ status,
+ sustainmentConfirmed,
+}: StageStateInputs): StageStateMap {
+ if (sustainmentConfirmed) {
+ return { charter: 'done', approach: 'done', sustainment: 'done' };
+ }
+ switch (status) {
+ case 'draft':
+ return { charter: 'current', approach: 'upcoming', sustainment: 'upcoming' };
+ case 'active':
+ return { charter: 'done', approach: 'current', sustainment: 'upcoming' };
+ case 'closed':
+ return { charter: 'done', approach: 'done', sustainment: 'current' };
+ }
+}
+```
+
+- [ ] **Step 5: Update `stageState.test.ts` to assert the new 3-key shape**
+
+Open `packages/ui/src/components/IPDetail/__tests__/stageState.test.ts`. Remove every reference to `improveComplete` (the input field) and to the `improve` key (in the output). For every test case, replace the 4-key `expect(...).toEqual({ charter, approach, improve, sustainment })` with the 3-key form `{ charter, approach, sustainment }`. Mirror the table above.
+
+If existing tests covered the `improveComplete` signal explicitly (e.g., "when improveComplete is true, sustainment is current"), delete those tests — the signal no longer exists; `status === 'closed'` is the new "sustainment is current" trigger.
+
+- [ ] **Step 6: Update `IPDetailStageTabs.test.tsx` existing tests to match 3 stages**
+
+Find every assertion that pinned 4 tabs and trim to 3. Existing tests that asserted `stage-tab-improve` should now assert it's NOT present (covered by Step 1) or be deleted if redundant.
+
+- [ ] **Step 7: Run all stage-related tests to verify PASS**
+
+```bash
+pnpm --filter @variscout/ui test -- "IPDetailStageTabs|stageState"
+```
+
+Expected: green across both suites.
+
+- [ ] **Step 8: Run the full IPDetail suite to catch downstream breakage**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetail
+```
+
+Expected: green. If `IPDetailPage.test.tsx`'s "Improve stage routing" test (added in PR-WV1-2 Task 2) fails because `stage-tab-improve` is gone, that's expected — Task B deletes that test. Note the failure but proceed to commit; Task B fixes it.
+
+If the failure count is bigger than the one improve-routing test, STOP and report — there's more downstream coupling than the amendment spec anticipated.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace add packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx packages/ui/src/components/IPDetail/stageState.ts packages/ui/src/components/IPDetail/__tests__/IPDetailStageTabs.test.tsx packages/ui/src/components/IPDetail/__tests__/stageState.test.ts
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace commit -m "refactor(ui): trim StageName to 3 values — drop 'improve' stage per amendment"
+```
+
+---
+
+## Task B — Remove `ImproveStage` routing from `IPDetailPage`
+
+**Goal:** `IPDetailPage` no longer renders `` or accepts the three `onAction*` props. Stage router has 3 cases.
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/IPDetailPage.tsx`
+- Modify: `packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx`
+
+- [ ] **Step 1: Write the failing test asserting the props are gone**
+
+In `packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx`, add (near the top of the file, after the existing fixture imports):
+
+```typescript
+describe('IPDetailPageProps (amendment — no onAction* props)', () => {
+ it('does not accept onActionAdd / onActionUpdate / onActionRemove props', () => {
+ // @ts-expect-error onActionAdd should be removed from IPDetailPageProps
+ const props = { ip, onBackToList: () => {}, onActionAdd: () => {} } as IPDetailPageProps;
+ expect(props).toBeDefined();
+ });
+});
+```
+
+If the `IPDetailPageProps` type is not currently exported (it may be local), export it from `IPDetailPage.tsx` for this test:
+
+```typescript
+export interface IPDetailPageProps { ... }
+```
+
+- [ ] **Step 2: Run to verify FAIL (the @ts-expect-error fires red when types still accept the prop)**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetailPage
+```
+
+Expected: the test file fails to type-check OR the @ts-expect-error directive itself raises a tsc warning about being unused. (Vitest will surface the type error if its TS integration is enabled; otherwise this test pins via the type system at build time.)
+
+If the @ts-expect-error pattern doesn't fire in the vitest run, switch to a runtime assertion: after removing the prop from `IPDetailPageProps`, the existing "Improve stage routing" test (added in Task 2) will fail because the component no longer routes `'improve'`. That failure IS the red-light gate. Use whichever signal is firmer.
+
+- [ ] **Step 3: Remove `ImproveStage` import + props from `IPDetailPage.tsx`**
+
+In `packages/ui/src/components/IPDetail/IPDetailPage.tsx`:
+
+- Delete the line `import { ImproveStage } from './stages/ImproveStage';` (currently around line 22).
+- Delete the three optional props from `IPDetailPageProps`: `onActionAdd?`, `onActionUpdate?`, `onActionRemove?`. They retire entirely.
+- Delete any destructured references to these props in the component body.
+- Delete the `case 'improve':` branch from the stage routing switch (if it survived Task A — it shouldn't, since `StageName` no longer includes `'improve'`, but verify).
+
+- [ ] **Step 4: Delete the "Improve stage routing" describe block from `IPDetailPage.test.tsx`**
+
+The test added by PR-WV1-2 Task 2:
+
+```typescript
+describe('Improve stage routing', () => {
+ it('renders ImproveStage when activeStage = improve', () => { ... });
+});
+```
+
+Delete this describe block entirely. No replacement — the Improve stage no longer exists in IP detail.
+
+- [ ] **Step 5: Run the IPDetailPage suite to verify PASS**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetailPage
+```
+
+Expected: all remaining tests pass. The Sponsor placeholder test, the canAccess routing test, the Charter team section test, the ACL guard tests — all should remain green.
+
+- [ ] **Step 6: Update the app-side callers if they pass `onActionAdd`/`Update`/`Remove`**
+
+```bash
+grep -rn "onActionAdd\|onActionUpdate\|onActionRemove" apps/pwa/src apps/azure/src --include="*.ts" --include="*.tsx"
+```
+
+If any consumer (likely `ProjectsTabView.tsx` in both apps) passes these props to ``, remove the prop assignments. The callbacks themselves (if they reference any other code path) can stay for now — Task C reuses them for the Improve tab wiring.
+
+- [ ] **Step 7: Run app-side tests**
+
+```bash
+pnpm --filter @variscout/pwa test -- ProjectsTabView
+pnpm --filter @variscout/azure-app test -- ProjectsTabView
+```
+
+Expected: green. If a test asserted `` was called with specific props, update the test to drop those expectations.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace add packages/ui/src/components/IPDetail/IPDetailPage.tsx packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx apps/pwa/src apps/azure/src
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace commit -m "refactor(ui): remove ImproveStage routing from IPDetailPage per amendment"
+```
+
+---
+
+## Task C — Build `ImproveTabRoot` + `NoActiveProjectGuidance`, retire `ImproveStageAdvanced`, move files, wire apps
+
+**Goal:** Replace the existing top-level Improve tab body (`` content) with ``, which switches between `` (active IP) and `` (no active IP). Reuse `` as the Advanced-toggle target inside ``, retiring ``. Move the Improve components to `packages/ui/src/components/Improve/`.
+
+This is the largest task — split into 4 substantive sub-commits.
+
+### Sub-task C.1 — `NoActiveProjectGuidance` component
+
+**Files:**
+
+- Create: `packages/ui/src/components/Improve/NoActiveProjectGuidance.tsx`
+- Create: `packages/ui/src/components/Improve/__tests__/NoActiveProjectGuidance.test.tsx`
+
+- [ ] **Step 1: Write the failing test**
+
+```typescript
+// packages/ui/src/components/Improve/__tests__/NoActiveProjectGuidance.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { NoActiveProjectGuidance } from '../NoActiveProjectGuidance';
+
+describe('NoActiveProjectGuidance', () => {
+ it('renders the "No active project" heading + body copy', () => {
+ render( {}} />);
+ expect(screen.getByRole('alert')).toBeInTheDocument();
+ expect(screen.getByRole('heading', { name: /no active project/i })).toBeInTheDocument();
+ expect(screen.getByText(/improvement work happens inside a chartered project/i)).toBeInTheDocument();
+ });
+
+ it('renders a "Go to Home" button', () => {
+ render( {}} />);
+ expect(screen.getByRole('button', { name: /go to home/i })).toBeInTheDocument();
+ });
+
+ it('calls onGoHome when the button is clicked', () => {
+ const onGoHome = vi.fn();
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /go to home/i }));
+ expect(onGoHome).toHaveBeenCalledTimes(1);
+ });
+});
+```
+
+- [ ] **Step 2: Run to verify FAIL**
+
+```bash
+pnpm --filter @variscout/ui test -- NoActiveProjectGuidance
+```
+
+Expected: FAIL — module not found.
+
+- [ ] **Step 3: Implement the component**
+
+```tsx
+// packages/ui/src/components/Improve/NoActiveProjectGuidance.tsx
+export interface NoActiveProjectGuidanceProps {
+ onGoHome: () => void;
+}
+
+export function NoActiveProjectGuidance({ onGoHome }: NoActiveProjectGuidanceProps) {
+ return (
+
+
No active project
+
+ Improvement work happens inside a chartered project. Pick a project from Home, or create a
+ new one to start tracking actions and ideating with the PDCA workbench.
+
+
+
+ );
+}
+```
+
+- [ ] **Step 4: Run to verify PASS**
+
+```bash
+pnpm --filter @variscout/ui test -- NoActiveProjectGuidance
+```
+
+Expected: 3/3 pass.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git -C add packages/ui/src/components/Improve/NoActiveProjectGuidance.tsx packages/ui/src/components/Improve/__tests__/NoActiveProjectGuidance.test.tsx
+git -C commit -m "feat(ui): add NoActiveProjectGuidance empty state for Improve tab"
+```
+
+### Sub-task C.2 — Move `ImproveStage` + retire `ImproveStageAdvanced`; re-point Advanced toggle to ``
+
+**Files:**
+
+- Move: `packages/ui/src/components/IPDetail/stages/ImproveStage.tsx` → `packages/ui/src/components/Improve/ImproveStage.tsx`
+- Move: `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx` → `packages/ui/src/components/Improve/__tests__/ImproveStage.test.tsx`
+- Delete: `packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx`
+- Delete: `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx`
+
+- [ ] **Step 1: `git mv` ImproveStage and its test to the new location**
+
+```bash
+cd /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace
+git mv packages/ui/src/components/IPDetail/stages/ImproveStage.tsx packages/ui/src/components/Improve/ImproveStage.tsx
+git mv packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx packages/ui/src/components/Improve/__tests__/ImproveStage.test.tsx
+```
+
+(`__tests__/` directory will be auto-created by `git mv` if it doesn't exist; if it doesn't, create it first with `mkdir`.)
+
+- [ ] **Step 2: Delete `ImproveStageAdvanced`**
+
+```bash
+git rm packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx
+git rm packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx
+```
+
+- [ ] **Step 3: Find the production `ImprovementWorkspaceBase` import path**
+
+```bash
+grep -rn "ImprovementWorkspaceBase\b" packages/ui/src --include="*.tsx" --include="*.ts" | head -10
+```
+
+Note the export path (likely `from '@variscout/ui'` or `from './ImprovementPlan/ImprovementWorkspaceBase'`). Use it in step 4.
+
+- [ ] **Step 4: Update `ImproveStage.tsx` (now at new path) to use `ImprovementWorkspaceBase` for the Advanced view**
+
+Replace the `import { ImproveStageAdvanced } from './ImproveStageAdvanced';` line with:
+
+```typescript
+import { ImprovementWorkspaceBase } from '';
+```
+
+Replace the `` JSX with ``. The actual prop shape of `ImprovementWorkspaceBase` may differ — read its component declaration to identify required props. Likely needs: an `ip` reference, members, action callbacks. If `ImprovementWorkspaceBase` has 10+ required props that aren't reachable from `ImproveStage`'s current prop set, accept them on `ImproveStageProps` as a passthrough bundle (`advancedProps?: ComponentProps`).
+
+- [ ] **Step 5: Update `ImproveStage.test.tsx` (at new path)**
+
+The test "switches to Advanced workbench when toggle clicked" currently asserts `screen.getByLabelText(/context/i)` (from the deleted `ImproveStageAdvanced` skeleton's region label). After the swap to `ImprovementWorkspaceBase`, the toggle target's observable identity changes. Update the assertion:
+
+```typescript
+it('switches to Advanced workbench when toggle clicked', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ fireEvent.click(screen.getByRole('button', { name: /advanced/i }));
+ // ImprovementWorkspaceBase renders an identifiable region — adapt to its actual structure
+ expect(screen.getByTestId('improvement-workspace-base')).toBeInTheDocument();
+});
+```
+
+The exact selector depends on what `ImprovementWorkspaceBase` renders. If it doesn't have a stable test ID, add one (`data-testid="improvement-workspace-base"`) at its root in a tiny separate edit, OR pick a unique always-visible string from its content. Note the choice in the commit message.
+
+- [ ] **Step 6: Update import path in `IPDetailPage.tsx`** (it was removed in Task B, but verify no orphan imports remain)
+
+```bash
+grep -n "ImproveStage\b\|ImproveStageAdvanced\b" packages/ui/src/components/IPDetail/IPDetailPage.tsx
+```
+
+Expected: zero matches (Task B already removed the import).
+
+- [ ] **Step 7: Run tests to verify PASS**
+
+```bash
+pnpm --filter @variscout/ui test -- "ImproveStage|NoActiveProjectGuidance"
+```
+
+Expected: all green. ImproveStage's 8 tests (incl. the updated Advanced toggle test) + the 3 NoActiveProjectGuidance tests = 11 passing.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git -C add packages/ui/src/components/Improve/ packages/ui/src/components/IPDetail/stages/
+git -C commit -m "refactor(ui): move ImproveStage to Improve/; retire ImproveStageAdvanced; reuse ImprovementWorkspaceBase as Advanced view"
+```
+
+### Sub-task C.3 — `ImproveTabRoot` orchestration component
+
+**Files:**
+
+- Create: `packages/ui/src/components/Improve/ImproveTabRoot.tsx`
+- Create: `packages/ui/src/components/Improve/__tests__/ImproveTabRoot.test.tsx`
+- Create: `packages/ui/src/components/Improve/index.ts` (barrel)
+- Modify: `packages/ui/src/index.ts` (export the new barrel)
+
+- [ ] **Step 1: Write the failing tests for both branches**
+
+```typescript
+// packages/ui/src/components/Improve/__tests__/ImproveTabRoot.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { ImproveTabRoot } from '../ImproveTabRoot';
+import type { ImprovementProject } from '@variscout/core/improvementProject';
+import type { ProjectMember } from '@variscout/core/projectMembership';
+import type { ActionItem } from '@variscout/core/findings';
+
+const ip: ImprovementProject = {
+ id: 'ip-1',
+ hubId: 'hub-1',
+ createdAt: 0,
+ updatedAt: 0,
+ deletedAt: null,
+ status: 'active',
+ metadata: {
+ title: 'Test IP',
+ members: [
+ {
+ id: 'pm-1',
+ createdAt: 1,
+ deletedAt: null,
+ userId: 'lead@org',
+ displayName: 'Lead',
+ role: 'lead',
+ invitedAt: 1,
+ } satisfies ProjectMember,
+ ],
+ },
+ goal: { outcomeGoal: { outcomeSpecId: 'o-1', baseline: 0.5, target: 1.33 } },
+ sections: { background: {}, investigationLineage: {}, approach: {}, outcomeReference: {} },
+};
+
+const actions: ActionItem[] = [];
+
+describe('ImproveTabRoot', () => {
+ it('renders NoActiveProjectGuidance when activeIP is null', () => {
+ render(
+ {}}
+ onActionAdd={() => {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByRole('heading', { name: /no active project/i })).toBeInTheDocument();
+ expect(screen.queryByRole('heading', { name: /actions/i })).not.toBeInTheDocument();
+ });
+
+ it('renders ImproveStage scoped to activeIP when set', () => {
+ render(
+ {}}
+ onActionAdd={() => {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByRole('heading', { name: /actions/i })).toBeInTheDocument();
+ expect(screen.queryByRole('heading', { name: /no active project/i })).not.toBeInTheDocument();
+ });
+
+ it('passes onGoHome from NoActiveProjectGuidance through correctly', () => {
+ const onGoHome = vi.fn();
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ fireEvent.click(screen.getByRole('button', { name: /go to home/i }));
+ expect(onGoHome).toHaveBeenCalledTimes(1);
+ });
+});
+```
+
+- [ ] **Step 2: Run to verify FAIL**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveTabRoot
+```
+
+Expected: FAIL — module not found.
+
+- [ ] **Step 3: Implement `ImproveTabRoot`**
+
+```tsx
+// packages/ui/src/components/Improve/ImproveTabRoot.tsx
+import type { ImprovementProject } from '@variscout/core/improvementProject';
+import type { ActionItem } from '@variscout/core/findings';
+import { ImproveStage } from './ImproveStage';
+import { NoActiveProjectGuidance } from './NoActiveProjectGuidance';
+
+export interface ImproveTabRootProps {
+ activeIP: ImprovementProject | null;
+ actions: ActionItem[];
+ currentUserId?: string;
+ onGoHome: () => void;
+ onActionAdd: (action: Pick) => void;
+ onActionUpdate: (
+ actionId: string,
+ patch: Partial>
+ ) => void;
+ onActionRemove: (actionId: string) => void;
+}
+
+export function ImproveTabRoot({
+ activeIP,
+ actions,
+ currentUserId,
+ onGoHome,
+ onActionAdd,
+ onActionUpdate,
+ onActionRemove,
+}: ImproveTabRootProps) {
+ if (activeIP === null) {
+ return ;
+ }
+ const members = activeIP.metadata.members ?? [];
+ const scopedActions = actions.filter(a => a.parentImprovementProjectId === activeIP.id);
+ return (
+
+ );
+}
+```
+
+- [ ] **Step 4: Create the barrel**
+
+```typescript
+// packages/ui/src/components/Improve/index.ts
+export { ImproveTabRoot, type ImproveTabRootProps } from './ImproveTabRoot';
+export { ImproveStage, type ImproveStageProps } from './ImproveStage';
+export {
+ NoActiveProjectGuidance,
+ type NoActiveProjectGuidanceProps,
+} from './NoActiveProjectGuidance';
+```
+
+- [ ] **Step 5: Update `packages/ui/src/index.ts` to re-export the new barrel**
+
+Find an existing `export *` line near the project surfaces (e.g., `export * from './components/projects'` from PR-WV1-1 Task 6's app wiring) and add:
+
+```typescript
+export * from './components/Improve';
+```
+
+Place it alphabetically near other re-exports.
+
+- [ ] **Step 6: Run tests to verify PASS**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveTabRoot
+```
+
+Expected: 3/3 pass.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git -C add packages/ui/src/components/Improve/ImproveTabRoot.tsx packages/ui/src/components/Improve/__tests__/ImproveTabRoot.test.tsx packages/ui/src/components/Improve/index.ts packages/ui/src/index.ts
+git -C commit -m "feat(ui): add ImproveTabRoot orchestration component"
+```
+
+### Sub-task C.4 — Wire `ImproveTabRoot` into PWA + Azure shells
+
+**Files:**
+
+- Modify: `apps/pwa/src/components/ImprovementView.tsx` (or wherever the top-level Improve panel body lives)
+- Modify: `apps/azure/src/components/ImprovementView.tsx` (or equivalent)
+- Modify: existing app-shell test files that exercise the Improve tab
+
+- [ ] **Step 1: Read the existing `ImprovementView` in both apps**
+
+```bash
+find apps/pwa/src apps/azure/src -name "ImprovementView*" -type f
+```
+
+Read each to understand the current component shape, what it renders, and what props it accepts. The amendment replaces its body with `` but preserves the outer wrapper (which likely handles header, layout, etc.).
+
+- [ ] **Step 2: Discover the active-IP cascade pattern + ActionItem fetch pattern at the call site**
+
+```bash
+grep -n "useActiveIPContext\|activeIP\|actionItems.*listByHub" apps/pwa/src/App.tsx apps/azure/src/pages/Editor.tsx | head -15
+```
+
+Find:
+
+- How the existing app shell reads `activeIP` from `useActiveIPContext(sessionHub)`.
+- How it fetches ActionItems for the active hub (likely `pwaHubRepository.actionItems.listByHub(activeHubId)` in PWA; similar in Azure).
+- How it threads these into `` today (or if it doesn't yet).
+
+- [ ] **Step 3: Write the failing test for PWA**
+
+In the existing test file for `App.tsx` or `ImprovementView.test.tsx` (whichever exists), add:
+
+```typescript
+it('renders NoActiveProjectGuidance when Improve tab is opened with no active IP', () => {
+ // Setup: render the PWA shell with no active IP (use the existing pattern for shell tests)
+ render();
+ fireEvent.click(screen.getByRole('tab', { name: /improve/i }));
+ expect(screen.getByRole('heading', { name: /no active project/i })).toBeInTheDocument();
+});
+
+it('renders ImproveStage when Improve tab is opened with an active IP', () => {
+ // Setup: pre-activate an IP via useActiveIPStore.setActiveIP({ hubId, userId }, ip.id)
+ render();
+ fireEvent.click(screen.getByRole('tab', { name: /improve/i }));
+ expect(screen.getByRole('heading', { name: /actions/i })).toBeInTheDocument();
+});
+```
+
+Adapt the test scaffolding to the actual PWA shell test pattern. If integration testing at the App-level is heavy, write a simpler test against `ImprovementView` directly, with stub `activeIP` props.
+
+- [ ] **Step 4: Run to verify FAIL**
+
+```bash
+pnpm --filter @variscout/pwa test -- "ImprovementView|App"
+```
+
+Expected: FAIL on the two new assertions.
+
+- [ ] **Step 5: Update `apps/pwa/src/components/ImprovementView.tsx` to render ``**
+
+Pseudocode (adapt to actual shape):
+
+```tsx
+import { ImproveTabRoot } from '@variscout/ui';
+
+export function ImprovementView(props: ImprovementViewProps) {
+ const { activeIP } = useActiveIPContext(props.sessionHub);
+ const actions = useActionItems(props.sessionHub); // hook that lists actions for the active hub
+ const currentUserId = PWA_USER_ID; // from PR-WV1-1's app wiring
+
+ return (
+ props.onTabChange('home')}
+ onActionAdd={action =>
+ actionItemDispatch({
+ kind: 'ACTION_ITEM_ADD',
+ actionItem: {
+ ...action,
+ id: generateDeterministicId(),
+ createdAt: Date.now(),
+ deletedAt: null,
+ },
+ })
+ }
+ onActionUpdate={(id, patch) => {
+ /* PR-WV1-3 will wire — for now log a console warning */
+ }}
+ onActionRemove={id => {
+ /* PR-WV1-3 will wire */
+ }}
+ />
+ );
+}
+```
+
+`onActionUpdate` and `onActionRemove` lack live dispatch (per PR-WV1-2 Task 2's scoping decision — `ACTION_ITEM_UPDATE`/`REMOVE` action kinds don't exist yet). Wire them as no-op-with-console-warning stubs for V1; PR-WV1-3 adds the action kinds.
+
+`useActionItems` may not exist; if not, write a small hook that wraps `pwaHubRepository.actionItems.listByHub(activeHubId)` using the same pattern `FrameView.tsx:150` uses. Keep it in `apps/pwa/src/features/improvement/` or similar.
+
+- [ ] **Step 6: Run PWA tests to verify PASS**
+
+```bash
+pnpm --filter @variscout/pwa test -- "ImprovementView|App"
+```
+
+Expected: green.
+
+- [ ] **Step 7: Repeat Steps 3-6 for Azure**
+
+`apps/azure/src/components/ImprovementView.tsx` gets the same treatment, sourcing `currentUserId` from `currentUser?.email` (EasyAuth, per PR-WV1-1's app wiring) and using the Azure `azureHubRepository.actionItems.listByHub` fetch.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git -C add apps/pwa/src apps/azure/src
+git -C commit -m "feat(apps): wire ImproveTabRoot into PWA + Azure Improve tab"
+```
+
+---
+
+## Task D — Rename `workspace.projects` i18n key to `workspace.project`
+
+**Goal:** Singular noun. 32 locale files updated; the nav consumer reads the new key.
+
+**Files:**
+
+- Modify: `packages/core/src/i18n/messages/*.ts` (32 files)
+- Modify: `apps/pwa/src/components/layout/AppHeader.tsx` (tab config)
+- Modify: `apps/azure/src/components/layout/AppHeader.tsx` (or equivalent)
+- Test: `packages/core/src/i18n/__tests__/index.test.ts` (already exists; will guard the transition)
+
+- [ ] **Step 1: Write the failing test asserting the new key exists in every locale**
+
+In `packages/core/src/i18n/__tests__/index.test.ts`, add:
+
+```typescript
+describe('workspace.project key (amendment — replaces workspace.projects)', () => {
+ it('is defined in every locale', () => {
+ for (const [localeName, messages] of Object.entries(ALL_LOCALES)) {
+ expect(messages, `${localeName} missing workspace.project`).toHaveProperty(
+ 'workspace.project'
+ );
+ }
+ });
+
+ it('workspace.projects is no longer present in any locale', () => {
+ for (const [localeName, messages] of Object.entries(ALL_LOCALES)) {
+ expect(messages, `${localeName} still has workspace.projects`).not.toHaveProperty(
+ 'workspace.projects'
+ );
+ }
+ });
+});
+```
+
+`ALL_LOCALES` should be the existing aggregate the test file uses for the wall.\* key-coverage test (read `index.test.ts:228` for the pattern). Mirror it.
+
+- [ ] **Step 2: Run to verify FAIL**
+
+```bash
+pnpm --filter @variscout/core test -- i18n
+```
+
+Expected: FAIL — `workspace.project` not defined yet.
+
+- [ ] **Step 3: Inventory all 32 locale files + their current `workspace.projects` line**
+
+```bash
+grep -n "'workspace\.projects'" packages/core/src/i18n/messages/*.ts | head -40
+```
+
+Confirm 32 hits, one per file. Note each file's path.
+
+- [ ] **Step 4: Apply the rename across all 32 locales**
+
+For each locale file, replace the single line:
+
+```typescript
+'workspace.projects': '',
+```
+
+with:
+
+```typescript
+'workspace.project': '',
+```
+
+The translated value stays the same string verbatim (the singular form in each language). For English (`en.ts`), the value goes from `'Projects'` to `'Project'`. For other locales, judge case-by-case:
+
+- Finnish (`fi.ts`): `'Projektit'` → `'Projekti'`
+- Swedish (`sv.ts`): `'Projekt'` (already singular in Swedish for both) — change key, keep value
+- Japanese (`ja.ts`): `'プロジェクト'` (no plural in Japanese) — change key, keep value
+- Simplified Chinese (`zhHans.ts`): `'项目'` (no plural in Chinese) — change key, keep value
+- German (`de.ts`): `'Projekte'` → `'Projekt'`
+
+For each locale, the convention is: if the language distinguishes singular/plural, switch to singular. If not, keep the existing word. Use a single `sed -i` invocation per file if confident, OR open each file and edit manually for the 3-4 locales you're least confident in.
+
+Verify your edits for at least these 5 locales before committing:
+
+- `en.ts`
+- `fi.ts`
+- `de.ts`
+- `ja.ts`
+- `zhHans.ts`
+
+- [ ] **Step 5: Update the nav consumer in PWA**
+
+In `apps/pwa/src/components/layout/AppHeader.tsx:101-102`, change:
+
+```typescript
+{ id: 'projects', labelKey: 'workspace.projects' }
+```
+
+to:
+
+```typescript
+{ id: 'projects', labelKey: 'workspace.project' }
+```
+
+The tab `id` stays `'projects'` (internal identifier; not user-visible). Only the `labelKey` changes.
+
+- [ ] **Step 6: Update the Azure equivalent**
+
+```bash
+grep -rn "labelKey: 'workspace.projects'" apps/azure/src --include="*.ts" --include="*.tsx"
+```
+
+Apply the same edit at every hit.
+
+- [ ] **Step 7: Run tests to verify PASS**
+
+```bash
+pnpm --filter @variscout/core test -- i18n
+pnpm --filter @variscout/pwa test -- AppHeader
+pnpm --filter @variscout/azure-app test -- AppHeader
+```
+
+Expected: green on all three.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git -C add packages/core/src/i18n/ apps/pwa/src/components apps/azure/src
+git -C commit -m "refactor(i18n): rename workspace.projects to workspace.project (singular)"
+```
+
+---
+
+## Task E — Final verification + decision-log amendment
+
+**Goal:** Run pre-PR check suite. Browser walk. Decision-log entry.
+
+- [ ] **Step 1: Run targeted test sweep**
+
+```bash
+cd /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace
+pnpm --filter @variscout/core test 2>&1 | tail -5
+pnpm --filter @variscout/stores test 2>&1 | tail -5
+pnpm --filter @variscout/pwa test 2>&1 | tail -5
+pnpm --filter @variscout/azure-app test 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- IPDetail 2>&1 | tail -10
+pnpm --filter @variscout/ui test -- Improve 2>&1 | tail -10
+pnpm --filter @variscout/ui test -- projects 2>&1 | tail -10
+pnpm --filter @variscout/ui test -- Charter 2>&1 | tail -10
+pnpm --filter @variscout/ui test -- Sustainment 2>&1 | tail -10
+```
+
+Expected: all green. The full `@variscout/ui` suite has a documented Canvas hang per prior memory entries — touched suites in isolation are the gate.
+
+- [ ] **Step 2: Run full build**
+
+```bash
+pnpm build 2>&1 | tail -10
+```
+
+Expected: 5/5 packages/apps green.
+
+- [ ] **Step 3: Run pr-ready-check**
+
+```bash
+bash scripts/pr-ready-check.sh 2>&1 | tail -30
+```
+
+Expected: green.
+
+- [ ] **Step 4: Browser walk via `claude --chrome`**
+
+Verify each scenario in the amendment spec §"Acceptance criteria":
+
+1. App loads → nav shows 7 tabs in order `[Home] [Project] [Process] [Analyze] [Investigation] [Improve] [Report]`.
+2. Tab labels show "Project" (singular) in English.
+3. Switch locale to Finnish → tab shows "Projekti" (singular).
+4. Pick a project from Home (sets active IP).
+5. Click Project tab → 3 stage tabs visible (Charter / Approach / Sustainment); no Improve stage.
+6. Click Improve tab → simple action tracker renders for the active IP; "Add action" button visible.
+7. Click "Advanced" toggle → `` renders (PDCA workbench with Brainstorm + IdeaCard + Prioritization + What-If).
+8. Click "Simple view" → tracker returns.
+9. Exit IP (clear active IP from Home or chip) → click Improve tab → "No active project" guidance renders with "Go to Home" button.
+10. Click "Go to Home" → lands on Home tab.
+
+Capture any failures and decide: block PR or fold to follow-up.
+
+- [ ] **Step 5: Amend `docs/decision-log.md` under the existing 2026-05-16 wedge entry**
+
+Add a new "Amendment 2026-05-16 — PR-WV1-2 shipped (Improve restored as top-level tab + Project singular)" block. Cover:
+
+- 7-tab nav reinstated: `[Home] [Project] [Process] [Analyze] [Investigation] [Improve] [Report]`. Reverses decision-log §(1) "Improve tab removed as top-level".
+- "Projects" → "Project" singular rename across the i18n surface.
+- Project detail trimmed from 4 stages to 3 (`Charter / Approach / Sustainment`); the `'improve'` stage retires.
+- Improve tab content: `` mounts `` (simple action tracker) with the Advanced toggle re-pointed to the production ``. `` skeleton from PR-WV1-2 Task 3 retires.
+- `` and `` live in `packages/ui/src/components/Improve/`.
+- Improve tab activates active-IP cascade pattern from PR-PT-7: empty state when no active IP, scoped tracker when active.
+- Preserves decision-log §(3): cross-IP idea board / action conversion still retire. The Improve tab is single-IP-scoped, not free-roaming.
+- Remaining PR-WV1-1 followups: (a) `INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds → still owed to PR-WV1-3. (c) Per-user persistence key on `useProjectMembershipStore` → still owed to PR-WV1-5.
+- Canonical artifact: `docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md`.
+
+- [ ] **Step 6: Commit the decision-log amendment**
+
+```bash
+git -C add docs/decision-log.md
+git -C commit -m "docs(wedge): log PR-WV1-2 delivery + Improve-tab amendment"
+```
+
+- [ ] **Step 7: Push + open PR**
+
+```bash
+git -C push -u origin feat/wedge-pr-wv1-2-improve-workspace
+gh pr create --title "feat(wedge): PR-WV1-2 Improve workspace migration (amended)" \
+ --body "$(cat <<'EOF'
+## Summary
+
+Implements wedge V1 spec §3.2 + §3.3 with the 2026-05-16 amendment (Improve restored as top-level verb tab; Projects → Project singular). 4-stage IP detail collapses to 3 (`Charter / Approach / Sustainment`) with Handoff folded into Sustainment closure. Top-level Improve tab renders simple action tracker (default) + production `` PDCA workbench (Advanced toggle), scoped to active IP via PR-PT-7 cascade. Empty state guides to Home when no active IP.
+
+Also absorbs two of PR-WV1-1's deferred items:
+- (b) `team[] → members[]` eager cutover at .vrs / Dexie hydration via `migrateImprovementProjectMetadata`
+- (d) `canAccess` wired at consumer call sites (IPDetailPage + CharterOverview Invite gate)
+
+## Test plan
+
+- [x] pnpm test (per-package targeted; full @variscout/ui suite has documented Canvas hang)
+- [x] pnpm build green
+- [x] bash scripts/pr-ready-check.sh
+- [ ] `--chrome` browser walk per amendment spec §Acceptance criteria (10 scenarios)
+
+## Spec amendment
+
+`docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md`
+
+## Master plan
+
+`docs/superpowers/plans/2026-05-16-wedge-implementation.md` PR-WV1-2 row.
+
+## Sub-plans
+
+`docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md` (original)
+`docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md` (amendment)
+EOF
+)"
+```
+
+GitHub will show the PR's base as `feat/wedge-pr-wv1-1-project-membership` until PR-WV1-1 (#183) merges; then auto-updates to main.
+
+---
+
+## Verification
+
+End-to-end:
+
+1. **Schema:** `StageName` is 3 values; legacy `'handoff'` removed earlier; `'improve'` removed by this amendment. `migrateImprovementProjectMetadata` still folds team[] → members[] at hydration.
+2. **Stage flow:** Project detail renders 3 stage tabs. Sustainment closure absorbs Handoff close-action. No `'improve'` stage anywhere.
+3. **ACL:** `canAccess` is the single ACL entry point. `` gates Add/Mark-done/Remove via `canAccess('edit-improve')`.
+4. **Nav:** 7 tabs in order. `workspace.project` key live; `workspace.projects` gone.
+5. **Improve tab:** `` switches between `` (active IP) and `` (no active IP). Advanced toggle reaches ``.
+6. **Apps:** PWA + Azure shells render `` inside `` body.
+
+---
+
+## Self-review checklist
+
+- [ ] **Spec coverage**: each numbered item in `2026-05-16-improve-tab-amendment-design.md` §"Acceptance criteria" maps to a task above.
+- [ ] **Placeholder scan**: no TBD / TODO. Every code block has actual code.
+- [ ] **Type consistency**: `StageName`, `IPDetailPageProps`, `ImproveTabRootProps`, `ImproveStageProps`, `NoActiveProjectGuidanceProps` used consistently.
+- [ ] **No `Math.random`** in code or tests.
+- [ ] **No "root cause"** language anywhere (P5 amended).
+- [ ] **No `.toFixed()`** on stat values.
+- [ ] **TDD compliance**: every code-touching task has 5-step rhythm (test → fail → impl → pass → commit).
+- [ ] **Sub-path exports paired**: no new sub-path needed — `Improve/` flows through existing `packages/ui` barrel.
+- [ ] **Patch types**: `ActionItem` UPDATE patch uses `Omit` per `feedback_action_patch_omit_lifecycle`.
+- [ ] **`canAccess` is the single ACL entry point** after this amendment.
+- [ ] **No inline role-string comparisons** in IPDetailPage / CharterOverview / ImproveStage / ImproveTabRoot.
+
+---
+
+## Deferred to later PRs (still applies, unchanged from PR-WV1-2 original plan)
+
+- **PR-WV1-1 deferred item (a) — `INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds** — still owed to PR-WV1-3 (Investigation Wall + Inbox simplification).
+- **PR-WV1-1 deferred item (c) — Per-user persistence key on `useProjectMembershipStore`** — still owed to PR-WV1-5 (tier-gating retirement + nav reorder).
+- **ADR-080 auto-fire trigger** — still out of scope. Data model preserved; trigger pending.
+
+---
+
+## Execution handoff
+
+Plan complete and saved to `docs/superpowers/plans/2026-05-16-pr-wv1-2-amendment-improve-tab-restore.md`. Per `feedback_subagent_driven_default`, dispatching `superpowers:subagent-driven-development` next without asking — fresh implementer subagent per task (A through E), Sonnet workhorses, spec + quality reviewer pair per task, final architecture review (system-architect Opus) + code-review skill (Opus) at end.
diff --git a/docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md b/docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md
new file mode 100644
index 000000000..a90143e72
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md
@@ -0,0 +1,1387 @@
+---
+title: 'PR-WV1-2 — Improve Workspace Migration (bite-sized plan)'
+status: draft
+last-reviewed: 2026-05-16
+related:
+ - docs/superpowers/specs/2026-05-16-wedge-architecture-design.md
+ - docs/superpowers/plans/2026-05-16-wedge-implementation.md
+ - docs/superpowers/plans/2026-05-16-pr-wv1-1-project-membership.md
+ - docs/07-decisions/adr-082-wedge-architecture.md
+ - docs/07-decisions/adr-080-sustainment-auto-fire-pattern.md
+---
+
+# PR-WV1-2 — Improve Workspace Migration 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:** Migrate the legacy top-level Improve workspace into the Projects detail page as a 4-stage flow (`Charter / Approach / Improve / Sustainment` — Handoff folded into Sustainment closure). Introduce a simple `ActionItem`-backed tracker as the default Improve stage UI; expose the existing PDCA primitives behind an Advanced toggle. Cut the top-level Improve tab from the app shell, and absorb the two decision-log followups that are tightly coupled to this slice: (b) eager `team[] → members[]` migration and (d) `canAccess` wiring at consumer call sites.
+
+**Architecture:** Stage-list refactor in `@variscout/ui`'s `IPDetailStageTabs` (the canonical 4-stage source), a new `ImproveStage` + `ImproveStageAdvanced` pair under `packages/ui/src/components/IPDetail/stages/`, fold of `HandoffOverview`/`HandoffSections` into `SustainmentOverview`/`SustainmentSections`, and route-handler/i18n cleanup in the PWA + Azure shells. Stage rename ships with a `migrateImprovementProjectMetadata` helper in `@variscout/core/improvementProject` that performs the legacy → wedge migration once at .vrs / Dexie hydration time, calling the already-shipped `migrateTeamToMembers` from PR-WV1-1.
+
+**Tech Stack:** TypeScript + React 18 + Zustand + Dexie + Vitest + React Testing Library.
+
+**Parent plan:** [`docs/superpowers/plans/2026-05-16-wedge-implementation.md`](2026-05-16-wedge-implementation.md) (PR-WV1-2 row).
+
+**Canonical spec:** [`docs/superpowers/specs/2026-05-16-wedge-architecture-design.md`](../specs/2026-05-16-wedge-architecture-design.md) §3.2 (4-stage IP detail) + §3.3 (Improve = simple tracker by default).
+
+**PR-WV1-1 deferred items absorbed here:** (b) `team[]` eager cutover (Task 1) + (d) `canAccess` wiring (Task 0). Items (a) invitation lifecycle and (c) per-user persistence key are explicitly **NOT** absorbed — see "Deferred to later PRs" section at the bottom.
+
+---
+
+## PR sizing — single PR off this branch
+
+Per `feedback_slice_size_cap` ("Cap slices at ~6–8 tasks/PR; multi-PR off one branch when larger"), this plan is 8 tasks. The decision-log fold-ins (canAccess wiring, team[] cutover) compose cleanly with the master-plan content because:
+
+- Task 0 (canAccess wiring) MUST land before Task 2 builds ImproveStage — the new stage gates its edit affordances via `canAccess('edit-improve')`, so converging the ACL truth table FIRST keeps Task 2 from inventing a parallel inline check.
+- Task 1's stage rename already touches the `.vrs`/Dexie migration path; folding `team[] → members[]` here is one extra function call, not a new task.
+
+Recommendation: **single PR**. If reviewers ask for a split, the natural seam is at the end of Task 1 (canAccess + stage rename + migrations all coherent foundation work; remaining tasks are pure Improve-UI build-out).
+
+---
+
+## Branch setup
+
+Branch already exists: `feat/wedge-pr-wv1-2-improve-workspace`, based on PR-WV1-1's HEAD (`7f7d21ea`). Worktree at `.worktrees/feat/wedge-pr-wv1-2-improve-workspace`. `pnpm install` already completed.
+
+Verify:
+
+```bash
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace branch --show-current
+# expect: feat/wedge-pr-wv1-2-improve-workspace
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace log --oneline -5
+# expect top three commits to be PR-WV1-1 work (7f7d21ea, fce353ff, 695091e3)
+```
+
+---
+
+## Foundation already in place (from PR-WV1-1)
+
+- `@variscout/core/projectMembership`: `ProjectRole` (`'lead' | 'member' | 'sponsor'`), `ProjectMember`, `Invitation`, `MembershipAction`, `canAccess(userId, members, action)`, `ProjectAction` (`'edit-charter' | 'edit-approach' | 'edit-improve' | 'edit-sustainment' | 'manage-membership' | 'view-report'`).
+- `@variscout/core/improvementProject/migration.ts`: `migrateTeamToMembers(legacyTeam, migrationTimestamp): ProjectMember[]` — exists but currently invoked nowhere.
+- `ImprovementProjectMetadata` has BOTH `team?` (legacy) and `members?: ProjectMember[]` (wedge) — coexisting during the migration window.
+- `IPDetailPage` renders the Sponsor placeholder (`data-testid="sponsor-report-panel"`) and `NoAccessRedirect` for non-members when `currentUserId` + populated `members[]` are both present; inline role lookup is used today (will be replaced by Task 0).
+
+---
+
+## Surfaces touched (from Explore scout 2026-05-16)
+
+| Concern | Canonical file |
+| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Stage type + `STAGE_ORDER` | `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx` |
+| Stage routing in IP detail | `packages/ui/src/components/IPDetail/IPDetailPage.tsx` |
+| Sustainment + Handoff stage views | `packages/ui/src/components/IPDetail/stages/SustainmentOverview.tsx`, `SustainmentSections.tsx`, `HandoffOverview.tsx`, `HandoffSections.tsx` |
+| Existing PDCA primitives | `packages/ui/src/components/ImprovementPlan/{PrioritizationMatrix,BrainstormModal,IdeaGroupCard,ImprovementContextPanel}.tsx`, `packages/ui/src/components/WhatIfExplorer/WhatIfExplorer.tsx` |
+| Top-level Improve tab handler | `apps/pwa/src/App.tsx:398` (`else if (tab === 'improve') panels.showImprovement()`), `apps/azure/src/pages/Editor.tsx:561` (`ps.showImprovement()`) |
+| Tab i18n keys | `packages/core/src/i18n/messages/*.ts` (key `workspace.improve`) |
+| ActionItem entity (drives simple tracker) | `packages/core/src/findings/types.ts:161` |
+| ImprovementProject types | `packages/core/src/improvementProject/types.ts` |
+
+---
+
+## Task 0 — Wire `canAccess` at consumer call sites
+
+**Goal:** Replace the inline `members.find(m => m.userId === currentUserId)?.role` lookup in `IPDetailPage` and the `onInvite`-prop-presence gating in `CharterOverview` with calls to `canAccess` from `@variscout/core/projectMembership`. This converges the ACL truth table on one entry point BEFORE Task 2 adds new consumers.
+
+**Why first:** Per PR-WV1-1's final Opus review: "if PR-WV1-2 adds a 4th role or refines `ProjectAction` semantics, drift between `canAccess` and `IPDetailPage` is silent."
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/IPDetailPage.tsx`
+- Modify: `packages/ui/src/components/IPDetail/stages/CharterOverview.tsx`
+- Modify: `packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx`
+- Modify: `packages/ui/src/components/IPDetail/stages/__tests__/CharterOverview.test.tsx`
+
+### Steps
+
+- [ ] **Step 1: Read both files end-to-end first.** Verify the current inline-lookup shape and the `onInvite`-prop gate location. Note the line numbers; don't write any code yet.
+
+- [ ] **Step 2: Write a failing test asserting `IPDetailPage` uses `canAccess` for the Sponsor placeholder branch**
+
+Add to `packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx` (inside the existing `describe('ACL guard', ...)` block):
+
+```typescript
+it('uses canAccess view-report for Sponsor placeholder gating', () => {
+ // Sponsor's only allowed action is 'view-report'; the placeholder must render
+ // and stage tabs must be hidden — same observable behavior as before, but
+ // routed through canAccess.
+ render(
+ {}}
+ currentUserId="sponsor@org"
+ />
+ );
+ expect(screen.getByTestId('sponsor-report-panel')).toBeInTheDocument();
+ expect(screen.queryByTestId('stage-tab-charter')).not.toBeInTheDocument();
+});
+```
+
+- [ ] **Step 3: Run to verify the test passes today (it tests current observable behavior)**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetailPage
+```
+
+Expected: PASS. This test pins the observable contract; the refactor below must preserve it.
+
+- [ ] **Step 4: Refactor `IPDetailPage.tsx` to use `canAccess`**
+
+Replace the inline lookups (around the `isExplicitlyExcluded` / `isSponsor` derivations) with:
+
+```tsx
+import { canAccess } from '@variscout/core/projectMembership';
+
+// ... inside the component, after `const members = ip.metadata.members ?? [];`
+const hasIdentity = currentUserId !== undefined && members.length > 0;
+const isExplicitlyExcluded = hasIdentity && !canAccess(currentUserId, members, 'view-report');
+const isSponsor =
+ hasIdentity &&
+ canAccess(currentUserId, members, 'view-report') &&
+ !canAccess(currentUserId, members, 'edit-charter');
+```
+
+This preserves the existing two-branch logic but routes every role decision through `canAccess`. Delete any leftover `userRole` const if it's only used by these branches; if it's used elsewhere, leave it.
+
+- [ ] **Step 5: Run the full IPDetailPage suite to verify all 6 ACL-guard tests + the new one pass**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetailPage
+```
+
+Expected: all green (existing 6 ACL tests + Sponsor placeholder test from step 2).
+
+- [ ] **Step 6: Write a failing test asserting CharterOverview gates Invite on `canAccess('manage-membership')`**
+
+Add to `packages/ui/src/components/IPDetail/stages/__tests__/CharterOverview.test.tsx` (inside the existing `describe('Team section', ...)` block):
+
+```typescript
+it('hides the Invite button for non-Lead viewers even when onInvite is provided', () => {
+ // Member-role current user; should NOT see Invite even though onInvite handler is wired
+ render(
+ {}}
+ onMemberRemove={() => {}}
+ />
+ );
+ expect(screen.queryByRole('button', { name: /invite team/i })).not.toBeInTheDocument();
+});
+```
+
+(Use the existing test's members fixture — it should include a Member-role entry with `userId: 'member@org'`. If not, extend the fixture in the same step.)
+
+- [ ] **Step 7: Run the test to verify it fails today** (Member currently sees the Invite button because gating uses `onInvite` prop presence, not role)
+
+```bash
+pnpm --filter @variscout/ui test -- CharterOverview
+```
+
+Expected: FAIL on the new test; the existing 6 tests still pass.
+
+- [ ] **Step 8: Refactor `CharterOverview.tsx` to gate the Invite button via `canAccess`**
+
+Inside the component (where the Team section is rendered), replace the `{onInvite ? ... : null}` gating with:
+
+```tsx
+import { canAccess } from '@variscout/core/projectMembership';
+
+// inside the component body, after destructuring props:
+const canManageMembership =
+ currentUserId !== undefined && canAccess(currentUserId, members, 'manage-membership');
+
+// in JSX, render the Team section only when membership data exists,
+// and render the Invite button only when canManageMembership is true:
+{
+ members !== undefined && (
+
+ ...
+ {canManageMembership && onInvite && (
+
+ )}
+ {})}
+ />
+ ...
+
+ );
+}
+```
+
+If `members` is typed as optional, narrow it explicitly. Keep `onInvite` as a continued requirement for actually opening the modal — `canManageMembership` is a strictly tighter gate.
+
+- [ ] **Step 9: Run the test to verify it now passes**
+
+```bash
+pnpm --filter @variscout/ui test -- CharterOverview
+```
+
+Expected: all 7 Team-section tests pass (6 existing + new Member-hidden Invite test).
+
+- [ ] **Step 10: Commit**
+
+```bash
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace add packages/ui/src/components/IPDetail/IPDetailPage.tsx packages/ui/src/components/IPDetail/stages/CharterOverview.tsx packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx packages/ui/src/components/IPDetail/stages/__tests__/CharterOverview.test.tsx
+git -C /Users/jukka-mattiturtiainen/Projects/VariScout_lite/.worktrees/feat/wedge-pr-wv1-2-improve-workspace commit -m "refactor(ui): route IPDetailPage + Charter ACL through canAccess"
+```
+
+---
+
+## Task 1 — Stage type rename + .vrs migration + team[] → members[] eager cutover
+
+**Goal:** Rename the IP stage list from `['charter', 'approach', 'sustainment', 'handoff']` to `['charter', 'approach', 'improve', 'sustainment']`. Add a single migration helper (`migrateImprovementProjectMetadata`) that runs at `.vrs` load / Dexie rehydration and folds (a) the legacy stage rename + (b) the `team[]` → `members[]` cutover from PR-WV1-1's deferred item (b).
+
+**Why combined:** the `.vrs` / Dexie migration codepath is a single seam. Adding two separate migration passes here would be ceremony; folding them into one helper keeps the migration window short and the migration logic discoverable.
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx` (StageName type, STAGE_ORDER, LABEL map)
+- Modify: `packages/ui/src/components/IPDetail/IPDetailPage.tsx` (stage routing — if any inline references to `'handoff'` exist)
+- Modify: `packages/ui/src/components/IPDetail/index.ts` (barrel — verify the new `StageName` shape still flows through)
+- Create: `packages/core/src/improvementProject/migrateMetadata.ts` — `migrateImprovementProjectMetadata(ip, now)` helper
+- Create: `packages/core/src/improvementProject/__tests__/migrateMetadata.test.ts`
+- Modify: `packages/core/src/improvementProject/index.ts` — export `migrateImprovementProjectMetadata`
+- Modify: `apps/azure/src/services/localDb.ts` AND/OR `apps/azure/src/db/schema.ts` (find via grep — the .vrs / Dexie hydration call site)
+- Modify: `apps/pwa/src/services/` equivalent (find via grep — PWA hydration is likely in-memory only via `useProjectStore`)
+- Modify: `packages/ui/src/components/IPDetail/__tests__/IPDetailStageTabs.test.tsx` (if it pins `'handoff'`)
+
+### Steps
+
+- [ ] **Step 1: Read `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx`** to confirm the exact shape of `StageName`, `STAGE_ORDER`, and the `LABEL` map. Also read `packages/ui/src/components/IPDetail/IPDetailPage.tsx` to identify every `'handoff'` / `'sustainment'` reference (active-stage default, persistence of last-viewed stage, etc.).
+
+- [ ] **Step 2: Find the .vrs / Dexie hydration call site**
+
+```bash
+grep -rn "improvementProjects\|ImprovementProject\b" apps/azure/src/services apps/azure/src/db apps/pwa/src/services 2>/dev/null | grep -v test | head -20
+```
+
+Identify the function(s) that load `ImprovementProject` records from storage (Dexie `.toArray()` / `.get()` reads in Azure; localStorage/Zustand hydration in PWA). The migration call should hook here — every load goes through `migrateImprovementProjectMetadata`.
+
+If unclear after 5 minutes, STOP and report. Don't guess at the call site.
+
+- [ ] **Step 3: Write the failing migration helper test**
+
+```typescript
+// packages/core/src/improvementProject/__tests__/migrateMetadata.test.ts
+import { describe, it, expect } from 'vitest';
+import { migrateImprovementProjectMetadata } from '../migrateMetadata';
+import type { ImprovementProject } from '../types';
+
+describe('migrateImprovementProjectMetadata', () => {
+ const baseIP: ImprovementProject = {
+ id: 'ip-1',
+ hubId: 'hub-1',
+ createdAt: 0,
+ updatedAt: 0,
+ deletedAt: null,
+ status: 'active',
+ metadata: { title: 'Test IP' },
+ goal: { outcomeGoal: { outcomeSpecId: 'o-1', baseline: 0.5, target: 1.33 } },
+ sections: { background: {}, investigationLineage: {}, approach: {}, outcomeReference: {} },
+ };
+
+ it('migrates legacy team[] to members[] when members is absent', () => {
+ const legacy: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [
+ { role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } },
+ { role: 'teamMember', person: { displayName: 'Mira' } },
+ ],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(legacy, 1234);
+ expect(out.metadata.members).toBeDefined();
+ expect(out.metadata.members).toHaveLength(2);
+ expect(out.metadata.members?.[0].role).toBe('lead');
+ expect(out.metadata.members?.[1].role).toBe('member');
+ // Legacy team[] preserved for backward compat readers in this PR
+ expect(out.metadata.team).toBeDefined();
+ });
+
+ it('does not migrate when members[] already populated', () => {
+ const alreadyMigrated: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [{ role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } }],
+ members: [
+ {
+ id: 'pm-existing',
+ createdAt: 100,
+ deletedAt: null,
+ userId: 'someone-else@org',
+ displayName: 'Someone Else',
+ role: 'lead',
+ invitedAt: 100,
+ },
+ ],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(alreadyMigrated, 1234);
+ expect(out.metadata.members).toHaveLength(1);
+ expect(out.metadata.members?.[0].userId).toBe('someone-else@org');
+ });
+
+ it('is a no-op when neither team nor members exist', () => {
+ const out = migrateImprovementProjectMetadata(baseIP, 1234);
+ expect(out.metadata.members).toBeUndefined();
+ expect(out).toEqual(baseIP);
+ });
+
+ it('returns a new object (does not mutate input)', () => {
+ const legacy: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [{ role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } }],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(legacy, 1234);
+ expect(out).not.toBe(legacy);
+ expect(legacy.metadata.members).toBeUndefined();
+ });
+});
+```
+
+- [ ] **Step 4: Run to verify it fails** (module not found)
+
+```bash
+pnpm --filter @variscout/core test -- improvementProject/__tests__/migrateMetadata
+```
+
+Expected: FAIL.
+
+- [ ] **Step 5: Implement `migrateImprovementProjectMetadata`**
+
+```typescript
+// packages/core/src/improvementProject/migrateMetadata.ts
+import type { ImprovementProject } from './types';
+import { migrateTeamToMembers } from './migration';
+
+/**
+ * Idempotent migration applied at hydration time. Folds two PR-WV1-2 changes
+ * over the wedge V1 metadata shape:
+ * 1. legacy `team[]` → wedge `members[]` (via migrateTeamToMembers)
+ *
+ * Legacy `team[]` is preserved on the output for now; PR-WV1-5 (tier-gating
+ * retirement + nav reorder) drops it.
+ */
+export function migrateImprovementProjectMetadata(
+ ip: ImprovementProject,
+ now: number
+): ImprovementProject {
+ const hasMembers = ip.metadata.members !== undefined;
+ const hasLegacyTeam = ip.metadata.team !== undefined && ip.metadata.team.length > 0;
+
+ if (hasMembers || !hasLegacyTeam) {
+ return ip;
+ }
+
+ const members = migrateTeamToMembers(ip.metadata.team, now);
+
+ return {
+ ...ip,
+ metadata: {
+ ...ip.metadata,
+ members,
+ },
+ };
+}
+```
+
+- [ ] **Step 6: Export from `improvementProject` barrel**
+
+In `packages/core/src/improvementProject/index.ts`, add:
+
+```typescript
+export { migrateImprovementProjectMetadata } from './migrateMetadata';
+```
+
+- [ ] **Step 7: Run tests to verify they pass**
+
+```bash
+pnpm --filter @variscout/core test -- improvementProject
+```
+
+Expected: all four migration tests pass, plus all existing core/improvementProject tests.
+
+- [ ] **Step 8: Rename `StageName` + `STAGE_ORDER` in `IPDetailStageTabs.tsx`**
+
+Edit `packages/ui/src/components/IPDetail/IPDetailStageTabs.tsx`:
+
+```typescript
+export type StageName = 'charter' | 'approach' | 'improve' | 'sustainment';
+
+const STAGE_ORDER: StageName[] = ['charter', 'approach', 'improve', 'sustainment'];
+```
+
+Update the `LABEL` map: drop `'handoff'`, add `'improve'`. Use the existing `workspace.improve` i18n key — that key already exists in all locales (per Explore scout finding §3).
+
+- [ ] **Step 9: Update `IPDetailPage.tsx` to remove any hard-coded `'handoff'` references**
+
+Search the file: any `activeStage === 'handoff'` or `'handoff' as StageName` references should map to `'sustainment'` (since Handoff is folded into Sustainment closure in Task 5).
+
+Also update the Sponsor placeholder's stage-tab-absent assertion path if it referenced 4 named stages.
+
+- [ ] **Step 10: Fix existing tests that assert the legacy 4-stage shape**
+
+```bash
+grep -rln "'handoff'\b" packages/ui/src/components/IPDetail/ apps/ --include="*.tsx" --include="*.ts" | head
+```
+
+Update each hit:
+
+- Test fixtures using `status: 'handoff'` should still be valid IP shapes (per `ImprovementProject.status: 'draft' | 'active' | 'closed'` — `'handoff'` is NOT a project status, it's a stage; confirm by checking `improvementProject/types.ts`).
+- Any test assertion like `expect(screen.getByTestId('stage-tab-handoff')).toBeInTheDocument()` should become `'stage-tab-improve'`.
+
+- [ ] **Step 11: Wire the migration helper into Azure Dexie hydration**
+
+From Step 2's discovery, identify the Dexie `.toArray()` call that loads ImprovementProject records on app startup or hub-open. Wrap each loaded IP through `migrateImprovementProjectMetadata(ip, Date.now())` before handing to the store / UI.
+
+If the load happens in multiple places, prefer to add the migration at the lowest call site (closest to the Dexie read) — single seam.
+
+- [ ] **Step 12: Wire the migration helper into PWA hydration**
+
+PWA likely hydrates from `localStorage` + `useProjectStore`. The migration call should happen at the same boundary as the Azure one — wherever the raw stored shape becomes a typed `ImprovementProject` in the store.
+
+If both apps share a hydration helper (e.g., in `@variscout/stores` or a `useEditorDataFlow` hook), wire it once at that helper level.
+
+- [ ] **Step 13: Run the full UI + apps test suites for touched files**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetail 2>&1 | tail -10
+pnpm --filter @variscout/azure-app test -- ProjectsTabView 2>&1 | tail -10
+pnpm --filter @variscout/pwa test -- ProjectsTabView 2>&1 | tail -10
+```
+
+Expected: all green.
+
+- [ ] **Step 14: Commit**
+
+```bash
+git -C add packages/core/src/improvementProject/ packages/ui/src/components/IPDetail/ apps/azure/src/ apps/pwa/src/
+git -C commit -m "feat(core): rename Handoff stage to Improve; eager-migrate legacy team[]"
+```
+
+If the diff is large and touches both core + UI + apps, split into two commits: `feat(core): ...` for the migration helper + stage type, and `refactor(ui,apps): wire stage rename + metadata migration` for everything else. Use judgment.
+
+---
+
+## Task 2 — Build `ImproveStage` (simple ActionItem tracker)
+
+**Goal:** Render a focused list of `ActionItem` entities scoped to the current IP. Show title (`text`), owner (`assignedTo?.displayName`), due date (`dueAt`), status (`status`), and the linked suspected cause name (looked up via `parentImprovementIdeaId → ImprovementIdea`, falling back to "Unattributed"). Gate edit affordances via `canAccess(currentUserId, members, 'edit-improve')`.
+
+**Why an existing entity:** `ActionItem` already carries every field the simple tracker needs (`packages/core/src/findings/types.ts:161`). No new types — PR-WV1-2 is a UI/integration slice, not a data-modeling slice.
+
+**Files:**
+
+- Create: `packages/ui/src/components/IPDetail/stages/ImproveStage.tsx`
+- Create: `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx`
+- Modify: `packages/ui/src/components/IPDetail/IPDetailPage.tsx` (route `'improve'` stage to ``)
+
+### Steps
+
+- [ ] **Step 1: Discover the ActionItem data source**
+
+```bash
+grep -rn "actionItems\b\|ActionItem\[\]\|HubAction.*ACTION_ITEM\|actionItemActions" packages/core/src packages/stores/src apps --include="*.ts" --include="*.tsx" | head -15
+```
+
+Identify where `actionItems[]` lives in app state (likely on `ProcessHub.actionItems` per `findings/types.ts:161` `EntityBase` peers). Identify any reducer / dispatch helper that adds / updates / removes them.
+
+If ActionItems are NOT yet wired through a store / dispatch surface today, this task gets larger — STOP and report scope concern. (Master plan assumed they exist; verify.)
+
+- [ ] **Step 2: Write the failing component test**
+
+```typescript
+// packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { ImproveStage } from '../ImproveStage';
+import type { ActionItem } from '@variscout/core/findings';
+import type { ProjectMember } from '@variscout/core/projectMembership';
+
+const leadMembers: ProjectMember[] = [
+ {
+ id: 'pm-1',
+ createdAt: 1,
+ deletedAt: null,
+ userId: 'lead@org',
+ displayName: 'Lead',
+ role: 'lead',
+ invitedAt: 1,
+ },
+];
+
+const actions: ActionItem[] = [
+ {
+ id: 'ai-1',
+ createdAt: 1,
+ deletedAt: null,
+ text: 'Run a pilot on Line 3',
+ assignedTo: { displayName: 'Mira', upn: 'mira@org' },
+ dueAt: '2026-06-01',
+ status: 'open',
+ parentImprovementProjectId: 'ip-1',
+ },
+ {
+ id: 'ai-2',
+ createdAt: 2,
+ deletedAt: null,
+ text: 'Document the new SOP',
+ status: 'done',
+ parentImprovementProjectId: 'ip-1',
+ },
+];
+
+describe('ImproveStage', () => {
+ it('renders the scoped ActionItem list', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByText('Run a pilot on Line 3')).toBeInTheDocument();
+ expect(screen.getByText('Document the new SOP')).toBeInTheDocument();
+ });
+
+ it('shows owner display name when present', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByText('Mira')).toBeInTheDocument();
+ });
+
+ it('renders an Add Action affordance for users with edit-improve', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByRole('button', { name: /add action/i })).toBeInTheDocument();
+ });
+
+ it('hides Add Action for users without edit-improve (Sponsor)', () => {
+ const mixedMembers: ProjectMember[] = [
+ ...leadMembers,
+ {
+ id: 'pm-2',
+ createdAt: 1,
+ deletedAt: null,
+ userId: 'sponsor@org',
+ displayName: 'Sponsor',
+ role: 'sponsor',
+ invitedAt: 1,
+ },
+ ];
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.queryByRole('button', { name: /add action/i })).not.toBeInTheDocument();
+ });
+
+ it('calls onActionAdd with a typed payload when Add is submitted', () => {
+ const onActionAdd = vi.fn();
+ render(
+ {}}
+ onActionRemove={() => {}}
+ />
+ );
+ fireEvent.click(screen.getByRole('button', { name: /add action/i }));
+ fireEvent.change(screen.getByLabelText(/title/i), { target: { value: 'New action' } });
+ fireEvent.click(screen.getByRole('button', { name: /save/i }));
+ expect(onActionAdd).toHaveBeenCalledWith(
+ expect.objectContaining({ text: 'New action', parentImprovementProjectId: 'ip-1' })
+ );
+ });
+
+ it('renders an empty state when there are no actions', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByText(/no actions yet/i)).toBeInTheDocument();
+ });
+});
+```
+
+- [ ] **Step 3: Run to verify the test fails**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStage
+```
+
+Expected: FAIL (module not found).
+
+- [ ] **Step 4: Implement `ImproveStage.tsx`**
+
+```tsx
+// packages/ui/src/components/IPDetail/stages/ImproveStage.tsx
+import { useState } from 'react';
+import type { ActionItem } from '@variscout/core/findings';
+import { canAccess, type ProjectMember } from '@variscout/core/projectMembership';
+
+export interface ImproveStageProps {
+ projectId: string;
+ actions: ActionItem[];
+ members: ProjectMember[];
+ currentUserId?: string;
+ onActionAdd: (action: Pick) => void;
+ onActionUpdate: (
+ actionId: string,
+ patch: Partial>
+ ) => void;
+ onActionRemove: (actionId: string) => void;
+}
+
+export function ImproveStage({
+ projectId,
+ actions,
+ members,
+ currentUserId,
+ onActionAdd,
+ onActionUpdate,
+ onActionRemove,
+}: ImproveStageProps) {
+ const canEdit = currentUserId !== undefined && canAccess(currentUserId, members, 'edit-improve');
+ const [addOpen, setAddOpen] = useState(false);
+ const [newTitle, setNewTitle] = useState('');
+
+ const submit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const trimmed = newTitle.trim();
+ if (!trimmed) return;
+ onActionAdd({ text: trimmed, parentImprovementProjectId: projectId });
+ setNewTitle('');
+ setAddOpen(false);
+ };
+
+ return (
+
+
+
+ {a.assignedTo?.displayName && {a.assignedTo.displayName}}
+ {a.dueAt && Due {a.dueAt}}
+
+ {canEdit && (
+
+
+
+
+ )}
+
+ ))}
+
+ )}
+
+ );
+}
+```
+
+- [ ] **Step 5: Run the test to verify it passes**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStage
+```
+
+Expected: 6/6 pass.
+
+- [ ] **Step 6: Route `'improve'` stage in `IPDetailPage.tsx` to render ``**
+
+Locate the stage-routing switch in `IPDetailPage.tsx` (the place that picks between CharterOverview / ApproachOverview / SustainmentOverview / HandoffOverview today). Add an `'improve'` case rendering `` with the right props plumbed:
+
+- `projectId={ip.id}`
+- `actions={...}` — filtered from app state. Source: from Task 2 step 1's discovery. If actions live on `ProcessHub.actionItems`, get them via the existing hook / prop the page already uses.
+- `members={ip.metadata.members ?? []}`
+- `currentUserId={currentUserId}`
+- `onActionAdd` / `onActionUpdate` / `onActionRemove` — callback props on `IPDetailPage` mirroring the `onMembersChange` pattern from PR-WV1-1. Add the three optional props to `IPDetailPageProps`.
+
+The PWA + Azure call sites in `apps/{pwa,azure}/src/components/ProjectsTabView.tsx` get the three new callbacks wired in Task 6.
+
+- [ ] **Step 7: Add an integration test in IPDetailPage.test.tsx for the Improve stage routing**
+
+Inside the existing test file, add:
+
+```typescript
+describe('Improve stage routing', () => {
+ it('renders ImproveStage when activeStage = improve', () => {
+ // Configure default active stage to 'improve' (likely via a status that puts the IP in that stage,
+ // or by passing an explicit activeStage prop if IPDetailPage supports one — confirm via existing tests).
+ render(
+ {}}
+ currentUserId="anybody@org"
+ />
+ );
+ // Click the Improve stage tab
+ fireEvent.click(screen.getByTestId('stage-tab-improve'));
+ expect(screen.getByRole('heading', { name: /actions/i })).toBeInTheDocument();
+ });
+});
+```
+
+- [ ] **Step 8: Run all IPDetail tests**
+
+```bash
+pnpm --filter @variscout/ui test -- IPDetail
+```
+
+Expected: green.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git -C add packages/ui/src/components/IPDetail/stages/ImproveStage.tsx packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx packages/ui/src/components/IPDetail/IPDetailPage.tsx packages/ui/src/components/IPDetail/__tests__/IPDetailPage.test.tsx
+git -C commit -m "feat(ui): add ImproveStage with canAccess-gated ActionItem tracker"
+```
+
+---
+
+## Task 3 — Build `ImproveStageAdvanced` (PDCA primitives)
+
+**Goal:** Mount the four existing PDCA primitives (`PrioritizationMatrix`, `BrainstormModal`, `IdeaGroupCard`, `ImprovementContextPanel`) + `WhatIfExplorer` inside a single `ImproveStageAdvanced` wrapper. This wrapper renders when the user toggles the Improve stage to Advanced mode (Task 4 wires the toggle).
+
+**Why minimal:** the primitives are already modular (Explore scout §4). This task is composition, not new functionality. The wrapper just lays out the existing components in a sensible Advanced workspace shape — left rail = ContextPanel, main = BrainstormModal + IdeaGroupCard list, right rail = PrioritizationMatrix + WhatIfExplorer.
+
+**Files:**
+
+- Create: `packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx`
+- Create: `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx`
+
+### Steps
+
+- [ ] **Step 1: Read each PDCA primitive's prop shape**
+
+```bash
+grep -A 10 "interface PrioritizationMatrixProps\|interface BrainstormModalProps\|interface IdeaGroupCardProps\|interface ImprovementContextPanelProps\|interface WhatIfExplorerProps" packages/ui/src/components/ImprovementPlan/ packages/ui/src/components/WhatIfExplorer/ -r 2>/dev/null | head -40
+```
+
+Note the required props for each. If any primitive's prop shape mismatches the Improve-stage data context (e.g., it expects data shapes that don't exist in `ImprovementProject`), STOP and report — task will need design adjustment.
+
+- [ ] **Step 2: Write the failing test**
+
+```typescript
+// packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ImproveStageAdvanced } from '../ImproveStageAdvanced';
+
+describe('ImproveStageAdvanced', () => {
+ it('renders all four PDCA workspace regions', () => {
+ render();
+ expect(screen.getByLabelText(/context/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/ideas/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/prioritization/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/what-if/i)).toBeInTheDocument();
+ });
+});
+```
+
+This test pins region presence only — the primitives' own tests cover their internal behavior. Don't duplicate.
+
+- [ ] **Step 3: Run to verify the test fails**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStageAdvanced
+```
+
+Expected: FAIL (module not found).
+
+- [ ] **Step 4: Implement `ImproveStageAdvanced.tsx`**
+
+```tsx
+// packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx
+import { ImprovementContextPanel } from '../../ImprovementPlan/ImprovementContextPanel';
+import { BrainstormModal } from '../../ImprovementPlan/BrainstormModal';
+import { IdeaGroupCard } from '../../ImprovementPlan/IdeaGroupCard';
+import { PrioritizationMatrix } from '../../ImprovementPlan/PrioritizationMatrix';
+import { WhatIfExplorer } from '../../WhatIfExplorer/WhatIfExplorer';
+
+export interface ImproveStageAdvancedProps {
+ projectId: string;
+ // Additional props flow in as the existing primitives reveal their requirements
+ // during Step 1 inspection. Add them here and propagate from IPDetailPage in Step 6.
+}
+
+export function ImproveStageAdvanced({ projectId }: ImproveStageAdvancedProps) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+Fill in the actual required props per Step 1. If any primitive requires substantive props that don't exist in the IP detail context (e.g., a whole `ProcessHub` reference), accept the prop on `ImproveStageAdvancedProps` and pass through.
+
+- [ ] **Step 5: Run the test to verify it passes**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStageAdvanced
+```
+
+Expected: 1/1 pass.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git -C add packages/ui/src/components/IPDetail/stages/ImproveStageAdvanced.tsx packages/ui/src/components/IPDetail/stages/__tests__/ImproveStageAdvanced.test.tsx
+git -C commit -m "feat(ui): add ImproveStageAdvanced mounting PDCA primitives"
+```
+
+---
+
+## Task 4 — Advanced toggle inside `ImproveStage`
+
+**Goal:** Add a "Show advanced workbench" toggle to `ImproveStage` that swaps in `ImproveStageAdvanced`. State persists per-IP in `useViewStore` (View layer — transient is fine; per IP scope so a user's mode choice doesn't leak across projects within a session).
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/stages/ImproveStage.tsx`
+- Modify: `packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx`
+- Modify: `packages/stores/src/useViewStore.ts` — add a `improveAdvancedByIp: Record` field (or similar) if View store doesn't already track per-IP UI state
+
+### Steps
+
+- [ ] **Step 1: Read `packages/stores/src/useViewStore.ts`** to confirm its current shape + how per-IP state is keyed (if at all). Note the test pattern used for resetting the store.
+
+- [ ] **Step 2: Write a failing test for the toggle behavior**
+
+Append to `ImproveStage.test.tsx`:
+
+```typescript
+describe('ImproveStage advanced toggle', () => {
+ it('renders simple tracker by default', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ expect(screen.getByRole('heading', { name: /actions/i })).toBeInTheDocument();
+ expect(screen.queryByLabelText(/context/i)).not.toBeInTheDocument();
+ });
+
+ it('switches to Advanced workbench when toggle clicked', () => {
+ render(
+ {}}
+ onActionUpdate={() => {}}
+ onActionRemove={() => {}}
+ />
+ );
+ fireEvent.click(screen.getByRole('button', { name: /advanced/i }));
+ expect(screen.getByLabelText(/context/i)).toBeInTheDocument();
+ });
+});
+```
+
+- [ ] **Step 3: Run to verify the new tests fail**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStage
+```
+
+Expected: 2 new tests fail; 6 existing pass.
+
+- [ ] **Step 4: Add the toggle to `ImproveStage.tsx`**
+
+Add a state hook (or, if Step 1 found a per-IP view-store hook, use that):
+
+```tsx
+import { ImproveStageAdvanced } from './ImproveStageAdvanced';
+import { useViewStore } from '@variscout/stores'; // if applicable
+
+// inside the component:
+const [showAdvanced, setShowAdvanced] = useState(false);
+// Optional: hydrate/persist via useViewStore per-IP if Step 1 confirmed the shape.
+
+// In the header (next to Add action button):
+
+
+// In the body:
+{showAdvanced ? (
+
+) : (
+ /* existing simple tracker JSX */
+)}
+```
+
+If a per-IP view-store slice gets added, keep it minimal: `improveAdvancedByIp: Record` + `setImproveAdvanced(projectId, advanced)` + a getter selector. Tests for the slice should follow the existing useViewStore test pattern.
+
+- [ ] **Step 5: Run the tests**
+
+```bash
+pnpm --filter @variscout/ui test -- ImproveStage
+```
+
+Expected: 8/8 pass.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git -C add packages/ui/src/components/IPDetail/stages/ImproveStage.tsx packages/ui/src/components/IPDetail/stages/__tests__/ImproveStage.test.tsx packages/stores/
+git -C commit -m "feat(ui): add Advanced toggle on ImproveStage"
+```
+
+---
+
+## Task 5 — Fold Handoff close-logic into Sustainment closure
+
+**Goal:** Move the Handoff stage's "close project" action into `SustainmentOverview` (or a new `SustainmentClosure` extraction inside it), and delete `HandoffOverview` + `HandoffSections`. Preserve every external observable behavior the existing Handoff tests verified, including any auto-fire path tied to ADR-080's pattern.
+
+**Note on ADR-080 auto-fire:** the Explore scout confirmed the data model exists (`sustainmentRecords[]`, `controlHandoffs[]`) but found NO live "auto-create on SuspectedCause confirmation" trigger in the current tree. Per the master plan: the requirement here is to **preserve whatever is shipped**, not to add the trigger. If shipped behavior doesn't include the auto-fire today, then there's nothing to preserve beyond the data model + close-action flow.
+
+**Files:**
+
+- Modify: `packages/ui/src/components/IPDetail/stages/SustainmentOverview.tsx`
+- Modify: `packages/ui/src/components/IPDetail/stages/SustainmentSections.tsx`
+- Delete: `packages/ui/src/components/IPDetail/stages/HandoffOverview.tsx`
+- Delete: `packages/ui/src/components/IPDetail/stages/HandoffSections.tsx`
+- Modify: existing `HandoffOverview.test.tsx` + `HandoffSections.test.tsx` — port their assertions into the Sustainment test files
+- Modify: any consumer that imports `HandoffOverview` / `HandoffSections` (grep first)
+
+### Steps
+
+- [ ] **Step 1: Read `HandoffOverview.tsx`, `HandoffSections.tsx`, `SustainmentOverview.tsx`, `SustainmentSections.tsx` end-to-end.** Identify what Handoff does that Sustainment doesn't:
+ - Close-project action and its handler
+ - Any HubAction dispatched (search for `HANDOFF_` action kinds via `grep`)
+ - Activity-feed events emitted
+ - Any data-flow specific to Handoff (e.g., controlHandoffs[] CRUD)
+
+- [ ] **Step 2: Find every consumer of HandoffOverview / HandoffSections**
+
+```bash
+grep -rln "HandoffOverview\|HandoffSections" packages/ui/src apps/ --include="*.ts" --include="*.tsx" | head
+```
+
+Expected: imports in `IPDetailPage.tsx` (stage routing switch) and possibly a barrel.
+
+- [ ] **Step 3: Port the close-project action into `SustainmentOverview.tsx`**
+
+Move the Handoff close-project button, modal, and submit handler into SustainmentOverview's UI. If the close-project action dispatched a `HANDOFF_CLOSE` HubAction kind, rename it to `SUSTAINMENT_CLOSE` (or absorb into an existing `IMPROVEMENT_PROJECT_UPDATE` patch — confirm via Step 1's discovery). If the action kind needs renaming, the dispatch-pattern follows `feedback_action_patch_omit_lifecycle` — verify the patch type uses the standard `Omit` shape.
+
+- [ ] **Step 4: Port the Handoff tests into Sustainment test files**
+
+Open `HandoffOverview.test.tsx` and `HandoffSections.test.tsx`. For each test:
+
+- If the test asserts Handoff-specific UI (close button, modal, signoff field), move the assertion into the matching Sustainment test file, adapting the rendered component.
+- If the test asserts a Handoff-only data flow that's being collapsed, decide: keep the assertion (renaming to `Sustainment closure`) or drop it (and document why in the commit message).
+
+- [ ] **Step 5: Run all Sustainment + Handoff tests in their new shape**
+
+```bash
+pnpm --filter @variscout/ui test -- "Sustainment|Handoff"
+```
+
+Expected: all ported tests pass; no dangling references to the old Handoff components.
+
+- [ ] **Step 6: Delete HandoffOverview.tsx + HandoffSections.tsx + their `__tests__` files**
+
+```bash
+rm packages/ui/src/components/IPDetail/stages/HandoffOverview.tsx
+rm packages/ui/src/components/IPDetail/stages/HandoffSections.tsx
+rm packages/ui/src/components/IPDetail/stages/__tests__/HandoffOverview.test.tsx
+rm packages/ui/src/components/IPDetail/stages/__tests__/HandoffSections.test.tsx
+```
+
+- [ ] **Step 7: Remove Handoff imports from `IPDetailPage.tsx`** (and any barrel). Update the stage-routing switch: there's no `'handoff'` case any more — that StageName value no longer exists per Task 1.
+
+- [ ] **Step 8: Run `pnpm build` to catch cross-package type-export gaps**
+
+```bash
+pnpm build 2>&1 | tail -10
+```
+
+Per `feedback_ui_build_before_merge`: build catches type-export gaps that per-package vitest misses. Expected: green.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git -C add packages/ui/src/components/IPDetail/stages/
+git -C commit -m "refactor(ui): fold Handoff close-logic into Sustainment closure"
+```
+
+---
+
+## Task 6 — Remove top-level Improve tab (PWA + Azure)
+
+**Goal:** Remove the `'improve'` tab from the app shell. Both apps redirect users to `/projects` with a one-time toast when they hit the legacy URL. The 7→6 tab transition lands here; the **nav reorder** to wedge's `[Home] [Projects] [Process] [Analyze] [Investigation] [Report]` is explicitly deferred to PR-WV1-5.
+
+**Files:**
+
+- Modify: `apps/pwa/src/App.tsx` (remove `else if (tab === 'improve') panels.showImprovement()` and any tab list that includes 'improve')
+- Modify: `apps/azure/src/pages/Editor.tsx` (same: remove `ps.showImprovement()` branch + tab list entry)
+- Modify: `packages/core/src/i18n/messages/*.ts` — delete the `workspace.improve` key from every locale
+- Modify: matching test files
+
+### Steps
+
+- [ ] **Step 1: Find every `tab === 'improve'` reference**
+
+```bash
+grep -rn "tab === 'improve'\|'improve' as\|showImprovement\|workspace.improve" packages/ apps/ --include="*.ts" --include="*.tsx" | head -25
+```
+
+Note each call site.
+
+- [ ] **Step 2: Write a failing test asserting the legacy `/improve` route redirects to `/projects` with a toast** (one test per app, in its existing app-shell test file)
+
+Adapt the redirect mechanism to whatever the app shell uses for routing (URL / hash / tab state). If the app shell has no current "navigate to tab X with toast Y" mechanism, document the workaround used.
+
+- [ ] **Step 3: Run the tests to verify they fail**
+
+```bash
+pnpm --filter @variscout/pwa test -- App
+pnpm --filter @variscout/azure-app test -- Editor
+```
+
+Expected: new tests fail.
+
+- [ ] **Step 4: Implement the redirect in both apps**
+
+In `apps/pwa/src/App.tsx`, replace:
+
+```typescript
+} else if (tab === 'improve') {
+ panels.showImprovement();
+}
+```
+
+with:
+
+```typescript
+} else if (tab === 'improve') {
+ // Wedge V1: Improve moved to a stage inside Projects detail (ADR-082, wedge spec §3.2).
+ showToast({ kind: 'info', message: 'Improve is now a stage in each project.' });
+ setActiveTab('projects');
+}
+```
+
+(Adapt `showToast` / `setActiveTab` to the app's actual toast + nav helpers — read 1-2 existing tab-switch call sites to mirror the pattern.) Do the same in `apps/azure/src/pages/Editor.tsx`.
+
+- [ ] **Step 5: Remove the `'improve'` entry from the tab list array**
+
+If the apps construct their tab list from a hardcoded array (PWA scout found this in `App.tsx`; Azure equivalent in `Editor.tsx`), drop `'improve'` from the array. Drop the i18n key from every locale file. This is mechanical; use a single `sed` if all locales use the identical key shape, but verify the change in one locale first.
+
+- [ ] **Step 6: Run all tests**
+
+```bash
+pnpm --filter @variscout/pwa test
+pnpm --filter @variscout/azure-app test
+```
+
+Expected: both green.
+
+- [ ] **Step 7: Run `pnpm docs:check`** (i18n key deletions can cascade)
+
+```bash
+pnpm docs:check 2>&1 | tail -10
+```
+
+Expected: green.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git -C add apps/ packages/core/src/i18n/
+git -C commit -m "feat(apps): retire top-level Improve tab (now a project stage)"
+```
+
+---
+
+## Task 7 — Final verification + decision-log amendment
+
+**Goal:** Run the full pre-PR check suite, perform a `--chrome` browser walk covering the wedge V1 spec §3.2 verification scenarios, and amend the decision-log to mark PR-WV1-2 shipped + explicitly defer the two remaining PR-WV1-1 follow-ups (invitation lifecycle to PR-WV1-3, per-user persistence key to PR-WV1-5).
+
+### Steps
+
+- [ ] **Step 1: Targeted test sweep**
+
+```bash
+pnpm --filter @variscout/core test 2>&1 | tail -5
+pnpm --filter @variscout/stores test 2>&1 | tail -5
+pnpm --filter @variscout/pwa test 2>&1 | tail -5
+pnpm --filter @variscout/azure-app test 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- IPDetail 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- projects 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- Charter 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- Improve 2>&1 | tail -5
+pnpm --filter @variscout/ui test -- Sustainment 2>&1 | tail -5
+```
+
+Expected: all green. (Full `@variscout/ui` suite has a documented unchanged-Canvas hang per prior memory entries — touched suites are run in isolation.)
+
+- [ ] **Step 2: Full build**
+
+```bash
+pnpm build 2>&1 | tail -10
+```
+
+Expected: 5/5 packages/apps green.
+
+- [ ] **Step 3: pr-ready-check**
+
+```bash
+bash scripts/pr-ready-check.sh 2>&1 | tail -30
+```
+
+Expected: green (architecture grep, ADR-074 boundary, lint, docs).
+
+- [ ] **Step 4: Browser walk (`claude --chrome`)**
+
+Verify each wedge V1 §3.2 + §3.3 scenario end-to-end:
+
+1. Create a new project as Lead → IP detail shows 4 stage tabs: Charter, Approach, Improve, Sustainment (no Handoff).
+2. Open the Improve stage → simple ActionItem tracker shows; "Add action" button visible; "Advanced" toggle in the header.
+3. Click "Add action" → enter title → save → action appears in list.
+4. Click "Advanced" → ImproveStageAdvanced workspace shows Context / Ideas / Prioritization / What-If regions.
+5. Click "Simple view" → returns to tracker.
+6. Sign in as a Sponsor → IPDetailPage shows the Sponsor placeholder pointing to the top Report nav tab; Improve stage isn't reachable from this view.
+7. Sign in as a Member → can see + edit Improve actions; cannot see the Invite-team button on Charter.
+8. Open the Sustainment stage → closure UI present; absorb Handoff close-action invokes correctly (project status moves to `closed`).
+9. Navigate to the legacy `/improve` URL → redirected to Projects list with toast "Improve is now a stage in each project."
+10. Load an existing customer `.vrs` with legacy `team[]` populated and no `members[]` → after hydration, `members[]` is auto-populated via `migrateImprovementProjectMetadata`; the Team rail on Charter shows entries; legacy `team[]` is still readable by the legacy `IPDetailTeamRail`.
+
+If any step fails, capture a screenshot, log to `docs/investigations.md`, and decide whether to block the PR or fold into a follow-up. Document the choice in the PR description.
+
+- [ ] **Step 5: Amend `docs/decision-log.md` 2026-05-16 wedge entry**
+
+Add a new "Amendment 2026-05-16 — PR-WV1-2 shipped" block under the existing wedge entry. Cover:
+
+- 4-stage rename (`Sustainment+Handoff` → `Improve+Sustainment`); Handoff stage views deleted; `SustainmentOverview` absorbs close-action.
+- New `ImproveStage` (simple ActionItem tracker) + `ImproveStageAdvanced` (PDCA primitives mounted) + Advanced toggle.
+- `migrateImprovementProjectMetadata` helper folds (a) stage rename + (b) `team[] → members[]` eager cutover; invoked at `.vrs` / Dexie hydration in both apps.
+- Top-level Improve tab retired (7 → 6 tabs; reorder still deferred to PR-WV1-5).
+- `canAccess` wired at `IPDetailPage` (Sponsor placeholder + non-member gate) and `CharterOverview` Invite-button gating. PR-WV1-1 deferred item (d) closed.
+- PR-WV1-1 deferred item (b) closed.
+- **Remaining PR-WV1-1 followups:** (a) `INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds — re-owned by **PR-WV1-3** (Investigation Wall + MeasurementPlans is the natural carrier; Inbox simplification is part of that slice). (c) Per-user persistence key on `useProjectMembershipStore` — re-owned by **PR-WV1-5** (tier-gating retirement + nav reorder, where auth-wiring refinement naturally lands).
+
+- [ ] **Step 6: Commit the decision-log amendment**
+
+```bash
+git -C add docs/decision-log.md
+git -C commit -m "docs(wedge): log PR-WV1-2 delivery + re-owner remaining followups"
+```
+
+- [ ] **Step 7: Push + open PR**
+
+```bash
+git -C push -u origin feat/wedge-pr-wv1-2-improve-workspace
+gh pr create --title "feat(wedge): PR-WV1-2 Improve workspace migration" \
+ --body "$(cat <<'EOF'
+## Summary
+
+Implements wedge V1 spec §3.2 + §3.3 — IP detail flattens to 4 stages (Charter / Approach / Improve / Sustainment with Handoff folded into Sustainment closure), top-level Improve tab retires, simple ActionItem tracker is the default Improve stage UI with PDCA primitives behind an Advanced toggle.
+
+Also absorbs two of PR-WV1-1's deferred items per decision-log 2026-05-16 amendment:
+- (b) `team[]` → `members[]` eager cutover at .vrs / Dexie hydration via new `migrateImprovementProjectMetadata`
+- (d) `canAccess` wired at consumer call sites (IPDetailPage + CharterOverview Invite gate)
+
+## Test plan
+
+- [x] pnpm test (per-package; full @variscout/ui suite has documented unchanged-Canvas hang)
+- [x] pnpm build green
+- [x] bash scripts/pr-ready-check.sh
+- [ ] `--chrome` browser walk per wedge §3.2 + §3.3 (10 scenarios in plan §Task 7 Step 4)
+
+## Master plan
+
+`docs/superpowers/plans/2026-05-16-wedge-implementation.md` (PR-WV1-2 row).
+
+## Sub-plan
+
+`docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md`
+EOF
+)"
+```
+
+If the PR is opened while PR-WV1-1 (#183) is still open, GitHub will show this PR's base as `feat/wedge-pr-wv1-1-project-membership`. When #183 squash-merges to main, GitHub auto-updates this PR's base to main.
+
+---
+
+## Verification (end-to-end)
+
+1. **Schema:** legacy `team[]` and wedge `members[]` coexist on `ImprovementProjectMetadata`; `migrateImprovementProjectMetadata` populates `members[]` from `team[]` at hydration only when `members` is absent.
+2. **Stage flow:** 4 named stages render in `IPDetailStageTabs`. The Improve stage routes to `` by default; Sustainment closure absorbs Handoff. No `'handoff'` StageName anywhere.
+3. **ACL:** `canAccess` is the single ACL entry point. IPDetailPage Sponsor / non-member branches route through it. CharterOverview Invite button is gated on `canAccess('manage-membership')`. ImproveStage edit affordances gate on `canAccess('edit-improve')`.
+4. **Apps:** PWA + Azure shells retire the `'improve'` tab handler with a redirect-to-Projects + toast; existing `currentUserId` + `onMembersChange` wiring from PR-WV1-1 propagates unchanged.
+5. **Test sweep:** core + stores + PWA + Azure green; UI touched suites (IPDetail, projects, Charter, Improve, Sustainment) green.
+6. **Build:** `pnpm build` green across all 5 packages/apps.
+
+---
+
+## Self-review checklist
+
+- [ ] **Spec coverage**: every wedge spec §3.2 (4-stage IP detail) + §3.3 (simple-by-default Improve + Advanced toggle) commitment lands in a task.
+- [ ] **Placeholder scan**: no TBD / TODO / placeholder code; every step has the actual content.
+- [ ] **Type consistency**: `StageName`, `ProjectAction`, `MembershipAction`, `ImproveStageProps`, `ImproveStageAdvancedProps` used consistently across tasks.
+- [ ] **No `Math.random`** in code or tests (per `packages/core/CLAUDE.md` hard rule).
+- [ ] **No "root cause" language** anywhere (per P5 amended).
+- [ ] **Sub-path exports paired**: no new sub-path added in this PR; if Task 1's migration helper goes through the existing `improvementProject` barrel (it should), no change to `package.json#exports` or `tsconfig.json#paths` is needed. Verify before committing.
+- [ ] **Patch types**: any new `*_UPDATE` HubAction added (e.g., a renamed `SUSTAINMENT_CLOSE`) uses `Omit` per `feedback_action_patch_omit_lifecycle`.
+- [ ] **`canAccess` is the single ACL entry point** after this PR. No inline role-string comparisons remain in IPDetailPage / CharterOverview / ImproveStage.
+
+---
+
+## Deferred to later PRs
+
+- **PR-WV1-1 deferred item (a) — `INVITATION_ACCEPT` / `INVITATION_REVOKE` action kinds** — moves to PR-WV1-3 (Investigation Wall + Measurement Plans). The Inbox simplification context lands there; acceptance is the user action that transitions an `Invitation.status` → emits a `PROJECT_MEMBER_ADD` via composite reducer.
+- **PR-WV1-1 deferred item (c) — Per-user persistence key on `useProjectMembershipStore`** — moves to PR-WV1-5 (tier-gating retirement + nav reorder). That PR refines auth wiring (single-user PWA vs. multi-user Azure) and is the natural place to adopt the `useActiveIPStore`-style dynamic-key pattern.
+- **ADR-080 auto-fire trigger** — Out of scope for this PR. The data model is preserved; the auto-creation hook (SuspectedCause confirmed + matching improvement implemented → Sustainment record) does not yet exist in the current tree per the Explore scout. Track as a separate workstream once the canonical trigger source is decided.
+
+---
+
+## Execution handoff
+
+Plan complete. Recommended approach:
+
+**Subagent-Driven Development** (recommended):
+
+- Fresh implementer subagent per task (8 tasks)
+- Per-task spec reviewer + quality reviewer pair
+- Final architecture review (`system-architect` Opus) + code review (`superpowers:requesting-code-review` Opus) at end of branch
+- Sonnet for implementer + per-task reviewers (~70%+ of dispatches per CLAUDE.md memory)
+
+Invoke `superpowers:subagent-driven-development` with this plan as input.
diff --git a/docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md b/docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md
new file mode 100644
index 000000000..da7bd48fe
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md
@@ -0,0 +1,182 @@
+---
+title: 'Wedge V1 amendment — Improve as top-level verb tab, Project as singular noun'
+status: draft
+last-reviewed: 2026-05-16
+category: design-spec
+audience: [designer, engineer]
+related:
+ - docs/superpowers/specs/2026-05-16-wedge-architecture-design.md
+ - docs/superpowers/specs/2026-05-14-variscout-coherence-design.md
+ - docs/superpowers/specs/2026-05-14-projects-tab-design.md
+ - docs/07-decisions/adr-082-wedge-architecture.md
+ - docs/superpowers/plans/2026-05-16-wedge-implementation.md
+ - docs/superpowers/plans/2026-05-16-pr-wv1-2-improve-workspace.md
+---
+
+# Wedge V1 amendment — Improve as top-level verb tab, Project as singular noun
+
+## Context
+
+The wedge V1 spec (`2026-05-16-wedge-architecture-design.md` §3.1–3.3) locked a 6-tab workflow nav — `[Home] [Projects] [Process] [Analyze] [Investigation] [Report]` — and folded the legacy Improve workspace into Projects detail as a stage. PR-WV1-2 was mid-execution to deliver that fold-in: Tasks 0–5 had shipped (canAccess wiring, stage rename `Sustainment+Handoff → Improve+Sustainment`, `ImproveStage` simple action tracker, `ImproveStageAdvanced` PDCA workbench, Advanced toggle, Handoff close-logic folded into Sustainment closure). Task 6 was about to retire the top-level Improve tab.
+
+During mid-execution review, the user surfaced a sharp design objection: the wedge V1 nav keeps **Analyze** and **Investigation** as top-level verb tabs (with active-IP cascade per PR-PT-7), but removes **Improve** entirely. That asymmetry is hard to defend — improvement work is the _doing_ verb of a chartered project; it deserves the same first-class verb treatment as data exploration and question-driven inquiry. Removing it forces specialists to drill into Project detail every time they want to update an action — even though the IP-context chip already tells them which project they're in.
+
+This amendment restores Improve as a top-level verb tab, makes the Project tab singular (active-IP-scoped, matching the cascade pattern), removes the Improve stage from Project detail (since the same UI now lives at the top level), and preserves every other wedge V1 decision unchanged.
+
+## Decision summary
+
+1. **7-tab nav order:** `[Home] [Project] [Process] [Analyze] [Investigation] [Improve] [Report]`. Wedge V1 §3.1's 6-tab list is superseded.
+2. **Project tab is singular.** Active-IP-centric — it shows ONE project's lifecycle, matching how Analyze / Investigation / Improve / Report behave under the PR-PT-7 cascade. The full project _list_ lives on the Home launchpad (already shipped per PR-PT-6).
+3. **Project detail has 3 stage tabs**, not 4: `Charter / Approach / Sustainment`. The `'improve'` stage is removed. Sustainment continues to absorb Handoff close-logic per Task 5.
+4. **Improve tab is a top-level verb workspace.** With an active IP, it renders the simple action tracker (default) + Advanced PDCA workbench (toggle). Without an active IP, it renders a guidance state directing the user to Home to pick or charter a project. Implementations of `` + `` from PR-WV1-2 Tasks 2-4 are reused verbatim — only the routing surface changes.
+5. **Improvement actions are project-scoped data.** `ActionItem.parentImprovementProjectId` continues to anchor each action to one IP. The Improve tab filters to the active IP's actions via the same cascade that PR-PT-7 uses for other verb tabs. Cross-IP action views are out of V1 scope.
+
+## Surface responsibilities
+
+| Tab | Role | Active-IP behavior |
+| ----------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
+| **Home** | Project picker + launchpad (Mira's day-start) | Sets active IP via launchpad cards; full list lives here |
+| **Project** | Active project's lifecycle authoring with 3 stage tabs (Charter / Approach / Sustainment) | Required — empty state directs to Home |
+| **Process** | Process Hub view scoped to active IP | Required — empty state directs to Home |
+| **Analyze** | Data analysis surface, scoped to active IP | Required — empty state directs to Home |
+| **Investigation** | Question-driven EDA scoped to active IP | Required — empty state directs to Home |
+| **Improve** | Daily execution surface — simple action tracker (default) + Advanced PDCA workbench (toggle) | Required — empty state directs to Home |
+| **Report** | Overview / Technical reporting, scoped to active IP (or Hub portfolio when no IP set, per existing PR-PT-9 behavior) | Required for IP-scoped views |
+
+Every non-Home tab follows the same pattern: with active IP, show the scoped working surface; without active IP, show a brief guidance state pointing back to Home. Improve adopts this pattern; it does NOT get a free-roaming "all projects" or "all ideas" view.
+
+## Improve tab UX
+
+### With active IP
+
+Renders `` (the component built in PR-WV1-2 Task 2):
+
+- **Default view (simple tracker):** Action list scoped to active IP. Shows title (`text`), owner (`assignedTo?.displayName`), due date (`dueAt`), status (`open | in-progress | done`), and the linked suspected cause name where derivable from `parentImprovementIdeaId`.
+- **Add action button** gated by `canAccess(currentUserId, members, 'edit-improve')`.
+- **Per-row Mark-done / Remove buttons** with the same canAccess gate.
+- **"Advanced" toggle** in the header. Clicking it renders `` (the component from Task 3): `ImprovementContextPanel` (causes), `BrainstormModal` + `IdeaGroupCard` (ideas), `PrioritizationMatrix` + `WhatIfExplorer` (prioritization + projection). All five primitives are scoped to the active IP.
+- **"Simple view" toggle** returns to the tracker.
+
+The IP-context chip at the top of the page surfaces which project is active (per PR-PT-7 pattern). Users can switch projects from Home or via the chip's switch affordance.
+
+### Without active IP
+
+Renders a guidance state — single section, single role="alert" panel:
+
+> **No active project**
+>
+> Improvement work happens inside a chartered project. Pick a project from Home, or create a new one to start tracking actions and ideating with the PDCA workbench.
+>
+> **[Go to Home]**
+
+This mirrors how PR-WV1-1's `NoAccessRedirect` handles the ACL empty state — informative, action-oriented, no functionality that could mislead the user into thinking free-roaming ideation is available.
+
+## Project tab structure
+
+Project detail (the surface that opens when the active IP is set and the user clicks the Project tab):
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ ← Back to Home ●Active Project: Heads 5-8 Cpk shortfall │
+│ Goal · invited team avatars · Invite │
+├──────────────────────┬──────────────────────────────────────┤
+│ Stages: │ Overview / Sections toggle │
+│ • Charter │ │
+│ • Approach │ (stage body — render switch) │
+│ • Sustainment │ │
+└──────────────────────┴──────────────────────────────────────┘
+```
+
+3 stage tabs. The `'improve'` stage is removed from `StageName`, `STAGE_ORDER`, the `LABEL` map, and `deriveStageState`. PR-WV1-2 Task 1's prior rename `Sustainment+Handoff → Improve+Sustainment` is replaced by a simpler `Sustainment+Handoff → Sustainment` (Handoff folds into Sustainment closure per Task 5).
+
+Stage progression semantics under the new 3-stage model:
+
+| IP state | Charter | Approach | Sustainment |
+| ------------------------------- | --------- | ---------- | ----------- |
+| `status === 'draft'` | `current` | `upcoming` | `upcoming` |
+| `status === 'active'` | `done` | `current` | `upcoming` |
+| `status === 'closed'` | `done` | `done` | `current` |
+| `sustainmentConfirmed === true` | `done` | `done` | `done` |
+
+The previous `improveComplete` signal (introduced in Task 1) retires — there is no Improve stage to gate.
+
+## Impact on PR-WV1-2 in-flight work
+
+PR-WV1-2 was 5 tasks deep when this amendment landed. Disposition per task:
+
+| Task | Prior status | Disposition under amendment |
+| --------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Task 0** — Wire `canAccess` at consumer call sites | Done | **Keep verbatim.** ACL wiring is unchanged. |
+| **Task 1** — Stage rename + `migrateImprovementProjectMetadata` | Done (renamed to 4 stages incl. `'improve'`) | **Partial rework.** Stage list flattens further — `StageName` becomes `'charter' | 'approach' | 'sustainment'`. `STAGE_ORDER`is the same 3 values.`LABEL`map drops the`'improve'`entry.`StageStateInputs.improveComplete`retires.`deriveStageState`simplifies per the table above.`migrateImprovementProjectMetadata`is unchanged — it does`team[] → members[]` cutover only, never touched stage names. |
+| **Task 2** — Build `ImproveStage` simple tracker | Done | **Keep component verbatim.** Routing moves: the component is rendered by the top-level Improve tab handler instead of by `IPDetailPage`. The Improve tab handler reads active IP from the cascade. |
+| **Task 3** — Build `ImproveStageAdvanced` PDCA workspace | Done | **Keep verbatim.** Reused identically inside the Improve tab when Advanced toggle is on. |
+| **Task 4** — Advanced toggle | Done | **Keep verbatim.** Toggle behavior unchanged. |
+| **Task 5** — Fold Handoff into Sustainment closure | Done | **Keep verbatim.** Handoff fold stands. Sustainment is now the project's closure stage. |
+| **Task 6** — _Was:_ retire top-level Improve tab | Pending | **Rewritten.** New scope: (a) wire the existing `'improve'` tab handler in both apps to render `` with the active IP's actions + members + currentUserId, OR the empty-state guidance when no active IP; (b) rename `workspace.projects` i18n key to `workspace.project` (singular) across all locale files; (c) update the i18n message text in each locale to "Project" (singular); (d) un-prefix `_handoffInputs` / `_onOpenLegacyHandoff` cleanup that Task 5 absorbed remains as-is. |
+| **Task 7** — Final verification + decision-log amendment | Pending | **Updated copy.** Decision-log amendment captures this amendment-of-an-amendment honestly: wedge §3.1 6-tab nav is superseded by 7-tab nav with Improve restored; `'improve'` stage removed from Project detail; Project (singular) noun. |
+
+Specifically removed from Project detail:
+
+- `` rendering inside the `'improve'` case of `IPDetailPage.tsx`'s stage router (Task 2's wiring at the IP detail level is reverted).
+- The 3 optional callback props `onActionAdd` / `onActionUpdate` / `onActionRemove` on `IPDetailPageProps` are removed (those props move to the top-level Improve tab handler).
+- The new "Improve stage routing" integration test in `IPDetailPage.test.tsx` is removed (no `'improve'` stage to route).
+
+The `` component itself, its tests, ``, and the Advanced toggle all live in `packages/ui/src/components/IPDetail/stages/` today. Either leave them there (the path becomes archaeologically inaccurate but the file path is internal) OR move to `packages/ui/src/components/Improve/`. Recommendation: **move** during Task 6 to keep the codebase shape honest — these components no longer belong to IP detail.
+
+## Journey × persona × cognitive shape
+
+Per `feedback_journey_first_then_ui`, the asymmetry the wedge V1 created (Analyze + Investigation as top-level verbs, Improve folded inside) is justified only if Improve has no real journey outside a project. The brainstorm surfaced the opposite: improvement work IS the daily verb for a specialist. The Improve tab makes that daily journey first-class.
+
+**V1 Specialist daily journey, post-amendment:**
+
+1. Open app → Home → pick today's project (sets active IP)
+2. Click Project tab → check stage status, edit Charter / Approach / Sustainment as needed
+3. Click Analyze tab → run capability/control charts on the active IP's data
+4. Click Investigation tab → drill into outstanding questions for the active IP
+5. Click Improve tab → update action statuses, add new actions, ideate via the Advanced toggle when needed
+6. Click Report tab → see how the active IP is tracking against goal
+
+Each tab is a lens on the _same_ project. The active-IP cascade (already wired in PR-PT-7) means switching tabs doesn't lose context — the chip carries the project's name across all surfaces. The user objection that drove this amendment ("we have analysis, investigate etc tabs still") was exactly right: those tabs already represent the verb-shaped daily journey for the specialist; Improve belongs alongside them.
+
+**Cross-IP work** (compare prioritization matrices across past projects, build a portfolio-level idea library) is **NOT** added by this amendment. The Improve tab is single-IP-scoped. If portfolio-level Improve patterns become real V2 work, the spec can add an Analyze-tab pivot (per wedge §3.3's existing note that "What-If may re-emerge in Analyze later") or a dedicated portfolio surface.
+
+## What this amendment supersedes
+
+- **Wedge V1 spec §3.1** 6-tab nav `[Home] [Projects] [Process] [Analyze] [Investigation] [Report]` → superseded by 7-tab nav with Improve restored + Project singular.
+- **Wedge V1 spec §3.2** "Project detail = 4 stages — `Charter / Approach / Improve / Sustainment`" → superseded by 3 stages `Charter / Approach / Sustainment`.
+- **Wedge V1 spec §3.3** "Improve stage UI = simple action tracker by default; PDCA workbench + What-If accessible via an 'Advanced' toggle (progressive disclosure)" → the toggle behavior is preserved verbatim, but it lives in the top-level Improve tab instead of inside the Improve stage of Project detail.
+- **Decision-log 2026-05-16 wedge entry §(1)** "Improve tab removed as top-level; becomes a stage inside Projects detail" → reversed. Improve restored as a top-level tab; Projects renamed to Project (singular).
+- **Decision-log 2026-05-16 wedge entry §(3)** "idea board / action conversion retire" → preserved. Free-roaming cross-IP idea board still retires. Improve tab is single-IP-scoped, not a free-roaming surface. Within an active IP, the PDCA workbench is fully available behind the Advanced toggle.
+
+The wedge meta-decisions (single-product specialist tool, Lead/Member/Sponsor membership ACLs, single €99 SKU, Hub internal-only, three response paths, ADR-080 sustainment auto-fire pattern) are **not** affected. ADR-082 stays in force.
+
+## What this amendment preserves
+
+- Coherence design 2026-05-14's verb/noun split is restored. The pre-wedge analysis ("Improve = legacy ImprovementView / PDCA workbench" + "Projects = new IP lifecycle + detail page" + "deliberate verb/noun split because the two surfaces serve genuinely different jobs") was correct; the wedge V1's collapse went too far.
+- PR-PT-6 active-IP launchpad on Home — unchanged.
+- PR-PT-7 active-IP cascade through verb tabs — extended to include the restored Improve tab.
+- PR-PT-8 team workspace right rail — unchanged. Still rendered inside Project detail.
+- PR-PT-9 Report tab IP-scoped vs. Hub portfolio behavior — unchanged.
+- Wedge spec §4 project membership model + canAccess truth table — unchanged. The Improve tab uses `canAccess(currentUserId, members, 'edit-improve')` exactly as the Improve stage would have.
+- Wedge spec §6 (single €99 SKU, Azure tenant-wide) — unchanged.
+
+## Open questions for V2 (not blockers for this amendment)
+
+1. **Process tab in 7-tab nav.** The wedge V1 spec brought "Process" as a tab for the canvas viewport (per ADR-081). This amendment preserves the Process tab. If wedge V2 redesigns Process navigation, it can be revisited then.
+2. **What-If re-emergence in Analyze.** Per wedge §3.3, "What-If may re-emerge in Analyze later." The Improve tab's Advanced toggle still includes the `WhatIfExplorer` primitive. The Analyze tab does not yet surface What-If. Whether it should ever — and how that would relate to the Improve tab's What-If — is V2 work.
+3. **Cross-IP improvement patterns.** Not addressed by V1. If specialists run multiple projects in parallel and want pattern-matching across them, a future Analyze pivot or portfolio surface may be needed. Track in `docs/investigations.md` if the question recurs.
+
+## Acceptance criteria
+
+This amendment lands when:
+
+1. PWA + Azure apps render 7 tabs in the order `[Home] [Project] [Process] [Analyze] [Investigation] [Improve] [Report]`.
+2. The `workspace.project` i18n key is added (replacing the `workspace.projects` key); all 32 locale files show "Project" (singular) in their language.
+3. Clicking the Improve tab with an active IP renders `` for that IP, with the Advanced toggle reaching ``.
+4. Clicking the Improve tab WITHOUT an active IP renders the guidance state with a "Go to Home" button.
+5. Project detail stage tabs show 3 stages — `Charter / Approach / Sustainment` — with no `'improve'` stage button visible anywhere.
+6. The `migrateImprovementProjectMetadata` helper (already shipped) continues to fold legacy `team[] → members[]` at hydration without touching stage data.
+7. Sustainment continues to absorb Handoff close-logic (Task 5's work stands).
+8. All `canAccess(currentUserId, members, action)` gates continue to enforce Lead / Member / Sponsor permissions identically to PR-WV1-1.
+9. Tests in `packages/core`, `packages/stores`, `packages/ui` (touched suites), `apps/pwa`, `apps/azure` all green; `pnpm build` green across all 5 packages/apps; `bash scripts/pr-ready-check.sh` green.
+10. Browser walk covers: pick project from Home → see Project tab show 3 stages → click Improve tab → see scoped tracker → toggle Advanced → see PDCA workbench → exit IP → click Improve tab → see guidance state.
diff --git a/docs/superpowers/specs/2026-05-16-wedge-architecture-design.md b/docs/superpowers/specs/2026-05-16-wedge-architecture-design.md
index d86b25476..976cd6c5b 100644
--- a/docs/superpowers/specs/2026-05-16-wedge-architecture-design.md
+++ b/docs/superpowers/specs/2026-05-16-wedge-architecture-design.md
@@ -5,6 +5,7 @@ category: design-spec
status: draft
last-reviewed: 2026-05-16
related:
+ - docs/superpowers/specs/2026-05-16-improve-tab-amendment-design.md
- docs/superpowers/specs/2026-05-14-variscout-coherence-design.md
- docs/superpowers/specs/2026-05-14-projects-tab-design.md
- docs/superpowers/specs/2026-05-03-variscout-vision-design.md
diff --git a/packages/core/src/i18n/__tests__/index.test.ts b/packages/core/src/i18n/__tests__/index.test.ts
index d94cb1f39..0bd363085 100644
--- a/packages/core/src/i18n/__tests__/index.test.ts
+++ b/packages/core/src/i18n/__tests__/index.test.ts
@@ -356,3 +356,21 @@ describe('detectLocale', () => {
expect(detectLocale('FR-ca')).toBe('fr');
});
});
+
+describe('workspace.project key (amendment — replaces workspace.projects)', () => {
+ it('is defined in every locale', () => {
+ for (const locale of LOCALES) {
+ const catalog = getMessages(locale);
+ expect(catalog, `${locale} missing workspace.project`).toHaveProperty('workspace.project');
+ }
+ });
+
+ it('workspace.projects is no longer present in any locale', () => {
+ for (const locale of LOCALES) {
+ const catalog = getMessages(locale);
+ expect(catalog, `${locale} still has workspace.projects`).not.toHaveProperty(
+ 'workspace.projects'
+ );
+ }
+ });
+});
diff --git a/packages/core/src/i18n/messages/ar.ts b/packages/core/src/i18n/messages/ar.ts
index aee928aa4..bc6693cc6 100644
--- a/packages/core/src/i18n/messages/ar.ts
+++ b/packages/core/src/i18n/messages/ar.ts
@@ -594,7 +594,7 @@ export const ar: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'مشروع',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/bg.ts b/packages/core/src/i18n/messages/bg.ts
index f9e6f6692..3223d00a7 100644
--- a/packages/core/src/i18n/messages/bg.ts
+++ b/packages/core/src/i18n/messages/bg.ts
@@ -602,7 +602,7 @@ export const bg: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Проект',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/cs.ts b/packages/core/src/i18n/messages/cs.ts
index c4e92036c..e617dc4ab 100644
--- a/packages/core/src/i18n/messages/cs.ts
+++ b/packages/core/src/i18n/messages/cs.ts
@@ -514,7 +514,7 @@ export const cs: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/da.ts b/packages/core/src/i18n/messages/da.ts
index b5d29d0cf..1e3f7de80 100644
--- a/packages/core/src/i18n/messages/da.ts
+++ b/packages/core/src/i18n/messages/da.ts
@@ -566,7 +566,7 @@ export const da: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/de.ts b/packages/core/src/i18n/messages/de.ts
index ca7a89a09..9c7d5341b 100644
--- a/packages/core/src/i18n/messages/de.ts
+++ b/packages/core/src/i18n/messages/de.ts
@@ -606,7 +606,7 @@ export const de: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/el.ts b/packages/core/src/i18n/messages/el.ts
index 6bfe09568..fefd2c667 100644
--- a/packages/core/src/i18n/messages/el.ts
+++ b/packages/core/src/i18n/messages/el.ts
@@ -604,7 +604,7 @@ export const el: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Έργο',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/en.ts b/packages/core/src/i18n/messages/en.ts
index 763617227..54c993fa9 100644
--- a/packages/core/src/i18n/messages/en.ts
+++ b/packages/core/src/i18n/messages/en.ts
@@ -607,7 +607,7 @@ export const en: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Project',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/es.ts b/packages/core/src/i18n/messages/es.ts
index 9d27ffb23..4f86968ec 100644
--- a/packages/core/src/i18n/messages/es.ts
+++ b/packages/core/src/i18n/messages/es.ts
@@ -607,7 +607,7 @@ export const es: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Proyecto',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/fi.ts b/packages/core/src/i18n/messages/fi.ts
index a15a97f47..b467c3818 100644
--- a/packages/core/src/i18n/messages/fi.ts
+++ b/packages/core/src/i18n/messages/fi.ts
@@ -604,7 +604,7 @@ export const fi: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekti',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/fr.ts b/packages/core/src/i18n/messages/fr.ts
index 3d43fa129..d0498cc83 100644
--- a/packages/core/src/i18n/messages/fr.ts
+++ b/packages/core/src/i18n/messages/fr.ts
@@ -610,7 +610,7 @@ export const fr: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projet',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/he.ts b/packages/core/src/i18n/messages/he.ts
index 6e07a8aa2..afa25d4bd 100644
--- a/packages/core/src/i18n/messages/he.ts
+++ b/packages/core/src/i18n/messages/he.ts
@@ -593,7 +593,7 @@ export const he: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'פרויקט',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/hi.ts b/packages/core/src/i18n/messages/hi.ts
index 158c4d18a..61ff16b2d 100644
--- a/packages/core/src/i18n/messages/hi.ts
+++ b/packages/core/src/i18n/messages/hi.ts
@@ -604,7 +604,7 @@ export const hi: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'परियोजना',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/hr.ts b/packages/core/src/i18n/messages/hr.ts
index 4850cebb8..f72e7d522 100644
--- a/packages/core/src/i18n/messages/hr.ts
+++ b/packages/core/src/i18n/messages/hr.ts
@@ -600,7 +600,7 @@ export const hr: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/hu.ts b/packages/core/src/i18n/messages/hu.ts
index 163685acd..5f921f728 100644
--- a/packages/core/src/i18n/messages/hu.ts
+++ b/packages/core/src/i18n/messages/hu.ts
@@ -518,7 +518,7 @@ export const hu: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/id.ts b/packages/core/src/i18n/messages/id.ts
index 142874d02..73c804e4d 100644
--- a/packages/core/src/i18n/messages/id.ts
+++ b/packages/core/src/i18n/messages/id.ts
@@ -552,7 +552,7 @@ export const id: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Proyek',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/it.ts b/packages/core/src/i18n/messages/it.ts
index 5ae734a56..a66e44f67 100644
--- a/packages/core/src/i18n/messages/it.ts
+++ b/packages/core/src/i18n/messages/it.ts
@@ -574,7 +574,7 @@ export const it: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Progetto',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/ja.ts b/packages/core/src/i18n/messages/ja.ts
index e53091da7..d48eab14c 100644
--- a/packages/core/src/i18n/messages/ja.ts
+++ b/packages/core/src/i18n/messages/ja.ts
@@ -566,7 +566,7 @@ export const ja: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'プロジェクト',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/ko.ts b/packages/core/src/i18n/messages/ko.ts
index 76814eb66..5607438b3 100644
--- a/packages/core/src/i18n/messages/ko.ts
+++ b/packages/core/src/i18n/messages/ko.ts
@@ -565,7 +565,7 @@ export const ko: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': '프로젝트',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/ms.ts b/packages/core/src/i18n/messages/ms.ts
index a60a355d5..dc1a9b7ee 100644
--- a/packages/core/src/i18n/messages/ms.ts
+++ b/packages/core/src/i18n/messages/ms.ts
@@ -604,7 +604,7 @@ export const ms: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projek',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/nb.ts b/packages/core/src/i18n/messages/nb.ts
index bcb08277b..3a8d0aba7 100644
--- a/packages/core/src/i18n/messages/nb.ts
+++ b/packages/core/src/i18n/messages/nb.ts
@@ -514,7 +514,7 @@ export const nb: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Prosjekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/nl.ts b/packages/core/src/i18n/messages/nl.ts
index df58bd98f..d2e45220d 100644
--- a/packages/core/src/i18n/messages/nl.ts
+++ b/packages/core/src/i18n/messages/nl.ts
@@ -573,7 +573,7 @@ export const nl: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Project',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/pl.ts b/packages/core/src/i18n/messages/pl.ts
index 677bb4f2d..02916307b 100644
--- a/packages/core/src/i18n/messages/pl.ts
+++ b/packages/core/src/i18n/messages/pl.ts
@@ -571,7 +571,7 @@ export const pl: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/pt.ts b/packages/core/src/i18n/messages/pt.ts
index 88bbbdfbe..3e490164f 100644
--- a/packages/core/src/i18n/messages/pt.ts
+++ b/packages/core/src/i18n/messages/pt.ts
@@ -606,7 +606,7 @@ export const pt: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projeto',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/ro.ts b/packages/core/src/i18n/messages/ro.ts
index a6382e7be..5f9939455 100644
--- a/packages/core/src/i18n/messages/ro.ts
+++ b/packages/core/src/i18n/messages/ro.ts
@@ -552,7 +552,7 @@ export const ro: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Proiect',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/sk.ts b/packages/core/src/i18n/messages/sk.ts
index 67465b955..6ccfda67c 100644
--- a/packages/core/src/i18n/messages/sk.ts
+++ b/packages/core/src/i18n/messages/sk.ts
@@ -602,7 +602,7 @@ export const sk: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/sv.ts b/packages/core/src/i18n/messages/sv.ts
index 6be264157..bc7620ebe 100644
--- a/packages/core/src/i18n/messages/sv.ts
+++ b/packages/core/src/i18n/messages/sv.ts
@@ -565,7 +565,7 @@ export const sv: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Projekt',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/th.ts b/packages/core/src/i18n/messages/th.ts
index 8bc6806df..1aa104988 100644
--- a/packages/core/src/i18n/messages/th.ts
+++ b/packages/core/src/i18n/messages/th.ts
@@ -543,7 +543,7 @@ export const th: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'โครงการ',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/tr.ts b/packages/core/src/i18n/messages/tr.ts
index fe49370cc..90fabb402 100644
--- a/packages/core/src/i18n/messages/tr.ts
+++ b/packages/core/src/i18n/messages/tr.ts
@@ -571,7 +571,7 @@ export const tr: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Proje',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/uk.ts b/packages/core/src/i18n/messages/uk.ts
index 574b2fe5e..8f742673e 100644
--- a/packages/core/src/i18n/messages/uk.ts
+++ b/packages/core/src/i18n/messages/uk.ts
@@ -553,7 +553,7 @@ export const uk: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Проєкт',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/vi.ts b/packages/core/src/i18n/messages/vi.ts
index 038647625..270326e63 100644
--- a/packages/core/src/i18n/messages/vi.ts
+++ b/packages/core/src/i18n/messages/vi.ts
@@ -551,7 +551,7 @@ export const vi: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': 'Dự án',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/zhHans.ts b/packages/core/src/i18n/messages/zhHans.ts
index 5be272f61..94561276b 100644
--- a/packages/core/src/i18n/messages/zhHans.ts
+++ b/packages/core/src/i18n/messages/zhHans.ts
@@ -555,7 +555,7 @@ export const zhHans: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': '项目',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/messages/zhHant.ts b/packages/core/src/i18n/messages/zhHant.ts
index 87f8bb950..d31e0f27b 100644
--- a/packages/core/src/i18n/messages/zhHant.ts
+++ b/packages/core/src/i18n/messages/zhHant.ts
@@ -555,7 +555,7 @@ export const zhHant: MessageCatalog = {
'workspace.findings': 'Findings',
'workspace.improvement': 'Improvement',
'workspace.improve': 'Improve',
- 'workspace.projects': 'Projects',
+ 'workspace.project': '項目',
'workspace.report': 'Report',
// Synthesis card
diff --git a/packages/core/src/i18n/types.ts b/packages/core/src/i18n/types.ts
index d7abcede2..33f9d2ad5 100644
--- a/packages/core/src/i18n/types.ts
+++ b/packages/core/src/i18n/types.ts
@@ -696,7 +696,7 @@ export interface MessageCatalog {
'workspace.investigation': string;
'workspace.improvement': string;
'workspace.improve': string;
- 'workspace.projects': string;
+ 'workspace.project': string;
'workspace.report': string;
'workspace.findings': string;
diff --git a/packages/core/src/improvementProject/__tests__/migrateMetadata.test.ts b/packages/core/src/improvementProject/__tests__/migrateMetadata.test.ts
new file mode 100644
index 000000000..ce480576e
--- /dev/null
+++ b/packages/core/src/improvementProject/__tests__/migrateMetadata.test.ts
@@ -0,0 +1,79 @@
+import { describe, it, expect } from 'vitest';
+import { migrateImprovementProjectMetadata } from '../migrateMetadata';
+import type { ImprovementProject } from '../types';
+
+describe('migrateImprovementProjectMetadata', () => {
+ const baseIP: ImprovementProject = {
+ id: 'ip-1',
+ hubId: 'hub-1',
+ createdAt: 0,
+ updatedAt: 0,
+ deletedAt: null,
+ status: 'active',
+ metadata: { title: 'Test IP' },
+ goal: { outcomeGoal: { outcomeSpecId: 'o-1', baseline: 0.5, target: 1.33 } },
+ sections: { background: {}, investigationLineage: {}, approach: {}, outcomeReference: {} },
+ };
+
+ it('migrates legacy team[] to members[] when members is absent', () => {
+ const legacy: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [
+ { role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } },
+ { role: 'teamMember', person: { displayName: 'Mira' } },
+ ],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(legacy, 1234);
+ expect(out.metadata.members).toBeDefined();
+ expect(out.metadata.members).toHaveLength(2);
+ expect(out.metadata.members?.[0].role).toBe('lead');
+ expect(out.metadata.members?.[1].role).toBe('member');
+ expect(out.metadata.team).toBeDefined(); // legacy preserved
+ });
+
+ it('does not migrate when members[] already populated', () => {
+ const alreadyMigrated: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [{ role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } }],
+ members: [
+ {
+ id: 'pm-existing',
+ createdAt: 100,
+ deletedAt: null,
+ userId: 'someone-else@org',
+ displayName: 'Someone Else',
+ role: 'lead',
+ invitedAt: 100,
+ },
+ ],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(alreadyMigrated, 1234);
+ expect(out.metadata.members).toHaveLength(1);
+ expect(out.metadata.members?.[0].userId).toBe('someone-else@org');
+ });
+
+ it('is a no-op when neither team nor members exist', () => {
+ const out = migrateImprovementProjectMetadata(baseIP, 1234);
+ expect(out.metadata.members).toBeUndefined();
+ expect(out).toEqual(baseIP);
+ });
+
+ it('returns a new object (does not mutate input)', () => {
+ const legacy: ImprovementProject = {
+ ...baseIP,
+ metadata: {
+ ...baseIP.metadata,
+ team: [{ role: 'projectLead', person: { displayName: 'Lead', upn: 'lead@org' } }],
+ },
+ };
+ const out = migrateImprovementProjectMetadata(legacy, 1234);
+ expect(out).not.toBe(legacy);
+ expect(legacy.metadata.members).toBeUndefined();
+ });
+});
diff --git a/packages/core/src/improvementProject/index.ts b/packages/core/src/improvementProject/index.ts
index bd3376982..90041d773 100644
--- a/packages/core/src/improvementProject/index.ts
+++ b/packages/core/src/improvementProject/index.ts
@@ -17,3 +17,4 @@ export { computeSourceHash, shouldShowDrift } from './snapshot';
export type { DriftableSnapshot, DriftableCurrent } from './snapshot';
export { migrateTeamToMembers } from './migration';
+export { migrateImprovementProjectMetadata } from './migrateMetadata';
diff --git a/packages/core/src/improvementProject/migrateMetadata.ts b/packages/core/src/improvementProject/migrateMetadata.ts
new file mode 100644
index 000000000..354596588
--- /dev/null
+++ b/packages/core/src/improvementProject/migrateMetadata.ts
@@ -0,0 +1,32 @@
+import type { ImprovementProject } from './types';
+import { migrateTeamToMembers } from './migration';
+
+/**
+ * Idempotent migration applied at hydration time. Folds PR-WV1-2 changes
+ * over the wedge V1 metadata shape:
+ * 1. legacy `team[]` → wedge `members[]` (via migrateTeamToMembers)
+ *
+ * Legacy `team[]` is preserved on the output for now; PR-WV1-5 (tier-gating
+ * retirement + nav reorder) drops it.
+ */
+export function migrateImprovementProjectMetadata(
+ ip: ImprovementProject,
+ now: number
+): ImprovementProject {
+ const hasMembers = ip.metadata.members !== undefined;
+ const hasLegacyTeam = ip.metadata.team !== undefined && ip.metadata.team.length > 0;
+
+ if (hasMembers || !hasLegacyTeam) {
+ return ip;
+ }
+
+ const members = migrateTeamToMembers(ip.metadata.team, now);
+
+ return {
+ ...ip,
+ metadata: {
+ ...ip.metadata,
+ members,
+ },
+ };
+}
diff --git a/packages/ui/src/components/IPDetail/IPDetailPage.tsx b/packages/ui/src/components/IPDetail/IPDetailPage.tsx
index 912bbfac0..ad440a6d6 100644
--- a/packages/ui/src/components/IPDetail/IPDetailPage.tsx
+++ b/packages/ui/src/components/IPDetail/IPDetailPage.tsx
@@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react';
import { usePreferencesStore } from '@variscout/stores';
import type { ImprovementProject } from '@variscout/core/improvementProject';
import type { ProjectMember, ProjectRole } from '@variscout/core/projectMembership';
+import { canAccess } from '@variscout/core/projectMembership';
import { generateDeterministicId } from '@variscout/core';
import { reduceProjectMembers, type MembershipAction } from '@variscout/core/actions';
import IPDetailHeader from './IPDetailHeader';
@@ -16,10 +17,8 @@ import CharterOverview from './stages/CharterOverview';
import CharterSections from './stages/CharterSections';
import ApproachOverview from './stages/ApproachOverview';
import ApproachSections from './stages/ApproachSections';
-import SustainmentOverview from './stages/SustainmentOverview';
+import SustainmentOverview, { type SustainmentClosureInputs } from './stages/SustainmentOverview';
import SustainmentSections from './stages/SustainmentSections';
-import HandoffOverview, { type HandoffChecklistInputs } from './stages/HandoffOverview';
-import HandoffSections from './stages/HandoffSections';
import type { CauseProjectionInputs, CauseRow } from './stages/causeProjection';
import type { ImprovementProjectFormProps } from '../ImprovementProject/ImprovementProjectForm';
import type { ActionItem, ImprovementIdea } from '@variscout/core/findings';
@@ -56,14 +55,12 @@ export interface IPDetailPageProps {
sustainmentRecord?: SustainmentRecord;
/** Linked ControlHandoff. Present when handoff stage is active or beyond. */
controlHandoff?: ControlHandoff;
- /** Inputs for Handoff checklist (derived from controlHandoff by caller). */
- handoffInputs?: HandoffChecklistInputs;
+ /** Closure checklist inputs for SustainmentOverview (folded in from former Handoff stage). */
+ closureInputs?: SustainmentClosureInputs;
/** Per-cause in-control rows for Sustainment Overview. */
sustainmentPerCauseRows?: Array<{ factor: string; inControl: boolean; observation?: string }>;
/** "Open legacy Sustainment panel" handler. */
onOpenLegacySustainment?: () => void;
- /** "Open legacy Handoff panel" handler. */
- onOpenLegacyHandoff?: () => void;
/** "Nudge owner" handler (Plan 3 wires actual notification). */
onNudgeProcessOwner?: () => void;
/** Activity/signoff inputs for the team rail. */
@@ -76,7 +73,6 @@ export interface IPDetailPageProps {
}
function defaultActiveStage(stages: ReturnType): StageName {
- if (stages.handoff === 'current') return 'handoff';
if (stages.sustainment === 'current') return 'sustainment';
if (stages.approach === 'current') return 'approach';
return 'charter';
@@ -98,11 +94,10 @@ const IPDetailPage: React.FC = ({
onOpenCauseWorkbench,
sustainmentRecord,
controlHandoff,
- handoffInputs,
+ closureInputs,
sustainmentPerCauseRows,
onOpenLegacySustainment,
- onOpenLegacyHandoff,
- onNudgeProcessOwner,
+ onNudgeProcessOwner: _onNudgeProcessOwner,
ideas,
actions,
now,
@@ -123,15 +118,12 @@ const IPDetailPage: React.FC = ({
// ACL guard: only apply when we have an identified user AND an explicit members list.
// If currentUserId is absent OR members[] is empty/absent → backward-compatible open access.
- const userRole =
- currentUserId !== undefined && members.length > 0
- ? members.find(m => m.userId === currentUserId)?.role
- : undefined;
-
- const isExplicitlyExcluded =
- currentUserId !== undefined && members.length > 0 && userRole === undefined;
-
- const isSponsor = userRole === 'sponsor';
+ const hasIdentity = currentUserId !== undefined && members.length > 0;
+ const isExplicitlyExcluded = hasIdentity && !canAccess(currentUserId, members, 'view-report');
+ const isSponsor =
+ hasIdentity &&
+ canAccess(currentUserId, members, 'view-report') &&
+ !canAccess(currentUserId, members, 'edit-charter');
const handleInviteClick = () => {
onInviteClick?.();
@@ -269,16 +261,20 @@ const IPDetailPage: React.FC = ({
{activeStage === 'sustainment' && mode === 'overview' && sustainmentRecord && (
setActiveStage('handoff')}
+ onStartHandoff={() => setActiveStage('sustainment')}
onOpenProcess={() => onJumpOut?.('process')}
onOpenAnalyze={() => onJumpOut?.('analyze')}
perCauseRows={sustainmentPerCauseRows}
+ closureInputs={closureInputs}
+ onNudgeOwner={_onNudgeProcessOwner}
+ onOpenReport={() => onJumpOut?.('report')}
/>
)}
{activeStage === 'sustainment' && mode === 'sections' && sustainmentRecord && (
onOpenLegacySustainment?.()}
+ controlHandoff={controlHandoff}
/>
)}
{activeStage === 'sustainment' && !sustainmentRecord && (
@@ -287,29 +283,6 @@ const IPDetailPage: React.FC = ({
ADR-080.
- The Handoff authoring form (control plan text, owner FK, training FK, acknowledgment toggle)
- is reachable today via the legacy Handoff activeView. This Sections-mode embedding will
- inline the same form in a follow-up plan; for V1 we link out.
-
+ The Handoff authoring form (control plan text, owner FK, training FK, acknowledgment
+ toggle) is reachable today via the legacy Handoff activeView. This Sections-mode
+ embedding will inline the same form in a follow-up plan; for V1 we link out.
+
+ Improvement work happens inside a chartered project. Pick a project from Home, or create a
+ new one to start tracking actions and ideating with the PDCA workbench.
+
+
+
+ );
+}
diff --git a/packages/ui/src/components/Improve/__tests__/ImproveStage.test.tsx b/packages/ui/src/components/Improve/__tests__/ImproveStage.test.tsx
new file mode 100644
index 000000000..7ef3b9e0b
--- /dev/null
+++ b/packages/ui/src/components/Improve/__tests__/ImproveStage.test.tsx
@@ -0,0 +1,197 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+// vi.mock BEFORE component imports per testing.md rule.
+// ImprovementWorkspaceBase uses useTranslation (i18n) and lucide icons that need mocking
+// to keep this test hermetic. Runtime integration verified via --chrome browser walk.
+vi.mock('../../../components/ImprovementPlan/ImprovementWorkspaceBase', () => ({
+ ImprovementWorkspaceBase: () => (
+