Files
projax/docs/plans/views-redesign.md
mAi 590bb28063 docs: Phase 5j Views-redesign plan — paliad-shape first-class views
m's feedback on 5i (verbatim): "It's not really what I wanted. It
should like the paliad custom views, not of the existing views a
variant but individually created views."

5i modelled views as overlays on existing pages (?view=<uuid>). m wants
the paliad model: views are first-class URLs (/views/{slug}), each one
its own page. System defaults (dashboard, calendar, timeline, ...)
share the route shape with reserved slugs; user-created views land
beside them.

Plan covers: schema redesign (slug as URL key, drop is_default_for +
pinned, add icon + sort_order + show_count + last_used_at), four-route
table (landing with MRU redirect, render, editor blank/edit), system-
view shape (hybrid alias recommendation under Q1), editor surface
(dedicated pages, not modal), migration path from 5i (drop table +
delete overlay code; keep view_type enum and per-view_type renderers),
seven-slice implementation chain (A schema → B routes → C system views
→ D editor → E sidebar → F cleanup → G polish).

11 open questions batched in §9 — head delegation pending. NO chip-
picker without head's explicit re-grant (5i permission was one-time).

No code changes; this branch ships docs only. Coder shifts wait on m's
sign-off via head's relay.
2026-05-26 15:23:35 +02:00

27 KiB

Views redesign — paliad-shape first-class views (Phase 5j)

Status: Phase A design (this doc). Branch: mai/kahn/phase-5j-views-redesign. Author: kahn (inventor), 2026-05-26. Source feedback (m, 13:19 2026-05-26): "It's not really what I wanted. It should like the paliad custom views, not of the existing views a variant but individually created views."

Replaces: Phase 5i. Hours-old, no real data, drop-and-rebuild is the cleanest path.


§1 — Diagnosis: why 5i diverged from intent

5i modelled views as an overlay on top of existing pages. The contract was:

User opens /?view=<uuid> → the saved filter+view_type fields onto whatever the existing tree handler renders.

That choice flowed from m's original phrasing: "view types (card / list / calendar / kanban)" — which sounded like skin-on-top-of-pages. Implementation followed: TreeFilter grew a ViewID, an applySavedView overlay landed in the tree handler, the sidebar Views entry pointed to /views as a list-management page, and saved views had no URL of their own.

m's actual mental model, anchored in paliad: a view IS a page. The slug goes in the URL. System defaults (dashboard, calendar, timeline, ...) and user-created views share the same /views/{slug} route shape. Nothing is "an overlay" — views are first-class destinations, indexed in the sidebar, with their own editor.

The fix: tear out the 5i overlay code and rebuild around the paliad model. This redesign mirrors paliad's structure but adapts to projax's constraints (single-user, no auth.uid(), no RLS, existing route surface).


§2 — paliad-shape data model for projax

Schema (migration 0017_views_redesign.sql)

Recommendation: hard-replace. Drop projax.views (created hours ago in 5i Slice D), recreate fresh. No real user data lost — at most a couple of throwaway saved-view rows from m's testing.

DROP TABLE IF EXISTS projax.views CASCADE;

CREATE TABLE projax.views (
  id            uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  slug          text NOT NULL,
  name          text NOT NULL,
  icon          text,                              -- nullable; matches frontend icon registry
  filter_json   jsonb NOT NULL DEFAULT '{}'::jsonb,
  view_type     text NOT NULL,                     -- card | list | calendar | kanban | timeline
  sort_field    text,
  sort_dir      text,
  group_by      text,
  sort_order    integer NOT NULL DEFAULT 0,
  show_count    boolean NOT NULL DEFAULT false,
  last_used_at  timestamptz,
  created_at    timestamptz NOT NULL DEFAULT now(),
  updated_at    timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT views_view_type_chk
    CHECK (view_type IN ('card','list','calendar','kanban','timeline')),
  CONSTRAINT views_sort_dir_chk
    CHECK (sort_dir IS NULL OR sort_dir IN ('asc','desc')),
  CONSTRAINT views_kanban_needs_group
    CHECK (view_type <> 'kanban' OR group_by IS NOT NULL),
  CONSTRAINT views_slug_format_chk
    CHECK (slug ~ '^[a-z0-9][a-z0-9-]{0,62}$')
);

