From 025e53da00b7d9862f7b5f27c8615dafc22239f1 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Thu, 19 Mar 2026 07:57:55 -0700 Subject: [PATCH] docs(apply): add implementation plan for desktop split-pane --- .../plans/2026-03-19-apply-split-pane.md | 924 ++++++++++++++++++ 1 file changed, 924 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-19-apply-split-pane.md diff --git a/docs/superpowers/plans/2026-03-19-apply-split-pane.md b/docs/superpowers/plans/2026-03-19-apply-split-pane.md new file mode 100644 index 0000000..460c343 --- /dev/null +++ b/docs/superpowers/plans/2026-03-19-apply-split-pane.md @@ -0,0 +1,924 @@ +# Apply View — Desktop Split-Pane 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:** Refactor the Apply view for desktop into a master-detail split pane (28% job list / 72% workspace) with an expand-from-divider animation, while leaving mobile completely unchanged. + +**Architecture:** `ApplyWorkspace.vue` is extracted from `ApplyWorkspaceView.vue` as a prop-driven component, allowing it to render both inline (split pane) and as a standalone route (mobile). `ApplyView.vue` owns the split layout, selection state, and three easter eggs. The fourth easter egg (Perfect Match shimmer) lives inside `ApplyWorkspace.vue` since it needs access to the loaded job's score. + +**Tech Stack:** Vue 3 + TypeScript + Pinia-free (local `ref` state) + CSS Grid column transitions + `useEasterEgg.ts` composables (existing) + +--- + +## File Map + +| File | Action | Responsibility | +|------|--------|----------------| +| `web/src/assets/peregrine.css` | Modify | Add `--score-mid-high` CSS variable; add `.score-badge--mid-high` class | +| `web/src/components/ApplyWorkspace.vue` | **Create** | Extracted workspace: `jobId: number` prop, emits `job-removed` + `cover-letter-generated`, Perfect Match shimmer | +| `web/src/views/ApplyWorkspaceView.vue` | Modify | Slim to thin wrapper: `` | +| `web/src/views/ApplyView.vue` | **Replace** | Split-pane layout, narrow list rows, Speed Demon + Marathon + (Konami already global) | + +No router changes — `/apply/:id` stays as-is. + +--- + +## Task 1: Score Badge 4-Tier CSS + +**Files:** +- Modify: `web/src/assets/peregrine.css` + +The current badge CSS has 3 tiers with outdated thresholds (`≥80`, `≥60`). The new spec uses 4 tiers aligned with the existing CSS variable comments: green ≥70%, **blue 50–69%** (new), amber 30–49%, red <30%. + +**Why `peregrine.css`?** Score tokens (`--score-high`, `--score-mid`, `--score-low`) are defined there. The new `--score-mid-high` token and the `.score-badge--mid-high` class belong alongside them. + +**Note:** `ApplyWorkspaceView.vue` defines `.score-badge--*` classes in its ` +``` + +- [ ] **Step 2: Run type-check** + +```bash +cd /Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa/web +./node_modules/.bin/vue-tsc --noEmit +``` + +Expected: 0 errors. Fix any type errors before continuing. + +- [ ] **Step 3: Smoke-test in the browser** + +Start the dev stack: +```bash +# Terminal 1 +cd /Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa +conda run -n job-seeker uvicorn dev-api:app --port 8601 --reload & + +# Terminal 2 +cd web && npm run dev +``` + +Open http://localhost:5173/apply and verify: +- Desktop (≥1024px): split pane renders, list is narrow on left, right shows empty state with 🦅 +- Click a job → panel expands from the divider with animation; workspace loads +- Click another job → panel content switches, selected row highlight updates +- Mark a job as Applied → panel closes, job disappears from list +- Mobile emulation (DevTools → 375px) → single-column list with RouterLink navigation (no split) + +- [ ] **Step 4: Test Speed Demon easter egg** + +Quickly click 5 different jobs within 3 seconds. Expected: 🦅 streaks across the panel, "You're on the hunt!" toast appears. + +With DevTools → Rendering → `prefers-reduced-motion: reduce`: toast only, no canvas animation. + +- [ ] **Step 5: Test Marathon easter egg** + +Generate cover letters for 5 jobs (or temporarily lower the threshold to 2 for testing, then revert). Expected: `📬 5 today` badge appears in list header. Tooltip on hover: "You're on a roll!". + +- [ ] **Step 6: Commit** + +```bash +cd /Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa +git add web/src/views/ApplyView.vue +git commit -m "feat(apply): desktop split-pane layout with narrow list, expand animation, speed demon + marathon easter eggs" +``` + +--- + +## Task 4: Type-Check and Test Suite + +**Files:** +- No changes — verification only + +- [ ] **Step 1: Run full type-check** + +```bash +cd /Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa/web +./node_modules/.bin/vue-tsc --noEmit +``` + +Expected: 0 errors. + +- [ ] **Step 2: Run full test suite** + +```bash +./node_modules/.bin/vitest run +``` + +Expected: all tests pass (minimum 3 from `interviews.test.ts`; any other tests that exist). + +- [ ] **Step 3: Commit fixes if needed** + +If any fixes were required: +```bash +cd /Library/Development/CircuitForge/peregrine/.worktrees/feature-vue-spa +git add -p +git commit -m "fix(apply): type-check and test fixes" +``` + +--- + +## Done Criteria + +- [ ] `--score-mid-high` CSS token added; `.score-badge--mid-high` class works +- [ ] `scoreBadgeClass()` uses 4-tier thresholds (≥70 / ≥50 / ≥30 / else) in all apply-flow files +- [ ] `ApplyWorkspace.vue` renders the full workspace from a `jobId: number` prop +- [ ] `ApplyWorkspace.vue` emits `job-removed` on mark-applied / reject-listing +- [ ] `ApplyWorkspace.vue` emits `cover-letter-generated` when polling completes +- [ ] Perfect Match shimmer fires once when a ≥70% job opens (`.score-badge--shimmer` keyframe) +- [ ] `ApplyWorkspaceView.vue` is a thin wrapper with `` +- [ ] Desktop (≥1024px): 28/72 CSS Grid split with `grid-template-columns` transition +- [ ] Panel expand animation uses `overflow: clip` + `min-width: 0` (not `overflow: hidden`) +- [ ] Panel content fades in with 100ms delay after column expands +- [ ] `prefers-reduced-motion`: no grid transition, no canvas animation (toast only for Speed Demon) +- [ ] Narrow list rows: title + score badge (top row), company + ✓ tick (bottom row) +- [ ] Selected row: border-left accent + tinted background (`color-mix` with `--app-primary-light` fallback) +- [ ] Empty panel state shows 🦅 + "Select a job to open the workspace" +- [ ] `@job-removed` clears `selectedJobId` + re-fetches job list +- [ ] Speed Demon: 5 clicks in <3s → canvas bird + toast (reduced-motion: toast only) +- [ ] Marathon: 5+ cover letters in session → `📬 N today` badge in list header +- [ ] Konami: already global in `App.vue` — no additional code needed +- [ ] Mobile (<1024px): unchanged — full-width list with `RouterLink` navigation +- [ ] Type-check: 0 errors; all tests pass