Commit Graph

3 Commits

Author SHA1 Message Date
mAi
316b4e408a feat(dashboard): tab strip + Tiles view + view-switcher URL routing
Phase 5h slice 2 — adds the three-tab dashboard chrome (Tiles / Tasks /
Events) and lands the Tiles view as the default landing surface per
m's §7 pick.

URL contract:
  /dashboard                — Tiles (default, elided)
  /dashboard?view=tasks     — today's 5-card layout
  /dashboard?view=events    — Events card promoted to a full-tab view
  Unknown ?view= falls back to Tiles.

Refactor: aggregator calls (Todos / Events / Issues) hoisted up into
buildDashboard so the rollup can consume the same uncapped rows without
a second DAV/Gitea round-trip. The legacy collect* helpers split into
pure projectTasks / projectEvents / projectIssues / projectDocs that
take pre-fetched rows. collectStale extended to return its per-item
repo-activity map alongside the trimmed stale list — the rollup uses
the map as a LastActivity signal.

Cache: key now composes (filter | view=X) so each tab has its own 60s
TTL slot. Tab switches don't poison the cache for siblings.

Tiles render with: pin star (when pinned), title + path + live badge,
counts row (open / overdue! / issues / quiet), NextSignal one-liner
(task wins over issue), and a tile-foot LastActivity stamp.

CSS:
- .dash-tabs strip with active-state border bridge.
- .dash-tiles grid: 1/2/3 cols at 600/900px breakpoints.
- .dash-events-view scaffolding for the promoted Events surface.

Templates: dashboard_section.tmpl restructured to dispatch by .View.
The cards layout is now {{define "dashboard-cards"}} and the
events-only surface is {{define "dashboard-events-view"}}. New
dashboard_tiles.tmpl defines {{define "dashboard-tiles"}}. Both
templates registered in the dashboard + dashboard_section bundles.

Tests:
- Existing dashboard tests retargeted at ?view=tasks for the legacy
  Tasks-tab expectations (5-card layout, inline writeback, stale card).
- New dashboard_view_test.go covers: default view = Tiles, three-tab
  strip rendering + active marker, view=tasks fallback, view=events
  promotion, unknown view fallback, tile rendering for seeded item,
  cache-key separation between views.
- TestLayoutNoTopHeader scoped to the body chrome before <main> so it
  no longer trips on legitimate <header> elements inside cards/tiles.

Out of scope (later slices): scope chip + Quiet fold (slice 3), pin
toggle handler (slice 4), Events tab dedicated polish (slice 5),
mobile polish (slice 7), design.md addendum (slice 8).
2026-05-26 12:22:32 +02:00
mAi
bd600633c9 feat(layout): mobile bottom-nav + drawer
Phase 5g slice B. Fills the ≤767px gap left by slice A (sidebar
display:none on mobile) with a fixed-bottom 5-slot nav + a drawer for
overflow items. iOS PWA install respects safe-area-inset-bottom so the
nav clears the home indicator.

web/templates/layout.tmpl:
- New <nav class="projax-bottom-nav"> with five slots:
    Tree (/) → Dashboard (/dashboard) → +New (/new, raised circle)
    → Calendar (/calendar) → Menu (drawer).
- Center "+ New" slot is a raised .capture-circle (margin-top: -10px,
  44×44px, accent background) — mBrian's capture-button pattern, but
  pointing at /new because projax has no separate capture flow.
- Menu slot is a <details class="projax-mobile-drawer"> whose <summary>
  IS the bottom-nav-item. Tapping pops a drawer-sheet absolutely
  positioned 8px above the bottom-nav with overflow items: Timeline,
  Graph, Admin, theme toggle, sign-out. Browser-default <details>
  handles open/close + tap-outside-dismiss — no JS, no gesture wiring.
- Active class on bottom-nav-item + drawer-item via same .Path-driven
  server-side pattern slice A introduced.
- Theme toggle handler now binds to BOTH #theme-toggle (sidebar) AND
  #theme-toggle-drawer (drawer). Flipping either updates the icon on
  both buttons, sets data-theme on <html>, writes the cookie.

web/static/style.css:
- .projax-bottom-nav: fixed bottom, height = calc(56px +
  env(safe-area-inset-bottom, 0)), flex justify-around, z-index 1021.
- .bottom-nav-item: 44×44px min, column-flex, touch-action: none for the
  capture-button so iOS doesn't intercept the tap.
- .capture-circle: 44×44px raised circle, accent background.
- .projax-mobile-drawer .drawer-sheet: fixed, bottom-right anchored
  above the nav, min(260px, calc(100vw - 16px)) wide, slide-up animation
  via @keyframes projax-drawer-up (translateY 8→0, 160ms ease-out).