CREATE UNIQUE INDEX views_slug_uniq ON projax.views (slug);
CREATE INDEX views_sort_order_idx ON projax.views (sort_order, name);

-- updated_at trigger reused from 0016 (kept under a new name or recreated).
CREATE OR REPLACE FUNCTION projax.views_touch_updated_at()
  RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
  NEW.updated_at := now();
  RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS views_touch_updated_at ON projax.views;
CREATE TRIGGER views_touch_updated_at
  BEFORE UPDATE ON projax.views
  FOR EACH ROW EXECUTE FUNCTION projax.views_touch_updated_at();

Key shifts from 5i

field 5i 5j reason
primary key uuid only uuid; slug is the URL key paliad parity — URLs use slugs, not uuids
slug absent required, unique, regex-validated URL routability
icon absent nullable text sidebar icon picker
sort_order absent server-assigned MAX+1 drag-reorder; paliad parity
show_count absent bool, opt-in sidebar row-count badge; opt-in cost
last_used_at absent nullable timestamptz /views landing MRU redirect
pinned bool dropped sort_order subsumes the use case
is_default_for text page dropped per-page-default model gone; MRU replaces it

filter_json shape

Unchanged from 5i (the JSON shape stayed correct). Keys mirror TreeFilter dims: q, tags[], management[], status[], has_links[], public, show_archived, project_path, include_descendants. The shape is forward-compatible; new TreeFilter dimensions land without migrations.

view_type stays a top-level column (not inside filter_json) because the editor + sidebar both read it without needing to parse JSON.

Single-user simplifications vs paliad

  • No user_id column — projax is Tailscale-only single-user.
  • No RLS — same reason.
  • UNIQUE (slug) is global, not per-user.

If multi-user ever lands, the column + index gain a user_id prefix; the rest of the design holds.


§3 — Reserved slugs (system views)

The big call: do existing pages become system views, or do they stay distinct routes?

Three options

(a) Keep current routes; add /views/{slug} for user views only.

  • /, /dashboard, /calendar, /timeline, /graph stay exactly as today.
  • /views/{slug} is exclusively for user-created views.
  • Reserved-slug list is just {new, edit} (the literal route segments) + any future top-level URL we'd not want a user view to shadow.
  • Cost: nothing changes for muscle memory. User views are an additive concept beside existing pages.
  • Drawback: the conceptual asymmetry m flagged stays — system pages live at ///dashboard, user views live at /views/{slug}. Two URL families.

(b) Full migration. Existing pages become system views at /views/{slug}.

  • New URLs: /views/tree, /views/dashboard, /views/calendar, /views/timeline, /views/graph (or drop graph from the unified shape — see §3.1).
  • Legacy /, /dashboard, etc. become 301 redirects to their /views/{slug} counterpart.
  • Reserved slugs: {tree, dashboard, calendar, timeline, graph, new, edit, admin, login, logout, healthz, mcp, static, i, views} — everything projax owns at the top level.
  • Cost: every internal link in templates needs updating; bookmarks 301 (fine); browser muscle memory absorbs after one shift.
  • Benefit: one URL family. The "create a new view" mental model is uniform with how system pages live.

