Persist the 6.1 filter builder's state as named saved filters: a project-scoped saved_filter entity owned by its creator, carrying the SAME versioned FilterAST envelope the ?filter=v1: URL codec serializes (one codec, two carriers — a saved filter is the URL param given a name, an owner, and permissions). Applied from a filter dropdown beside the builder on /issues (starred first, then mine / project-shared / built-in defaults, searchable), managed in a filters directory, starred per-user, optionally emailed on a schedule (subscriptions), and exposed to Story 6.3 dashboards/reports through a documented data-source contract.
Where it sits relative to the mirror (verified, deviations recorded). Jira Cloud: save a search as a filter; the owner of a modified filter gets Save + Save as, a non-owner Save as only — we mirror that dirty-state rule exactly. Jira filters are user-owned SITE-GLOBAL objects with six share scopes and separate Viewers/Editors grant lists; ours are project-contained (the AST's referents — statuses, custom fields, labels, components, sprints — are project data, and the whole shipped search substrate is project-scoped; the 6.1 recorded deviation) with two visibilities — private (owner + project/workspace admins) and project (everyone who can browse the project, riding the 6.4 gate) — and editing by owner + project admin (the per-filter editor-grant list and cross-project filters are documented extensions). Sharing (visibility project) requires role ≥ member — a viewer is a read-only persona and publishing into the project's shared namespace is a write; viewers still create/star PRIVATE filters freely (filters are a read-layer construct). Project admins (+ workspace owner/admin) can edit, delete, and change the owner of any project-shared filter — the Jira-admin powers, project-sized.
Built-in defaults (the system filters). The expressible subset of Jira's nine, as non-persisted AST constants (NOT rows): My open issues, Reported by me, All issues, Open issues, Done issues, Created / Updated / Resolved recently. Non-editable, non-deletable (the mirror rule); applying one loads its rows into the builder like any saved filter ("Viewed recently" omitted — no view-history substrate; extension).
Durability (the load-bearing piece). The stored payload is the versioned envelope the 6.1.1 codec defines — decode + registry-validate on EVERY resolve, never trust-and-compile: a stale referent inside a saved AST (deleted option / label / field) degrades to the 6.1.2 unknown-value condition (matches nothing + per-row notice — the rule 6.1 recorded for exactly this story); a malformed or future-versioned envelope yields the typed recoverable state, never a crash. Name uniqueness is per-project case-insensitive (our call, not mirror-verified — two "Sprint blockers" in one shared dropdown is ambiguity nobody wants).
Subscriptions. Per-user, per-filter email on a preset schedule (daily / weekdays / weekly at an hour — Jira's preset tier; advanced cron is the extension), running on the Story 1.6 jobs substrate (the dailyHealthCheck cron precedent) and the lib/emailTemplates/ layer: first 50 results (our page unit; Jira caps at 200) + total count + a deep link to the applied filter. Deleting a filter deletes its subscriptions — after a Cloud-style warning that names the dependents (subscription count now; 6.3 widget usages once they exist).
The 6.3 data-source contract (what "reuse for dashboards and reports" means). A documented service read — resolve a saved filter id to its compiled WHERE fragment + metadata (name, owner, visibility) — plus the usage seam: consumers reference filters by FK, the delete warning enumerates them, and a widget whose filter was deleted renders a designed "filter missing" state (the verified Cloud gadget behaviour), never a crash. Boards stay status-mapped — Motir mirrors the team-managed family (3.1/3.6 tested decision, the same family 6.4 chose), so filter-sourced boards (a company-managed shape) are the documented extension; the stub's "boards" consumer resolves to this contract.
Bounded + complete (finding #57). Directory and dropdown reads are paginated/bounded and searchable server-side (a project with 500 filters must not ship them all to a dropdown); star counts aggregate in the query, not in JS over all rows; empty / loading / error / no-access states designed + asserted; every list keyed for the project so one team's filters never leak into another's.
Out of scope (documented extension slots, each justified): cross-project / workspace-global filters (follows the 6.1 scope deviation); per-filter Viewers/Editors grant lists + group scopes (no groups in Motir; 6.4 roles draw the boundary); filter-sourced boards (company-managed shape; ours are team-managed-style status boards); advanced cron subscriptions; "Viewed recently" (no view-history substrate); public/anonymous sharing (no anonymous surface exists — rung 2).
pnpm install, pnpm prisma migrate dev (the saved_filter / star / subscription migrations apply cleanly; re-run reports "No difference detected"), pnpm db:seed, pnpm dev.pnpm test:coverage — Vitest (real Postgres) over the service/permission matrix, the persist→resolve round-trip, and the subscription scheduler ≥90% per-file branch/fn/line.zhuyue@motir.co / !QAZ1qaz → /issues → build a filter (Status is any of (To do) AND Priority is none of (Lowest)) → Save as → name it "Sprint blockers", visibility Project → the toolbar shows the applied filter's name; edit a row → the dirty state appears with Save + Save as (you own it); a second account (bophilips@motir.co) applying the same filter and editing a row sees Save as only (the mirror's non-owner rule).?filter=v1: URL (shareable; reload-safe); the built-in defaults (My open issues, Reported by me, …) apply but expose no edit/delete.bophilips@motir.co (dropdown + directory + direct id); flipping it to Project makes it appear; a project viewer can create/star private filters but gets no Project-visibility option; a project admin can rename/delete/change-owner on a member's shared filter.pnpm test:e2e --grep saved-filters — Playwright over the real stack: save → dirty → save-as → share → second-user apply/star → delete-with-warning journey.--el-*, shape via element tokens.