Files
projax/web/templates/dashboard_tiles.tmpl
mAi 87132ee166 feat(dashboard): scope chip + Quiet (N) ▾ fold + Stale folded into Tiles
Phase 5h slice 3 — splits the Tiles rollup into ProjectsCurrent (primary
grid) and ProjectsQuiet (collapsible fold) per m's §7 'pinned ∪
recently-active ∪ open-work' rule.

URL contract extended:
  /dashboard                      — Tiles, scope=current (defaults elided)
  /dashboard?scope=all            — every active project in the grid
  /dashboard?scope=current        — same as default (chip allows explicit)

Scope chip lives next to the tab strip on Tiles only; Tasks + Events
tabs hide it (no scope concept there). Default chip label: '◇ current',
flips to '○ all' when scope=all. Chip href toggles to the alternate
state preserving filter + view.

Quiet fold:
- <details> element opened on click — projects with IsCurrent=false land
  here, including all stale candidates.
- Fold summary: 'Quiet (N) — older than 14d · M stale' (M omitted when 0).
- Quiet tiles render with the same shape as primary tiles, slightly
  faded; stale tiles also carry a 'tile-stale' class (dashed border) and
  a 'stale' flag in the header.

Stale card on the Tasks tab retires entirely — m's pick. The
LastActivity stamp on each tile carries the staleness signal; the
'consider archiving?' nudge migrates to the Quiet fold framing. Stale
data still computes (collectStale runs in buildDashboard) because the
rollup needs the per-item stale flag and the repo-activity map for
LastActivity.

Cache key extends: (filter | view=X | scope=Y) so toggling scope from
the chip lands in a separate cache slot (no stale render).

Tests:
- TestDashboardStaleCardSurfacesDormantMaiProject retargeted at the new
  Quiet fold + tile-stale class on Tiles.
- TestDashboardStaleCardSkipsRecentRepo asserts the inverse via class
  inspection on the tile <article>.
- 4 new tests cover the scope chip: renders on Tiles only, label flips
  on scope=all, scope=all hides the Quiet fold, chip URL flips correctly.

Empty state: scope=current with no current projects shows a
'Nothing current. Pin a project, or show all active.' note with a
direct link to scope=all.
2026-05-26 12:27:13 +02:00

70 lines
2.7 KiB
Cheetah

{{define "dashboard-tiles"}}
{{if .P.ProjectsCurrent}}
<section class="dash-tiles">
{{range .P.ProjectsCurrent}}
{{template "tile" .}}
{{end}}
</section>
{{else}}
<p class="empty muted dash-tiles-empty">
{{if eq .Scope "current"}}
Nothing current. Pin a project from its detail page, or
<a href="?scope=all"
hx-get="?scope=all"
hx-target="#dashboard-section"
hx-swap="outerHTML"
hx-push-url="true">show all active</a>.
{{else}}
No projects to show.
{{end}}
</p>
{{end}}
{{if .P.ProjectsQuiet}}
<details class="dash-quiet">
<summary class="dash-quiet-summary muted">
Quiet ({{len .P.ProjectsQuiet}}) — older than {{.P.QuietWindowLabel}}{{if .P.QuietStaleCount}} · {{.P.QuietStaleCount}} stale{{end}}
</summary>
<section class="dash-tiles dash-tiles-quiet">
{{range .P.ProjectsQuiet}}
{{template "tile" .}}
{{end}}
</section>
</details>
{{end}}
{{end}}
{{define "tile"}}
{{$path := .Item.PrimaryPath}}
<article class="tile{{if .Item.Pinned}} tile-pinned{{end}}{{if .Stale}} tile-stale{{end}}" data-item-path="{{$path}}">
<header class="tile-head">
<a class="tile-title" href="/i/{{$path}}">
{{if .Item.Pinned}}<span class="tile-star" title="pinned">★</span>{{end}}
{{.Item.Title}}
</a>
<span class="tile-path muted">{{$path}}</span>
{{if .IsLive}}<a class="tile-live" href="{{.Item.PublicLiveURL}}" target="_blank" rel="noopener" title="live">live</a>{{end}}
{{if .Stale}}<span class="tile-stale-flag muted" title="mai-managed · quiet repo · no open work">stale</span>{{end}}
</header>
<p class="tile-counts">
{{if .OpenTasks}}<span class="tile-open"><strong>{{.OpenTasks}}</strong> open</span>{{end}}
{{if .Overdue}}<span class="tile-overdue"><strong>{{.Overdue}}</strong>!</span>{{end}}
{{if .OpenIssues}}<span class="tile-issues"><strong>{{.OpenIssues}}</strong> issue{{if ne .OpenIssues 1}}s{{end}}</span>{{end}}
{{if and (not .OpenTasks) (not .OpenIssues)}}<span class="tile-quiet muted">quiet</span>{{end}}
</p>
{{if .NextSignal}}
<p class="tile-signal" title="{{.NextSignalKind}}">
<span class="tile-signal-kind muted">{{if eq .NextSignalKind "task"}}•{{else}}◆{{end}}</span>
<span class="tile-signal-text">{{.NextSignal}}</span>
</p>
{{end}}
<footer class="tile-foot">
{{if .LastActivityRel}}
<span class="tile-stamp muted" title="{{.LastActivity.Format "2006-01-02 15:04"}}">{{.LastActivityRel}}</span>
{{else}}
<span class="tile-stamp muted">—</span>
{{end}}
</footer>
</article>
{{end}}