- @media (min-width: 768px): bottom-nav hidden.
- @media (max-width: 767px): main.projax-main gets padding-bottom =
  calc(56px + 1rem + env(safe-area-inset-bottom)) so rows aren't hidden
  behind the nav.

docs/design.md:
- New §18 (Layout: sidebar + bottom-nav, Phase 5g). Documents both
  surfaces' breakpoints, the .Path-driven active marker, the pre-paint
  localStorage restore, the theme-toggle dual-binding, and the four
  features I deliberately did not port from mBrian (resize handle,
  capture modal, quick-switcher/saved-searches/Today/Work, slide-up
  gesture).

Tests (web/layout_test.go):
- TestLayoutBottomNavMarkup: 5 slots present in documented order, +New
  is .capture-btn with .capture-circle, Menu is <details>, drawer holds
  Timeline/Graph/Admin/theme/sign-out.
- TestLayoutBottomNavActiveClass: /calendar render highlights Calendar
  slot only.
- TestLayoutThemeToggleBoundToBothButtons: handler enumerates both
  button ids so flipping either flips the theme.

All 10 layout tests pass (7 from slice A + 3 from slice B). Full web
suite green. No test source edits to pre-existing tests — the bottom-
nav is additive markup.
2026-05-25 16:40:14 +02:00
mAi
9d0dd74695 feat(layout): desktop sidebar replaces top-nav
Phase 5g slice A. m wants projax aligned with mBrian's nav layout: fixed-
left sidebar on desktop, bottom-nav on mobile (slice B). This slice drops
the top-nav <header> and ships the desktop sidebar; the ≤767px viewport
temporarily renders nav-less until slice B lands the bottom-nav.

web/templates/layout.tmpl:
- Delete the old <header><nav>...</nav></header>. Replace with
  <aside class="projax-sidebar"> carrying:
    * .sidebar-top: brand (▦ + "projax")
    * .sidebar-nav: 6 items (Tree → Dashboard → Calendar → Timeline →
      Graph → Admin) with inline SVG icons. Active class set server-side
      via `{{if eq $path "/dashboard"}}active{{end}}`.
    * .sidebar-bottom: theme toggle + sign-out form + collapse toggle.
- Content wrapped in <main class="projax-main">.
- New pre-paint <script> in <head> reads
  localStorage["projax.sidebar.collapsed"] and sets
  data-sidebar-collapsed="true" on <html> BEFORE first paint so the
  main-content margin doesn't flash 220px→56px on every navigation.
- Existing theme-toggle JS unchanged (the button is just relocated). New
  body-end <script> wires the #sidebar-collapse button: toggle the
  attribute, persist to localStorage, sync aria-expanded + title.
- DO NOT port mBrian's resize handle — that's the $effect-feedback bug
  mBrian debugged at length. Static 220/56px is fine for v1.

web/static/style.css:
- Strip the pre-5g `header { ... }`, `header nav { ... }`,
  `header .logout-form { ... }`, `header .brand { ... }`,
  `header .theme-toggle { ... }` rules and the matching @media
  overrides (320×, 480× targeted `header`).
- New `main.projax-main` rule: `margin-left: var(--projax-sidebar-width,
  220px)` on desktop, transitions on collapse. The
  `html[data-sidebar-collapsed="true"]` selector flips the var to 56px.
  Mobile (≤767px) zeros the margin.
- New `.projax-sidebar` block: fixed-left, z-index 50, .nav-item /
  .nav-icon / .nav-label rules, .active border-left accent (matches
  mBrian's `border-left: 2px solid #8cf` pattern but uses var(--accent)
  so it round-trips dark/light theme).
- @media (max-width: 767px) hides the sidebar so the phone isn't stuck
  with a 220px-wide hole until slice B.

web/server.go:
- render() injects `Path: r.URL.Path` into the template data map (unless
  caller pre-set it for tests) so the layout can mark the active nav
  item without any per-handler boilerplate.

Tests (web/layout_test.go):
- TestLayoutSidebarOnDesktop: aside present, all six href + label pairs
  rendered.
- TestLayoutActiveClass: /dashboard render has the Dashboard item with
  .active and Tree without.
- TestLayoutCollapseScript: pre-paint localStorage restore + the
  collapse-toggle handler both present.
- TestLayoutNoTopHeader: belt-and-braces — the pre-5g <header> and
  .logout-btn classes are gone.

All existing tests stay green (TestLayoutHasAdminNavLink,
TestLayoutHasManifestAndAppleTouchIcon, TestLayoutHasViewportMeta,
TestCalendar*, TestTreeRenders, etc.). No test source edits required —
existing assertions look at page CONTENT, not chrome.
2026-05-25 16:36:10 +02:00