feat(dashboard): polish Events tab — summary header, fuller day labels

Phase 5h slice 5 — the Events tab's routing landed in slice 2; this
slice adds the dedicated-surface polish that distinguishes it from
the Events card on the Tasks tab.

Changes:
- Top header summary: 'N events · next 7 days' so m sees the window
  shape at a glance without scanning rows.
- Day headings now carry three columns: the relative label
  ('Today' / 'Tomorrow' / weekday), the ISO date (mono font), and a
  right-aligned event count. Bigger visual hierarchy than the cards-tab
  flavour to justify the dedicated tab's existence.
- Empty-state copy invites linking a CalDAV calendar from a project's
  detail page so a never-seen-events tab doesn't feel broken.
- StartLabel fallback to '—' when an event has no parseable start
  time so the row doesn't collapse weirdly.

CSS adds .dash-events-summary, .event-day-heading flex layout,
.event-day-label / .event-day-date / .event-day-count spans, and a
constrained .dash-events-empty for the empty-state width.

Test: TestDashboardEventsViewRenders now also asserts the empty-state
copy ships, so a future refactor that drops the invite-to-link prose
gets caught.
This commit is contained in:
mAi
2026-05-26 12:35:10 +02:00
parent c4a4ba0687
commit fee3251946
3 changed files with 27 additions and 6 deletions

View File

@@ -93,6 +93,8 @@ func TestDashboardTasksViewFallback(t *testing.T) {
// TestDashboardEventsViewRenders confirms that ?view=events renders the
// promoted Events surface (dash-events-view) and not the cards or tiles.
// Also asserts the slice-5 polish: the empty state copy invites the user
// to link a CalDAV calendar so the dedicated tab doesn't feel broken.
func TestDashboardEventsViewRenders(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
@@ -107,6 +109,9 @@ func TestDashboardEventsViewRenders(t *testing.T) {
if strings.Contains(body, `class="card card-tasks"`) {
t.Errorf("view=events should NOT render the Tasks 5-card layout")
}
if !strings.Contains(body, "Link a CalDAV calendar") {
t.Errorf("empty-state copy should invite linking a calendar")
}
}
// TestDashboardUnknownViewFallsBackToTiles confirms graceful default

View File

@@ -421,11 +421,20 @@ table.bulk .chip-add-btn:hover { background: var(--accent); color: var(--accent-
.dashboard .dash-tiles-quiet .tile { opacity: 0.85; }
.dashboard .dash-events-view { margin-top: 8px; }
.dashboard .dash-events-view .event-day-large { margin: 16px 0; }
.dashboard .dash-events-view .event-day-large h2 {
font-size: 1em; font-weight: 600; margin: 0 0 8px 0;
.dashboard .dash-events-summary { margin: 4px 0 16px; }
.dashboard .dash-events-summary h2 { margin: 0; font-size: 1.05em; font-weight: 600; }
.dashboard .dash-events-view .event-day-large { margin: 18px 0; }
.dashboard .dash-events-view .event-day-heading {
font-size: 0.95em; font-weight: 600; margin: 0 0 8px 0;
border-bottom: 1px dotted var(--border); padding-bottom: 4px;
display: flex; gap: 10px; align-items: baseline;
}
.dashboard .dash-events-view .event-day-label { color: var(--fg); }
.dashboard .dash-events-view .event-day-date {
font-family: ui-monospace, SFMono-Regular, monospace; font-size: 0.85em;
}
.dashboard .dash-events-view .event-day-count { margin-left: auto; font-size: 0.85em; }
.dashboard .dash-events-empty { margin: 24px 0; max-width: 540px; }
.dashboard .dash-events-view .event-list { list-style: none; padding: 0; margin: 0; }
.dashboard .dash-events-view .event-row {
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;

View File

@@ -236,13 +236,20 @@
{{define "dashboard-events-view"}}
<section class="dash-events-view">
{{if .P.Events}}
<header class="dash-events-summary">
<h2>{{.P.EventsTotal}} event{{if ne .P.EventsTotal 1}}s{{end}} <span class="muted">· next 7 days</span></h2>
</header>
{{range .P.Events}}
<section class="event-day-large">
<h2 class="muted">{{.DayLabel}} <small>({{len .Events}})</small></h2>
<h3 class="event-day-heading">
<span class="event-day-label">{{.DayLabel}}</span>
<span class="muted event-day-date">{{.DayKey}}</span>
<span class="muted event-day-count">{{len .Events}} event{{if ne (len .Events) 1}}s{{end}}</span>
</h3>
<ul class="event-list">
{{range .Events}}
<li class="event-row">
<span class="start">{{.StartLabel}}</span>
<span class="start">{{if .StartLabel}}{{.StartLabel}}{{else}}—{{end}}</span>
<a class="proj" href="/i/{{.Item.PrimaryPath}}">{{.Item.PrimaryPath}}</a>
<span class="summary">{{.Event.Summary}}</span>
{{if .Event.Location}}<span class="loc muted">· {{.Event.Location}}</span>{{end}}
@@ -253,7 +260,7 @@
</section>
{{end}}
{{else}}
<p class="empty muted">No events in the next 7 days.</p>
<p class="empty muted dash-events-empty">No events in the next 7 days. Link a CalDAV calendar from a project's detail page to start surfacing events here.</p>
{{end}}
</section>
{{end}}