Universal filter + view-mode primitive across all entity-views #23

Open
opened 2026-05-08 19:35:21 +00:00 by mAi · 1 comment
Collaborator

m's verbatim 2026-05-08 21:34, after reviewing /inbox?tab=mine:

This looks really bad. I am considering... should we not have a "universal filtering" for these views? Like... what's related to which project, in which timeframe (where applicable), what type of event (approvals, changes, whatever... with sorting in a table, views with cards or list... wouldn't that be nice?! I feel like we are halfway there without custom views, but now we would need this universal filter and we apply it to all kinds of entity views... worth an inventor?

Why this matters

Paliad already exposes ~7 list-shaped surfaces — /deadlines, /appointments, /agenda, /inbox (both tabs), /projects (cards / tree / list), the custom user-views (/views/<id>), the new dashboard inline Agenda + Letzte Aktivität — and each one has reinvented its own filter chrome:

  • Project-scope: chip on /agenda; checkbox group on /views/new; URL ?project= on /tools/fristenrechner; absent on /inbox.
  • Timeframe: bucket chips on /deadlines (heute/diese-woche/später); date-range picker on /appointments; absent on /inbox.
  • Type: multi-select on /events; per-route hardcoded subset on /agenda; absent on /inbox.
  • View mode: cards/tree/list on /projects; columns/list on Fristenrechner timeline; list-only on /inbox + /agenda.
  • Sort: header-click on the entity-table component; URL ?sort= on a couple of routes; nothing on /inbox.

The result is exactly what m saw on /inbox?tab=mine — bare list, no scope to project, no timeframe, no type, no sort, looks unfinished compared to the surfaces that did get filter polish. Filing each as a separate bug would mean re-deriving the same primitives 5+ times.

Inventor's brief

Design a single composable filter primitive that every list-shaped surface consumes. Take a position on:

  1. Filter axes that are universal vs per-surface. Project-scope is universal; "requester / approver" only applies to /inbox; "deadline status" only applies to /deadlines. Decide which axes go in the shared bar and which stay per-surface, and how the bar declares its supported axes (config object? slot composition? higher-order component?).

  2. State model. URL-driven (every filter is a query param, deep-linkable, refresh-survives) vs in-memory (faster but loses on reload) vs hybrid (URL for primary axes, localStorage for view-mode preferences). Match paliad's existing pattern where it makes sense; deviate where it improves.

  3. View-mode switcher. cards / list / table — universal, or per-surface declared? table-mode implies sortable column headers — does the primitive own column-sort state, or does each surface manage that internally? Density toggle (compact vs comfortable) belongs here too.

  4. Composability. The bar needs to drop into existing TSX pages (/inbox, /deadlines, /appointments, /agenda, custom views) without forcing every surface to refactor. Propose a clean API.

  5. Reuse with the existing /projects layout-spec (custom user-views in paliad.user_view_layouts). The custom-view editor already lets users pick filter axes — does the universal bar inherit from that spec, or does the spec become a special case of "saved bar state"? m's hint: "halfway there without custom views" — leaning towards the latter.

  6. Migration path. Each existing list surface has its own filter UI today. Phase the rollout: ship the primitive on one surface (likely /inbox since it has the least to lose), then port others as separate PRs. Identify the one surface with the most complex filter today (likely /deadlines or /events) so the primitive is designed wide enough to absorb it.

  7. "Save this filter as a view". The custom-view editor already does this for a few surfaces. The universal bar should make this affordance trivial — every surface gets "save current filter as named view" for free.

