docs(plans): dashboard overhaul Phase 5h design

Phase A inventor deliverable: 3 candidate shapes (Tiles + view switcher,
Project rows table, 3-pane workspace), recommendation = Tiles, and m's
chip-picker decisions captured in §7.

Scope locked for coder gate: Tiles default, Pinned ∪ active ∪ open-work
as 'current', 3 tabs (Tiles/Tasks/Events) — Activity deferred, Stale
folded into Quiet under Tiles.

No code touched. Coder shift only after head's go/no-go.
This commit is contained in:
mAi
2026-05-26 12:02:29 +02:00
parent 69d872f7d2
commit 3647472ce8

View File

@@ -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/<path>/`.
- **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 600900px, 3-col ≥ 900px.
- `web/dashboard_pin.go` (new) — `handleDashboardPin` POSTs `?id=<uuid>&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 <sha>` 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 600900px, 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.