(c) Hybrid. Legacy routes stay; /views/{slug} aliases system pages and hosts user views.

  • / keeps serving the tree; also /views/tree resolves to the same handler.
  • /dashboard keeps; also /views/dashboard. Etc.
  • Reserved slugs match (b) for the same coverage.
  • User views land at /views/{their-slug} alongside system slugs in one URL family.
  • Cost: small — system-view handlers register two route entries instead of one. No redirects to maintain.
  • Benefit: muscle memory + bookmark stability AND first-class /views/{slug} URL family. Two paths to the same render; user picks whichever they remember. If /views/{slug} catches on, a future shift can deprecate the legacy URLs cleanly.

Inventor pick: (c) hybrid

Reasoning: m's bug report explicitly said "individually created views" — the gap was user-view first-classness, not legacy-URL banishment. (c) closes the gap with zero migration cost. (b) is cleaner architecturally but introduces avoidable churn; the upside (one URL family) doesn't outweigh the risk of breaking some link or muscle-memory in m's daily flow. (a) leaves the two-families asymmetry m's feedback was pointing at.

This is Q1 in §9 — head should ratify or override before coder.

§3.1 — Graph as a system view?

Graph is the DAG SVG render. It's NOT in the view_type enum (per 5i design, intentionally — graph is its own visualization, not a "list of items rendered as X"). Recommend: keep /graph and /views/graph (under (c)) but graph is not a user-creatable view_type — the create form omits it. Reserved slug graph blocks user views from clobbering it.