Hard requirements

  • Read first (in this order):

    1. frontend/src/client/projects-cards.ts — cards/tree/list switcher + layout-spec consumer
    2. frontend/src/client/agenda.ts + client/agenda-render.ts — current chip + project filter pattern
    3. frontend/src/client/deadlines.ts + client/events.ts — bucket chips + multi-select filter
    4. frontend/src/client/inbox.ts — what's missing today
    5. frontend/src/client/views.ts + client/views-editor.ts — saved-view shape + how layout specs persist
    6. frontend/src/client/views/shape-cards.ts + views/shape-list.ts + views/shape-calendar.ts — view-mode renderers
    7. internal/services/layout_spec.go — server-side layout-spec model
    8. paliad.user_view_layouts table
  • DO NOT auto-flip to coder. Report mai report completed "DESIGN READY FOR REVIEW" and stop. Head will surface to m for go/no-go before any coder shift (m/mAi#142).

  • No hour estimates. Slice/phase the rollout; describe order, not duration.

  • No .entity-table row-click contract violations — the bar's cards | list | table modes must respect paliad's existing row-handler pattern (see CLAUDE.md frontend conventions).

  • Output as docs/design-universal-filter-2026-05-08.md. Commit + push to a branch.

Out of scope

  • New entity surfaces. Only what already exists.
  • Backend SQL changes beyond the layout_spec extensions naturally implied by adding a few axes. The bar is a frontend primitive.
  • Mobile-specific layout: the bar must work mobile, but a re-imagining of mobile-list-mode is a separate workstream.
  • Internationalization beyond DE+EN — paliad is bilingual today and stays bilingual.

Filed by

paliad/head from m's 2026-05-08 21:34 instruction. Linked to the current dogfood on /inbox?tab=mine which is the most visibly under-filtered surface.

m's verbatim 2026-05-08 21:34, after reviewing /inbox?tab=mine: > This looks really bad. I am considering... should we not have a "universal filtering" for these views? Like... what's related to which project, in which timeframe (where applicable), what type of event (approvals, changes, whatever... with sorting in a table, views with cards or list... wouldn't that be nice?! I feel like we are halfway there without custom views, but now we would need this universal filter and we apply it to all kinds of entity views... worth an inventor? ## Why this matters Paliad already exposes ~7 list-shaped surfaces — `/deadlines`, `/appointments`, `/agenda`, `/inbox` (both tabs), `/projects` (cards / tree / list), the custom user-views (`/views/<id>`), the new dashboard inline Agenda + Letzte Aktivität — and each one has reinvented its own filter chrome: - Project-scope: chip on /agenda; checkbox group on /views/new; URL `?project=` on /tools/fristenrechner; absent on /inbox. - Timeframe: bucket chips on /deadlines (heute/diese-woche/später); date-range picker on /appointments; absent on /inbox. - Type: multi-select on /events; per-route hardcoded subset on /agenda; absent on /inbox. - View mode: cards/tree/list on /projects; columns/list on Fristenrechner timeline; list-only on /inbox + /agenda. - Sort: header-click on the entity-table component; URL `?sort=` on a couple of routes; nothing on /inbox. The result is exactly what m saw on /inbox?tab=mine — bare list, no scope to project, no timeframe, no type, no sort, looks unfinished compared to the surfaces that did get filter polish. Filing each as a separate bug would mean re-deriving the same primitives 5+ times. ## Inventor's brief Design a single composable filter primitive that every list-shaped surface consumes. Take a position on: 1. **Filter axes that are universal** vs **per-surface**. Project-scope is universal; "requester / approver" only applies to /inbox; "deadline status" only applies to /deadlines. Decide which axes go in the shared bar and which stay per-surface, and how the bar declares its supported axes (config object? slot composition? higher-order component?). 2. **State model**. URL-driven (every filter is a query param, deep-linkable, refresh-survives) vs in-memory (faster but loses on reload) vs hybrid (URL for primary axes, localStorage for view-mode preferences). Match paliad's existing pattern where it makes sense; deviate where it improves. 3. **View-mode switcher**. cards / list / table — universal, or per-surface declared? table-mode implies sortable column headers — does the primitive own column-sort state, or does each surface manage that internally? Density toggle (compact vs comfortable) belongs here too. 4. **Composability**. The bar needs to drop into existing TSX pages (/inbox, /deadlines, /appointments, /agenda, custom views) without forcing every surface to refactor. Propose a clean API. 5. **Reuse with the existing /projects layout-spec** (custom user-views in `paliad.user_view_layouts`). The custom-view editor already lets users pick filter axes — does the universal bar inherit from that spec, or does the spec become a special case of "saved bar state"? m's hint: "halfway there without custom views" — leaning towards the latter. 6. **Migration path**. Each existing list surface has its own filter UI today. Phase the rollout: ship the primitive on one surface (likely /inbox since it has the least to lose), then port others as separate PRs. Identify the one surface with the most complex filter today (likely /deadlines or /events) so the primitive is designed wide enough to absorb it. 7. **"Save this filter as a view"**. The custom-view editor already does this for a few surfaces. The universal bar should make this affordance trivial — every surface gets "save current filter as named view" for free. ## Hard requirements - **Read first** (in this order): 1. `frontend/src/client/projects-cards.ts` — cards/tree/list switcher + layout-spec consumer 2. `frontend/src/client/agenda.ts` + `client/agenda-render.ts` — current chip + project filter pattern 3. `frontend/src/client/deadlines.ts` + `client/events.ts` — bucket chips + multi-select filter 4. `frontend/src/client/inbox.ts` — what's missing today 5. `frontend/src/client/views.ts` + `client/views-editor.ts` — saved-view shape + how layout specs persist 6. `frontend/src/client/views/shape-cards.ts` + `views/shape-list.ts` + `views/shape-calendar.ts` — view-mode renderers 7. `internal/services/layout_spec.go` — server-side layout-spec model 8. `paliad.user_view_layouts` table - **DO NOT auto-flip to coder**. Report `mai report completed "DESIGN READY FOR REVIEW"` and stop. Head will surface to m for go/no-go before any coder shift (m/mAi#142). - **No hour estimates**. Slice/phase the rollout; describe order, not duration. - **No `.entity-table` row-click contract violations** — the bar's `cards | list | table` modes must respect paliad's existing row-handler pattern (see CLAUDE.md frontend conventions). - Output as `docs/design-universal-filter-2026-05-08.md`. Commit + push to a branch. ## Out of scope - New entity surfaces. Only what already exists. - Backend SQL changes beyond the layout_spec extensions naturally implied by adding a few axes. The bar is a frontend primitive. - Mobile-specific layout: the bar must work mobile, but a re-imagining of mobile-list-mode is a separate workstream. - Internationalization beyond DE+EN — paliad is bilingual today and stays bilingual. ## Filed by paliad/head from m's 2026-05-08 21:34 instruction. Linked to the current dogfood on /inbox?tab=mine which is the most visibly under-filtered surface.
Author
Collaborator

Locked positions (m greenlit 2026-05-08 21:47)

# Decision
1 URL state for filter axes; localStorage for shape/density prefs
2 Save-as-view = modal (matches existing <dialog>s)
3 /events 5-card summary stays above bar; click drives bar's time chip
4 /inbox tabs collapse into the bar's approval_viewer_role chip cluster
5 URL params short + namespaced (?time= ?sources= ?d_status= etc.)
6 Save-as hidden on dashboard mini-bars
7 ?type= legacy redirect kept for one release
8 /views/{slug} URL drafts not persisted in localStorage
9 Sortable column headers list-shape only for v1
10 Dashboard double-bar uses per-bar urlNamespace
11 Multi-project select deferred to phase C
12 "Ohne Typ" event-type filter kept

Design doc: docs/design-universal-filter-2026-05-08.md on mai/riemann/inventor-universal (commit 1e23745).

riemann shifted to /mai-coder. Phase 1 = /inbox + <FilterBar> primitive + axis registry + URL codec. Slices ship at boundaries; we discuss Phase 2 after m dogfoods Phase 1.

## Locked positions (m greenlit 2026-05-08 21:47) | # | Decision | |---|---| | 1 | URL state for filter axes; localStorage for shape/density prefs | | 2 | Save-as-view = modal (matches existing `<dialog>`s) | | 3 | /events 5-card summary stays above bar; click drives bar's time chip | | **4** | **/inbox tabs collapse into the bar's `approval_viewer_role` chip cluster** | | 5 | URL params short + namespaced (`?time=` `?sources=` `?d_status=` etc.) | | 6 | Save-as hidden on dashboard mini-bars | | 7 | `?type=` legacy redirect kept for one release | | 8 | /views/{slug} URL drafts not persisted in localStorage | | 9 | Sortable column headers list-shape only for v1 | | 10 | Dashboard double-bar uses per-bar `urlNamespace` | | 11 | Multi-project select deferred to phase C | | 12 | "Ohne Typ" event-type filter kept | Design doc: `docs/design-universal-filter-2026-05-08.md` on `mai/riemann/inventor-universal` (commit `1e23745`). riemann shifted to `/mai-coder`. Phase 1 = /inbox + `<FilterBar>` primitive + axis registry + URL codec. Slices ship at boundaries; we discuss Phase 2 after m dogfoods Phase 1.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#23
No description provided.