Files
mission-control/PLAN.md
teernisse 954067a38b docs: initial Mission Control planning documents
- PLAN.md: Complete implementation plan with architecture, ACs, phases
- CLAUDE.md: Project context for AI agents

Architecture: Tauri + React, beads as universal work graph,
manual-first priority with rich decision logging.
2026-02-25 16:23:07 -05:00

1442 lines
62 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mission Control — Implementation Plan
> **Version:** 1.0
> **Created:** 2026-02-25
> **Status:** Planning Complete, Ready for Implementation
## Executive Summary
Mission Control (MC) is an ADHD-centric personal productivity hub that unifies GitLab activity, beads task tracking, and manual task management into a single, beautiful native interface. The core UX principle: **surface THE ONE THING you should be doing right now**.
This is NOT a dashboard of everything. It's a trusted advisor that understands your work and helps you decide what matters.
---
## Table of Contents
1. [Vision & Principles](#vision--principles)
2. [Architecture Overview](#architecture-overview)
3. [Tech Stack](#tech-stack)
4. [Data Model](#data-model)
5. [GitLab → Beads Bridge](#gitlab--beads-bridge)
6. [Views & UX](#views--ux)
7. [Priority & Decision System](#priority--decision-system)
8. [Implementation Phases](#implementation-phases)
9. [Acceptance Criteria](#acceptance-criteria)
10. [Open Questions](#open-questions)
---
## Vision & Principles
### The Core Question
> "What should I actually be doing right now?"
MC answers this question. Not with 47 items. With ONE.
### ADHD-Centric Design Principles
| Principle | Implementation |
|-----------|----------------|
| **The One Thing** | UI's primary job is surfacing THE single most important thing. Everything else is peripheral. |
| **Achievable Inbox Zero** | Every view must have a clearable state. The psychological win of "inbox zero" everywhere. |
| **Time Decay Visibility** | Age is visceral, not hidden in timestamps. Fresh=bright, 3d=amber, 7d+=red pulse. |
| **Batch Mode for Flow** | "You have 4 code reviews. Want to batch them? (~20 min)" |
| **Quick Capture, Trust the System** | One hotkey, type it, gone. System triages later. |
| **Context Bookmarking** | When you switch away, system bookmarks exactly where you were. |
| **Ambient Awareness, Not Interruption** | Notifications are visible (badge, color) but never modal. |
### What MC Is NOT
- Not a GitLab replacement (use GitLab for actual work)
- Not a beads replacement (beads is the task graph, MC is the interface)
- Not an automated prioritization system (you decide, MC learns)
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ MISSION CONTROL │
│ Tauri Desktop App │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ React Frontend │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Focus View │ │ Queue View │ │ Timeline │ │ │
│ │ │ (THE ONE) │ │ (all) │ │ (stream) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Batch Mode │ │ Inbox │ │ Capture │ │ │
│ │ │ (flow) │ │ (triage) │ │ (overlay) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Tauri IPC │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Rust Backend │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Bridge │ │ Data Layer │ │ Decision Logger │ │ │
│ │ │ ────────── │ │ ────────── │ │ ─────────────── │ │ │
│ │ │ Watch lore │ │ Call lore │ │ Log all actions │ │ │
│ │ │ Create beads│ │ Call br/bv │ │ Capture context │ │ │
│ │ │ Sync state │ │ Read mapping│ │ Store reasons │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ lore CLI │ │ br/bv CLI │ │ MC Local State │
│ (--robot) │ │ (beads) │ │ │
│ │ │ │ │ mapping.json │
│ lore.db │ │ .beads/ │ │ decisions.jsonl│
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### Key Architectural Decisions
| Decision | Choice | Reasoning |
|----------|--------|-----------|
| Desktop framework | Tauri 2.0 | Rust backend, tiny bundle (~15MB vs Electron 150MB), native APIs |
| Frontend | React 19 + Vite | Fast iteration, huge ecosystem, AI-friendly |
| lore integration | CLI (`lore --robot`) | Clean API boundary, no schema coupling |
| beads integration | CLI (`br` commands) | Battle-tested, guaranteed compatibility |
| Local state | JSON files | Simple, portable, easy to inspect/debug |
---
## Tech Stack
| Layer | Choice | Why |
|-------|--------|-----|
| **Shell** | Tauri 2.0 | Rust backend, tiny bundle, native APIs, system tray, global hotkeys |
| **Frontend** | React 19 + Vite | Fast iteration, huge ecosystem |
| **Styling** | Tailwind + shadcn/ui | Beautiful defaults, easy customization |
| **Animations** | Framer Motion | Smooth, spring-based, ADHD-friendly micro-interactions |
| **State** | Zustand + TanStack Query | Simple global state + async data fetching |
| **Backend DB** | JSON files | mc_state.json, mapping.json, decisions.jsonl |
| **IPC** | Tauri commands + events | Type-safe, bidirectional |
| **Color scheme** | Dark only | User preference, nail down details later |
---
## Data Model
### Beads as Universal Work Graph
Everything is a bead. GitLab items, personal tasks, quick captures — all flow into the beads task graph.
```
┌─────────────────────────────────────────────────────────────────┐
│ BEADS │
│ (Universal Task Graph) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ MR Review │────▶│ My Feature │────▶│ Deploy PR │ │
│ │ !847 │ │ (manual) │ │ (manual) │ │
│ │ [from gitlab]│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Source types: │
│ • gitlab (auto-created from lore sync via MC bridge) │
│ • manual (your tasks, quick captures) │
│ │
│ Dependencies span everything. bv's graph algos work on all. │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ MISSION CONTROL │
│ (The interface to your work graph) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • Shows THE ONE THING (manually curated by you) │
│ • Suggests what to work on (bv recommendations as hints) │
│ • You decide. You drag. You defer. You're in control. │
│ • Quick capture → creates bead → you triage │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### MC Does NOT Modify Beads Schema
Beads stays clean. MC layers its own tracking on top via mapping files.
---
## GitLab → Beads Bridge
### Event → Bead Mapping
| GitLab Event | Bead Created | Mapping Key Format |
|--------------|--------------|-------------------|
| MR review requested to you | `Review MR !{iid}: {title}` | `mr_review:{host}:{project_id}:{iid}` |
| Issue assigned to you | `Issue #{iid}: {title}` | `issue:{host}:{project_id}:{iid}` |
| MR you authored (opened) | `Your MR !{iid}: {title}` | `mr_authored:{host}:{project_id}:{iid}` |
| Mention in discussion | `Mentioned in {type} #{iid}: {snippet}` | `mention:{host}:{project_id}:{iid}:{note_id}` |
| Comment on your authored MR | `Respond to @{actor} on MR !{iid}` | `feedback:{host}:{project_id}:{iid}:{note_id}` |
**Key format rationale:** We use numeric `project_id` instead of project path because paths can change (renames, transfers). Numeric IDs are immutable.
### Data Source
All events come from `lore --robot me`, specifically the `since_last_check` section which tracks new activity since the cursor was last advanced.
**This is key:** We don't track resolution status or complex thread state. We use `since_last_check` — "someone said something to you" = create bead. You close it when handled.
### Item Lifecycle States
Each mapped item has a lifecycle state:
```
┌─────────────────────────────────────┐
│ │
▼ │
┌────────┐ ┌────────┐ ┌─────────────┐ ┌──────┴───┐
│ (new) │────▶│ active │────▶│ suspect_ │────▶│ closed │
│ event │ │ │ │ orphan │ │ │
└────────┘ └────────┘ └─────────────┘ └──────────┘
│ ▲
│ (user closes bead) │
└─────────────────────────────────────┘
```
| State | Meaning |
|-------|---------|
| `active` | Bead exists, GitLab item is open |
| `suspect_orphan` | Missing from lore for 1 reconciliation cycle |
| `closed` | Bead closed (by user OR auto-close), entry removed from map |
### Two-Strike Close Rule
**Problem:** What if GitLab's API hiccups and temporarily says "you have no reviews"? Without protection, we'd delete all your tasks.
**Solution:** Items must be missing for TWO consecutive reconciliations before auto-close.
| Check #1 | Check #2 | Result |
|----------|----------|--------|
| Missing | Missing | Close the task (confirmed gone) |
| Missing | Found | Keep it (was just a glitch) |
| Found | — | Keep it (still active) |
This prevents false closes from transient API failures or partial fetches.
### Mapping File Schema
```
~/.local/share/mc/gitlab_bead_map.json
```
```json
{
"schema_version": 1,
"cursor": {
"last_check_timestamp": "2026-02-25T10:30:00Z",
"last_reconciliation": "2026-02-25T06:00:00Z"
},
"mappings": {
"mr_review:gitlab.com:12345:847": {
"bead_id": "br-x7f",
"created_at": "2026-02-23T14:00:00Z",
"suspect_orphan": false,
"pending": false
},
"issue:gitlab.com:12345:312": {
"bead_id": "br-c9d",
"created_at": "2026-02-24T09:00:00Z",
"suspect_orphan": true,
"pending": false
},
"mr_authored:gitlab.com:12345:903": {
"bead_id": null,
"created_at": "2026-02-25T10:29:00Z",
"suspect_orphan": false,
"pending": true
}
}
}
```
**Field meanings:**
- `bead_id`: The beads task ID, or `null` if creation was interrupted
- `suspect_orphan`: `true` if item was missing in last reconciliation (first strike)
- `pending`: `true` if bead creation is in-flight (crash recovery will retry)
### Bridge Flow
```
lore.db changes (file watcher on mtime)
MC calls: lore --robot me
MC iterates: since_last_check events
├── Event key in mapping? → Skip (already have bead)
└── Event key NOT in mapping? → Create bead via br CLI
Store mapping: {key} → {bead_id, created_at, suspect_orphan: false}
```
### Reconciliation
**Problem:** `since_last_check` is incremental. If MC is offline, clock skews, or lore's cursor gets stale, events can be missed.
**Solution:** Periodic full reconciliation pass.
| Trigger | Action |
|---------|--------|
| App startup | Full reconciliation |
| Every 6 hours | Full reconciliation |
| `since_last_check` empty but items exist | Full reconciliation |
**Full Reconciliation Algorithm:**
1. Fetch all open items from `lore --robot me --issues` and `lore --robot me --mrs`
2. Build set of expected keys from lore response
3. For each key in map:
- If key in expected AND `suspect_orphan=true` → clear flag
- If key NOT in expected AND `suspect_orphan=false` → set `suspect_orphan=true`
- If key NOT in expected AND `suspect_orphan=true` → close bead, remove from map
4. For each key in expected:
- If key NOT in map → create bead, add to map
### Cursor Semantics
| Operation | Cursor Update |
|-----------|---------------|
| Successful incremental sync | Advance `last_check_timestamp` |
| Successful full reconciliation | Advance `last_reconciliation` |
| Partial/failed sync | **Do not advance** (retry will reprocess) |
**Recovery:** If cursor appears stale (`since_last_check` empty but open items exist), trigger full reconciliation and reset cursor.
### Crash-Safe Operation Ordering
**Problem:** If MC crashes mid-sync, we risk duplicates (bead created but not mapped) or lost events (cursor advanced but bead not created).
**Solution:** Write-ahead pattern with idempotent operations.
**For each new event:**
```
1. Check if key exists in mapping → if yes, skip (idempotent)
2. Write mapping entry FIRST: {key} → {bead_id: null, pending: true}
3. Create bead via `br create`
4. Update mapping: {bead_id: actual_id, pending: false}
5. On success of all events: advance cursor
```
**Crash recovery (on startup):**
```
1. Scan mapping for entries with pending: true
2. For each pending entry:
- If bead_id is null → retry `br create`, update mapping
- If bead_id exists but pending → verify bead exists, clear pending flag
3. Do NOT advance cursor until all pending entries resolved
```
**Why this works:**
- Step 1 is idempotent (duplicate events skip)
- Step 2 happens before bead creation (we know we intend to create)
- Step 5 only advances cursor after ALL events processed
- Recovery finds incomplete work and finishes it
### Bridge Invariants
These must ALWAYS hold. Violations are bugs.
| ID | Invariant |
|----|-----------|
| INV-1 | **No duplicate beads.** Each mapping key maps to exactly one bead ID. |
| INV-2 | **No orphan beads.** Every bead ID in the map exists in beads. |
| INV-3 | **No false closes.** Items only auto-closed after missing in TWO reconciliations. |
| INV-4 | **Cursor monotonicity.** Cursor only advances forward, never backward. |
### Single-Instance Lock
MC enforces single-instance operation via an OS advisory lock:
```
~/.local/share/mc/mc.lock
```
**Implementation:** Use `flock(2)` (Unix) or equivalent OS advisory lock — NOT "file exists" semantics.
**Startup behavior:**
1. Open `mc.lock` (create if missing)
2. Attempt `flock(fd, LOCK_EX | LOCK_NB)` (non-blocking exclusive lock)
3. If `EWOULDBLOCK` → another instance holds lock → show error dialog, exit
4. If lock acquired → proceed, OS auto-releases on process exit/crash
**Why advisory lock over "file exists":**
- Automatically released on crash (no stale lockfiles)
- No cleanup needed on abnormal exit
- Race-free (OS handles atomicity)
**Rationale:** Atomic file writes protect against mid-write crashes, but not concurrent writers. Lock file prevents race conditions in bead creation.
### CLI Contract Testing
**Risk:** `lore`/`br`/`bv` robot output schema can drift, silently breaking MC.
**Mitigation:**
- Store fixture files of expected CLI outputs in `tests/fixtures/`
- Contract tests validate MC's parsing against real CLI outputs
- On CLI version bump, regenerate fixtures and verify parsing still works
- Rust types with `#[serde(deny_unknown_fields)]` to catch unexpected fields
### Bead Creation via br CLI
```rust
// MC shells out to br for all bead operations
fn create_bead(title: &str, bead_type: &str) -> Result<String> {
let output = Command::new("br")
.args(["create", "--title", title, "--type", bead_type, "--json"])
.output()?;
let result: BrCreateResult = serde_json::from_slice(&output.stdout)?;
Ok(result.id)
}
fn close_bead(id: &str, reason: &str) -> Result<()> {
Command::new("br")
.args(["close", id, "--reason", reason])
.output()?;
Ok(())
}
```
### Auto-Close on GitLab State Change
When GitLab state changes (MR merged, issue closed), the corresponding bead closes via the two-strike rule:
| GitLab State Change | Detection | Bead Action |
|---------------------|-----------|-------------|
| MR merged | Item disappears from open list | Two-strike close with reason "MR merged in GitLab" |
| MR closed | Item disappears from open list | Two-strike close with reason "MR closed in GitLab" |
| Issue closed | Item disappears from open list | Two-strike close with reason "Issue closed in GitLab" |
### Error Handling
| Error | Handling |
|-------|----------|
| `lore` CLI unavailable | Log error, skip sync, retry next cycle |
| `br create` fails | Log error, do NOT add to map (will retry next sync) |
| `br close` fails | Log error, keep in map as suspect_orphan (will retry) |
| JSON parse error | Log error, skip that event, continue processing others |
| Map file corrupted | Load backup, log warning, trigger full reconciliation |
**Backup Strategy:** Before each write to `gitlab_bead_map.json`, copy current file to `.bak`, write to `.tmp`, then atomic rename.
---
## Views & UX
### Window Behavior: B+D Hybrid
1. **Menu bar icon** with badge count (always present)
2. **Popover** for quick glance (click menu bar icon)
3. **Full window** for deep work (hotkey or button in popover)
4. **Optional floating widget** (toggle in settings) for constant visibility
```
Menu Bar: [other items] 🔴3 MC │ 🔋 ...
click ───────┘
┌─────────────────────────────┐
│ THE ONE THING │
│ Review MR !847 │
│ 2d waiting · @sarah │
│ │
│ [Start] [Defer] [Skip] │
├─────────────────────────────┤
│ Queue: 4 Inbox: 3 │
│ ⌘⇧F Full window │
└─────────────────────────────┘
```
### View 1: Focus View (Home)
The default. Shows THE ONE THING.
```
┌─────────────────────────────────────────────────────────────┐
│ │
│ ┌───────────────────┐ │
│ │ 🔴 MR REVIEW │ │
│ └───────────────────┘ │
│ │
│ Fix authentication token refresh logic │
│ ───────────────────────────────────── │
│ │
│ !847 in platform/core • 47 lines changed │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ @sarah requested 2 days ago │ │
│ │ "Can you take a look? I need this for the │ │
│ │ release tomorrow" │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────┐ │
│ │ Start │ │ 1 hour │ │ Tomorrow │ │ Skip │ │
│ │ ↵ │ │ ⌘1 │ │ ⌘2 │ │ ⌘S │ │
│ └──────────┘ └──────────┘ └──────────┘ └───────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ Queue: 3 more reviews • 2 assigned issues • 5 mentions │
└─────────────────────────────────────────────────────────────┘
```
**Behavior:**
- "Start" opens GitLab in browser
- Defer options: 1 hour, tomorrow, custom
- Skip removes from today's list (logged with reason)
- Keyboard-driven: Enter to start, numbers for defer, S to skip
### View 2: Queue View
All pending work, organized by type.
```
┌─────────────────────────────────────────────────────────────┐
│ Queue ⌘K filter │
├─────────────────────────────────────────────────────────────┤
│ │
│ REVIEWS (4) [Batch All · 25min] │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔴 !847 Fix auth token refresh 2d @sarah │ │
│ │ 🟡 !902 Add rate limiting middleware 1d @mike │ │
│ │ 🟢 !915 Update README badges 4h @alex │ │
│ │ 🟢 !918 Typo fix in error messages 2h @bot │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ASSIGNED ISSUES (2) │
│ BEADS (3) │
│ MANUAL TASKS (1) │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Behavior:**
- Items colored by staleness (fresh=green, aging=amber, stale=red)
- Click to make it THE ONE THING
- Drag to reorder (manual priority)
- "Batch All" enters batch mode
### View 3: Timeline View
Chronological activity stream.
### View 4: Batch Mode
Full-screen focus for clearing similar items rapidly.
```
┌─────────────────────────────────────────────────────────────┐
│ BATCH: CODE REVIEWS │
│ 1 of 4 · 25 min │
│ ━━━━━━━━━━░░░░░░░░░░ │
├─────────────────────────────────────────────────────────────┤
│ │
│ Fix authentication token refresh logic │
│ !847 in platform/core │
│ │
│ 47 lines changed across 3 files │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────┐ │
│ │ Open in GL │ │ Done │ │ Skip │ │
│ │ ⌘O │ │ ⌘D │ │ ⌘S │ │
│ └───────────────┘ └───────────────┘ └───────────┘ │
│ │
│ ESC to exit batch │
└─────────────────────────────────────────────────────────────┘
```
### View 5: Inbox View
New stuff requiring triage.
### View 6: Quick Capture (Overlay)
Global hotkey (⌘⇧C) summons this from anywhere:
```
┌────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ │
│ │ Quick thought... │ │
│ │ │ │
│ │ Need to check if webhook │ │
│ │ retry uses exponential backoff │ │
│ └────────────────────────────────┘ │
│ │
│ ⏎ Save & close ESC Cancel │
│ │
└────────────────────────────────────────┘
```
Creates a bead immediately. You triage later.
---
## Priority & Decision System
### Philosophy: Manual-First, Learn from Data
We do NOT know the right prioritization algorithm yet. Instead:
1. **You manually set THE ONE THING**
2. **MC logs every decision with context and reasoning**
3. **Post-process logs to extract patterns**
4. **Eventually codify patterns into suggestions**
### Decision Log
```
~/.local/share/mc/decision_log.jsonl
```
Every action gets logged:
```json
{
"timestamp": "2026-02-25T10:30:00Z",
"action": "set_focus",
"bead_id": "br-x7f",
"reason": "Sarah pinged me on Slack, she's blocked",
"tags": ["blocking", "urgent"],
"context": {
"previous_focus": "br-a3b",
"queue_size": 12,
"time_of_day": "morning",
"day_of_week": "Tuesday",
"available_items": ["br-x7f", "br-a3b", "br-c9d"],
"item_ages_days": {"br-x7f": 2, "br-a3b": 5, "br-c9d": 1},
"items_completed_today": 3,
"focus_session_duration_min": 45
}
}
```
### Actions to Log
| Action | What to Capture |
|--------|-----------------|
| `set_focus` | Which bead, why, what else was available |
| `reorder` | Old order, new order, why |
| `defer` | Which bead, duration, why |
| `snooze` | Which bead, until when, why |
| `skip` | Which bead, why (explicitly chose not to do it) |
| `complete` | Which bead, duration if tracked, notes |
| `create_manual` | New bead from quick capture |
| `change_priority` | Old priority, new priority, why |
### "Why" Capture UX
Every significant action prompts for optional reason:
```
┌─────────────────────────────────────────────────────────────┐
│ Setting focus to: Review MR !847 │
│ │
│ Why? (optional, helps learn your patterns) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Sarah pinged me, she's blocked on release │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Quick tags: [Blocking] [Urgent] [Context switch] [Energy] │
│ │
│ [Confirm] [Skip reason] │
└─────────────────────────────────────────────────────────────┘
```
**Quick tags** = one-click common reasons
**Skip reason** = don't force it, but make it easy
### Context Snapshot
Each log entry captures decision context:
```rust
struct DecisionContext {
queue_snapshot: Vec<QueueItem>, // All items at decision time
time_of_day: TimeOfDay, // morning/afternoon/evening
day_of_week: Weekday,
focus_session_duration: Option<Duration>,
items_completed_today: u32,
last_sync_age: Duration,
}
```
### Log Retention & Privacy (Post-v1)
The decision log can grow indefinitely. For v1, we accept unbounded growth since:
- Single user, local storage only
- Log entries are small (~500 bytes each)
- 100 decisions/day × 365 days × 500 bytes = ~18 MB/year
**Post-v1 retention policy (when needed):**
| Policy | Implementation |
|--------|----------------|
| Size cap | Rotate when log exceeds 50 MB (keep last N entries) |
| Time-based | Optional: prune entries older than 1 year |
| Export before prune | Always export to dated backup before any deletion |
**Privacy considerations (post-v1):**
- Reasons may contain sensitive context (names, project details)
- Add optional redaction: strip free-text reasons, keep only tags
- Add export-with-redaction for sharing anonymized patterns
### Future: Pattern Extraction
Once we have enough data:
1. Cluster reasons → discover priority categories
2. Correlate context → time-of-day patterns, staleness thresholds
3. Train simple model → suggest priority, you confirm/override
4. Eventually: auto-prioritize with confidence scores
---
## Implementation Phases
**TDD Rule:** Every task below follows RED → GREEN. Write failing test first, then implement.
### Phase 0: Test Infrastructure (0.5 day)
- [ ] Configure Vitest for frontend unit/component tests
- [ ] Configure Playwright for E2E tests
- [ ] Set up Rust test harness with trait-based mocking
- [ ] Create fixture directory structure
- [ ] Capture initial CLI fixtures from real `lore`/`br` outputs
- [ ] Add test commands to `package.json` and `Cargo.toml`
### Phase 1: Foundation (2-3 days)
- [ ] Scaffold Tauri + Vite + React project
- [ ] Basic window with system tray icon
- [ ] Global hotkey to toggle window (⌘⇧M)
- [ ] **RED:** Write `lore.rs` parsing tests against fixtures
- [ ] **GREEN:** Implement lore CLI wrapper, parse `lore --robot me`
- [ ] **RED:** Write file watcher tests (mock filesystem events)
- [ ] **GREEN:** File watcher on lore.db to trigger refresh
- [ ] Display raw data in UI (no test needed — visual verification)
### Phase 2: Bridge + Data Layer (2-3 days)
- [ ] **RED:** Write state machine transition tests (all 6 scenarios from Testing Architecture)
- [ ] **RED:** Write invariant assertion helper
- [ ] **GREEN:** Implement GitLab → Beads bridge state machine
- [ ] **RED:** Write mapping file read/write tests (atomic writes, schema validation)
- [ ] **GREEN:** Create mapping file structure with atomic writes
- [ ] **RED:** Write `br create`/`br close` wrapper tests against fixtures
- [ ] **GREEN:** Bead creation via `br` CLI
- [ ] **RED:** Write two-strike auto-close tests
- [ ] **GREEN:** Auto-close beads on GitLab state change
- [ ] **RED:** Write reconciliation tests (startup, periodic, cursor recovery)
- [ ] **GREEN:** Full reconciliation pass (startup + periodic)
- [ ] **RED:** Write crash recovery tests (all 3 scenarios)
- [ ] **GREEN:** Implement write-ahead pattern with pending flag
- [ ] **RED:** Write single-instance lock tests
- [ ] **GREEN:** Implement flock-based single-instance lock
- [ ] **RED:** Write decision log append/read tests
- [ ] **GREEN:** Decision log infrastructure
- [ ] Verify all invariants pass after each test
### Phase 3: Focus View (1-2 days)
- [ ] **RED:** Write FocusCard component tests (render, Start/Defer/Skip, keyboard)
- [ ] **GREEN:** THE ONE THING card component
- [ ] **RED:** Write focus selection state tests
- [ ] **GREEN:** Manual focus selection
- [ ] **RED:** Write action tests (open URL, defer timing, skip logging)
- [ ] **GREEN:** Basic actions (open in browser, defer, skip)
- [ ] **RED:** Write ReasonPrompt tests (text capture, tags, submit/cancel)
- [ ] **GREEN:** Decision logging with reason capture
- [ ] **GREEN:** Quick tags for common reasons
### Phase 4: Queue + Inbox (2-3 days)
- [ ] **RED:** Write QueueList tests (render sections, staleness colors)
- [ ] **GREEN:** Queue list with sections
- [ ] **RED:** Write drag-reorder tests (order persisted, decision logged)
- [ ] **GREEN:** Drag to reorder (manual priority)
- [ ] **RED:** Write click-to-focus tests
- [ ] **GREEN:** Click to set as focus
- [ ] **RED:** Write Inbox triage tests
- [ ] **GREEN:** Inbox with triage actions
- [ ] **RED:** Write filter/search tests
- [ ] **GREEN:** Filter/search (⌘K)
### Phase 5: Batch Mode (1-2 days)
- [ ] **RED:** Write BatchMode tests (progress, cycling, completion)
- [ ] **GREEN:** Full-screen batch interface
- [ ] **GREEN:** Progress tracking
- [ ] **GREEN:** Rapid completion flow
- [ ] **GREEN:** Completion celebration
### Phase 6: Quick Capture (1 day)
- [ ] **RED:** Write QuickCapture tests (overlay, text input, bead creation)
- [ ] **GREEN:** Global hotkey overlay (⌘⇧C)
- [ ] **GREEN:** Instant bead creation
- [ ] **GREEN:** Appears over other apps
- [ ] **E2E:** Write quick-capture E2E test
### Phase 7: Polish + E2E (ongoing)
- [ ] Animations (Framer Motion) — visual verification
- [ ] **RED:** Write staleness color tests
- [ ] **GREEN:** Staleness visualization (color decay)
- [ ] Badge counts in menu bar — visual verification
- [ ] Settings UI — visual verification
- [ ] Floating widget (optional)
- [ ] **E2E:** Write focus-flow E2E test
- [ ] **E2E:** Write batch-mode E2E test
- [ ] **E2E:** Write sync-status E2E test
- [ ] Verify 90% Rust bridge coverage, 85% hooks coverage, 70% component coverage
---
## Post-v1: Deferred Features
### Timeline View (deferred)
- [ ] Chronological activity view
- [ ] Smart grouping (today, yesterday, older)
- [ ] Expand/collapse
**Rationale:** Timeline is "nice to have" but not core to the "ONE THING" thesis. Defer until trust metrics on bridge reliability are solid.
### Future Intelligence (deferred)
Once decision log has sufficient data:
- [ ] Cluster reasons → discover priority categories
- [ ] Correlate context → time-of-day patterns, staleness thresholds
- [ ] Train simple model → suggest priority, user confirms/overrides
---
## Acceptance Criteria
### AC-001: Bridge Creates Beads from GitLab Events
**Given** lore has synced new GitLab activity
**When** MC detects lore.db change and processes `since_last_check`
**Then** beads are created for: MR reviews requested, issues assigned, mentions, comments on authored MRs
**And** mapping file is updated with `{event_key} → {bead_id}`
**And** duplicate events do not create duplicate beads
### AC-002: Bridge Auto-Closes Beads (Two-Strike Rule)
**Given** a bead exists for a GitLab MR or issue
**When** that item is missing from lore for TWO consecutive reconciliations
**Then** the corresponding bead is closed via `br close`
**And** reason includes GitLab state change (e.g., "MR merged in GitLab")
**Note:** The two-strike rule prevents false closes from transient API failures. First miss sets `suspect_orphan=true`, second miss triggers close.
### AC-002b: Reconciliation Heals Missed Events
**Given** MC was offline or `since_last_check` cursor is stale
**When** MC starts up or 6-hour reconciliation timer fires
**Then** full reconciliation runs against all open items from lore
**And** missing beads are created for items not in mapping
**And** items missing from lore are marked `suspect_orphan` (first strike)
**And** items already marked `suspect_orphan` that are still missing are closed (second strike)
### AC-003: Focus View Shows THE ONE THING
**Given** user has set a focus item
**When** viewing Focus View
**Then** that single item is prominently displayed
**And** actions (Start, Defer, Skip) are available
**And** keyboard shortcuts work
### AC-004: Manual Priority via Drag Reorder
**Given** user is in Queue View
**When** user drags an item to new position
**Then** order is persisted
**And** decision is logged with context
**And** user is prompted for optional reason
### AC-005: Decision Logging Captures Context
**Given** user performs any significant action (set_focus, reorder, defer, skip, complete)
**When** action is executed
**Then** decision_log.jsonl is appended with: timestamp, action, bead_id, reason (if provided), tags, full context snapshot
### AC-006: Quick Capture Creates Bead
**Given** user presses global hotkey (⌘⇧C) from any app
**When** user types text and presses Enter
**Then** bead is created via `br create`
**And** overlay dismisses
**And** user returns to previous context
### AC-007: Menu Bar Badge Shows Counts
**Given** MC is running
**When** there are pending items
**Then** menu bar icon shows badge with count
**And** clicking icon opens popover
**And** popover shows THE ONE THING and queue summary
### AC-008: Batch Mode Enables Flow State
**Given** user has multiple items of same type (e.g., reviews)
**When** user enters Batch Mode
**Then** items are presented one at a time
**And** progress bar shows completion
**And** Done/Skip advances to next
**And** ESC exits batch
### AC-009: Sync Status Visible
**Given** lore cron syncs periodically
**When** viewing any MC screen
**Then** sync status is visible (last sync time, success/failure)
**And** errors are surfaced with actionable info
### AC-010: Staleness Visualization
**Given** items have different ages
**When** viewing Queue or Focus
**Then** fresh items appear bright/green
**And** 1-2 day items appear normal
**And** 3-6 day items appear amber
**And** 7+ day items appear red/pulsing
---
## File Structure
```
mission-control/
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── main.rs
│ │ ├── commands/ # Tauri command handlers
│ │ │ ├── mod.rs
│ │ │ ├── work_items.rs
│ │ │ ├── actions.rs
│ │ │ ├── capture.rs
│ │ │ └── decisions.rs
│ │ ├── bridge/ # GitLab → Beads bridge
│ │ │ ├── mod.rs
│ │ │ ├── sync.rs
│ │ │ └── mapping.rs
│ │ ├── data/ # Data layer
│ │ │ ├── mod.rs
│ │ │ ├── lore.rs # Shell to lore CLI
│ │ │ ├── beads.rs # Shell to br CLI
│ │ │ └── state.rs # MC-local state
│ │ ├── logging/ # Decision logging
│ │ │ ├── mod.rs
│ │ │ └── decision_log.rs
│ │ └── lib.rs
│ ├── tests/ # Rust integration tests
│ │ ├── bridge_test.rs
│ │ ├── mapping_test.rs
│ │ ├── crash_recovery_test.rs
│ │ └── fixtures/ # CLI output fixtures
│ │ ├── lore/
│ │ │ ├── me_empty.json
│ │ │ ├── me_with_reviews.json
│ │ │ └── me_stale_cursor.json
│ │ └── br/
│ │ ├── create_success.json
│ │ └── create_error.json
│ ├── Cargo.toml
│ └── tauri.conf.json
├── src/ # React frontend
│ ├── components/
│ │ ├── ui/ # shadcn components
│ │ ├── FocusCard.tsx
│ │ ├── QueueList.tsx
│ │ ├── Timeline.tsx
│ │ ├── BatchMode.tsx
│ │ ├── Inbox.tsx
│ │ ├── QuickCapture.tsx
│ │ └── ReasonPrompt.tsx
│ ├── views/
│ │ ├── FocusView.tsx
│ │ ├── QueueView.tsx
│ │ ├── TimelineView.tsx
│ │ └── InboxView.tsx
│ ├── hooks/
│ │ ├── useWorkItems.ts
│ │ ├── useTauriEvents.ts
│ │ ├── useKeyboard.ts
│ │ └── useDecisionLog.ts
│ ├── store/
│ │ └── index.ts # Zustand store
│ ├── lib/
│ │ ├── tauri.ts # Tauri invoke wrappers
│ │ └── utils.ts
│ ├── App.tsx
│ └── main.tsx
├── tests/ # Frontend tests
│ ├── unit/ # Vitest unit tests
│ │ ├── store.test.ts
│ │ └── utils.test.ts
│ ├── components/ # Component tests
│ │ ├── FocusCard.test.tsx
│ │ ├── QueueList.test.tsx
│ │ └── ReasonPrompt.test.tsx
│ └── e2e/ # Playwright E2E
│ ├── focus-flow.spec.ts
│ ├── batch-mode.spec.ts
│ └── quick-capture.spec.ts
├── scripts/
│ └── regenerate-fixtures.sh # Capture real CLI outputs
├── package.json
├── tailwind.config.js
├── vite.config.ts
├── vitest.config.ts # Vitest configuration
├── playwright.config.ts # Playwright configuration
├── PLAN.md # This document
└── README.md
```
---
## Local State Files
```
~/.local/share/mc/
├── gitlab_bead_map.json # {event_key} → {bead_id}
├── decision_log.jsonl # Append-only decision log
├── state.json # Current focus, queue order, UI state
└── settings.json # User preferences
```
### State File Reliability
**Atomic Writes:** All JSON state files use write-to-temp + rename pattern to prevent corruption on crash.
```rust
fn write_state_atomic(path: &Path, data: &impl Serialize) -> Result<()> {
let tmp = path.with_extension("json.tmp");
let file = File::create(&tmp)?;
serde_json::to_writer_pretty(file, data)?;
fs::rename(&tmp, path)?; // Atomic on POSIX
Ok(())
}
```
**Crash Recovery:** On startup, check for `.json.tmp` files — if found, previous write was interrupted. Delete tmp and use existing `.json` (last known good state).
**Schema Versioning:** Each JSON file includes a `"schema_version": 1` field. On load, migrate if version < current.
---
## Testing Architecture
### TDD Philosophy
Every feature is implemented RED → GREEN:
1. **RED:** Write failing test that specifies behavior
2. **GREEN:** Write minimal code to pass
3. **REFACTOR:** Clean up while tests stay green
Tests are written BEFORE implementation. If you can't write the test first, the spec isn't clear enough.
### Test Directory Structure
```
mission-control/
├── src-tauri/
│ ├── src/
│ │ └── ...
│ └── tests/ # Rust integration tests
│ ├── bridge_test.rs # Bridge state machine tests
│ ├── mapping_test.rs # Mapping file operations
│ ├── crash_recovery_test.rs
│ └── fixtures/ # CLI output fixtures
│ ├── lore/
│ │ ├── me_empty.json
│ │ ├── me_with_reviews.json
│ │ ├── me_with_issues.json
│ │ └── me_mixed.json
│ └── br/
│ ├── create_success.json
│ ├── create_error.json
│ ├── close_success.json
│ └── list.json
├── src/
│ └── ...
└── tests/ # Frontend tests
├── unit/ # Vitest unit tests
│ ├── store.test.ts
│ ├── utils.test.ts
│ └── hooks/
│ ├── useWorkItems.test.ts
│ └── useDecisionLog.test.ts
├── components/ # React component tests
│ ├── FocusCard.test.tsx
│ ├── QueueList.test.tsx
│ └── ReasonPrompt.test.tsx
└── e2e/ # Playwright E2E tests
├── focus-flow.spec.ts
├── batch-mode.spec.ts
└── quick-capture.spec.ts
```
### Mocking Strategy
**Rust backend — trait-based mocking:**
```rust
// Define traits for external dependencies
trait LoreCli {
fn me(&self) -> Result<LoreMeResponse>;
fn me_issues(&self) -> Result<Vec<LoreIssue>>;
fn me_mrs(&self) -> Result<Vec<LoreMr>>;
}
trait BeadsCli {
fn create(&self, title: &str, bead_type: &str) -> Result<String>;
fn close(&self, id: &str, reason: &str) -> Result<()>;
fn exists(&self, id: &str) -> Result<bool>;
}
// Production implementation shells out to CLI
struct RealLoreCli;
impl LoreCli for RealLoreCli {
fn me(&self) -> Result<LoreMeResponse> {
let output = Command::new("lore").args(["--robot", "me"]).output()?;
// ...
}
}
// Test implementation returns fixtures
struct MockLoreCli {
responses: HashMap<&'static str, String>,
}
impl LoreCli for MockLoreCli {
fn me(&self) -> Result<LoreMeResponse> {
let json = self.responses.get("me").unwrap();
Ok(serde_json::from_str(json)?)
}
}
```
**Frontend — MSW for Tauri IPC mocking:**
```typescript
// Mock Tauri invoke calls
import { mockIPC } from '@tauri-apps/api/mocks';
beforeEach(() => {
mockIPC((cmd, args) => {
if (cmd === 'get_work_items') {
return fixtures.workItems;
}
if (cmd === 'set_focus') {
return { ok: true };
}
});
});
```
### Fixture Strategy
**CLI Output Fixtures** — Real outputs captured from actual CLIs, stored as JSON files:
| Fixture | Contents | Used By |
|---------|----------|---------|
| `lore/me_empty.json` | Empty `since_last_check`, no open items | Cursor recovery test |
| `lore/me_with_reviews.json` | 3 MR reviews in `since_last_check` | New event processing test |
| `lore/me_stale_cursor.json` | Empty `since_last_check` but has open items | Reconciliation trigger test |
| `br/create_success.json` | Successful bead creation response | Happy path tests |
| `br/create_error.json` | Error response (e.g., validation failure) | Error handling tests |
**Fixture Regeneration:**
```bash
# Capture real CLI outputs for fixtures
lore --robot me > tests/fixtures/lore/me_current.json
br create --title "Test" --type task --json > tests/fixtures/br/create_success.json
```
**Contract Validation:** On CI, compare fixture schema against current CLI version:
```rust
#[test]
fn lore_me_fixture_matches_schema() {
let fixture = include_str!("fixtures/lore/me_with_reviews.json");
let parsed: LoreMeResponse = serde_json::from_str(fixture)
.expect("Fixture should match current schema");
}
```
### State Machine Test Scenarios
Each state transition gets an explicit test. Write these RED first.
**Lifecycle Transitions:**
| Test Name | Initial State | Action | Expected State | Invariants Checked |
|-----------|---------------|--------|----------------|-------------------|
| `new_event_creates_active` | (no entry) | Process new MR review event | `active` | INV-1 (no duplicates) |
| `duplicate_event_skips` | `active` | Process same event again | `active` (unchanged) | INV-1 |
| `missing_once_sets_suspect` | `active` | Reconciliation, item missing | `suspect_orphan=true` | INV-3 |
| `missing_twice_closes` | `suspect_orphan=true` | Reconciliation, item still missing | Entry removed, bead closed | INV-3 |
| `reappears_clears_suspect` | `suspect_orphan=true` | Reconciliation, item reappears | `suspect_orphan=false` | — |
| `user_close_removes` | `active` | User closes bead manually | Entry removed | — |
**Crash Recovery Transitions:**
| Test Name | Initial State | Simulated Crash Point | Recovery Action | Expected |
|-----------|---------------|----------------------|-----------------|----------|
| `crash_before_br_create` | `pending=true, bead_id=null` | After map write, before br | Startup recovery | Retries `br create`, updates map |
| `crash_after_br_create` | `pending=true, bead_id="br-xxx"` | After br, before pending clear | Startup recovery | Verifies bead exists, clears pending |
| `crash_before_cursor_advance` | Multiple `pending=false` | After all beads, before cursor | Startup recovery | Re-processes events (idempotent), advances cursor |
### Invariant Tests
Automated verification of INV-1 through INV-4 after every operation:
```rust
fn assert_invariants(mapping: &Mapping, beads: &impl BeadsCli) -> Result<()> {
// INV-1: No duplicate beads
let bead_ids: Vec<_> = mapping.values().map(|e| &e.bead_id).collect();
let unique: HashSet<_> = bead_ids.iter().filter_map(|id| id.as_ref()).collect();
assert_eq!(bead_ids.len(), unique.len(), "INV-1 violated: duplicate bead IDs");
// INV-2: No orphan beads (every bead_id exists)
for entry in mapping.values() {
if let Some(id) = &entry.bead_id {
assert!(beads.exists(id)?, "INV-2 violated: bead {} not found", id);
}
}
// INV-3: No false closes (tested via state machine tests)
// INV-4: Cursor monotonicity (tested via cursor tests)
Ok(())
}
// Run after every test
#[test]
fn reconciliation_maintains_invariants() {
let mut bridge = setup_bridge_with_mock();
bridge.reconcile();
assert_invariants(&bridge.mapping, &bridge.beads).unwrap();
}
```
### Error Path Tests
Every error in the error handling table gets a test:
| Test Name | Error Simulated | Expected Behavior |
|-----------|-----------------|-------------------|
| `lore_cli_unavailable` | Mock returns `Err(CommandNotFound)` | Logs error, skips sync, no crash |
| `br_create_fails` | Mock returns error JSON | Logs error, entry NOT added to map |
| `br_close_fails` | Mock returns error | Logs error, keeps `suspect_orphan=true` |
| `json_parse_error` | Malformed fixture | Logs error, skips event, continues |
| `map_file_corrupted` | Invalid JSON in map file | Loads backup, triggers reconciliation |
| `lock_held` | Lock file already locked | Shows error dialog, exits cleanly |
### Frontend Test Strategy
**Unit Tests (Vitest):**
| Module | What to Test |
|--------|--------------|
| `store/index.ts` | State mutations, selectors, computed values |
| `lib/utils.ts` | Staleness calculation, time formatting |
| `hooks/useWorkItems.ts` | Data fetching, caching, error states |
**Component Tests (React Testing Library):**
| Component | Test Cases |
|-----------|------------|
| `FocusCard` | Renders item, handles Start/Defer/Skip, keyboard shortcuts |
| `QueueList` | Renders list, drag reorder, click to focus, staleness colors |
| `ReasonPrompt` | Shows prompt, captures text, handles quick tags, submit/cancel |
| `BatchMode` | Progress bar, item cycling, completion state |
**Example Component Test (RED first):**
```typescript
// tests/components/FocusCard.test.tsx
describe('FocusCard', () => {
it('calls onStart when Start button clicked', async () => {
const onStart = vi.fn();
render(<FocusCard item={mockItem} onStart={onStart} />);
await userEvent.click(screen.getByRole('button', { name: /start/i }));
expect(onStart).toHaveBeenCalledWith(mockItem.id);
});
it('calls onStart when Enter pressed', async () => {
const onStart = vi.fn();
render(<FocusCard item={mockItem} onStart={onStart} />);
await userEvent.keyboard('{Enter}');
expect(onStart).toHaveBeenCalledWith(mockItem.id);
});
it('shows amber color for 3-day-old items', () => {
const oldItem = { ...mockItem, createdAt: daysAgo(4) };
render(<FocusCard item={oldItem} />);
expect(screen.getByTestId('staleness-indicator')).toHaveClass('bg-amber-500');
});
});
```
### E2E Test Strategy (Playwright + Tauri)
Full app tests using `@tauri-apps/cli` test mode:
| Test | Flow |
|------|------|
| `focus-flow.spec.ts` | Launch app → See focus card → Click Start → Verify browser opens |
| `batch-mode.spec.ts` | Queue with 4 reviews → Enter batch → Complete all → See celebration |
| `quick-capture.spec.ts` | Global hotkey → Type text → Enter → Verify bead created |
| `sync-status.spec.ts` | Mock lore failure → Verify error shown → Mock success → Verify recovers |
**E2E Test Setup:**
```typescript
// tests/e2e/focus-flow.spec.ts
import { test, expect } from '@playwright/test';
import { spawn } from 'child_process';
test.beforeAll(async () => {
// Start Tauri app in test mode with mocked CLIs
process.env.MC_TEST_MODE = 'true';
process.env.MC_MOCK_LORE = 'fixtures/lore/me_with_reviews.json';
});
test('clicking Start opens GitLab URL', async ({ page }) => {
// Tauri exposes window at localhost:1420 in dev mode
await page.goto('http://localhost:1420');
// Wait for focus card to load
await expect(page.getByTestId('focus-card')).toBeVisible();
// Click start
const [newPage] = await Promise.all([
page.waitForEvent('popup'),
page.click('button:has-text("Start")'),
]);
// Verify GitLab URL opened
expect(newPage.url()).toContain('gitlab.com');
});
```
### Test Commands
```bash
# Rust tests
cargo test # All unit + integration tests
cargo test bridge # Bridge tests only
cargo test --test crash_recovery # Crash recovery integration tests
# Frontend tests
npm run test # Vitest unit + component tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
# E2E tests
npm run test:e2e # Playwright tests
npm run test:e2e -- --headed # With browser visible
# All tests (CI)
npm run test:all # Runs everything
```
### CI Test Matrix
```yaml
# .github/workflows/test.yml
jobs:
rust-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cargo test --all-features
frontend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test -- --coverage
- run: npm run lint
e2e-tests:
runs-on: macos-latest # Tauri E2E needs native
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run tauri build -- --debug
- run: npm run test:e2e
contract-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Install actual lore/br CLIs
- run: cargo install lore br
# Regenerate fixtures and compare
- run: ./scripts/regenerate-fixtures.sh
- run: git diff --exit-code tests/fixtures/
```
### Coverage Requirements
| Layer | Minimum Coverage | Focus Areas |
|-------|------------------|-------------|
| Rust bridge | 90% | State transitions, crash recovery, error paths |
| Rust data layer | 80% | CLI parsing, file I/O |
| Frontend hooks | 85% | Data fetching, state management |
| Frontend components | 70% | User interactions, edge cases |
| E2E | N/A (scenario coverage) | Critical user flows |
---
## Open Questions
1. **Floating widget details** — Size, position, what info to show?
2. **Notification behavior** — When to notify vs. just badge?
3. **Calendar integration** — Worth adding for energy/time awareness?
4. **Mobile surface** — Any desire for iOS widget in future?
5. **bv recommendations** — Show as separate "suggestions" section or inline hints?
---
## Changelog
| Date | Change |
|------|--------|
| 2026-02-25 | Initial plan document created |
| 2026-02-25 | Added reliability improvements: reconciliation pass, cursor semantics, CLI contract testing, atomic writes, schema versioning. Deferred Timeline to post-v1. |
| 2026-02-25 | Integrated Bridge State Machine spec: lifecycle states, two-strike close rule, stable mapping keys (project_id), cursor semantics, invariants, single-instance lock, error handling. |
| 2026-02-25 | Added crash-safe operation ordering (write-ahead pattern with pending flag). Fixed AC-002/AC-002b to match two-strike rule. Added decision log retention policy (post-v1). Clarified flock-based lock semantics. |
| 2026-02-25 | Added comprehensive Testing Architecture: TDD philosophy, test directory structure, mocking strategy (trait-based Rust, MSW frontend), fixture strategy, state machine test scenarios, invariant tests, error path tests, frontend test strategy, E2E test strategy, CI matrix, coverage requirements. Updated Implementation Phases with explicit RED→GREEN TDD tasks. |