6.15 Board filtering (wire the disabled board `[Filter]` seam)
Wire the board filter that the /boards toolbar reserves as a permanently disabled [Filter] button today (page.tsx: "Filter is a disabled seam here — Epic 6 wires board filtering"). Stories 3.7 / 3.8 / 4.2 / 4.5 / 4.7 all defer board filtering to "Epic 6" in prose, and the 3.8.4 over-cap banner ("refine the board filter") points at this seam — but no Epic-6 story ever owned it: Story 6.1 scoped its filter builder to the /issues navigator only, naming the board "the documented extension when a use case lands." This is that use case. (Not a bug — the disabled state is a deliberate seam; the gap is the missing story, the same shape as the board-column-admin planning gap.)
The verified shape (rung 1 — Atlassian docs). A Jira board is backed by a filter and the user narrows it with board quick-filters / its saved filter — filtering is a first-class board affordance, not navigator-only. Today the board read (boardsService.getBoard / loadColumnCards) takes no predicate and projects every card (bounded only by the 3.8 cap), so a user cannot shrink an over-cap board even though the banner tells them to.
The fix — REUSE, not rebuild. The structured filter AST + compiler (lib/filters/*, 6.1) and saved filters (lib/savedFilters/*, 6.2) are done, and the issue list already threads a compiled predicate into its read (findByProjectFiltered via resolveFilterAst). This story threads that SAME predicate into the board projection (each column shows only matching cards), mounts the SAME filter builder + saved-filter picker on the board toolbar (board-scoped, URL-addressable, reload-safe), and points the over-cap "refine filter" CTA at it. The 3.8 bounded/virtualized load + over-cap truncated flag now apply to the filtered set, and the 4.5 Scrum sprint-scope filter composes with it (narrow WITHIN the active sprint).
Plus — the missing Work type facet (6.15.5). The shipped quick filter (IssueFilterBar) is a curated four-facet subset (text · kind · status · assignee); the Work type field (WorkItemType — code/design/test/… + the nullable Untyped bucket) was reachable only via [Advanced], so a user could not filter the board (or the /issues list) by Work type from the quick popover. The FilterAST + 6.1 registry already DEFINE and COMPILE type (enumField('type', 'type-select', { nullable, WORK_ITEM_TYPES })), so this is a missing quick-filter facet, not a grammar change — 6.15.5 adds the Work type facet group to the SHARED IssueFilterBar (so /issues gains it too), and the board reuses it.
Out of scope. The filter grammar/compiler/registry (6.1 owns type — already complete, no change) and the board read (6.15.2 threads whatever AST — type already compiles); cross-project boards (still the 3.7 Epic-6 extension); altering the Scrum scope filter (4.5). No new migration.
Verification
- Pull the Story branch,
pnpm install,pnpm prisma migrate dev(reports "No difference detected" — this story adds no schema; it reuses the 6.1 filter substrate),pnpm db:seed,pnpm dev. pnpm test:coverage— Vitest (real Postgres) over the filtered board read (predicate applied per column, the cap/truncatedcomputed over the FILTERED set, workspace + 6.4-permission scope) ≥ 90% per-file branch/fn/line.- The repro, now passing: sign in as
zhuyue@motir.co/!QAZ1qaz→moooon/motir→/boards. The toolbar Filter is now enabled; open it, build a quick filter (e.g.type = Bug) or pick a saved filter → the board re-projects so every column shows only matching cards; the active filter shows as a chip/summary; Clear restores the full board. The selection survives a reload (it is in the URL). - Work type facet (6.15.5): open the filter — the popover now shows a Work type group (Code / Design / Test / … + Untyped) between Kind and Status; selecting e.g.
Designnarrows the board to design work items. The SAME facet appears on/issues(sharedIssueFilterBar). - Over-cap CTA: on a board that exceeds the 3.8 cap, the "refine the board filter" banner’s CTA opens the filter (it no longer points at a dead seam); applying a filter that brings the set under the cap dismisses the banner.
- Scrum compose: on a Scrum board with an active sprint, a board filter narrows WITHIN the sprint scope (4.5) — it does not show cards outside the sprint.
pnpm test:e2e --grep board-filter— apply a filter on a board end-to-end → only matching cards across columns → clear restores.- a11y: the board filter affordance (closed / open builder / saved-filter picker / active chips / no-results board) passes the strict axe sweep; fully keyboard-operable; colour via
--el-*, shape via element-semantic tokens; next-intl (en + zh).