Scope folder realtime refreshes by device
This commit is contained in:
197
docs/superpowers/plans/2026-04-10-sse-refresh-noise-reduction.md
Normal file
197
docs/superpowers/plans/2026-04-10-sse-refresh-noise-reduction.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# SSE Refresh Noise Reduction 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:** Reduce unnecessary realtime reloads on scoped conversation folder pages without breaking global conversation refresh behavior.
|
||||
|
||||
**Architecture:** Keep server-side event names stable and narrow consumption at scoped clients. The shared Web realtime refresh utility accepts optional device scope, while the Android folder activity applies the same project-first, device-only fallback filter.
|
||||
|
||||
**Tech Stack:** Next.js App Router, TypeScript `node:test`, Android Java native activities, Server-Sent Events.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Device Scope To Shared Web Refresh Filtering
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/realtime-refresh.ts`
|
||||
- Test: `tests/realtime-refresh-utils.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
```ts
|
||||
assert.equal(
|
||||
shouldRefreshRealtimeEvent({
|
||||
eventType: "conversation.updated",
|
||||
eventData: JSON.stringify({ deviceId: "mac-studio" }),
|
||||
projectIds: ["project-a", "project-b"],
|
||||
deviceId: "mac-studio",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
shouldRefreshRealtimeEvent({
|
||||
eventType: "conversation.updated",
|
||||
eventData: JSON.stringify({ deviceId: "windows-box" }),
|
||||
projectIds: ["project-a", "project-b"],
|
||||
deviceId: "mac-studio",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test and verify the new device-scope test fails**
|
||||
|
||||
Run: `npx tsx --test tests/realtime-refresh-utils.test.ts`
|
||||
|
||||
Expected: the new device-scope case fails before `deviceId` support exists in `RealtimeRefreshScope`.
|
||||
|
||||
- [ ] **Step 3: Add minimal device scope support**
|
||||
|
||||
```ts
|
||||
export interface RealtimeRefreshScope {
|
||||
projectId?: string;
|
||||
projectIds?: string[];
|
||||
deviceId?: string;
|
||||
deviceIds?: string[];
|
||||
conversationUpdatedNotes?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
Then parse `deviceId` from the JSON payload and require scoped listeners to match at least one scoped project or device identifier before refreshing.
|
||||
|
||||
- [ ] **Step 4: Run the focused test and verify it passes**
|
||||
|
||||
Run: `npx tsx --test tests/realtime-refresh-utils.test.ts`
|
||||
|
||||
Expected: all realtime refresh utility tests pass.
|
||||
|
||||
### Task 2: Wire Web Folder Device Scope
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/app-runtime.tsx`
|
||||
- Modify: `src/app/conversations/folders/[folderKey]/page.tsx`
|
||||
- Test: `tests/project-scoped-realtime-refresh.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing route wiring assertion**
|
||||
|
||||
```ts
|
||||
assert.match(
|
||||
folderPage,
|
||||
/deviceId=\{folder\.deviceId\}/,
|
||||
"expected folder page to scope device-only refreshes to the folder device",
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the route test and verify the assertion fails**
|
||||
|
||||
Run: `npx tsx --test tests/project-scoped-realtime-refresh.test.ts`
|
||||
|
||||
Expected: the assertion fails until the folder page passes `deviceId`.
|
||||
|
||||
- [ ] **Step 3: Pass device scope through the runtime component**
|
||||
|
||||
```tsx
|
||||
<RealtimeRefresh
|
||||
projectIds={folder.threads.map((thread) => thread.projectId)}
|
||||
deviceId={folder.deviceId}
|
||||
events={["conversation.updated", "project.messages.updated"]}
|
||||
/>
|
||||
```
|
||||
|
||||
Also pass `deviceId` and `deviceIds` from `RealtimeRefresh` into `shouldRefreshRealtimeEvent`.
|
||||
|
||||
- [ ] **Step 4: Run the route test and verify it passes**
|
||||
|
||||
Run: `npx tsx --test tests/project-scoped-realtime-refresh.test.ts`
|
||||
|
||||
Expected: the folder route wiring test passes.
|
||||
|
||||
### Task 3: Mirror Filtering In Android Folder Activity
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java`
|
||||
- Test: `tests/android-folder-realtime-refresh.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing Android source assertion**
|
||||
|
||||
```ts
|
||||
assert.match(
|
||||
source,
|
||||
/String payloadDeviceId = event\.payload\.optString\("deviceId", ""\)\.trim\(\);/,
|
||||
"expected folder activity to inspect device-scoped realtime payloads",
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the Android static test and verify it fails**
|
||||
|
||||
Run: `npx tsx --test tests/android-folder-realtime-refresh.test.ts`
|
||||
|
||||
Expected: the test fails until the folder activity reads `deviceId`.
|
||||
|
||||
- [ ] **Step 3: Add project-first and device-only filtering**
|
||||
|
||||
```java
|
||||
String payloadProjectId = event.payload.optString("projectId", "").trim();
|
||||
if (!payloadProjectId.isEmpty()) {
|
||||
return trackedProjectIds.contains(payloadProjectId)
|
||||
|| (!targetProjectIds.isEmpty() && targetProjectIds.contains(payloadProjectId))
|
||||
|| (targetProjectId != null && targetProjectId.equals(payloadProjectId));
|
||||
}
|
||||
String payloadDeviceId = event.payload.optString("deviceId", "").trim();
|
||||
if (payloadDeviceId.isEmpty() || folderDeviceId == null || folderDeviceId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return payloadDeviceId.equals(folderDeviceId);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the Android static test and verify it passes**
|
||||
|
||||
Run: `npx tsx --test tests/android-folder-realtime-refresh.test.ts`
|
||||
|
||||
Expected: the Android folder realtime filtering test passes.
|
||||
|
||||
### Task 4: Verify Integration
|
||||
|
||||
**Files:**
|
||||
- Verify: `src/lib/realtime-refresh.ts`
|
||||
- Verify: `src/components/app-runtime.tsx`
|
||||
- Verify: `src/app/conversations/folders/[folderKey]/page.tsx`
|
||||
- Verify: `android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java`
|
||||
- Verify: `scripts/deploy-server.sh`
|
||||
|
||||
- [ ] **Step 1: Run focused realtime tests**
|
||||
|
||||
Run: `npx tsx --test tests/realtime-refresh-utils.test.ts tests/project-scoped-realtime-refresh.test.ts tests/android-folder-realtime-refresh.test.ts`
|
||||
|
||||
Expected: all focused realtime tests pass.
|
||||
|
||||
- [ ] **Step 2: Run deployment script guard test**
|
||||
|
||||
Run: `npx tsx --test tests/deploy-server-script.test.ts`
|
||||
|
||||
Expected: the deploy script tests pass and assert `.project`, `.classpath`, and `.settings` are excluded from rsync.
|
||||
|
||||
- [ ] **Step 3: Run broader realtime page tests**
|
||||
|
||||
Run: `npx tsx --test tests/realtime-refresh-utils.test.ts tests/project-scoped-realtime-refresh.test.ts tests/android-folder-realtime-refresh.test.ts tests/config-pages-realtime-refresh.test.ts tests/config-state-events.test.ts tests/settings-page-realtime-refresh.test.ts tests/settings-state-events.test.ts tests/status-pages-realtime-refresh.test.ts`
|
||||
|
||||
Expected: all selected realtime and event contract tests pass.
|
||||
|
||||
- [ ] **Step 4: Run project gates**
|
||||
|
||||
Run: `npm run lint`
|
||||
|
||||
Expected: lint exits 0.
|
||||
|
||||
Run: `npm run build`
|
||||
|
||||
Expected: production build exits 0.
|
||||
|
||||
- [ ] **Step 5: Stage only intended files**
|
||||
|
||||
```bash
|
||||
git add src/lib/realtime-refresh.ts src/components/app-runtime.tsx 'src/app/conversations/folders/[folderKey]/page.tsx' tests/realtime-refresh-utils.test.ts tests/project-scoped-realtime-refresh.test.ts tests/android-folder-realtime-refresh.test.ts android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java scripts/deploy-server.sh tests/deploy-server-script.test.ts docs/superpowers/specs/2026-04-10-sse-refresh-noise-reduction-design.md docs/superpowers/plans/2026-04-10-sse-refresh-noise-reduction.md
|
||||
```
|
||||
|
||||
Expected: generated IDE metadata under `android/.project`, `android/.settings`, `android/app/.project`, `android/app/.settings`, and `android/app/.classpath` remains untracked.
|
||||
Reference in New Issue
Block a user