From d1e5a1ac5e409f25a63fbdb0793f29083361974e Mon Sep 17 00:00:00 2001 From: kris Date: Tue, 7 Apr 2026 18:43:26 +0800 Subject: [PATCH] Refresh settings page in realtime --- .../2026-04-07-settings-page-realtime.md | 99 +++++++++++++++++++ ...026-04-07-settings-page-realtime-design.md | 21 ++++ src/app/me/settings/page.tsx | 2 + src/lib/boss-data.ts | 2 +- src/lib/boss-events.ts | 1 + tests/settings-page-realtime-refresh.test.ts | 18 ++++ tests/settings-state-events.test.ts | 62 ++++++++++++ 7 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-04-07-settings-page-realtime.md create mode 100644 docs/superpowers/specs/2026-04-07-settings-page-realtime-design.md create mode 100644 tests/settings-page-realtime-refresh.test.ts create mode 100644 tests/settings-state-events.test.ts diff --git a/docs/superpowers/plans/2026-04-07-settings-page-realtime.md b/docs/superpowers/plans/2026-04-07-settings-page-realtime.md new file mode 100644 index 0000000..8585a67 --- /dev/null +++ b/docs/superpowers/plans/2026-04-07-settings-page-realtime.md @@ -0,0 +1,99 @@ +# Settings Page Realtime Refresh Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 让 `/me/settings` 在用户设置变更后自动刷新。 + +**Architecture:** 复用现有 `boss-events -> /api/v1/events -> RealtimeRefresh` SSE 链路,新增一个语义明确的 `settings.updated` 事件。`updateUserSettings` 写入文件状态后发布事件,设置页只监听这个事件。 + +**Tech Stack:** Next.js App Router, TypeScript, Node test runner, SSE + +--- + +### Task 1: 写红灯测试 + +**Files:** +- Create: `tests/settings-page-realtime-refresh.test.ts` +- Create: `tests/settings-state-events.test.ts` + +- [ ] **Step 1: 写设置页接线测试** + +```ts +assert.match(source, / +``` + +- [ ] **Step 4: 跑本轮测试确认转绿** + +Run: `npx tsx --test tests/settings-page-realtime-refresh.test.ts tests/settings-state-events.test.ts` +Expected: PASS + +### Task 3: 验证与提交 + +**Files:** +- Test: `tests/settings-page-realtime-refresh.test.ts` +- Test: `tests/settings-state-events.test.ts` + +- [ ] **Step 1: 跑相关 realtime 测试** + +Run: `npx tsx --test tests/settings-page-realtime-refresh.test.ts tests/settings-state-events.test.ts tests/config-pages-realtime-refresh.test.ts tests/config-state-events.test.ts tests/realtime-refresh-utils.test.ts tests/project-scoped-realtime-refresh.test.ts tests/status-pages-realtime-refresh.test.ts` +Expected: PASS + +- [ ] **Step 2: 跑 lint** + +Run: `npm run lint` +Expected: exit code 0 + +- [ ] **Step 3: 跑 build** + +Run: `npm run build` +Expected: exit code 0 + +- [ ] **Step 4: 提交** + +```bash +git add docs/superpowers/specs/2026-04-07-settings-page-realtime-design.md docs/superpowers/plans/2026-04-07-settings-page-realtime.md tests/settings-page-realtime-refresh.test.ts tests/settings-state-events.test.ts src/lib/boss-events.ts src/lib/boss-data.ts src/app/me/settings/page.tsx +git commit -m "Refresh settings page in realtime" +``` diff --git a/docs/superpowers/specs/2026-04-07-settings-page-realtime-design.md b/docs/superpowers/specs/2026-04-07-settings-page-realtime-design.md new file mode 100644 index 0000000..2688e6d --- /dev/null +++ b/docs/superpowers/specs/2026-04-07-settings-page-realtime-design.md @@ -0,0 +1,21 @@ +# 设置页实时刷新 Design + +## 目标 + +让 Web 设置页 `/me/settings` 在用户设置被其他窗口或设备修改后自动刷新。 + +## 范围 + +本轮只处理设置页: + +- 新增 `settings.updated` SSE 事件 +- `updateUserSettings` 写入成功后发布 `settings.updated` +- `/me/settings` 页面通过 `RealtimeRefresh` 监听 `settings.updated` + +不处理 `devices/add`、`me/security` 和根重定向页;这些页面没有同等价值的实时刷新需求。 + +## 验证 + +- 页面接线测试确认设置页渲染 `RealtimeRefresh` 并监听 `settings.updated` +- 状态事件测试确认 `updateUserSettings` 会发布 `settings.updated` +- 跑相关 realtime 测试、`npm run lint`、`npm run build` diff --git a/src/app/me/settings/page.tsx b/src/app/me/settings/page.tsx index 88b36ab..ca06307 100644 --- a/src/app/me/settings/page.tsx +++ b/src/app/me/settings/page.tsx @@ -1,3 +1,4 @@ +import { RealtimeRefresh } from "@/components/app-runtime"; import { AppShell, PageNav, SettingsForm, StatusBar } from "@/components/app-ui"; import { requirePageSession } from "@/lib/boss-auth"; import { readState } from "@/lib/boss-data"; @@ -10,6 +11,7 @@ export default async function SettingsPage() { return ( +
diff --git a/src/lib/boss-data.ts b/src/lib/boss-data.ts index 0412322..3b1a0b8 100644 --- a/src/lib/boss-data.ts +++ b/src/lib/boss-data.ts @@ -9630,7 +9630,7 @@ export async function updateUserSettings(settings: Partial) { }; return state.user.settings; }); - publishBossEvent("conversation.updated", { deviceId: PRIMARY_CODEX_NODE_ID }); + publishBossEvent("settings.updated"); return nextSettings; } diff --git a/src/lib/boss-events.ts b/src/lib/boss-events.ts index ee5fac3..e335df6 100644 --- a/src/lib/boss-events.ts +++ b/src/lib/boss-events.ts @@ -10,6 +10,7 @@ export type BossEventName = | "ai_accounts.updated" | "devices.updated" | "devices.skills.updated" + | "settings.updated" | "storage.updated" | "ota.updated"; diff --git a/tests/settings-page-realtime-refresh.test.ts b/tests/settings-page-realtime-refresh.test.ts new file mode 100644 index 0000000..a5909f0 --- /dev/null +++ b/tests/settings-page-realtime-refresh.test.ts @@ -0,0 +1,18 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +test("settings page refreshes when user settings change", async () => { + const source = await readFile( + new URL("../src/app/me/settings/page.tsx", import.meta.url), + "utf8", + ); + + assert.match(source, /import \{ RealtimeRefresh \}/, "expected settings page to import RealtimeRefresh"); + assert.match(source, / { + await setup(); + await resetSettingsState(); +}); + +test.after(async () => { + if (runtimeRoot) { + await rm(runtimeRoot, { recursive: true, force: true }); + } +}); + +test("updateUserSettings publishes settings refresh event", async () => { + const events: Array<{ event: string }> = []; + const unsubscribe = subscribeBossEvents((event) => { + events.push({ event }); + }); + + await updateUserSettings({ liveUpdates: false }); + unsubscribe(); + + assert.equal(events.at(-1)?.event, "settings.updated"); +});