diff --git a/docs/plans/dashboard-overhaul.md b/docs/plans/dashboard-overhaul.md new file mode 100644 index 0000000..d952531 --- /dev/null +++ b/docs/plans/dashboard-overhaul.md @@ -0,0 +1,325 @@ +# Dashboard overhaul — Phase 5h design + +**Status:** Phase A (design). No code touched. Awaiting m's go/no-go via head. +**Branch:** `mai/fuller/phase-5h-phase-a-design`. +**Author:** fuller (inventor), 2026-05-26. +**Source request, verbatim:** *"The Dashboard could really use an overhaul as well. Of course we should continue to use unified filters but the dashboard should allow all kinds of views and easily show a helpful overview over my current projects."* + +--- + +## §1 — Current state diagnosis + +`/dashboard` today is a 5-card stream renderer. The shape, with row counts derived from the live data: + +| Card | What it shows | Sort | Cap | +|---|---|---|---| +| Open tasks | Every open VTODO across all linked CalDAV lists, bucketed Overdue/Today/Tomorrow/Week/No-due | overdue→due asc→prio→summary | 30 | +| Events | Every VEVENT next 7d, grouped by day | start asc | 50 | +| Open issues | Every open Gitea issue across all linked repos | updated_at desc | 30 | +| Recent documents | Every dated `item_link` in the last 30d | event_date desc | 30 | +| Stale projects | mai-managed items with quiet repo + 0 open tasks + 0 open issues | stale_days desc | 20 | + +Filter strip: `tag`, `mgmt`, `has` chips reusing `TreeFilter`. 60s cache keyed by encoded filter. `?refresh=1` busts. Empty cards collapse to one-liners when no filter is active. Inline VTODO ✓/✎/× on the Tasks card. + +### What works (don't throw away) +- **Aggregation pipeline** (`internal/aggregate.Aggregator`) — solid, cached, fan-out workers. Any new shape should ride on the same Todos/Events/Issues/Docs primitives. +- **Filter parity** with `/`, `/timeline`, `/calendar`, `/graph`. The chip vocabulary is a real asset; m said keep it. +- **Cache shape** — 60s TTL keyed by filter is fine. Tasks-card writeback already invalidates correctly. +- **Inline VTODO writeback** is genuinely useful — one ✓ click clears a row without leaving the page. +- **Stale card** as a "consider archiving" candidate list. Tiny, surfaces a question that's otherwise hidden. + +### What doesn't fit m's request +1. **Task-centric, not project-centric.** m sees "every open task" / "every open issue" as a giant flat stream. He cannot scan the list and answer *"how is project X doing?"* without ctrl-F. He explicitly asked for a project overview. +2. **No notion of "current".** Every active project contributes equally. Of 47 active items, m's likely day-to-day focus is ~5-10 (the ones with recent activity + open work). The dashboard buries those signals. +3. **One view, no switcher.** m said *"all kinds of views"*. Today it's the 5-card stream and that's it. +4. **Pinning is underused.** Only 2 items are pinned (`dev`, `projax`) — pinning today has no visible payoff anywhere, so m doesn't bother. A project-centric dashboard would give pins a job. +5. **Stale card surfaces non-actionable noise.** Once m sees a project once and decides "not now, not archive", the row stays forever. No snooze, no dismiss. Becomes wallpaper. +6. **Per-project signals are scattered.** To see paliad's status m has to scan five separate cards for paliad rows. Should be one tile. + +### What's already settled (don't redesign) +- Unified filter chips (`?q`, `?tag`, `?mgmt`, `?has`, `?public`) — preserved verbatim. +- Server-rendered Go `html/template` + HTMX. No JS framework. +- Mobile + desktop both via the new sidebar (≥768px) / bottom-nav (≤767px) layout. +- No file uploads, no new schema columns without head sign-off, no CLI surface. + +--- + +## §2 — m's mental model: what "current projects" means + +Reading the live data (`mcp__projax__list_items`): + +- **47 active items**, of which 7 are root areas (`admin`, `dev`, `finances`, `health`, `home`, `social`, `work`, plus the synthetic `public` parent). +- **14 items are public** — `dev.fdbck`, `dev.flexsiebels`, `dev.goldi`, `dev.imagen`, `dev.mai`, `dev.mclone`, `dev.mvoice`, `dev.otto`, `dev.mcompete`, `dev.mlex`, `dev.upc-kommentar`, `dev.youpcorg`, `health.sports.manjin`, `work.paliad`. These are clearly the projects m considers *portfolio-worthy* — code projects he wants the world to see. +- **Only 2 items are pinned**: `dev` (root area) and `projax` (this very project). Pinning is structurally there but visually invisible, so it's not used. That's an opportunity, not a constraint — if pins surface clearly, m starts using them. +- **Activity signal** is reachable cheaply: every mai-managed item has a `gitea-repo` link → repo `updated_at` (already cached for the Stale card). Every linked CalDAV list has VTODO/VEVENT counts (already aggregated). A project is "alive" if any of: open VTODO, open issue, repo touched in last 14d, dated link in last 14d. + +So "**current projects**" isn't a fixed predicate — it's a small ordered subset. A workable definition for v1 is the union of: +- **Pinned** (m's explicit "this matters now" signal — promote pins to UI-visible), +- **Active recently** (repo activity OR open VTODO with due ≤ 14d OR dated link in last 14d), +- **At risk** (any overdue VTODO, or open issues with stale `updated_at`). + +Everything else — the 30+ projects that are technically `active` but not on m's plate this week — should be reachable but **not on the primary surface**. A "show all active" toggle handles them. + +Crucially: the *definition* of "current" is configurable. The chip strip already lets m narrow by tag / mgmt / has-links. The new dashboard adds an implicit "current" prefilter on top of that, with a chip to lift it. + +--- + +## §3 — Candidate shapes + +Three viable directions. For each: sketch, signals, view switcher behaviour, filter integration, tradeoffs, cost. + +### Candidate A — Project Tiles + view switcher + +**Sketch (desktop, ~1200px main column, 3-column tile grid):** + +``` +┌─ Dashboard ──────────────────────────────────────────────────────────┐ +│ [Tiles] [Tasks] [Events] [Activity] · tag▾ mgmt▾ has▾ · ↻ updated 2m │ +│ ◇ pinned + active ○ show all active │ +├──────────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ ★ paliad │ │ ★ projax │ │ ★ youpcorg │ │ +│ │ work · live │ │ dev · this │ │ dev · live │ │ +│ │ 4 open · 2! │ │ 7 open │ │ 1 open │ │ +│ │ • next call │ │ • 5h dash │ │ • 1600+ judg │ │ +│ │ 2d ago │ │ now │ │ 4d ago │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ flexsiebels │ │ otto │ │ mai │ │ +│ │ dev · live │ │ dev · live │ │ dev · live │ │ +│ │ 2 open │ │ 9 open · 3! │ │ 5 open │ │ +│ │ • home page │ │ • i64 deploy │ │ • #218 mAi │ │ +│ │ 1d ago │ │ 3h ago │ │ 6h ago │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ Quiet (12) ▾ Archived (3) ▾ │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +**Per-tile signals** (top→bottom): +- Title + pin star (click to toggle). Primary path under title (`work.paliad`). Live URL pill if `public_live_url` is set. +- **Counts row**: `N open` (open VTODOs across linked calendars), `M!` (overdue), optional `K issues` (open Gitea issues). Color: overdue red, otherwise default. Click navigates to `/i//`. +- **Top signal line**: the single most-relevant next thing — either the soonest-due open VTODO, or the most-recently-updated open issue, or last commit summary. One line, truncated. +- **Last-activity stamp**: `2d ago` / `3h ago` / `now`. Derived from max(repo.updated_at, latest_vtodo_modified, latest_dated_link). +- **Optional micro-bar** at the bottom — colored stripe showing the ratio overdue/today/week tasks. Skip for v1 if cluttered. + +**View switcher** (top tab strip, 4 views, same filter + scope chips apply across all): +- **Tiles** (default) — the grid above. Project-centric. +- **Tasks** — the existing Open-tasks card + Events card, full-width. Task-centric, today's view. Same inline ✓/✎/× writeback. +- **Events** — the existing Events card alone, full-width with bigger day headers. The "what's on my calendar" view. +- **Activity** — a chronological feed merging recent commits (repo `updated_at` per linked repo), closed issues, completed VTODOs, dated docs. "What just happened across my projects." This is genuinely new; deferable to v2 if scope-tight. + +**Scope chip** (next to filter chips, top-row): `◇ pinned + active` (default — the "current projects" prefilter) ↔ `○ show all active`. Single radio. Lifts the implicit "current" filter so m can browse the long tail. + +**Filter integration**: existing `tag` / `mgmt` / `has` / `public` chips narrow the project set across all four views. URL stays a single `/dashboard?view=tiles&tag=work&scope=current` so links bookmark cleanly. Default view (`tiles`) + default scope (`current`) elided from URL. + +**Tradeoffs**: +- ✅ Best match for m's explicit ask. "Helpful overview over my current projects" maps almost literally to the tile grid. +- ✅ Pinning gets a visible job. Stars on tiles make the feature legible. +- ✅ View switcher gives m the "all kinds of views" he asked for, without losing today's task-centric value (it's the `Tasks` tab). +- ✅ Per-tile signals tell a story at a glance. Scrolling 8 tiles is faster than scrolling 30 unrelated task rows. +- ❌ Tile signals require a per-project rollup the current `dashboardPayload` doesn't compute. Moderate aggregation work — group existing TodoRow/IssueRow/repo-updated by item.ID. +- ❌ The Activity view is the most novel and the highest risk; can be cut from v1 if needed (3 tabs is fine). +- ❌ Tiles trade information density for legibility. A power user wanting "everything everywhere" has to flip to Tasks view. + +**Implementation cost**: moderate. +- `dashboard.go` grows a per-project rollup (group existing rows by `Item.ID`, compute `LastActivity = max(repoUpdated, dueSoonest, eventSoonest, docLatest)`). +- New `dashboardProject` struct + tiles template partial. +- View-switcher = a URL `?view=` param routing into one of 4 template partials. Each partial re-uses the existing `*_section` templates wherever possible. +- Scope chip = one boolean URL param + a `IsCurrent(item, rollup, now)` predicate. +- Pin toggle = a POST handler that flips `Pinned` and re-renders. The detail page already has the field; surface it on the tile. + +### Candidate B — Project Rows + signal columns + +**Sketch (desktop, full-width table):** + +``` +┌─ Dashboard ──────────────────────────────────────────────────────────────┐ +│ [Compact] [Detailed] [Kanban] · tag▾ mgmt▾ has▾ · ◇ pinned + active │ +├──────────────────────────────────────────────────────────────────────────┤ +│ Project Path Open Over Issues Last Status │ +│ ─────────────────────────────────────────────────────────────────────────│ +│ ★ paliad work.paliad 4 2 3 2d ago live │ +│ ★ projax dev.projax 7 0 1 now dev │ +│ ★ youpcorg dev.youpcorg 1 0 2 4d ago live │ +│ ★ otto dev.otto 9 3 5 3h ago dev │ +│ mai dev.mai 5 1 8 6h ago dev │ +│ flexsiebels dev.flex… 2 0 1 1d ago live │ +│ fdbck dev.fdbck 0 0 0 12d ago live │ +│ ─────────────────────────────────────────────────────────────────────────│ +│ Quiet (12) ▾ Archived (3) ▾ │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +**Per-row signals**: pin star, title, primary path, open-task count, overdue count, open-issue count, last-activity relative, status pill. Each column sortable (column-header click flips sort, persisted in URL). + +**View switcher**: +- **Compact** (default) — the table above. +- **Detailed** — the current 5-card layout, preserved verbatim. "Take me back to the old dashboard." +- **Kanban-by-status** — three columns: `Active`, `Stale`, `At risk` (overdue or open-issue-heavy). Cards in each column = same per-row signals condensed. + +**Filter integration**: identical to Candidate A — chips narrow the project set across all three views. + +**Tradeoffs**: +- ✅ Highest information density. m sees ~25 projects without scrolling on a wide screen. +- ✅ Sortable columns are a "spreadsheet" superpower — *"show me everything ordered by overdue count desc"* is one click. +- ✅ Includes a "back to old layout" via the Detailed view — zero regret for muscle memory. +- ✅ Same per-project rollup work as Candidate A, so impl-cost largely overlaps. +- ❌ Tables feel less like a "dashboard" and more like a report. The visual texture is monotone — no big numbers, no color, no story per row. Less daily-driver, more weekly review. +- ❌ Mobile is uglier — a 6-column table degrades into a tall card list, losing the table's compactness advantage. +- ❌ Kanban view as proposed is shallow — three status columns is just a filter dimension dressed up. Not a real flow. + +**Implementation cost**: moderate, mostly overlapping Candidate A. Sortable headers add ~50 lines of JS (or server-side via URL `?sort=col,dir`). + +### Candidate C — Workspace-style 3-pane + +**Sketch (desktop, fixed 3-column layout):** + +``` +┌─ Dashboard · tag▾ mgmt▾ has▾ · ↻ updated 2m ago ────────────────────────┐ +│ ┌─ FOCUS ──────────┐ ┌─ TODAY ────────────┐ ┌─ ACTIVITY ───────────────┐│ +│ │ ★ paliad │ │ Overdue (2) │ │ otto · i64 deployed 3h ││ +│ │ 4 · 2! 2d │ │ • paliad/call 1d │ │ projax · 5h commit 4h ││ +│ │ ★ projax │ │ • otto/i65 2d │ │ mai · #218 closed 6h ││ +│ │ 7 · 0 now │ │ Today (5) │ │ youpcorg · scrape 1d ││ +│ │ ★ youpcorg │ │ • paliad/spec │ │ flex · #45 merged 1d ││ +│ │ 1 · 0 4d │ │ • projax/dash │ │ paliad · push 2d ││ +│ │ ★ otto │ │ ⋮ │ │ ⋮ ││ +│ │ 9 · 3! 3h │ │ Events (3) │ │ ││ +│ │ ───────── │ │ • 14:00 paliad-call│ │ ││ +│ │ Pinned (4) │ │ • 16:30 sport │ │ ││ +│ │ + Pin a project │ │ • Tue 09:00 standup│ │ ││ +│ └──────────────────┘ └────────────────────┘ └─────────────────────────┘│ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +**Three fixed panes** (each independently scrollable): +- **FOCUS** (left, ~25%) — pinned-only project tiles. Vertical strip. Star-toggle to pin/unpin. The "what matters this week" surface. Empty state has a "+ Pin a project" affordance. +- **TODAY** (middle, ~40%) — the existing Open-tasks + Events cards condensed, no per-project cards. Overdue → Today → Tomorrow → Events for today/tomorrow. +- **ACTIVITY** (right, ~35%) — chronological feed: recent commits + issue updates + completed VTODOs + dated docs, newest first. The "what happened recently" view. + +**View switcher**: none. The layout is the layout. Mobile collapses to a vertical stack (FOCUS → TODAY → ACTIVITY). + +**Filter integration**: chips narrow all three panes simultaneously (filter to `tag=work` and FOCUS shows only pinned work-projects, TODAY shows only work-tagged tasks, ACTIVITY shows only work-tagged events). + +**Tradeoffs**: +- ✅ Strongest "workspace" feel — closer to Linear / Notion than to a stream reader. Most "dashboard-y". +- ✅ FOCUS pane is the most direct expression of "show me my current projects" and forces pinning to do real work. +- ✅ No view-switcher means no decision fatigue — open it, see everything that matters in three panes. +- ❌ No view-switcher also means **no answer to "I want a different view"** — directly contradicts m's "all kinds of views" ask. +- ❌ Three-pane on desktop only. Mobile becomes a vertical scroll that's not meaningfully different from today's stack. The whole shape is desktop-first. +- ❌ Requires pinning to be useful, but pinning is currently unused. Bootstrap problem — m has to invest before he sees value. +- ❌ Highest implementation cost: three new layouts (pane shells + responsive collapse + per-pane templates) and the brand-new Activity feed. + +**Implementation cost**: large. Three new template surfaces, the Activity aggregator is new code, and the responsive mobile collapse needs care. + +--- + +## §4 — Recommendation: **Candidate A (Project Tiles + view switcher)** + +A best matches m's three signals in order: + +1. *"Helpful overview over my current projects"* → the Tiles grid is literally that. One tile per project, scannable, the most-relevant next thing surfaced per tile. +2. *"All kinds of views"* → the view switcher gives 3-4 different shapes (Tiles / Tasks / Events / Activity), all sharing the same filter + scope, all bookmarkable. C has no switcher and B's "Detailed" view is just nostalgia for today's layout. +3. *"Continue to use unified filters"* → same chip strip as today, extended with a `scope=current|all` chip that's the only addition. No filter vocabulary changes. + +B is the runner-up, and a strong runner-up — its sortable table is genuinely useful for weekly-review, and its "Detailed" tab is a comfortable escape hatch. But the *first impression* of a table-of-projects on the daily-driver surface is "report," not "dashboard," and m opened the request with the word *helpful*. Tiles feel more helpful at a glance. + +C has the best architectural shape (no mode-switching, everything visible) but flunks the "all kinds of views" ask outright, and depends on pinning that m hasn't adopted. The FOCUS pane idea is good enough that it shows up inside Candidate A's Tiles view as the "pinned-first sort" — we keep the strongest signal from C without paying its cost. + +**Recommended scope-cut for v1** (if cost is a worry): ship Tiles + Tasks + Events tabs only. Defer Activity to a follow-up phase. That's 3 tabs, the first of which is new and the other two are partial reuses of today's templates. + +--- + +## §5 — Implementation plan (if greenlit) + +### Files touched + +- `web/dashboard.go` + - Add `dashboardProject` rollup struct: `{Item, OpenTasks, Overdue, OpenIssues, LastActivity, NextSignal, IsLive}`. + - Add `collectProjectRollups(items, todos, events, issues, repos)` — groups the existing rows by `Item.ID`, computes `LastActivity = max(repo.updated_at, last_modified_vtodo, latest_dated_link, last_event)`, picks `NextSignal` (soonest-due VTODO summary, else latest issue title, else latest commit summary). + - Add `IsCurrent(rollup, now)` predicate: pinned OR (open tasks > 0) OR (open issues > 0) OR (LastActivity within 14d) OR (any overdue VTODO). + - Extend `handleDashboard` to read `?view=` (tiles | tasks | events | activity, default tiles) and `?scope=` (current | all, default current). Both elide from URL when default. + - Cache key extends to include `view` + `scope` so `/dashboard?view=tasks` is a separate cache entry — keeps the 60s TTL meaningful per shape. +- `web/templates/dashboard.tmpl` — top tab strip + scope chip + per-view partial dispatch. +- `web/templates/dashboard_tiles.tmpl` (new) — tile grid + "Quiet (N) ▾" collapsible footer. +- `web/templates/dashboard_section.tmpl` — retained as the `tasks` view's partial (the existing 5-card layout, minus Stale which moves under Quiet). +- `web/templates/dashboard_events.tmpl` (new, lightweight) — the existing Events card surface promoted to a full-tab view with bigger day headers. +- `web/static/style.css` — `.dash-tiles` grid (CSS grid, auto-fill, `minmax(280px, 1fr)`), `.tile` shell, `.tile-counts`, `.tile-signal`, `.tile-stamp`. Mobile breakpoint stacks tiles 1-column ≤ 600px, 2-col 600–900px, 3-col ≥ 900px. +- `web/dashboard_pin.go` (new) — `handleDashboardPin` POSTs `?id=&pin=true|false`, calls `Store.SetPin([id], bool)`, invalidates dashboard cache, re-renders. +- `store/store.go` — add `SetPin(ctx, ids []string, pinned bool) error` if not already there (mirrors `SetPublic`). + +Activity view (if v1): `web/dashboard_activity.go` — merges recent gitea commits (new: lift commit lister out of detail page), recent closed issues, recent completed VTODOs, recent dated docs. Sort by event_time desc. Cap 50. Cache piggybacks on dashboard's 60s. + +### Commit slicing + +1. **Rollup + tile data model**, no UI change. Adds `dashboardProject` + `collectProjectRollups` + tests against a fixture set of items+todos+issues. `view=tiles` not wired yet. +2. **Tiles tab + view switcher chrome**. `?view=tiles` (default), `?view=tasks` falls through to today's layout. Tab strip in `dashboard.tmpl`. CSS for tiles. +3. **Scope chip** (`?scope=current|all`) + `IsCurrent` predicate + "Quiet (N) ▾" expandable footer that lists collapsed projects. +4. **Pin toggle** on tile + `handleDashboardPin` + cache invalidation. +5. **Events tab** as its own URL — `?view=events` renders the events partial standalone. +6. (Optional, v2-mergeable) **Activity tab**: aggregator extension + template + tab entry. +7. **Mobile polish**: media-query breakpoints for tile grid, touch-friendly pin star, drawer fits. +8. **Design.md addendum**: a new "Dashboard overhaul (Phase 5h)" section documenting the final shape, the URL contract, and the rollup definition. + +Each commit on this branch is independently revertable. The Tasks tab (today's layout) survives the whole sequence — if m hates Tiles, switching to Tasks gets him back his familiar dashboard with zero data loss. + +### Test coverage + +- `dashboard_rollup_test.go` — fixture-driven: feed in items + synthetic todos/issues, assert `dashboardProject` fields. Cover edge cases: project with 0 links, project with multiple linked repos, overdue counting. +- `dashboard_view_test.go` — request `/dashboard?view=tiles`, `/dashboard?view=tasks`, `/dashboard?view=events`; assert the right template selected + the expected sections present. +- `dashboard_scope_test.go` — assert `scope=current` filters out the right items; `scope=all` includes them. +- `dashboard_pin_test.go` — POST flip → SetPin called → cache invalidated → re-render shows star state. +- Existing `dashboard_test.go`, `dashboard_events_test.go`, `dashboard_edit_test.go` keep passing unchanged — the Tasks tab is the same surface. + +### Deploy strategy + +Standard projax flow: push to `mai/fuller/phase-5h-*` worktree-branch as commits land, head merges to `main` per slice, Gitea webhook → Dockerfile → `https://projax.msbls.de/healthz` SHA check. The view-switcher default of `tiles` means the first deploy that lands flips m's daily surface — but the Tasks tab is one click away, so m has a fallback the moment he wants it. + +### Rollback if it doesn't feel right + +Three safety levers, in order of cheapness: +1. **Change the default tab**: flip `view` default from `tiles` to `tasks` in one line of `handleDashboard`. m sees today's dashboard, the Tiles view still exists for opt-in. +2. **Hide the tab strip**: comment out the tab-strip template block. View URL params still work but no UI to switch. +3. **Revert the merge commits**: each phase is a clean merge, so `git revert -m 1 ` per slice cleanly unwinds. + +A week of dogfood after Phase 5h ships is the natural soak. If m still likes today's dashboard better after 7 days, lever 1. + +--- + +## §6 — Open questions (the 4 worth chip-asking) + +These are decisions with 2-4 mutually-exclusive options. The remaining design choices have sensible defaults and can be settled in implementation chat. + +1. **Default tab on `/dashboard`** — Tiles (new) or Tasks (today's layout preserved)? +2. **"Current" definition** — pinned ∪ recently-active (recommended), or activity-only, or pinned-only, or pinned ∪ open-work? +3. **Activity tab in v1** — ship it now (4th tab), or defer to a follow-up phase (3 tabs at launch)? +4. **Stale card fate** — fold into Quiet section under Tiles, keep as its own card on the Tasks tab, or retire entirely (replaced by the LastActivity stamp on each tile)? + +--- + +## §7 — m's decisions (2026-05-26) + +Picks via `AskUserQuestion`, all four matching the inventor's recommendation: + +- **Q1 (Default tab): Tiles.** New project-tile grid is the landing surface. Tasks tab one click away. +- **Q2 (Current = ?): Pinned ∪ recently-active ∪ open-work.** Generous union — catches everything plausibly relevant without forcing m to pin first. Quiet long-tail collapses into a "Quiet (N) ▾" fold under the grid. +- **Q3 (Activity tab): Defer to v2.** Phase 5h ships 3 tabs (Tiles / Tasks / Events). Activity feed is the most novel piece and can land as a follow-up after Tiles has dogfood. +- **Q4 (Stale card): Fold into Quiet under Tiles.** Per-tile `LastActivity` stamp carries the staleness signal; the "consider archiving?" nudge migrates to the Quiet fold's framing. The standalone Stale card retires. + +No inventor↔m disagreements; no reasoning notes needed. All four picks tighten the recommended scope to "Tiles-first, 3 tabs, no separate Stale card" — a smaller phase than the doc's optional-Activity v1 sketch. + +### Scope-locked summary for the coder gate + +If head greenlights the coder shift, the brief is: + +1. Rollup data model + `IsCurrent(rollup, now)` = pinned OR (open tasks > 0) OR (open issues > 0) OR (LastActivity within 14d). +2. Tab strip on `/dashboard` with three tabs: **Tiles** (default), **Tasks** (today's 5-card layout minus Stale), **Events** (events card promoted to its own surface). +3. Tile grid with per-project signals + pin star + "Quiet (N) ▾" fold containing both `IsCurrent=false` projects AND the Stale candidates. +4. URL contract: `?view=tiles|tasks|events` (default `tiles`, elided), `?scope=current|all` (default `current`, elided). Existing chip vocabulary unchanged. +5. Cache key extends to `(filter, view, scope)`. Pin toggle invalidates dashboard cache. +6. Tests cover rollup math + view routing + scope predicate + pin flip. +7. Mobile: 1-col ≤ 600px, 2-col 600–900px, 3-col ≥ 900px tile grid; tab strip stays at top. +8. Design.md addendum documenting the final shape lands in the same branch. + +Activity tab + sortable-table view + multi-pane workspace are explicit non-goals for Phase 5h. Revisit after dogfood if Tiles proves the model.