2 Commits

Author SHA1 Message Date
mAi
825894f511 refactor(mcp): wire aggregator directly, drop TimelineBuilder seam
Phase 5a slice D. The MCP timeline tool no longer depends on
*web.Server — it talks to *aggregate.Aggregator directly. The wrong-way
mcp → web layering that necessitated the TimelineBuilder interface is
gone.

- mcp/tools.go: TimelineBuilder interface deleted.
  RegisterProjaxTools(s, st, agg *aggregate.Aggregator) now takes the
  aggregator directly; passing nil keeps the timeline tool unregistered
  (kill-switch contract unchanged).
- mcp/tools.go: TimelineArgs moved from web/ to mcp/ since it is the
  MCP-facing input shape. The timeline tool runs the full pipeline:
  store.ListByFilters → in-mem timeline-exclude + has-link narrowing →
  agg.All(...) → Result.ToTimelineRows() → aggregate.BuildTimelineDays
  → timelineView. No web/ import in the timeline path.
- internal/aggregate/rows.go: new Result.ToTimelineRows() helper that
  projects the typed rows into the flat TimelineRow sum-type both
  web/timeline.go and mcp/tools.go consume. Single source of truth for
  the Date-anchor choice across kinds.
- internal/aggregate/timeline_days.go: FormatPERDate lifted from web/
  so timeline-row builders outside web/ can render PER strings without
  re-importing web/.
- web/timeline.go: BuildTimelinePayloadFromArgs + TimelineArgs deleted
  (no remaining callers — slice D inlined the MCP path).
- cmd/projax/main.go: pass srv.Aggregator() into RegisterProjaxTools.

MCP tree-filter parity note: the move to store.ListByFilters narrows
status to a single value (first of args.Status) and AND-matches
management (vs the web TreeFilter's OR). m's documented MCP uses
(tag + default status) round-trip identically. Logged as a footnote in
docs/plans/aggregator-refactor.md.

All mcp + web + aggregate tests green.

Task: t-projax-5a-aggregator
2026-05-22 00:15:07 +02:00
mAi
326f4c83b9 feat(aggregate): introduce internal/aggregate/ for fan-out + day-grouping
Phase 5a slice A: a new package that concentrates the "fan out across
linked items" pattern web/dashboard.go, web/timeline.go and mcp/tools.go
each had separate copies of. No callers touch it yet — slices B/C/D
migrate them in turn.

- Aggregator with five methods (Todos/Events/Issues/Docs/Creations) plus
  All convenience for the MCP timeline. Each method takes a *store.Item
  slice and (optionally) a Window, returns typed Row slices.
- Row types embed the underlying caldav.Todo / caldav.Event / gitea.Issue
  so existing html/template field accesses (.Todo.UID, .Event.Summary,
  …) keep resolving via Go field promotion in slices B/C.
- TimelineRow sum-type wrapper (with pointer slots per Kind) plus the
  flat template-friendly fields. Lifted-but-untouched from web/.
- BuildTimelineDays + SortTimelineRows + EventStartLabel +
  EventDurationHint lifted near-verbatim from web/timeline.go.
- CalDAV/Gitea/Store interfaces in the aggregator so unit tests stub IO
  cleanly. Real *caldav.Client / *gitea.Client / *store.Store satisfy
  by method set.
- Per-source error handling preserved: log at WARN + skip the bad
  fetch, return surviving rows.

Tests cover empty inputs, fan-out call counts, per-source error
recovery, window narrowing for todos, issue-cache hit path, doc/creation
allow-list filtering, BuildTimelineDays asc/desc order, sticky pills,
far-future fade, within-day sort.

Plan doc captures the slicing strategy + design decisions:
docs/plans/aggregator-refactor.md.

Task: t-projax-5a-aggregator
2026-05-21 23:57:54 +02:00