Skip to content

实验室:按用户开关的实验功能基座 - #117

Merged
DGuang21 merged 1 commit into
codex/saved-link-exportfrom
feat/labs
Sep 9, 2026
Merged

DGuang21 merged 1 commit into
codex/saved-link-exportfrom
feat/labs

Conversation

@wulujia

@wulujia wulujia commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

实验室是设置页的一张卡片:实验功能按用户开关,默认关,开关存服务端。这个 PR 是开源基座,注册表为空,自托管看不到任何变化;托管后端用同名子类注册具体功能。

  • 新表 sr_user_lab_feature(user_id, feature, enabled, created_at, updated_at),一人一功能一行,没有行就是关。迁移建表和两个索引。
  • LabRepo:listByUser / isEnabled / upsert。
  • LabService:注册表是可覆盖的方法。三种状态:active 读用户开关,graduated 对所有人开,retired 对所有人关。assertUrlAllowed() 在保存前拦截需要实验功能的链接,gatedFeatureForUrl() 是托管端覆盖的钩子,这里返回 null。
  • 错误码 LAB_FEATURE_DISABLED(400),三语文案带功能名,客户端可以原样显示。
  • GET /v1/user/labs 返回 { features: [{ key, status, enabled, enabled_at }] }POST /v1/user/setting/enable|disable 的 key 以 lab: 开头时写开关。UserService 多一个 LabService 参数。
  • 重新生成了 router 和 DI。

验证:vitest 6 个文件 29 条通过(新增 13 条);tsc 没有新增错误。

基于 #116(导出)的分支,那个合并后这个会自动改成对 main。

关联 PR:

Labs is a card on the settings page: features still being tried out,
off by default, switch stored server-side per user.

- sr_user_lab_feature (user_id, feature, enabled, created_at, updated_at);
  no row means off. Migration adds the table and its two indexes.
- LabRepo: listByUser / isEnabled / upsert on the Postgres client.
- LabService: registry is an overridable method (empty here so a
  self-hosted build sees no change); status active reads the switch,
  graduated is on for everyone, retired is off for everyone.
  assertUrlAllowed() throws LAB_FEATURE_DISABLED when a URL needs a
  feature the user has not turned on; gatedFeatureForUrl() is the hook a
  hosted backend overrides to say which URLs are gated.
- LAB_FEATURE_DISABLED (400) carries the feature's display name in the
  localized message (zh / en / es), so clients show it as-is.
- GET /v1/user/labs returns { features: [{ key, status, enabled, enabled_at }] }.
- POST /v1/user/setting/enable|disable with key "lab:<feature>" flips
  the switch; UserService takes LabService.
- Regenerated router and DI.

Claude-Session: https://claude.ai/code/session_01Xg119Pyy2B2UQQddrxw5Dv

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@DGuang21
DGuang21 merged commit 1874891 into codex/saved-link-export Sep 9, 2026
1 check passed
@DGuang21
DGuang21 deleted the feat/labs branch September 9, 2026 04:25
DGuang21 added a commit that referenced this pull request Sep 9, 2026
* feat: export saved links with account-scoped pagination

* 🧪 per-user switches for Labs features (#117)

Labs is a card on the settings page: features still being tried out,
off by default, switch stored server-side per user.

- sr_user_lab_feature (user_id, feature, enabled, created_at, updated_at);
  no row means off. Migration adds the table and its two indexes.
- LabRepo: listByUser / isEnabled / upsert on the Postgres client.
- LabService: registry is an overridable method (empty here so a
  self-hosted build sees no change); status active reads the switch,
  graduated is on for everyone, retired is off for everyone.
  assertUrlAllowed() throws LAB_FEATURE_DISABLED when a URL needs a
  feature the user has not turned on; gatedFeatureForUrl() is the hook a
  hosted backend overrides to say which URLs are gated.
- LAB_FEATURE_DISABLED (400) carries the feature's display name in the
  localized message (zh / en / es), so clients show it as-is.
- GET /v1/user/labs returns { features: [{ key, status, enabled, enabled_at }] }.
- POST /v1/user/setting/enable|disable with key "lab:<feature>" flips
  the switch; UserService takes LabService.
- Regenerated router and DI.

Claude-Session: https://claude.ai/code/session_01Xg119Pyy2B2UQQddrxw5Dv

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: daguang <28806852+DGuang21@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants