m
b516201110
feat(t-paliad-144 A1): backend substrate + Custom Views API
Phase A1 of the data-display-model rethink (m/paliad#5). Backend-only;
no user-visible change in A1. A2 (frontend) lands separately.
What's new:
- Migration 056: paliad.user_views table with RLS scoped to caller
(user_views_owner_all on auth.uid()=user_id). Composite UNIQUE
(user_id, slug). No is_system flag — system defaults stay code-
resident per Q8 lock-in.
- internal/services/filter_spec.go (+test): structured FilterSpec
with Sources / Scope / Time / Predicates. Server-side validator
rejects unknown sources, duplicate sources, conflicting scope
modes, horizon=all without explicit projects (Q26 clamp), and
every per-source enum (deadline.status, appointment_types,
project_event kinds, approval_request status / viewer_role).
- internal/services/render_spec.go (+test): RenderSpec with three
shapes (list / cards / calendar — Q4 lock-in 2026-05-07).
Per-shape config kept separately so flipping shapes preserves
tweaks. Validator over column / sort / density / group_by /
default_view enums.
- internal/services/system_views.go (+test): code-resident
SystemView definitions for dashboard / agenda / events / inbox /
inbox-mine. Reserved-slug list (Q23) prevents user-views from
colliding with top-level URLs. Case-folded matching.
- internal/services/view_service.go: extends EventService with
RunSpec — runs a FilterSpec across all four substrate sources
(deadline + appointment + project_event + approval_request)
and merges into []ViewRow sorted by event_date. ViewRow is a
discriminated projection (kind + common header + per-source
Detail json.RawMessage). Q17 fail-open attribution: returns
inaccessible_project_ids for explicit-scope queries where the
caller can't see some IDs.
- internal/services/user_view_service.go (+test): CRUD on
paliad.user_views — Create (server-assigns sort_order MAX+1
in tx), GetBySlug, GetByID, Update (partial), Delete, Touch
(last_used_at), MostRecent. Reserved-slug + slug-format
validators on every write.
- internal/handlers/views.go: nine HTTP handlers wiring the
endpoints (GET/POST/PATCH/DELETE /api/user-views/...,
POST /api/user-views/{id}/touch, POST /api/views/run,
POST /api/views/{slug}/run, GET /api/views/system).
- main.go + handlers.go + projects.go: wire UserViewService
into the bundle; conditional route registration when both
UserView + Event services are present.
Pure-Go tests (no DB): 32 cases pass — filter spec validators,
render spec validators, system view registry, reserved slugs.
Live-DB tests (skip when TEST_DATABASE_URL unset): 12 cases
covering create / list / get / uniqueness / update / delete /
touch / most-recent / reserved-slug / bad-slug / empty-name /
invalid-spec.
Coexists with t-139 (in-flight on noether's other branch) and
t-138 (shipped) without coordination commits — RunSpec uses the
existing visibility predicate that t-139's migration 055 will
extend with derivation. Approval-request source delegates to
ApprovalService.ListPendingForApprover / ListSubmittedByUser
(both already extended for derived_peer authority in t-139 Phase 3).
Files: 15 changed, 3134 insertions. Build clean. Tests green.
2026-05-07 12:51:37 +02:00
..
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-22 23:38:03 +02:00
2026-04-30 16:46:31 +02:00
2026-05-04 14:40:53 +02:00
2026-05-06 16:24:31 +02:00
2026-05-06 16:00:17 +02:00
2026-04-29 19:12:11 +02:00
2026-04-30 16:46:31 +02:00
2026-04-18 02:23:50 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-05-01 09:48:25 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-16 17:27:42 +02:00
2026-04-30 16:46:31 +02:00
2026-05-06 12:47:12 +02:00
2026-05-04 14:40:53 +02:00
2026-05-06 16:24:31 +02:00
2026-05-06 16:41:41 +02:00
2026-04-14 19:32:07 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-05-04 14:40:53 +02:00
2026-05-04 19:49:37 +02:00
2026-04-30 16:46:31 +02:00
2026-05-05 11:18:38 +02:00
2026-05-05 11:39:30 +02:00
2026-05-06 12:50:59 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-05-07 12:51:37 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-26 00:36:33 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 16:46:31 +02:00
2026-04-20 17:44:45 +02:00
2026-05-07 12:51:37 +02:00
2026-04-26 10:48:27 +02:00
2026-04-30 02:29:09 +02:00
2026-04-30 02:29:09 +02:00
2026-04-22 23:36:10 +02:00
2026-04-30 16:46:31 +02:00
2026-04-30 03:42:25 +02:00
2026-04-30 04:39:23 +02:00
2026-04-30 16:46:31 +02:00
2026-05-07 12:51:37 +02:00