diff --git a/PRPs/PRP-showcase-workspace-E3-workspace-tagged-plans.md b/PRPs/PRP-showcase-workspace-E3-workspace-tagged-plans.md new file mode 100644 index 00000000..e612a890 --- /dev/null +++ b/PRPs/PRP-showcase-workspace-E3-workspace-tagged-plans.md @@ -0,0 +1,613 @@ +name: "PRP — Showcase Workspace E3: Workspace-Tagged Scenario Plans (issue #392)" +description: | + +## Purpose + +Implement the workspace-tagging epic of the showcase-workspace initiative +(umbrella #389): the two scenario plans the showcase pipeline saves +(`showcase-price-cut-10pct`, `showcase-holiday-uplift`) gain workspace-aware +tags — `["showcase", "", "source:showcase"]` plus `workspace:` on +`preservation="keep"` runs — via the existing GIN-indexed `scenario_plan.tags` +column, and the What-If Planner's saved-plans library gains a tag filter (with +deep-linkable `?tags=` URL state) so a workspace's plans are retrievable via +`GET /scenarios?tags=workspace:`. Parallel epic after Foundation E1 +(#390); independent of E2 (#391, merged) and E4 (#393, PRP authored, not +started). + +## Core Principles + +1. **Context is King**: every reference below was verified against the live code on 2026-06-12 (branch `dev` @ 3194fe8, post-E1/E2 merge). +2. **Validation Loops**: each level is executable as written. +3. **Information Dense**: patterns cite exact file:line. +4. **Progressive Success**: tag helper → pipeline steps → step tests → planner filter → URL state → docs. +5. **Global rules**: follow CLAUDE.md / AGENTS.md; all five CI gates must pass; UI work follows `.claude/rules/ui-design.md` + `shadcn-ui.md`. + +--- + +## Goal + +A showcase run that saves scenario plans stamps them with discoverable, +namespaced tags: every pipeline-saved plan carries `source:showcase` (alongside +the existing `showcase` + `price`/`holiday` tags, kept for back-compat), and a +`preservation="keep"` run additionally stamps `workspace:` (falling back +to `workspace:` on unnamed keep runs). On the What-If Planner, +the operator filters the saved-plans library by tag — clicking a tag badge in +the table adds it to the filter, active filters render as removable chips, the +filter round-trips through the `?tags=` query string (so +`/visualize/planner?tags=workspace:black-friday` deep-links straight to one +workspace's plans), and the server does the filtering via the existing JSONB +containment query. Ephemeral runs and legacy plans behave exactly as today. + +**Deliverable** (all additive — no migration, no schema change, no new endpoints): + +- `app/features/demo/pipeline.py` — `DemoContext.workspace_name` field; new pure `_showcase_plan_tags()` helper; the two `POST /scenarios` bodies use it. +- `app/features/demo/tests/test_pipeline.py` — helper unit tests + updated step-body assertions (keep vs ephemeral, named vs unnamed). +- `app/features/scenarios/tests/test_routes_integration.py` — one integration test proving the umbrella criterion verbatim: plans saved with `workspace:` are retrievable via `GET /scenarios?tags=workspace:`. +- `frontend/src/lib/url-params.ts` — `parseTagsParam()` reader (+ tests in `url-params.test.ts`). +- `frontend/src/pages/visualize/planner.tsx` — tag-filter state wired into `useScenarios(tags)`, clickable tag badges, active-filter chips, `?tags=` URL sync. +- `docs/_base/API_CONTRACTS.md` — additive E3 note on the `WS /demo/stream` planning steps. + +**Success definition**: all Success Criteria below check off, the five CI gates +are green, frontend gates green, and a manual dogfood shows a keep-run's plans +filtered by `workspace:` in the planner — and reachable by pasting the +deep-link URL. + +## Why + +- E1 records *which* plan ids a workspace created (`created_objects.scenario_plan_ids`, `app/features/demo/workspace.py:97`), but the plans themselves are unfindable from the planner — the library has NO filter UI even though the backend (`GET /scenarios?tags=`, JSONB `@>` containment, `app/features/scenarios/service.py:462-465`) and the frontend hook (`useScenarios(tags)`, `frontend/src/hooks/use-scenarios.ts:28-38`) have supported tag filtering since PRP-27. +- The pipeline already tags plans — but with fixed, workspace-blind values: `["showcase", "price"]` (`app/features/demo/pipeline.py:1309`) and `["showcase", "holiday"]` (`pipeline.py:1371`). Across runs, every plan looks identical. +- Umbrella #389 success criterion: "Showcase-saved scenario plans carry `["showcase", "workspace:", "source:showcase"]` and are retrievable via `GET /scenarios?tags=workspace:`". +- E4 (#393, PRP authored) renders per-workspace plan deep links from `created_objects`; E3's tag filter is the complementary bulk view ("all plans of workspace X") and the `?tags=` deep link gives E4/E5 a stable URL target. + +## What + +### Designed tag taxonomy (locked decisions) + +| Run | Tags on `showcase-price-cut-10pct` | Tags on `showcase-holiday-uplift` | +|-----|-----|-----| +| Ephemeral showcase run | `["showcase", "price", "source:showcase"]` | `["showcase", "holiday", "source:showcase"]` | +| Keep run, named `bf-demo` | `[..., "source:showcase", "workspace:bf-demo"]` | same + `workspace:bf-demo` | +| Keep run, unnamed | `[..., "source:showcase", "workspace:"]` | same | + +1. **`showcase` + `price`/`holiday` stay** — existing plans and any operator filters keep working (back-compat; tags are append-only semantics). +2. **`source:showcase` always** — every pipeline-saved plan is showcase-sourced regardless of preservation; this is the namespaced successor to the bare `showcase` tag (umbrella triple). +3. **`workspace: