feat(search): multi-select quick filters for search results and site releases (SEARCH-3)

- domain release-facets: bucket resolution / codec / release type / site / promotion / HDR /
  release group / season / episode; OR within a dimension, AND across; disjunctive counts
- GET /search/releases and GET /sites/{id}/releases accept qf and return facets; site list
  facets come from local source_release rows only (no site requests), paging stays consistent
- web QuickFilterBar shared by search results and site resources tab: per-dimension popover
  (bottom sheet on phones) with counts, removable tags, clear all, localStorage memory per page
- runbook: quick filter params and bucket definitions

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 20:52:29 +08:00
1 parent d282212a6b
commit 970296218c
20 files changed
+1273 -81

No files matched your search

+9
View File
@@ -1,5 +1,6 @@
import { z } from "zod";
import { CursorQuery, DateOnly, EntityBase, Id, JsonObject, NonNegInt, SERVER_MANAGED, Timestamp } from "./common.ts";
import { QuickFilterParam, ReleaseQuickFacets } from "./release-facets.ts";
// 来源域:站点、站点账号、Feed、站点资源、观察(REQ §4)
// 含 passkey 的 Feed URL 只以 urlSecretRef 保存,对外只给脱敏的 urlDisplay。
@@ -594,6 +595,8 @@ export const WorkbenchReleaseQuery = z.object({
resolution: ResolutionStatus.optional(),
/** true = 强制实时补查;false = 只看本地快照;缺省 = 本地无结果或快照过期时补查 */
live: z.enum(["true", "false"]).optional(),
/** 快速筛选(SEARCH-3,格式同 GET /search/releases 的 qf);只筛本地数据,不触发站点请求 */
qf: QuickFilterParam,
cursor: z.string().min(1).optional(),
limit: z.coerce.number().int().min(1).max(200).default(50),
});
@@ -633,6 +636,12 @@ export const WorkbenchReleasePage = z.object({
items: z.array(WorkbenchRelease),
nextCursor: z.string().nullable(),
coverage: WorkbenchCoverage,
/** 快速筛选后的条数 */
total: NonNegInt.optional(),
/** 快速筛选前的条数(q / 分类 / 促销 / 识别状态筛选之后) */
unfilteredTotal: NonNegInt.optional(),
/** 快速筛选分面(SEARCH-3,基于本地资源表数据);site 只有本站一项 */
facets: ReleaseQuickFacets.optional(),
});
export type WorkbenchReleasePage = z.infer<typeof WorkbenchReleasePage>;