Files
crearte-monorepo/docs/specs/2026-10-01-p8-long-tail-design.md

58 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P8 长尾打包(第一批)· 设计 spec
日期:2026-10-01 | 决策:owner「可以」批准本批范围与顺序(②⑤ 后端小项+文案,① 权限开关 UI)。
本批**不含** ③账号注销(新功能,待产品决策)与 ④静态兜底目录(按需)。
## 0. 一句话
清账批:把前几波刻意记为遗留的碎片收干净——后端三处 triage 小修、两份 CHANGELOG 去模板腔、前端把后端已支持但 UI 未开放的五个权限开关补齐。
## 1. 现状勘查(grounded,2026-10-01)
### ②-1 `games.Detail` 400 细分
`handler/games.go:44-50`:user/slug 格式校验失败时统一 `WriteError(400, "invalid_request", "invalid game id")`——调用方无法区分「user 格式非法」「slug 格式非法」。校验辅助已存在:`service.UsernamePattern`(`namespace.go:11`)、`SlugPattern`(`:12`)。改进:逐字段细分错误消息(如 `user: invalid format` / `slug: invalid format`),沿用 `ValidationError.Fields` 的既有表达风格。
### ②-2 admin 工作路由 slug 校验
`router.go:26-29` 四条路由(unpublish/republish/features/revoke)→ `handler/admin.go` 各 handler 直接调 `adminWorkID(c)`(`:115-117` = `JoinWorkID(c.Param("user"), c.Param("slug"))`),**无格式校验**:非法格式会带进 service/DB,最终多半落 404(掩盖了「格式错」与「不存在」的区别)。对照公开面 `games.Detail:47` 是有校验的。改进:四条 admin 路由的 handler 入口补 `UsernamePattern/SlugPattern` 前置校验,非法即 400。
### ②-3 approve 同名竞态测试
`JoinWorkID(user, slug)` 同名撞车在 service 层有处理(approve precheck + `Works().Create` 的 `ErrConflict`→`ErrWorkIDTaken`),但无**并发**针对性测试。补一个并发 approve 同名 new_work 的竞态测试,断言最终只有一条成功、其余拿 `ErrWorkIDTaken`/`ErrSubmissionConflict`,且库中不出现重复行。
### ⑤ CHANGELOG 模板腔
- `crearte/docs/CHANGELOG.md` 头部仍写「本**模板**的重要变更建议统一记录在此文件中」+「参考了 Keep a Changelog 的思路,也可以根据团队习惯调整」——仓库已迭代到 0.19.x,是项目不是模板。
- `crearte-server/docs/CHANGELOG.md` 头部「本项目的重要变更统一记录在此文件中」尚可,但英文行 "should be documented" 偏模板口吻。
改进:两仓头部改为项目自己的口径(保留 Keep a Changelog 的引用作格式出处说明),去掉「模板/建议/可以调整」的占位口吻。
### ① 权限开关 UI 全量
后端 feature key 全集(`service/validate.go:60`):`eval, inlineScript, inlineStyle, wasm, coop, fullscreen, gamepad`(7 个)。
前端 `content/features.ts` 的 `EditableFeatures`/`FEATURE_ITEMS` **只开放前 2 个**,注释明写「其余 flag(inline…epad)schema 与后端均已支持,但 UI 暂不开放(YAGNI):默认值已覆盖绝大多数作品,按需再添」。
本批补齐剩余 **5 个**:`inlineStyle`、`wasm`、`coop`、`fullscreen`、`gamepad`。三处 UI 共用 `FEATURE_ITEMS`(投稿表单勾选区、admin 作品管理展开行、审核详情只读展示),故扩 `EditableFeatures`/`FEATURE_ITEMS`/`collectFeatures`/`featuresToForm`/`hasAnyFeature` 即三处同步生效;后端零改动。
## 2. 决策
- **D-1 错误粒度**:`games.Detail` 沿用 400 + `invalid_request`,仅**细分 message**(`user:`/`slug:` 前缀)——不改状态码、不加新错误码,最小变更;既有测试只断言 400 不断言文案者不受影响,若有断言文案则同步更新(实现者核)。
- **D-2 admin 校验位置**:在 **handler 入口**补校验(与公开面 `games.Detail` 同层),不在 `adminWorkID` 内改(其为纯拼接 helper,改它会波及调用点语义);四条 handler 复用同一小段校验。
- **D-3 开关默认值全 false**:五个新开关默认**关闭**(与现有一致——「默认值已覆盖绝大多数作品」)。`collectFeatures` 输出**七键齐全**(与既有「两键总是存在」纪律一致,后端据此区分「显式空对象=不放宽」vs「旧提交无键=保留」)。
- **D-4 文案优先**:五个新开关的 `hint` 要写清风险与适用场景(这是①的主要工作量);文案风格沿用既有两条。
- **D-5 ⑤ 仅头部**:只改 CHANGELOG 头部占位话术,不动历史条目。
## 3. 范围
**server(→0.14.0)**:②-1 错误细分、②-2 admin 校验、②-3 竞态测试、⑤ 头部文案;CHANGELOG 0.14.0 记 ②+⑤。
**crearte(→0.20.0)**:① 五开关 UI 全量(三处同步 + 测试)、⑤ 头部文案;CHANGELOG 0.20.0 记 ①+⑤。
**deploy**:零改动。
## 4. 红线
- 不动 approve 落库逻辑本身(②-3 只**加测试**,不改行为);不动路由常量值;不动 CSP 执行面(① 只补 UI,运行时的 flag 消费逻辑已存在)。
- 不新增依赖、不新增迁移。
- 前端三处 UI 一致性:投稿表单 / admin 展开行 / 审核只读展示由同一 `FEATURE_ITEMS` 驱动,不得为某一处另开硬编码。
## 5. 验收
1. server 容器四连 RC=0;新增/改动的单测全绿;②-3 竞态测试在 `-race` 下通过(若仓内既有 race 跑法,沿用)。
2. 空库 `-count=1` 集成腿全过、零静默 skip。
3. curl 冒烟:`GET /api/games/<非法user>/x` 与 `/x/<非法slug>` 各返 400 且 message 可区分;admin 四路由对非法 slug 返 400(负例)。
4. FE:vitest / typecheck / admin-flow / 主套件 RC=0;五开关在投稿表单可见可勾选、载荷含七键、admin 展开行可改、审核详情只读展示。
5. 双路独立审查(**依据 spec §1-§3 全量**,非仅任务书)PASS 后合主;wrapper 记账(ROADMAP P8 行分批完成、CHANGELOG 0.2.8、spec/plan 索引)。