Project Timeline / Chart — inventor design pass (broader visualisation, vertical/horizontal, single/multi-column, colors, export) #35

Open
opened 2026-05-09 16:35:31 +00:00 by mAi · 9 comments
Collaborator

Source: m @ 2026-05-09 18:32:

lets hire an inventor — one could chose to show the timeline in one or in separate columns and with different colors even... bigger feature development but ... a project timeline / chart would be nice in general. So we need to make some considerations on how to design one.
Another aspect to this is vertical or horizontal... and an export functionality would also be great.

Scope

This is a broader project-chart feature that goes beyond the SmartTimeline (which lives in the Verlauf tab on /projects/<id>). The SmartTimeline (t-paliad-169) is the data substrate; this feature is a richer visualisation layer with:

  1. Layout options:

    • Vertical (today's SmartTimeline default)
    • Horizontal (Gantt-strip style)
    • Single-column (events in one stream)
    • Multi-column / lane-grouped (current Slice 3 parallel-track + Slice 4 lane view, but generalised)
  2. Visual customisation:

    • Color schemes per category / track / project type
    • Density modes (compact / standard / spacious)
    • Status pills, kind badges, shape variants
  3. Export:

    • PDF (lawyer-shareable timeline export)
    • PNG / SVG (for slides / docs)
    • CSV / JSON (data export for Excel)
    • iCal feed (already exists for deadlines+appointments — the chart-export should reuse / extend)
  4. Surfaces:

    • Embedded in the Verlauf tab (like today)
    • Standalone full-page chart at /projects/<id>/chart or similar
    • Embeddable in Custom Views (t-paliad-144 substrate)

What the inventor must answer

  • Renderer choice: SVG / Canvas / D3 / a Bun-compatible chart library / hand-rolled DOM grid?
  • How does horizontal Gantt interact with the SmartTimeline's date-anchoring affordance (click-to-anchor inline editor) — does it survive in horizontal mode?
  • Export pipeline: client-side (html2canvas / svg-to-pdf) vs server-side (headless browser render via chromedp or similar) — trade-offs.
  • Color-scheme strategy: per-rule-category? per-our_side? user-configurable theme? Do we expose colour pickers?
  • Performance: a Patent-level project might have 5 child cases × 30 events each = 150 nodes. A Client-level project could have 100+. Where does each renderer stop scaling?
  • Mobile behaviour: vertical-only fallback on small screens? Horizontal-with-scroll?
  • Phasing: 3-4 implementation slices a coder can ship sequentially.
  • Open questions for m's gate.

Files to read first

  1. docs/design-smart-timeline-2026-05-08.md — the existing SmartTimeline design (your data substrate)
  2. frontend/src/client/views/shape-timeline.ts — current vertical render (~700 LoC after Slice 4)
  3. internal/services/projection_service.go — the data composition (returns {events, lanes})
  4. frontend/src/components/FilterBar* — the filter primitive (extends to chart filtering)
  5. frontend/src/client/views/format.ts — the date-formatter you'll likely reuse

Output

docs/design-project-chart-2026-05-09.md. End with DESIGN READY FOR REVIEW. NO IMPLEMENTATION. m gates the inventor → coder transition per project rules.

Filed by maria/paliad-head per m's hire directive.

**Source:** m @ 2026-05-09 18:32: > lets hire an inventor — one could chose to show the timeline in one or in separate columns and with different colors even... bigger feature development but ... a project timeline / chart would be nice in general. So we need to make some considerations on how to design one. > Another aspect to this is vertical or horizontal... and an export functionality would also be great. ## Scope This is a **broader project-chart feature** that goes beyond the SmartTimeline (which lives in the Verlauf tab on `/projects/<id>`). The SmartTimeline (t-paliad-169) is the data substrate; this feature is a richer visualisation layer with: 1. **Layout options:** - Vertical (today's SmartTimeline default) - Horizontal (Gantt-strip style) - Single-column (events in one stream) - Multi-column / lane-grouped (current Slice 3 parallel-track + Slice 4 lane view, but generalised) 2. **Visual customisation:** - Color schemes per category / track / project type - Density modes (compact / standard / spacious) - Status pills, kind badges, shape variants 3. **Export:** - PDF (lawyer-shareable timeline export) - PNG / SVG (for slides / docs) - CSV / JSON (data export for Excel) - iCal feed (already exists for deadlines+appointments — the chart-export should reuse / extend) 4. **Surfaces:** - Embedded in the Verlauf tab (like today) - Standalone full-page chart at `/projects/<id>/chart` or similar - Embeddable in Custom Views (t-paliad-144 substrate) ## What the inventor must answer - Renderer choice: SVG / Canvas / D3 / a Bun-compatible chart library / hand-rolled DOM grid? - How does horizontal Gantt interact with the SmartTimeline's date-anchoring affordance (click-to-anchor inline editor) — does it survive in horizontal mode? - Export pipeline: client-side (html2canvas / svg-to-pdf) vs server-side (headless browser render via `chromedp` or similar) — trade-offs. - Color-scheme strategy: per-rule-category? per-our_side? user-configurable theme? Do we expose colour pickers? - Performance: a Patent-level project might have 5 child cases × 30 events each = 150 nodes. A Client-level project could have 100+. Where does each renderer stop scaling? - Mobile behaviour: vertical-only fallback on small screens? Horizontal-with-scroll? - Phasing: 3-4 implementation slices a coder can ship sequentially. - Open questions for m's gate. ## Files to read first 1. `docs/design-smart-timeline-2026-05-08.md` — the existing SmartTimeline design (your data substrate) 2. `frontend/src/client/views/shape-timeline.ts` — current vertical render (~700 LoC after Slice 4) 3. `internal/services/projection_service.go` — the data composition (returns `{events, lanes}`) 4. `frontend/src/components/FilterBar*` — the filter primitive (extends to chart filtering) 5. `frontend/src/client/views/format.ts` — the date-formatter you'll likely reuse ## Output `docs/design-project-chart-2026-05-09.md`. End with **DESIGN READY FOR REVIEW**. NO IMPLEMENTATION. m gates the inventor → coder transition per project rules. Filed by maria/paliad-head per m's hire directive.
Author
Collaborator

Inventor design filed — DESIGN READY FOR REVIEW

Doc: docs/design-project-chart-2026-05-09.md (607 lines, commit 8402002 on mai/faraday/project-timeline-chart, pushed)

TL;DR

Two renderers coexist; one substrate. Existing shape-timeline.ts (vertical DOM/CSS) keeps powering the Verlauf-tab embed unchanged. New shape-timeline-chart.ts (hand-rolled SVG, ~500 LoC) powers a new standalone /projects/{id}/chart page in horizontal Gantt mode. Both consume the same TimelineEvent[] + LaneInfo[] substrate from projection_service.go. No backend data work needed.

Big calls

  • Renderer: hand-rolled SVG over D3 / Chart.js / canvas. Reasons in §2 — our scale (≤150 nodes) fits comfortably; we have no chart lib in the repo today; rolling our own keeps the IIFE-bundle pattern intact and gives free SVG-export.
  • Layout: vertical (DOM, existing) for embed; horizontal Gantt (SVG, new) for standalone. Hybrid not in v1.
  • Columns: chart inherits substrate's LaneInfo — no new lane axis defined here.
  • Color: five built-in palette presets via CSS-var swap (default, kind-coded, track-coded, high-contrast, print). No per-user picker in v1.
  • Density: three modes (compact, standard, spacious) via [data-density] attr swap.
  • Export: SVG/PNG/PDF client-side via stdlib browser APIs (XMLSerializer → Blob; canvas.drawImage → PNG; window.print() → PDF). CSV/JSON/iCal server-side. Recommend NO chromedp for v1 — adds ~150 MB Chromium dep to Dokploy image with browser-print quality being good enough for lawyer-shareable PDFs.
  • Mobile: force vertical on <640px (horizontal Gantt unreadable on phones).
  • Custom Views integration (shape="timeline"): Slice 4, opt-in. Lossy (no projected rows in CV) but unlocks cross-project chart on the existing CV substrate.

Phasing (4 sequential slices)

  1. Standalone /chart page + horizontal SVG renderer (no exports yet).
  2. Export pipeline (SVG / PNG / PDF / CSV / JSON / iCal).
  3. Density + palette + zoom + range controls.
  4. Custom Views shape="timeline" registration.

Slice 1 is ~700 LoC frontend (new SVG renderer + page TSX + ~120 LoC CSS + 25 i18n keys) + ~50 LoC backend (page handler). Zero migrations.

12 open questions for m's gate

Q1–Q12 cover: default landing layout, embed-vs-standalone, chromedp rule-out, palette presets count, iCal scope, Custom Views priority, default date-range, mobile rule, etc. Each has my (inventor) pick + the reason.

Files implementer will touch (Slice 1)

NEW  internal/handlers/chart_pages.go              (~50 LoC)
NEW  frontend/src/projects-chart.tsx                (~100 LoC)
NEW  frontend/src/client/projects-chart.ts          (~150 LoC)
NEW  frontend/src/client/views/shape-timeline-chart.ts  (~500 LoC)
NEW  frontend/src/client/views/shape-timeline-chart.test.ts (table-driven layout() tests)
EDIT internal/handlers/handlers.go                  (+1 route)
EDIT frontend/src/styles/global.css                 (+~120 LoC palette tokens + base styles)
EDIT frontend/src/client/i18n.ts                    (+~25 keys DE+EN)
EDIT frontend/build.ts                              (+1 page bundle)
EDIT frontend/src/client/projects-detail.ts         (+ "Als Chart anzeigen ↗" link)

Pattern-fluent Sonnet coder. Pre-Slice-1 sketch layout(events, lanes, viewport) as a pure function with table-driven tests — that's where the math lives.

Inventor (faraday) parks pending m's go/no-go.

— filed by faraday/paliad per /mai-inventor shift.

## Inventor design filed — DESIGN READY FOR REVIEW **Doc:** `docs/design-project-chart-2026-05-09.md` (607 lines, commit 8402002 on `mai/faraday/project-timeline-chart`, pushed) ### TL;DR **Two renderers coexist; one substrate.** Existing `shape-timeline.ts` (vertical DOM/CSS) keeps powering the Verlauf-tab embed unchanged. New `shape-timeline-chart.ts` (hand-rolled SVG, ~500 LoC) powers a new standalone `/projects/{id}/chart` page in horizontal Gantt mode. Both consume the same `TimelineEvent[]` + `LaneInfo[]` substrate from `projection_service.go`. **No backend data work needed.** ### Big calls - **Renderer:** hand-rolled SVG over D3 / Chart.js / canvas. Reasons in §2 — our scale (≤150 nodes) fits comfortably; we have no chart lib in the repo today; rolling our own keeps the IIFE-bundle pattern intact and gives free SVG-export. - **Layout:** vertical (DOM, existing) for embed; horizontal Gantt (SVG, new) for standalone. Hybrid not in v1. - **Columns:** chart inherits substrate's `LaneInfo` — no new lane axis defined here. - **Color:** five built-in palette presets via CSS-var swap (`default`, `kind-coded`, `track-coded`, `high-contrast`, `print`). No per-user picker in v1. - **Density:** three modes (`compact`, `standard`, `spacious`) via `[data-density]` attr swap. - **Export:** SVG/PNG/PDF client-side via stdlib browser APIs (`XMLSerializer` → Blob; `canvas.drawImage` → PNG; `window.print()` → PDF). CSV/JSON/iCal server-side. **Recommend NO chromedp** for v1 — adds ~150 MB Chromium dep to Dokploy image with browser-print quality being good enough for lawyer-shareable PDFs. - **Mobile:** force vertical on <640px (horizontal Gantt unreadable on phones). - **Custom Views integration** (`shape="timeline"`): Slice 4, opt-in. Lossy (no projected rows in CV) but unlocks cross-project chart on the existing CV substrate. ### Phasing (4 sequential slices) 1. Standalone `/chart` page + horizontal SVG renderer (no exports yet). 2. Export pipeline (SVG / PNG / PDF / CSV / JSON / iCal). 3. Density + palette + zoom + range controls. 4. Custom Views `shape="timeline"` registration. Slice 1 is ~700 LoC frontend (new SVG renderer + page TSX + ~120 LoC CSS + 25 i18n keys) + ~50 LoC backend (page handler). **Zero migrations.** ### 12 open questions for m's gate Q1–Q12 cover: default landing layout, embed-vs-standalone, chromedp rule-out, palette presets count, iCal scope, Custom Views priority, default date-range, mobile rule, etc. Each has my (inventor) pick + the reason. ### Files implementer will touch (Slice 1) ``` NEW internal/handlers/chart_pages.go (~50 LoC) NEW frontend/src/projects-chart.tsx (~100 LoC) NEW frontend/src/client/projects-chart.ts (~150 LoC) NEW frontend/src/client/views/shape-timeline-chart.ts (~500 LoC) NEW frontend/src/client/views/shape-timeline-chart.test.ts (table-driven layout() tests) EDIT internal/handlers/handlers.go (+1 route) EDIT frontend/src/styles/global.css (+~120 LoC palette tokens + base styles) EDIT frontend/src/client/i18n.ts (+~25 keys DE+EN) EDIT frontend/build.ts (+1 page bundle) EDIT frontend/src/client/projects-detail.ts (+ "Als Chart anzeigen ↗" link) ``` ### Recommended implementer Pattern-fluent Sonnet coder. Pre-Slice-1 sketch `layout(events, lanes, viewport)` as a pure function with table-driven tests — that's where the math lives. Inventor (faraday) parks pending m's go/no-go. — filed by faraday/paliad per `/mai-inventor` shift.
Author
Collaborator

Slice 1 shipped — coder pass (tesla)

Branch: mai/tesla/project-timeline-chart (pushed)

Commits

  • b0a6b09 feat(t-paliad-177): chart page handler + GET /projects/{id}/chart route
  • ed4e731 feat(t-paliad-177): chart layout() pure-function + 27 table-driven tests
  • a3adb6b feat(t-paliad-177): chart SVG paint() + mount() + palette CSS tokens
  • 30f7031 feat(t-paliad-177): chart page TSX + boot client + i18n + Verlauf link

What landed (Slice 1)

  • New page GET /projects/{id}/chart — static-file handler, visibility piggybacks on /api/projects/{id}/timeline.
  • Horizontal SVG renderer in frontend/src/client/views/shape-timeline-chart.ts (~760 LoC incl. paint+mount). Pure layout(events, lanes, viewport) separated from DOM-mutating paint(); mount(host, opts) returns a handle with refresh / dispose / getLayout.
  • 27 table-driven tests on layout() covering canvas geometry, pxPerDay math, today-rule clipping, lane stacking, mark bucketing by lane_id, out-of-range clipping, undated zone, mark-shape mapping, axis tick density. All green.
  • CSS palette tokens (--chart-*) on .smart-timeline-chart so Slice 3 can swap palettes via [data-palette=...] attribute without touching renderer.
  • 11 new i18n keys DE+EN.
  • "Als Chart anzeigen ↗" link added to the SmartTimeline controls on /projects/{id} — opens chart in new tab (design §8.1).

Verification

  • go build ./... clean
  • bun run build clean (2182 keys; data-i18n scan clean)
  • bun test src/client/views/shape-timeline-chart.test.ts → 27/27 pass
  • go test ./internal/handlers/... → ok

Inert controls in Slice 1 (Slice 3 wires them)

  • Layout: Horizontal (vertical-toggle deferred)
  • Spalten: Auto / Dichte: Standard / Palette: Standard
  • Export ↓ (Slice 2)

Edge cases flagged during impl

  1. Mobile fallback is partial. Design §9 wants "force vertical on phones". Slice 1 lets the host overflow-x scroll because the vertical renderer is the existing shape-timeline.ts and isn't wired into the chart host yet. Real flip lands when the vertical-toggle goes live (Slice 3 dep).
  2. GET /chart returns 200 for any {id}. Same pattern as /projects/{id} (static file, client enforces auth via API). Means URL existence leaks "valid UUID shape" of the project id. Worth m's gate before Slice 2 export adds more surface (e.g. PDF download URL).
  3. Milestone + projected clicks are silent no-ops. Deep-linking to /projects/{id}#event-{id} would need a new anchor target in projects-detail. Deferred.
  4. Undated zone placement. Marks for events without a date field pile up at x = chartLeft - laneLabelWidth/4 (left of canvas). Design §3.2 didn't pin this — choice is reversible if m prefers a separate dedicated gutter or bottom row.

DESIGN READY FOR REVIEW → SLICE 1 READY FOR REVIEW

m gates the merge.

## Slice 1 shipped — coder pass (tesla) **Branch:** `mai/tesla/project-timeline-chart` (pushed) ### Commits - [b0a6b09](../commit/b0a6b09) feat(t-paliad-177): chart page handler + GET /projects/{id}/chart route - [ed4e731](../commit/ed4e731) feat(t-paliad-177): chart layout() pure-function + 27 table-driven tests - [a3adb6b](../commit/a3adb6b) feat(t-paliad-177): chart SVG paint() + mount() + palette CSS tokens - [30f7031](../commit/30f7031) feat(t-paliad-177): chart page TSX + boot client + i18n + Verlauf link ### What landed (Slice 1) - New page `GET /projects/{id}/chart` — static-file handler, visibility piggybacks on `/api/projects/{id}/timeline`. - Horizontal SVG renderer in `frontend/src/client/views/shape-timeline-chart.ts` (~760 LoC incl. paint+mount). Pure `layout(events, lanes, viewport)` separated from DOM-mutating `paint()`; `mount(host, opts)` returns a handle with `refresh` / `dispose` / `getLayout`. - 27 table-driven tests on `layout()` covering canvas geometry, pxPerDay math, today-rule clipping, lane stacking, mark bucketing by `lane_id`, out-of-range clipping, undated zone, mark-shape mapping, axis tick density. All green. - CSS palette tokens (`--chart-*`) on `.smart-timeline-chart` so Slice 3 can swap palettes via `[data-palette=...]` attribute without touching renderer. - 11 new i18n keys DE+EN. - "Als Chart anzeigen ↗" link added to the SmartTimeline controls on `/projects/{id}` — opens chart in new tab (design §8.1). ### Verification - `go build ./...` clean - `bun run build` clean (2182 keys; data-i18n scan clean) - `bun test src/client/views/shape-timeline-chart.test.ts` → 27/27 pass - `go test ./internal/handlers/...` → ok ### Inert controls in Slice 1 (Slice 3 wires them) - Layout: Horizontal (vertical-toggle deferred) - Spalten: Auto / Dichte: Standard / Palette: Standard - Export ↓ (Slice 2) ### Edge cases flagged during impl 1. **Mobile fallback is partial.** Design §9 wants "force vertical on phones". Slice 1 lets the host overflow-x scroll because the vertical renderer is the existing `shape-timeline.ts` and isn't wired into the chart host yet. Real flip lands when the vertical-toggle goes live (Slice 3 dep). 2. **`GET /chart` returns 200 for any `{id}`.** Same pattern as `/projects/{id}` (static file, client enforces auth via API). Means URL existence leaks "valid UUID shape" of the project id. Worth m's gate before Slice 2 export adds more surface (e.g. PDF download URL). 3. **Milestone + projected clicks are silent no-ops.** Deep-linking to `/projects/{id}#event-{id}` would need a new anchor target in `projects-detail`. Deferred. 4. **Undated zone placement.** Marks for events without a `date` field pile up at `x = chartLeft - laneLabelWidth/4` (left of canvas). Design §3.2 didn't pin this — choice is reversible if m prefers a separate dedicated gutter or bottom row. ### DESIGN READY FOR REVIEW → SLICE 1 READY FOR REVIEW m gates the merge.
Author
Collaborator

Slice 1 shipped to main: bdb3d8a (merge), preceded by b0a6b09 handler+route, ed4e731 layout() pure-fn + 27 table-driven tests, a3adb6b paint() + mount() + palette CSS tokens, 30f7031 page TSX + boot client + i18n + Verlauf link.

What's live:

  • GET /projects/{id}/chart — standalone horizontal Gantt SVG.
  • [Als Chart anzeigen ↗] link in the project Verlauf tab (opens in new tab per design §8.1).
  • 5 palette CSS tokens, density toggle CSS hooks (chips inert in Slice 1 per scope).
  • Pure-function layout(events, lanes, viewport): ChartLayout with 27 table-driven tests covering ranges, tick generation, lane stacking, today-rule, undated rows.
  • go build ./... clean, bun run build clean, 27/27 tests pass.

Faraday-design edge cases surfaced during impl (deferred to later slices or follow-ups):

  1. Mobile fallback partial — host overflows on <640px; true vertical fallback needs Slice 3's vertical-renderer toggle. Flagged in CSS.
  2. Handler returns 200 for any /projects/{id}/chart URL; visibility is enforced when the client fetches /api/projects/{id}/timeline. URL existence leaks valid-id shape — same pattern as projects-detail.html, but worth tightening before Slice 2 export adds more surface.
  3. Milestone + projected event clicks are silent no-ops (no detail page). Deep-link #event-{id} anchor on projects-detail would need a new anchor target — deferred.
  4. Undated marks pile up in the lane-label gutter at x = chartLeft - laneLabelWidth/4. Design §3.2 didn't pin this; choice is reversible if it bothers anyone.

Next: m gates Slice 2 (palette picker + density toggle + export paths — SVG/PNG/CSV/JSON/iCal + browser-print PDF + [Als Chart anzeigen ↗] from sidebar). Tesla stays parked on branch.

Slice 1 shipped to main: [bdb3d8a](https://mgit.msbls.de/m/paliad/commit/bdb3d8a) (merge), preceded by [b0a6b09](https://mgit.msbls.de/m/paliad/commit/b0a6b09) handler+route, [ed4e731](https://mgit.msbls.de/m/paliad/commit/ed4e731) `layout()` pure-fn + 27 table-driven tests, [a3adb6b](https://mgit.msbls.de/m/paliad/commit/a3adb6b) `paint()` + `mount()` + palette CSS tokens, [30f7031](https://mgit.msbls.de/m/paliad/commit/30f7031) page TSX + boot client + i18n + Verlauf link. What's live: - `GET /projects/{id}/chart` — standalone horizontal Gantt SVG. - `[Als Chart anzeigen ↗]` link in the project Verlauf tab (opens in new tab per design §8.1). - 5 palette CSS tokens, density toggle CSS hooks (chips inert in Slice 1 per scope). - Pure-function `layout(events, lanes, viewport): ChartLayout` with 27 table-driven tests covering ranges, tick generation, lane stacking, today-rule, undated rows. - `go build ./...` clean, `bun run build` clean, 27/27 tests pass. Faraday-design edge cases surfaced during impl (deferred to later slices or follow-ups): 1. Mobile fallback partial — host overflows on <640px; true vertical fallback needs Slice 3's vertical-renderer toggle. Flagged in CSS. 2. Handler returns 200 for any `/projects/{id}/chart` URL; visibility is enforced when the client fetches `/api/projects/{id}/timeline`. URL existence leaks valid-id shape — same pattern as projects-detail.html, but worth tightening before Slice 2 export adds more surface. 3. Milestone + projected event clicks are silent no-ops (no detail page). Deep-link `#event-{id}` anchor on projects-detail would need a new anchor target — deferred. 4. Undated marks pile up in the lane-label gutter at `x = chartLeft - laneLabelWidth/4`. Design §3.2 didn't pin this; choice is reversible if it bothers anyone. Next: m gates Slice 2 (palette picker + density toggle + export paths — SVG/PNG/CSV/JSON/iCal + browser-print PDF + `[Als Chart anzeigen ↗]` from sidebar). Tesla stays parked on branch.
Author
Collaborator

Slice 2 shipped — coder pass (tesla)

Branch: mai/tesla/project-timeline-chart (pushed, 5 commits ahead of main)

Commits

  • 968b0bc close visibility leak on /projects/{id}/chart handler (Slice 1 edge case #2)
  • d1314a4 palette picker — 5 CSS-token sets + URL state (faraday-Q5)
  • b24063b density toggle — compact/standard/spacious + URL state
  • 98a51fa chart exports — SVG/PNG/CSV/JSON + browser-print CSS (faraday-Q4)
  • d8f7745 chart export — iCal feed (deadlines+appointments only, faraday-Q6)

What landed

  • Visibility gate: GET /projects/{id}/chart resolves the project via ProjectService.GetByID before serving — invisible / unknown ids return 404 + standard notfound chrome. Closes Slice 1 edge case.
  • Palette picker: 5 presets (default / kind-coded / track-coded / high-contrast / print). Pure CSS-var swap on [data-palette="..."]; no repaint. ?palette=… URL state.
  • Density toggle: compact/standard/spacious. Triggers layout() repaint (lane height + mark radius change). ?density=… URL state.
  • Export menu: native <details>/<summary> so it’s keyboard-accessible without JS. 6 outputs:
    • SVGXMLSerializer over an SVG clone with --chart-* tokens inlined via getComputedStyle so standalone files paint correctly in image viewers.
    • PNG — SVG → Image → Canvas at 2× DPR, white background painted before drawImage.
    • PDFwindow.print() + @media print stylesheet (A4 landscape, palette forced to b&w, chrome hidden). No chromedp dep.
    • CSV — 20-column flat schema, UTF-8 BOM (Excel-DE), RFC 4180 escaping.
    • JSON — wire envelope + export metadata header.
    • iCalGET /api/projects/{id}/timeline.ics (server-side, reuses internal/services/caldav_ical.go). Only deadlines + appointments; projected/milestone explicitly skipped per faraday-Q6.

Verification

  • go build ./... clean
  • bun run build clean (2199 i18n keys; data-i18n scan clean)
  • go test ./internal/... all packages ok (incl. 4 new FormatTimelineICS cases + 1 serveChartNotFound test)
  • bun test src/client/views/shape-timeline-chart.test.ts 27/27 pass

Deferred to Slice 3

Sidebar entry for active project (head Slice 2 item 5): the current Sidebar component reads only static config — there’s no per-page-context plumbing. Adding a project-scoped chip needs either a sidebar override mechanism or a separate contextual-action area; both are real refactors. The Slice 1 "Als Chart anzeigen ↗" link in the Verlauf tab already covers the discovery path. Sidebar entry pairs cleanly with Slice 3 (zoom + range + permalink state) since both are page-scoped UX work.

Edge cases flagged during impl

  1. SVG export inlines palette tokens via getComputedStyle — works for --chart-* colours but doesn’t inline density-derived geometry. Geometry is already baked into x/y/r attrs by layout(), so this is intentional, not a leak.
  2. PNG export paints white before drawImage so dark-mode users don’t get an invisible transparent PNG on a white slide.
  3. iCal endpoint runs GetByID twice (once inside ProjectionService.For for visibility, once for the X-WR-CALNAME title). Minor double-query; not worth a service refactor for one endpoint. Flagged for future opt.
  4. Browser-print PDF on Safari — Safari ignores some @page rules; design §13 already documented "use Chrome for archival exports". No code change.

SLICE 2 READY FOR REVIEW

m gates the merge.

## Slice 2 shipped — coder pass (tesla) **Branch:** `mai/tesla/project-timeline-chart` (pushed, 5 commits ahead of main) ### Commits - [968b0bc](../commit/968b0bc) close visibility leak on /projects/{id}/chart handler (Slice 1 edge case #2) - [d1314a4](../commit/d1314a4) palette picker — 5 CSS-token sets + URL state (faraday-Q5) - [b24063b](../commit/b24063b) density toggle — compact/standard/spacious + URL state - [98a51fa](../commit/98a51fa) chart exports — SVG/PNG/CSV/JSON + browser-print CSS (faraday-Q4) - [d8f7745](../commit/d8f7745) chart export — iCal feed (deadlines+appointments only, faraday-Q6) ### What landed - **Visibility gate:** `GET /projects/{id}/chart` resolves the project via `ProjectService.GetByID` before serving — invisible / unknown ids return 404 + standard notfound chrome. Closes Slice 1 edge case. - **Palette picker:** 5 presets (default / kind-coded / track-coded / high-contrast / print). Pure CSS-var swap on `[data-palette="..."]`; no repaint. `?palette=…` URL state. - **Density toggle:** compact/standard/spacious. Triggers layout() repaint (lane height + mark radius change). `?density=…` URL state. - **Export menu:** native `<details>/<summary>` so it’s keyboard-accessible without JS. 6 outputs: - **SVG** — `XMLSerializer` over an SVG clone with --chart-* tokens inlined via `getComputedStyle` so standalone files paint correctly in image viewers. - **PNG** — SVG → Image → Canvas at 2× DPR, white background painted before drawImage. - **PDF** — `window.print()` + `@media print` stylesheet (A4 landscape, palette forced to b&w, chrome hidden). No chromedp dep. - **CSV** — 20-column flat schema, UTF-8 BOM (Excel-DE), RFC 4180 escaping. - **JSON** — wire envelope + export metadata header. - **iCal** — `GET /api/projects/{id}/timeline.ics` (server-side, reuses `internal/services/caldav_ical.go`). Only deadlines + appointments; projected/milestone explicitly skipped per faraday-Q6. ### Verification - `go build ./...` clean - `bun run build` clean (2199 i18n keys; data-i18n scan clean) - `go test ./internal/...` all packages ok (incl. 4 new `FormatTimelineICS` cases + 1 `serveChartNotFound` test) - `bun test src/client/views/shape-timeline-chart.test.ts` 27/27 pass ### Deferred to Slice 3 Sidebar entry for active project (head Slice 2 item 5): the current Sidebar component reads only static config — there’s no per-page-context plumbing. Adding a project-scoped chip needs either a sidebar override mechanism or a separate contextual-action area; both are real refactors. The Slice 1 "Als Chart anzeigen ↗" link in the Verlauf tab already covers the discovery path. Sidebar entry pairs cleanly with Slice 3 (zoom + range + permalink state) since both are page-scoped UX work. ### Edge cases flagged during impl 1. **SVG export inlines palette tokens via `getComputedStyle`** — works for `--chart-*` colours but doesn’t inline density-derived geometry. Geometry is already baked into x/y/r attrs by `layout()`, so this is intentional, not a leak. 2. **PNG export paints white before drawImage** so dark-mode users don’t get an invisible transparent PNG on a white slide. 3. **iCal endpoint runs `GetByID` twice** (once inside `ProjectionService.For` for visibility, once for the X-WR-CALNAME title). Minor double-query; not worth a service refactor for one endpoint. Flagged for future opt. 4. **Browser-print PDF on Safari** — Safari ignores some `@page` rules; design §13 already documented "use Chrome for archival exports". No code change. ### SLICE 2 READY FOR REVIEW m gates the merge.
Author
Collaborator

Slice 2 shipped to main: 3ba5727 (merge).

Five atomic commits in order:

  1. 968b0bc — closed Slice 1 edge case #2 (visibility-leak on /projects/{id}/chart handler — now resolves via ProjectService.GetByID, ErrNotVisible → 404 + notfound chrome, never leaks 'valid id' shape).
  2. d1314a4 — palette picker, 5 CSS-token sets + URL state (faraday-Q5).
  3. b24063b — density toggle compact/standard/spacious + URL state.
  4. 98a51fa — exports SVG/PNG/CSV/JSON + browser-print CSS (faraday-Q4 client-side path; chromedp ruled out, abstraction in place for future server-side route).
  5. d8f7745 — iCal feed (deadlines + appointments only, faraday-Q6; no projected rules, avoids trust erosion).

go build ./... clean, bun run build clean, all chart tests green (27 layout-fn + 4 iCal + 1 visibility = 32 tests).

Deferred from Slice 2 → Slice 3: Sidebar project-scoped 'Als Chart anzeigen' entry. The current Sidebar component reads only static config — adding a per-page contextual chip needs either a sidebar-override mechanism or a context-action area outside the sidebar. Both are real refactors. The Slice 1 link on the Verlauf tab still covers the discovery path; sidebar entry is nice-to-have.

Edge cases worth noting:

  • SVG export inlines --chart-* tokens via getComputedStyle so .svg files paint right in viewers without document-css context.
  • PNG export paints a white background under the SVG before drawImage (dark-mode users get visible-on-white slides).
  • iCal endpoint runs GetByID twice (visibility check + title fetch). Minor double-query, flagged for future optimization.
  • Browser-print PDF: Safari ignores some @page rules — design §13 already documented 'use Chrome for archival exports'.

Next: m gates Slice 3 (zoom + range chips + lane filter + permalink state + sidebar entry deferred from Slice 2).

Slice 2 shipped to main: [3ba5727](https://mgit.msbls.de/m/paliad/commit/3ba5727) (merge). Five atomic commits in order: 1. [968b0bc](https://mgit.msbls.de/m/paliad/commit/968b0bc) — closed Slice 1 edge case #2 (visibility-leak on /projects/{id}/chart handler — now resolves via `ProjectService.GetByID`, ErrNotVisible → 404 + notfound chrome, never leaks 'valid id' shape). 2. [d1314a4](https://mgit.msbls.de/m/paliad/commit/d1314a4) — palette picker, 5 CSS-token sets + URL state (faraday-Q5). 3. [b24063b](https://mgit.msbls.de/m/paliad/commit/b24063b) — density toggle compact/standard/spacious + URL state. 4. [98a51fa](https://mgit.msbls.de/m/paliad/commit/98a51fa) — exports SVG/PNG/CSV/JSON + browser-print CSS (faraday-Q4 client-side path; chromedp ruled out, abstraction in place for future server-side route). 5. [d8f7745](https://mgit.msbls.de/m/paliad/commit/d8f7745) — iCal feed (deadlines + appointments only, faraday-Q6; no projected rules, avoids trust erosion). `go build ./...` clean, `bun run build` clean, all chart tests green (27 layout-fn + 4 iCal + 1 visibility = 32 tests). Deferred from Slice 2 → Slice 3: Sidebar project-scoped 'Als Chart anzeigen' entry. The current Sidebar component reads only static config — adding a per-page contextual chip needs either a sidebar-override mechanism or a context-action area outside the sidebar. Both are real refactors. The Slice 1 link on the Verlauf tab still covers the discovery path; sidebar entry is nice-to-have. Edge cases worth noting: - SVG export inlines `--chart-*` tokens via `getComputedStyle` so .svg files paint right in viewers without document-css context. - PNG export paints a white background under the SVG before drawImage (dark-mode users get visible-on-white slides). - iCal endpoint runs `GetByID` twice (visibility check + title fetch). Minor double-query, flagged for future optimization. - Browser-print PDF: Safari ignores some `@page` rules — design §13 already documented 'use Chrome for archival exports'. Next: m gates Slice 3 (zoom + range chips + lane filter + permalink state + sidebar entry deferred from Slice 2).
Author
Collaborator

Slice 3 shipped — coder pass (tesla)

Branch: mai/tesla/project-timeline-chart (pushed, 5 commits ahead of main)

Commits

  • 581fbe7 range chips + custom-range URL state (faraday-Q8)
  • 731e762 lane visibility filter + URL state
  • 5e1f1fe permalink copy-link (faraday-Q10)
  • 1ad7891 sidebar contextual entry (head option-a)
  • e9bcf3a reciprocal "Zurück zum Verlauf" link

What landed

  • Range chips: 4 presets — 1y default (today-1y..today+1y per Q8), 2y, all (derives bounds from loaded events with +30d right pad), custom (date-pair). URL state ?range=…[&from=…&to=…].
  • Lane visibility filter: chip group rendered dynamically after refresh() reports lanes via a new onDataLoaded callback (label/id only exist post-fetch). Hidden when projection has 0-1 lanes. URL state ?lanes=id1,id2. Stale ids auto-drop on refresh; "everything off" collapses to "show all".
  • Permalink copy: 🔗 Link kopieren button reads window.location.href and writes it to clipboard (Clipboard API with execCommand fallback for older / file:// contexts). 1.8s flash on the button = no toast component needed.
  • Sidebar contextual entry: new hidden <a id="sidebar-project-chart-link"> slot in Sidebar TSX, revealed by sidebar.ts.initProjectContextChartLink() when URL matches /projects/{uuid}/... but not the chart itself. Pure URL-parse — pages never touch the sidebar slot. Hidden on /projects (list), /projects/new, and the chart page itself.
  • Reciprocal Verlauf link: back-link on chart page now targets /projects/{id}/history explicitly (Verlauf tab anchor). Label "Zurück zum Verlauf" matches the round-trip from the Slice 1 "Als Chart anzeigen ↗" affordance.

Verification

  • go build ./... clean
  • bun run build clean (2212 i18n keys)
  • go test ./internal/... all packages ok
  • bun test src/client/views/shape-timeline-chart.test.ts 27/27 pass

Edge cases flagged

  1. "all" range derives bounds from events on every repaint, not just initial fetch — adding a milestone reflows after a setRange("all") re-application; the chip re-applies via the existing change handler.
  2. Lane chip group renders client-side (lane labels are post-fetch state). onDataLoaded callback added to ChartMountOpts.
  3. Stale lane ids in URL allowlist auto-drop on every refresh. If everything drops → null (show all) so an empty chart never persists.
  4. Sidebar entry is cleaner than head's original option-a description: pure URL parse in sidebar.ts, no body data-attribute or per-page handshake. Pages stay unaware of the sidebar slot.
  5. Permalink uses navigator.clipboard in secure contexts, execCommand fallback otherwise. Visual feedback flashes 1.8s on the button.

SLICE 3 READY FOR REVIEW

m gates the merge. Slice 4 next is Custom Views shape="timeline" integration.

## Slice 3 shipped — coder pass (tesla) **Branch:** `mai/tesla/project-timeline-chart` (pushed, 5 commits ahead of main) ### Commits - [581fbe7](../commit/581fbe7) range chips + custom-range URL state (faraday-Q8) - [731e762](../commit/731e762) lane visibility filter + URL state - [5e1f1fe](../commit/5e1f1fe) permalink copy-link (faraday-Q10) - [1ad7891](../commit/1ad7891) sidebar contextual entry (head option-a) - [e9bcf3a](../commit/e9bcf3a) reciprocal "Zurück zum Verlauf" link ### What landed - **Range chips:** 4 presets — 1y default (today-1y..today+1y per Q8), 2y, **all** (derives bounds from loaded events with +30d right pad), **custom** (date-pair). URL state `?range=…[&from=…&to=…]`. - **Lane visibility filter:** chip group rendered dynamically after refresh() reports lanes via a new `onDataLoaded` callback (label/id only exist post-fetch). Hidden when projection has 0-1 lanes. URL state `?lanes=id1,id2`. Stale ids auto-drop on refresh; "everything off" collapses to "show all". - **Permalink copy:** `🔗 Link kopieren` button reads `window.location.href` and writes it to clipboard (Clipboard API with execCommand fallback for older / file:// contexts). 1.8s flash on the button = no toast component needed. - **Sidebar contextual entry:** new hidden `<a id="sidebar-project-chart-link">` slot in Sidebar TSX, revealed by `sidebar.ts.initProjectContextChartLink()` when URL matches `/projects/{uuid}/...` but not the chart itself. Pure URL-parse — pages never touch the sidebar slot. Hidden on /projects (list), /projects/new, and the chart page itself. - **Reciprocal Verlauf link:** back-link on chart page now targets `/projects/{id}/history` explicitly (Verlauf tab anchor). Label "Zurück zum Verlauf" matches the round-trip from the Slice 1 "Als Chart anzeigen ↗" affordance. ### Verification - `go build ./...` clean - `bun run build` clean (2212 i18n keys) - `go test ./internal/...` all packages ok - `bun test src/client/views/shape-timeline-chart.test.ts` 27/27 pass ### Edge cases flagged 1. "all" range derives bounds from events on every repaint, not just initial fetch — adding a milestone reflows after a `setRange("all")` re-application; the chip re-applies via the existing change handler. 2. Lane chip group renders client-side (lane labels are post-fetch state). `onDataLoaded` callback added to `ChartMountOpts`. 3. Stale lane ids in URL allowlist auto-drop on every refresh. If everything drops → null (show all) so an empty chart never persists. 4. Sidebar entry is cleaner than head's original option-a description: pure URL parse in `sidebar.ts`, no body data-attribute or per-page handshake. Pages stay unaware of the sidebar slot. 5. Permalink uses navigator.clipboard in secure contexts, execCommand fallback otherwise. Visual feedback flashes 1.8s on the button. ### SLICE 3 READY FOR REVIEW m gates the merge. Slice 4 next is Custom Views shape="timeline" integration.
Author
Collaborator

Slice 3 shipped to main. Five atomic commits:

  1. 581fbe7 — range chips (default 1y, +2y / all / custom) + URL state (faraday-Q8).
  2. 731e762 — lane visibility filter + URL state.
  3. 5e1f1fe — permalink copy-link + URL params consolidation (faraday-Q10).
  4. 1ad7891 — sidebar contextual entry (URL-parse based, no per-page handshake — cleaner separation than the data-attr option originally sketched).
  5. e9bcf3a — reciprocal Zurück zum Verlauf link.

All tests green (32 = 27 chart-layout + 4 iCal + 1 visibility). Edge cases worth noting:

  • all range derives bounds from loaded events on every repaint; adding a milestone reflows the canvas if you re-trigger setRange.
  • Lane filter chips hidden when lanes < 2 (no UX value on single-track render).
  • Stale lane ids in URL allowlist auto-drop on refresh; if everything drops, falls back to show-all (instead of empty chart).
  • Permalink copy uses navigator.clipboard in secure contexts, falls back to execCommand for older browsers. 1.8s color flash confirms (no toast needed).

Next: Slice 4 = Custom Views shape=timeline integration (faraday-Q7 explicit go/no-go after Slice 3 ships). Tesla parked.

Slice 3 shipped to main. Five atomic commits: 1. [581fbe7](https://mgit.msbls.de/m/paliad/commit/581fbe7) — range chips (default 1y, +2y / all / custom) + URL state (faraday-Q8). 2. [731e762](https://mgit.msbls.de/m/paliad/commit/731e762) — lane visibility filter + URL state. 3. [5e1f1fe](https://mgit.msbls.de/m/paliad/commit/5e1f1fe) — permalink copy-link + URL params consolidation (faraday-Q10). 4. [1ad7891](https://mgit.msbls.de/m/paliad/commit/1ad7891) — sidebar contextual entry (URL-parse based, no per-page handshake — cleaner separation than the data-attr option originally sketched). 5. [e9bcf3a](https://mgit.msbls.de/m/paliad/commit/e9bcf3a) — reciprocal Zurück zum Verlauf link. All tests green (32 = 27 chart-layout + 4 iCal + 1 visibility). Edge cases worth noting: - `all` range derives bounds from loaded events on every repaint; adding a milestone reflows the canvas if you re-trigger setRange. - Lane filter chips hidden when lanes < 2 (no UX value on single-track render). - Stale lane ids in URL allowlist auto-drop on refresh; if everything drops, falls back to show-all (instead of empty chart). - Permalink copy uses navigator.clipboard in secure contexts, falls back to execCommand for older browsers. 1.8s color flash confirms (no toast needed). Next: Slice 4 = Custom Views `shape=timeline` integration (faraday-Q7 explicit go/no-go after Slice 3 ships). Tesla parked.
Author
Collaborator

Slice 4 (FINAL) shipped — coder pass (tesla)

Branch: mai/tesla/project-timeline-chart (pushed, 2 commits ahead of main)

This is the last slice of the 4-slice roadmap. The original design doc roadmap is complete.

Commits

  • 83a3d27 ShapeTimeline enum + render_spec wiring (backend)
  • 1c91563 Custom Views timeline-shape host (frontend — adapter + UI + caveat)

I combined the head's prescribed C2 + C3 (host + caveat tooltip) into one commit because the banner is meaningless without the timeline host markup — they ship as one user-visible surface. The optional C4 (cross-project lane aggregation) is built into C2's adapter — lane axis = project_id with first-seen ordering.

What landed

  • ShapeTimeline enum added to internal/services/render_spec.go alongside list/cards/calendar. New TimelineConfig struct persists palette/density/range_preset/range_from/range_to in user_views.render_spec (jsonb) so reopening a saved CV-timeline view restores the same visual. Validator rejects unknown enum values + length-bounds ISO date strings.
  • shape-timeline-cv.ts adapter (Custom Views host):
    • deadlinekind="deadline" + deadline_id, status from detail.status
    • appointmentkind="appointment" + appointment_id
    • project_eventkind="milestone" + project_event_id
    • approval_requestskipped (no chart-meaningful date)
    • Lane axis = project_id (cross-project chart use case per §10); rows without project_id collapse to a synthetic "self" lane
    • Status defaults to "open" (deadlines pull from detail.status when present)
  • ChartMountOpts.staticData escape hatch on the existing renderer: when supplied, mount() skips the project-timeline fetch and paints from pre-loaded data. Lets the CV adapter reuse the entire Slice 1-3 surface (palette / density / range / lane filter / permalink) for free.
  • Caveat banner: one-time-per-session dismissible banner explains that Custom Views show actual events only (no projected rows from ProjectionService — ViewService doesn't run the calculator). Per design §13.4.
  • Shape switcher: new "Timeline" chip on /views toolbar; views.ts dispatches to renderTimelineShape and disposes the prior chart handle on shape flips to prevent listener leaks.

Verification

  • go build ./... clean
  • go test ./internal/... ok (incl. 9-case TestRenderSpec_TimelineConfigValidates)
  • bun run build clean (2214 i18n keys, data-i18n scan clean)
  • bun test src/client/views/ 56/56 pass (27 chart layout + 13 new CV adapter + 16 existing format/url-codec)

Edge cases flagged

  1. Render-shape switch disposes the prior timeline chart handle on every flip — without this, resize listeners would leak between mounts.
  2. Caveat banner dismissal uses sessionStorage (resets across sessions). Could be promoted to localStorage if users complain.
  3. Default status="open" for non-deadline kinds = uniform colour in CV chart. Future polish could extract per-kind status from row.detail (appointment.approval_status, project_event.timeline_status).
  4. CV chart handle's refresh() would hit /api/projects/cv/timeline (404). Not currently wired because CV pages don't expose a refresh affordance — if a future feature wants live-reload, the adapter would need to re-run the ViewService query instead.

Slice 4 = roadmap complete

This wraps the 4-slice design-doc roadmap. Backlog (v2 nice-to-haves from §11.5): per-user palette picker beyond fixed presets, server-side PDF render via chromedp, live collaborative cursors / annotations, animations on zoom changes, hybrid layouts (compact-strip + detail-list), color-coding with custom user rules.

SLICE 4 READY FOR REVIEW

m gates the merge.

## Slice 4 (FINAL) shipped — coder pass (tesla) **Branch:** `mai/tesla/project-timeline-chart` (pushed, 2 commits ahead of main) This is the last slice of the 4-slice roadmap. The original design doc roadmap is complete. ### Commits - [83a3d27](../commit/83a3d27) ShapeTimeline enum + render_spec wiring (backend) - [1c91563](../commit/1c91563) Custom Views timeline-shape host (frontend — adapter + UI + caveat) I combined the head's prescribed C2 + C3 (host + caveat tooltip) into one commit because the banner is meaningless without the timeline host markup — they ship as one user-visible surface. The optional C4 (cross-project lane aggregation) is built into C2's adapter — lane axis = `project_id` with first-seen ordering. ### What landed - **`ShapeTimeline` enum** added to `internal/services/render_spec.go` alongside list/cards/calendar. New `TimelineConfig` struct persists palette/density/range_preset/range_from/range_to in `user_views.render_spec` (jsonb) so reopening a saved CV-timeline view restores the same visual. Validator rejects unknown enum values + length-bounds ISO date strings. - **`shape-timeline-cv.ts` adapter** (Custom Views host): - `deadline` → `kind="deadline"` + `deadline_id`, status from `detail.status` - `appointment` → `kind="appointment"` + `appointment_id` - `project_event` → `kind="milestone"` + `project_event_id` - `approval_request` → **skipped** (no chart-meaningful date) - Lane axis = `project_id` (cross-project chart use case per §10); rows without `project_id` collapse to a synthetic `"self"` lane - Status defaults to `"open"` (deadlines pull from `detail.status` when present) - **`ChartMountOpts.staticData` escape hatch** on the existing renderer: when supplied, mount() skips the project-timeline fetch and paints from pre-loaded data. Lets the CV adapter reuse the entire Slice 1-3 surface (palette / density / range / lane filter / permalink) for free. - **Caveat banner:** one-time-per-session dismissible banner explains that Custom Views show actual events only (no projected rows from ProjectionService — `ViewService` doesn't run the calculator). Per design §13.4. - **Shape switcher:** new "Timeline" chip on `/views` toolbar; `views.ts` dispatches to `renderTimelineShape` and disposes the prior chart handle on shape flips to prevent listener leaks. ### Verification - `go build ./...` clean - `go test ./internal/...` ok (incl. 9-case `TestRenderSpec_TimelineConfigValidates`) - `bun run build` clean (2214 i18n keys, data-i18n scan clean) - `bun test src/client/views/` 56/56 pass (27 chart layout + 13 new CV adapter + 16 existing format/url-codec) ### Edge cases flagged 1. Render-shape switch disposes the prior timeline chart handle on every flip — without this, resize listeners would leak between mounts. 2. Caveat banner dismissal uses sessionStorage (resets across sessions). Could be promoted to localStorage if users complain. 3. Default status="open" for non-deadline kinds = uniform colour in CV chart. Future polish could extract per-kind status from `row.detail` (appointment.approval_status, project_event.timeline_status). 4. CV chart handle's `refresh()` would hit `/api/projects/cv/timeline` (404). Not currently wired because CV pages don't expose a refresh affordance — if a future feature wants live-reload, the adapter would need to re-run the ViewService query instead. ### Slice 4 = roadmap complete This wraps the 4-slice design-doc roadmap. Backlog (v2 nice-to-haves from §11.5): per-user palette picker beyond fixed presets, server-side PDF render via chromedp, live collaborative cursors / annotations, animations on zoom changes, hybrid layouts (compact-strip + detail-list), color-coding with custom user rules. ### SLICE 4 READY FOR REVIEW m gates the merge.
Author
Collaborator

Slice 4 (FINAL) shipped. Two atomic commits:

  1. 83a3d27 — ShapeTimeline enum + render_spec wiring (backend). 9-case TestRenderSpec_TimelineConfigValidates.
  2. 1c91563 — Custom Views timeline-shape host (frontend adapter + UI + caveat banner). 13 new tests.

C2+C3 merged into one commit (banner tightly-coupled to host). C4 (cross-project lane aggregation) baked into C2: lane axis = project_id with first-seen ordering, project_title || project_reference fallback labels.

go build ./... clean, bun run build clean, 56/56 frontend tests.

Lossy adapter contract (per design §13.4):

  • deadline → kind=deadline + deadline_id, status from detail.status (done/overdue) else open.
  • appointment → kind=appointment + appointment_id, default status=open.
  • project_event → kind=milestone + project_event_id.
  • approval_request → SKIPPED (no chart-meaningful date semantics).
  • Lane id = project_id || self, label = project_title || project_reference || truncated-uuid.
  • Lane order = first-seen (deterministic).
  • NO projected rows (ViewService doesnt run fristenrechner; one-time-per-session caveat banner explains).

ChartMountOpts.staticData escape-hatch lets the CV adapter feed pre-loaded events directly into the same renderer. All Slice 1–3 features (palette / density / range / lane filter / permalink) work in the CV chart without extra wiring.

TimelineConfig added to backend RenderSpec persists palette / density / range_preset / range_from / range_to in user_views.render_spec so reopening a saved CV-timeline restores the visual.

Edge cases worth noting:

  • The render-shape switch in views.ts now disposes the prior timeline chart handle on every shape flip (prevents resize-listener leaks between mounts).
  • Caveat banner uses sessionStorage for dismissal — survives navigation within session, resets across sessions. Could be promoted to localStorage for permanent dismiss later if users complain.
  • Default status=open for milestones / appointments → uniform colour in CV chart. A future polish slice could extract per-kind status from row.detail (appointment.approval_status, project_event.timeline_status); for v1 the default is consistent.
  • CV chart handles refresh() still hits /api/projects/cv/timeline — a placeholder URL that 404s. CV pages dont expose a refresh affordance today; the adapter would need to re-run the ViewService query if a future feature wants refresh-on-CV-timeline.

Project Timeline / Chart roadmap COMPLETE — Slices 1–4 of docs/design-project-chart-2026-05-09.md all shipped. Issue stays open for m to close on review.

Slice 4 (FINAL) shipped. Two atomic commits: 1. [83a3d27](https://mgit.msbls.de/m/paliad/commit/83a3d27) — ShapeTimeline enum + render_spec wiring (backend). 9-case TestRenderSpec_TimelineConfigValidates. 2. [1c91563](https://mgit.msbls.de/m/paliad/commit/1c91563) — Custom Views timeline-shape host (frontend adapter + UI + caveat banner). 13 new tests. C2+C3 merged into one commit (banner tightly-coupled to host). C4 (cross-project lane aggregation) baked into C2: lane axis = project_id with first-seen ordering, project_title || project_reference fallback labels. `go build ./...` clean, `bun run build` clean, 56/56 frontend tests. **Lossy adapter contract** (per design §13.4): - `deadline` → kind=`deadline` + deadline_id, status from detail.status (done/overdue) else `open`. - `appointment` → kind=`appointment` + appointment_id, default status=`open`. - `project_event` → kind=`milestone` + project_event_id. - `approval_request` → SKIPPED (no chart-meaningful date semantics). - Lane id = project_id || `self`, label = project_title || project_reference || truncated-uuid. - Lane order = first-seen (deterministic). - NO projected rows (ViewService doesnt run fristenrechner; one-time-per-session caveat banner explains). **`ChartMountOpts.staticData`** escape-hatch lets the CV adapter feed pre-loaded events directly into the same renderer. All Slice 1–3 features (palette / density / range / lane filter / permalink) work in the CV chart without extra wiring. **`TimelineConfig`** added to backend `RenderSpec` persists palette / density / range_preset / range_from / range_to in `user_views.render_spec` so reopening a saved CV-timeline restores the visual. Edge cases worth noting: - The render-shape switch in `views.ts` now disposes the prior timeline chart handle on every shape flip (prevents resize-listener leaks between mounts). - Caveat banner uses sessionStorage for dismissal — survives navigation within session, resets across sessions. Could be promoted to localStorage for permanent dismiss later if users complain. - Default status=`open` for milestones / appointments → uniform colour in CV chart. A future polish slice could extract per-kind status from row.detail (appointment.approval_status, project_event.timeline_status); for v1 the default is consistent. - CV chart handles `refresh()` still hits `/api/projects/cv/timeline` — a placeholder URL that 404s. CV pages dont expose a refresh affordance today; the adapter would need to re-run the ViewService query if a future feature wants refresh-on-CV-timeline. **Project Timeline / Chart roadmap COMPLETE** — Slices 1–4 of `docs/design-project-chart-2026-05-09.md` all shipped. Issue stays open for m to close on review.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#35
No description provided.