Reserved-slug list (combining (c) + projax's existing top-level routes)

var reservedViewSlugs = []string{
    // System pages (also reachable via /views/<slug> as aliases under (c)):
    "tree", "dashboard", "calendar", "timeline", "graph",
    // /views sub-routes:
    "new", "edit",
    // Top-level application URLs:
    "admin", "login", "logout", "healthz", "mcp", "static", "i", "views",
}

§4 — Routes

For option (c). Under (b), drop the legacy entries; under (a), drop the /views/{system-slug} aliases.

route handler renders semantics
GET /views handleViewsLanding 302 to MRU view, else onboarding shell landing
GET /views/{slug} handleViewRender view template per view_type render saved or system view
GET /views/new handleViewEditor editor blank editor — new
GET /views/{slug}/edit handleViewEditor editor pre-filled editor — edit existing
POST /views handleViewCreate redirect to /views/{slug} create
POST /views/{slug} handleViewUpdate redirect to /views/{slug} update
POST /views/{slug}/delete handleViewDelete redirect to /views delete
POST /views/reorder handleViewReorder 204 / HTMX OK drag-reorder (slice G)
POST /views/{slug}/touch handleViewTouch 204 fire-and-forget bump last_used_at on render

The render path (GET /views/{slug}):

  1. Resolve slug. If a user view → load row. If a reserved system slug → load the corresponding code-resident SystemView struct.
  2. Touch last_used_at (user views only — system views don't track MRU per call).
  3. Dispatch to the view_type's renderer (the same per-view-type templates from 5i: tree_card.tmpl, tree_kanban.tmpl, tree_section.tmpl for list, plus the existing calendar_section.tmpl and timeline_section.tmpl).
  4. Apply chip-overlay semantics from the 5i fix — URL chips overlay the saved filter so chip clicks narrow within the view (the one piece of 5i worth keeping; see §7).

Editor (GET /views/new and GET /views/{slug}/edit) is a dedicated full-page form, not a modal. Paliad shipped dedicated pages; projax inherits the same shape.


§5 — Sidebar integration

Replace the single "Views" sidebar entry (5i) with a "Views" section listing every user view. System views stay in the existing main-nav block at the top; they're already the muscle-memory entries (Tree, Dashboard, Calendar, Timeline, Graph).

ASCII sketch (5g sidebar shape, with 5j additions):

[ sidebar ]
─────────────
  ⌂ Tree
  □ Dashboard
  ▣ Calendar
  ⊿ Timeline
  ⨀ Graph
─────────────
  Views                    ← new section header
    📂 Active mai work     ← user view (icon + name)
    ⏰ This week deadlines  ← row-count badge if show_count
    ★ Patents kanban       ← drag-reorder handle on hover
    + New view             ← /views/new
─────────────
  ⚙ Admin
─────────────
  ☾ Theme

The Views section's entries come from ListViews() ordered by sort_order ASC, then name. Each entry:

  • Icon resolved against a small frontend registry (the icon column is a key; the registry maps it to an SVG). Keys: folder, clock, star, tag, file-text, box, inbox, etc. Default key: folder.
  • Optional badge with row count when show_count=true — computed by running the view's filter against ListAll() (cheap; projax's scale is ~150 items max).
  • Active state when the current URL is /views/{this-slug} or a legacy alias resolving to it.

Drag-reorder lands in a later slice (G). Click-to-open is the v1 interaction.

Mobile bottom-nav drawer (5g slice B) gets the same section.


§6 — Editor surface

Single editor template (templates/view_editor.tmpl) reused for both /views/new and /views/{slug}/edit. Distinguishes via the presence of .View in the data map.

Fields:

  • Name — text input, required, max 80 chars.
  • Slug — text input, regex ^[a-z0-9][a-z0-9-]{0,62}$, auto-derived from name via HTMX on change against a POST /views/derive-slug?name=<x> helper endpoint OR on the client (simpler: derive on the server side in handleViewCreate if the field is empty; provide a "regenerate" link in edit mode). m can hand-edit.
  • Icon<select> with the registered icon keys + a visible preview. Slice D ships the form field; the registry SVG additions can grow incrementally.
  • View type — radio group (5 values: card/list/calendar/kanban/timeline).
  • Filter (chip strip) — full TreeFilter chip strip inline in the editor: tag, mgmt, status, has, public, project picker + descendants toggle. Each chip click updates a hidden filter_json field via HTMX — so the editor's preview pane reflects the saved filter live.
  • Sort field — text input (title / updated_at / start_time).
  • Sort dir<select> (asc/desc).
  • Group by<select> (status/area/tag/management). Required when view_type=kanban.
  • Show count — checkbox.

A small "Preview" pane next to the form shows the first N items the filter currently matches. Optional in slice D; can land in slice G if scope bites.

Save → 302 to /views/{slug}. Cancel → /views (or the previous URL if HTMX-loaded).

Drops the HTMX modal the 5i fix-shift added — dedicated pages are clearer for a page-level concept and match paliad's pattern.


§7 — Migration from 5i overlay

Specific deletions and salvages:

Code to delete

file what to remove
web/tree_filter.go ViewID field on TreeFilter; ParseTreeFilter/QueryString handling
web/views.go applySavedView, applyDefaultView, overlayURLFields, filterQueryToJSON/filterJSONToQuery, the Prefill index handler logic
web/server.go the ?view=<uuid> overlay block in handleTree; the DefaultBanner data map field
web/templates/tree_section.tmpl the default-banner block; the <input type="hidden" name="view">
web/templates/views.tmpl full rewrite — it's the list-management surface, redesigned in §5 + §6
web/templates/view_edit.tmpl full rewrite to the new editor shape

Code to keep

  • templates/tree_card.tmpl, templates/tree_kanban.tmpl — these are per-view_type renderers, reusable.
  • web/view_type.go (the 5-value enum + PageViewTypes catalog) — still valid as the renderer dispatch table.
  • web/kanban.go (BuildKanbanBoard) — view_type=kanban consumer.
  • templates/project_chip.tmpl — the project filter chip strip works inside the editor.
  • The 5i chip-overlay-on-saved-view fix is the one piece of substance worth keeping conceptually: on /views/{slug}, URL chip params overlay the saved filter. The overlay function gets a new home (handleViewRender's filter-resolution path) but the rule is the same.

Backwards compatibility for the old ?view=<uuid> URL

Two options:

  • (i) 404 on ?view= for existing pages — the URL never makes sense in the new model. Cost: any stale bookmark dies, but only m used it for hours.
  • (ii) 302-redirect /<page>?view=<uuid> to /views/<slug> by looking up the slug from the uuid. Smoother for m's recent bookmarks. Cost: one extra DB hit on the redirect path; the redirect can target the slug or, if the uuid no longer resolves (because we hard-recreated the table), 302 to /views.

Inventor pick: (ii) — small code, no broken bookmarks for the brief 5i window.

is_default_for semantics

Drop entirely. The MRU mechanism (last_used_at/views landing) replaces "what should I see on /views". Per-page defaults are gone; if m wants a specific view to be the landing experience, he opens it once and it becomes MRU.

If m later wants a "this is my default" hint stronger than MRU (i.e., pinning), sort_order=0 reserved for a pinned slot + an is_pinned flag is the natural extension. Not in scope for v1.


§8 — Implementation slicing

Seven slices; A → B → C → D → E are the critical path; F + G are polish.

Slice A — Schema redesign

  • Migration 0017_views_redesign.sql: DROP TABLE projax.views CASCADE; CREATE TABLE with new shape. (See §2 schema.)
  • store/views.go: rewrite. Rename View.ID flow to be slug-driven; GetView(slug) instead of GetView(uuid). Keep CRUD shape; add Touch(slug) for MRU; add MostRecent() returning the MRU view (or nil); add Reorder([]string slugs) for slice G.
  • Drop DefaultViewFor (no longer applicable).
  • Tests: round-trip CRUD by slug; reserved-slug rejection at the validator; slug-format regex enforcement; MRU.

Slice B — Route migration (paliad-shape)

  • Replace the 5i /views/<uuid> routes with the paliad-shape route table from §4.
  • handleViewsLanding → MRU redirect or onboarding shell.
  • handleViewRender → resolve slug (user view first, then system view), apply chip overlay, dispatch to the view_type's renderer.
  • handleViewEditor → dedicated form page (slug-driven).
  • handleViewCreate / handleViewUpdate / handleViewDelete → form POST handlers.
  • handleViewTouch → fire-and-forget MRU update.
  • Wire the legacy ?view=<uuid> redirect (per §7-ii) on existing pages.
  • Tests: each route hit, slug routing, MRU redirect, onboarding shell on empty state, reserved-slug rejection.

Slice C — System views

  • New web/system_views.go with SystemView struct + TreeSystemView(), DashboardSystemView(), CalendarSystemView(), TimelineSystemView(), AllSystemViews(), LookupSystemView(slug).
  • Each function returns the (filter_json, view_type, group_by, sort) tuple matching today's page.
  • handleViewRender falls back to LookupSystemView when the slug isn't in the DB.
  • Reserved-slug list (combining system slugs + route segments).
  • Under (c) hybrid: legacy routes /, /dashboard, /calendar, /timeline each gain a sibling registration so /views/{system-slug} resolves to the same handler. (Or: legacy routes 302 to /views/{slug} — simpler if m's fine with one canonical URL.)
  • Tests: system-view lookup, slug aliases hit the same template, reserved-slug rejection during user-view create.

Slice D — Editor surface

  • New templates/view_editor.tmpl — full form per §6.
  • Slug derivation helper (POST /views/derive-slug or server-side fill).
  • Icon picker (a <select> for v1 — frontend registry expansion is incremental).
  • Inline chip strip inside the form; HTMX updates a hidden filter_json on every chip click.
  • Tests: GET /views/new renders blank form; GET /views/{slug}/edit pre-fills; POST creates/updates round-trip.

Slice E — Sidebar integration

  • templates/layout.tmpl: insert a "Views" section between main nav and /admin.
  • Server-side: every page-render pulls ListViews() into the layout data map (cached lightly so each request doesn't hit the DB twice).
  • Active-state CSS + icon rendering.
  • Mobile drawer (5g slice B) gets the same section.
  • Tests: sidebar shows user views; clicking navigates to /views/{slug}; active state matches URL.

Slice F — Migration cleanup (delete 5i overlay)

  • Remove TreeFilter.ViewID.
  • Remove applySavedView, applyDefaultView, overlayURLFields, the default-view banner.
  • Remove the 5i /views/<id> redirect handler (slice B replaces it).
  • Tests adjusted: drop the ViewID round-trip test; drop TestSavedViewAppliedOnQueryParam, TestDefaultViewAppliedOnCleanURL, TestViewEditFlow — their slice-A successors cover the new shapes.

Slice G — Polish

  • Drag-reorder UI via HTMX hx-post="/views/reorder" with sortable.js or a tiny vanilla drag-handle (m's HTMX-only constraint allows minimal vendored JS if needed).
  • show_count badge wiring (run filter against ListAll(), render the count next to the sidebar entry).
  • Preview pane in the editor (optional).
  • Icon registry expansion (curated SVGs).

Slices F and G are independent. The implementation chain is A → B → C → D → E → (F either before or after E) → G.


§9 — Open questions for head delegation

Inventor picks marked. Process: NO direct chip-picker without head's explicit grant for this round.

Q1 — System-view shape (§3)

(a) Keep current routes only; user views beside them at /views/{slug} — current asymmetry stays. (b) Full migration; existing pages become system views, legacy URLs 301-redirect — paliad parity. (c) Hybrid; both URL families coexist, system slugs aliased — preserves muscle memory.

Inventor pick: (c). Closes the asymmetry m flagged, zero migration cost. (b) is cleaner but risks broken bookmarks for thin upside.

Q2 — view_type field placement

  • (a) Top-level column (5j inventor pick — matches 5i, query-able without parsing JSON).
  • (b) Inside filter_json.

Inventor pick: (a).

Q3 — Legacy ?view=<uuid> URL handling (§7)

  • (a) 404 — clean break.
  • (b) 302-redirect to /views/<slug> by uuid lookup — smoother for m's recent bookmarks. Inventor pick.

Inventor pick: (b).

Q4 — Editor surface (§6)

  • (a) Dedicated pages /views/new + /views/{slug}/edit — paliad parity, inventor pick.
  • (b) Keep the HTMX modal from the 5i fix — less navigation but harder to share/bookmark mid-edit.

Inventor pick: (a).

Q5 — /views landing MRU redirect

  • (a) 302 to MRU saved view if any, else onboarding shell (paliad model, inventor pick).
  • (b) Always show the views index list page.

Inventor pick: (a).

Q6 — Icon picker in v1?

  • (a) Yes — small select + 8-12 curated keys; rendered inline in the sidebar entries.
  • (b) v2 — ship without icons in v1; sidebar uses a generic folder glyph for every entry.

Inventor pick: (a) — the schema column lands either way; UI cost for a <select> is trivial.

Q7 — Drag-reorder in v1?

  • (a) Yes (slice G in v1).
  • (b) v2 — sort_order column is server-assigned MAX+1 on create; reorder UI lands later.

Inventor pick: (b). Don't expand v1 scope; reorder is a UX polish that can ship a week after.

Q8 — show_count badge in v1?

  • (a) Yes — opt-in checkbox in editor + sidebar badge.
  • (b) v2 — column lands in the schema; UI lands later.

Inventor pick: (a) — checkbox in editor + 2-line render in sidebar is cheap and answers the "how many things match my view" question m asks naturally.

Q9 — Legacy is_default_for semantics (§7)

Inventor picks dropped entirely, replaced by MRU. Flag if m wants pin / default semantics back.

Q10 — Drop and recreate projax.views?

  • (a) Hard-replace via DROP TABLE ... CASCADE — inventor pick (table is hours old, ~zero data loss).
  • (b) ALTER TABLE migration that adds new columns + drops old ones gracefully — more conservative; preserves any rows m has created.

Inventor pick: (a). The shape change is large enough that a clean re-create is cleaner than a 6-step ALTER.

Q11 — view_type=graph?

The graph DAG SVG render isn't in the view_type enum. Should:

  • (a) Stay outside the views system — /graph and /views/graph (system slug) both serve it, user views can't be view_type=graph. Inventor pick.
  • (b) Add graph as a sixth view_type — opens user-creatable graph views.

Inventor pick: (a). Graph layout is single-purpose (DAG); a "graph of my filtered set" doesn't have a clear product story today.


§10 — Risk register

risk likelihood mitigation
Slug collision on rename medium UNIQUE index + handler maps the unique-violation to a friendly "slug already in use" error
URL drift (legacy bookmarks break) low under (c), high under (b) (c) keeps legacy URLs; (b) ships with 301 redirects + a session of m verifying his bookmarks
MRU thrash on rapid view switches low last_used_at is fire-and-forget; the worst case is one stale 302
System-view + user-view slug collision n/a reserved-list rejection in validator (slice A)
sidebar query cost low ListViews() is one indexed lookup per page render; cache lightly if it shows in profiling
Editor's chip strip drifts from the page chip strip medium share the same template (project_chip.tmpl already shared); add a dedicated view_filter_chips.tmpl if drift bites

§11 — Test plan headlines

Slice A

  • TestViewSlugCRUD — create/get/update/delete by slug round-trip.
  • TestViewSlugFormatRejected — uppercase, underscore, leading-digit-allowed but no-leading-dash, length-cap 63.
  • TestViewReservedSlugRejected — create with slug tree / dashboard / admin / new etc. all 400.
  • TestViewTouch — Touch bumps last_used_at.
  • TestViewMostRecent — MRU returns most recently touched.

Slice B

  • TestViewsLandingMRU/views 302s to MRU view when one exists.
  • TestViewsLandingOnboarding/views renders shell when no views.
  • TestViewRender/views/{slug} resolves a user view; renders the right view_type template.
  • TestLegacyOverlayRedirect/?view=<uuid> 302s to /views/{slug}.

Slice C

  • TestSystemViewLookuptree / dashboard / calendar / timeline / graph resolve via LookupSystemView.
  • TestSystemViewSlugAlias/views/dashboard and /dashboard produce identical render output.

Slice D

  • TestEditorBlank/views/new renders empty form.
  • TestEditorPrefilled/views/{slug}/edit reflects every persisted field.
  • TestSlugDerivation — name "Active mai work" → slug "active-mai-work".

Slice E

  • TestSidebarListsViews — layout includes every user view.
  • TestSidebarActiveState/views/{slug} marks that entry active.

Slice F

  • All 5i overlay tests deleted; no residue references TreeFilter.ViewID.

Slice G

  • TestReorderUpdatesSortOrder — POST /views/reorder with a sorted slug list updates the column.
  • TestShowCountBadge — sidebar badge reflects the filter's match count.

§12 — References

  • ~/dev/paliad/internal/db/migrations/056_user_views.up.sql — schema reference.
  • ~/dev/paliad/internal/services/user_view_service.go — CRUD reference.
  • ~/dev/paliad/internal/services/system_views.go — reserved-slug + system-view registration.
  • ~/dev/paliad/internal/handlers/views_pages.go — route table.
  • ~/dev/paliad/frontend/src/{views,views-editor}.tsx — editor + sidebar reference (UX only; not ported).
  • docs/plans/views-system.md (5i) — historical record of the wrong-shape implementation.
  • docs/design.md §4 (Interfaces).

§13 — Status

  • Phase A (this doc): drafted by kahn, 2026-05-26. Awaiting head delegation of §9 questions to m.
  • No chip-picker for 5j unless head explicitly re-grants per the project's escalation rule.
  • Phase B (coder): blocked on m's sign-off via head. Slice ordering A → B → C → D → E → F → G.
  • No code changes in this branch beyond this doc.