verstak-docs/docs/superpowers/plans/2026-07-12-alpha-activity-j...

241 lines
11 KiB
Markdown

# Alpha Activity And Journal 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:** Make Activity a durable, background-maintained session log that safely proposes Journal entries without creating a Дело automatically.
**Architecture:** Desktop adds a plugin background-service lifecycle, unsubscribe-capable event bridge, and append-only Activity log. The Activity plugin owns session reconstruction and watermarks; Journal receives an explicit reviewed candidate and requires a destination for unassigned sessions.
**Tech Stack:** Go, Wails, Svelte, plain JavaScript plugin bundles, Go tests, Node smoke tests, Playwright.
## Global Constraints
- Activity raw events are retained for 60 days, 10,000 events, or 8 MiB, whichever is reached first.
- Raw activity uses append/compaction, not settings.json rewrites.
- Session scope is workspaceId plus path cache or unassigned.
- Point-event duration is capped at 10 minutes per adjacent pair; gap over 20 minutes starts another session.
- Accepted/dismissed watermarks consume only the reviewed slice.
- Journal save is always an explicit user action.
---
### Task 1: Add background services, event unsubscription, and an append-only Activity log
**Files:**
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/plugin/plugin.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/plugin/plugin_test.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/events/bus.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/storage/api.go
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/storage/api_test.go
- Create: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-host/BackgroundPluginHost.svelte
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/App.svelte
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/plugin-host/VerstakPluginAPI.js
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/src/types.ts
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/schemas/contributions.json
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/schemas/manifest.json
- Modify: /home/mirivlad/git/verstak2/verstak-sdk/src/plugin-api.test.ts
**Interfaces:**
- Adds contributes.backgroundServices with id and component fields.
- Bus Subscribe returns subscription ID; Unsubscribe removes only that handler.
- Adds ActivityLogAppend, ActivityLogRead, ActivityLogDeleteWorkspace, and ActivityLogCompact backend methods.
- Background bundle registration exposes start(api) returning a cleanup function.
- [ ] **Step 1: Write failing manifest, bus, and storage tests**
Add a manifest validation test and isolated bus-unsubscribe test:
~~~go
subA := bus.Subscribe("browser.activity.domain", handlerA)
subB := bus.Subscribe("browser.activity.domain", handlerB)
bus.Unsubscribe("browser.activity.domain", subA)
bus.Publish(events.Event{Name: "browser.activity.domain"})
if gotA != 0 || gotB != 1 { t.Fatalf("wrong selective unsubscribe: %d %d", gotA, gotB) }
~~~
Add an append test asserting two appends create two NDJSON records and a
compaction test keeping only the newest record under a small test limit.
- [ ] **Step 2: Run red**
Run:
~~~bash
cd /home/mirivlad/git/verstak2/verstak-desktop
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/plugin ./internal/core/events ./internal/core/storage ./internal/api -run 'Test.*(Background|Unsubscribe|ActivityLog)' -count=1
~~~
Expected: FAIL because subscriptions have no IDs and the log API does not exist.
- [ ] **Step 3: Implement the platform boundary**
Define:
~~~go
type ContributionBackgroundService struct {
ID string `json:"id"`
Component string `json:"component"`
}
type ActivityLogRecord struct {
ActivityID string `json:"activityId"`
Payload json.RawMessage `json:"payload"`
}
~~~
Append NDJSON under plugin-data/verstak.activity/events.ndjson using a locked
append. Compact to the declared retention limits by rewriting a temporary file.
Keep only compact candidate state in plugin data. BackgroundPluginHost loads
enabled plugins with a background contribution once at app startup and calls the
registered start function; it calls cleanup on disable/reload/destroy.
- [ ] **Step 4: Run green and commit**
Run:
~~~bash
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/plugin ./internal/core/events ./internal/core/storage ./internal/api -count=1
git add internal/core/plugin internal/core/events internal/core/storage internal/api frontend/src/App.svelte frontend/src/lib/plugin-host
git commit -m "feat: add background activity runtime"
git -C /home/mirivlad/git/verstak2/verstak-sdk add src/types.ts schemas/contributions.json schemas/manifest.json src/plugin-api.test.ts
git -C /home/mirivlad/git/verstak2/verstak-sdk commit -m "feat: define plugin background services"
~~~
Expected: focused tests prove one subscription, selective cleanup, append, and compaction.
### Task 2: Rebuild Activity sessions and persistent candidate watermarks
**Files:**
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/plugin.json
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/frontend/src/index.js
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/locales/en.json
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/locales/ru.json
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-activity-plugin.js
**Interfaces:**
- Activity background service registers ActivityService.start(api).
- Event record includes activityId, scope, occurredAt, durationSeconds, and sessionId.
- Candidate contains sessionId, handledThrough, estimatedMinutes, dateSlices, and source activities.
- [ ] **Step 1: Write failing smoke scenarios**
Add three explicit scenarios:
~~~js
const nineMinutes = eventsAt('2026-07-12T10:00:00Z', '2026-07-12T10:09:00Z');
assert.equal(buildSessions(nineMinutes)[0].estimatedMinutes, 9);
const late = appendLateEvent(existingSession, eventAt('2026-07-12T09:58:00Z'));
assert.equal(late.sessionId, existingSession.sessionId);
assert.equal(candidateAfterDismiss.sourceActivityIds.includes('a'), false);
~~~
Also assert a 23:50 to 00:30 session has one session ID and date slices for both
local dates.
- [ ] **Step 2: Run red**
Run:
~~~bash
cd /home/mirivlad/git/verstak2/verstak-official-plugins
node scripts/smoke-activity-plugin.js
~~~
Expected: FAIL because Activity derives candidate IDs from first and last events.
- [ ] **Step 3: Implement background session state**
Replace view-mounted event recording with ActivityService.start(api). Persist
immutable generated session IDs and anchors in activity-state.json. Build scope
as either workspaceId plus root path or unassigned. Sum explicit browser
duration and only zero-duration adjacent point intervals, cap a point interval
at 10 minutes, split at 20 minutes, and cap a session at 120 minutes.
For accept or dismiss, store the ordered handledThrough watermark. New events
after the watermark require another 10 minutes before a new candidate appears;
late events at or before the watermark remain diagnostic only.
- [ ] **Step 4: Run green and commit**
Run:
~~~bash
node scripts/smoke-activity-plugin.js
./scripts/check.sh
git add plugins/activity scripts/smoke-activity-plugin.js
git commit -m "feat: persist activity sessions and watermarks"
~~~
Expected: session, late-event, dismissal, browser-duration, and midnight smoke tests pass.
### Task 3: Review candidates safely in Journal and expose sessions in UI
**Files:**
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/activity/frontend/src/index.js
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/frontend/src/index.js
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/locales/en.json
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/plugins/journal/locales/ru.json
- Modify: /home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-journal-plugin.js
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/activity.spec.js
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/e2e/todo.spec.js
- Modify: /home/mirivlad/git/verstak2/verstak-desktop/frontend/src/lib/test/wails-mock.js
**Interfaces:**
- Journal candidate request accepts destinationWorkspaceId only for unassigned scope.
- Saved Journal entry stores sourceSessionId and handledThrough in addition to activity IDs.
- Activity UI has Sessions default and Events diagnostic view.
- [ ] **Step 1: Write failing E2E and smoke assertions**
Require an unassigned candidate to show a destination selector and reject Save
until an active workspace ID is selected:
~~~js
await page.getByRole('button', { name: /review/i }).click();
await expect(page.getByText(/choose.*дело/i)).toBeVisible();
await expect(page.getByRole('button', { name: /save/i })).toBeDisabled();
~~~
Require the mock WritePluginSetting path to retain sourceSessionId after reload.
Require Clear Activity and Journal Delete to show cancellation-safe confirmation
before changing stored rows.
- [ ] **Step 2: Run red**
Run:
~~~bash
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend
npm run test:e2e -- activity.spec.js todo.spec.js
~~~
Expected: FAIL because the current mock discards settings and Journal only accepts path-scoped candidates.
- [ ] **Step 3: Implement review UI and local dates**
Render domain rows as hostname plus duration without URLs. Keep raw event
details in the Events tab. For unassigned scope, list active workspace IDs and
require a selection before handing the candidate to Journal. Preselect the
largest date slice, show both date slices, use local date conversion, and save
the session watermark with the Journal entry. Show a visible action if Journal
is disabled instead of silently dropping the request. Put a confirm/cancel
dialog in front of Clear Activity and Journal Delete; cancelling leaves the
append log, candidate state, and entry list untouched.
- [ ] **Step 4: Run green and commit**
Run:
~~~bash
cd /home/mirivlad/git/verstak2/verstak-official-plugins && node scripts/smoke-activity-plugin.js && node scripts/smoke-journal-plugin.js
cd /home/mirivlad/git/verstak2/verstak-desktop/frontend && npm run test:e2e -- activity.spec.js todo.spec.js
git -C /home/mirivlad/git/verstak2/verstak-official-plugins add plugins/activity plugins/journal scripts/smoke-activity-plugin.js scripts/smoke-journal-plugin.js
git -C /home/mirivlad/git/verstak2/verstak-official-plugins commit -m "feat: review activity sessions in journal"
git -C /home/mirivlad/git/verstak2/verstak-desktop add frontend/e2e/activity.spec.js frontend/e2e/todo.spec.js frontend/src/lib/test/wails-mock.js
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "test: cover activity journal review flow"
~~~
Expected: candidate-to-Journal, local date, and persisted mock workflows pass.