diff --git a/.planning/phases/20-demo-instance-with-every-shared-plugin/20-04-SUMMARY.md b/.planning/phases/20-demo-instance-with-every-shared-plugin/20-04-SUMMARY.md new file mode 100644 index 00000000..66ce4e6e --- /dev/null +++ b/.planning/phases/20-demo-instance-with-every-shared-plugin/20-04-SUMMARY.md @@ -0,0 +1,303 @@ +--- +phase: 20-demo-instance-with-every-shared-plugin +plan: 04 +subsystem: vue-forum-layer, vue-userfriends-layer, vue-chatvideo-layer, vue-demo-app, sm-demo-app +tags: [forum, friends, video-calls, livekit, nuxt-layer, herald, vapid, tour, i18n] + +requires: + - phase: 20-02 + provides: "Chat family mounted in sm-demo-app, seeded forum and friends, local LiveKit, Centrifugo namespaces chat/user/video, GET /_demo/api/v1/chat/members" + - phase: 20-03 + provides: "vue-chat-layer v0.1.0: conversation-actions (DM only) and sidebar-footer slots, onUserEvent and subscribe on the page's one realtime client, $chatAuth" +provides: + - "vue-forum-layer v0.1.0 on git.golem15.com (public), tag v0.1.0 = dcd6549" + - "vue-userfriends-layer v0.1.0 on git.golem15.com (public), tag v0.1.0 = bcc8e99" + - "vue-chatvideo-layer v0.1.0 on git.golem15.com (public), tag v0.1.0 = 75e9a40, livekit-client 2.23.0 exact" + - "vue-demo-app /forum, /forum/t/{id}, /friends, /call/{conversation}, the DM call button, the incoming-call prompt, ten tour cards, all three layers pinned by tag" + - "sm-demo-app check-local forum, friends, call, herald; tour expects ten cards; smoke.sh covers /forum and /friends; dev.sh writes herald's VAPID keys into .env" + - "Phase COVERAGE.md (livekit-client, LiveKit server, Centrifugo, Web Push)" +affects: [20-05, 20-06] + +actuals: + tokens: 56900 # chars/4: the three layers' tracked files without lockfiles (56107 + 54654 + 63266) + vue-demo-app diff 3fe8018..9357711 without the lockfile (28917) + sm-demo-app diff b4fc7dc..fc23b4c in scripts, README and deploy (19375) + COVERAGE.md (5418) = 227737 chars + tasks: 7 + commits: 1 # MEASURED in summercms.go: git rev-list --count b40c519..0301dc2 (COVERAGE.md) before this SUMMARY commit; code commits: vue-forum-layer 1, vue-userfriends-layer 1, vue-chatvideo-layer 1, vue-demo-app 5, sm-demo-app 4 +plan_head_before: b40c519 # summercms.go +plan_head_after: 0301dc2ae48607c69847b3300ec844f80cad25d3 # summercms.go before this SUMMARY commit + +tech-stack: + added: + - "livekit-client 2.23.0 (exact, vue-chatvideo-layer dependency; approved at the Task 3 checkpoint)" + - "vue-forum-layer, vue-userfriends-layer, vue-chatvideo-layer (new shared Nuxt layers); every other package is an existing workspace pin (@nuxtjs/i18n 10.4.0, nuxt 4.4.8, vue 3.5.35, typescript 5.9.3, vitest 3.2.7)" + patterns: + - "Layer clients in app/utils carry a layer prefix on every export (forumError, friendTarget), because Nuxt auto-imports app/utils into the app" + - "A companion layer (vue-chatvideo-layer) takes vue-chat-layer as a peer dependency, does not extend it, and dedupes it in Vite so the realtime module stays one instance" + - "Token getter hooks per layer ($forumAuth, $userfriendsAuth), optional data hook ($userfriendsDirectory), $chatAuth reused by the companion layer" + +key-files: + created: + - ../vue-forum-layer/app/utils/forumClient.ts + - ../vue-forum-layer/app/components/Forum/Thread.vue + - ../vue-forum-layer/README.md + - ../vue-userfriends-layer/app/utils/friendsClient.ts + - ../vue-userfriends-layer/app/components/Friends/Groups.vue + - ../vue-userfriends-layer/README.md + - ../vue-chatvideo-layer/app/lib/callMachine.ts + - ../vue-chatvideo-layer/app/lib/videoClient.ts + - ../vue-chatvideo-layer/app/composables/useCall.ts + - ../vue-chatvideo-layer/app/composables/useIncomingCalls.ts + - ../vue-chatvideo-layer/README.md + - ../sm-demo-app/vue-demo-app/app/pages/forum/index.vue + - ../sm-demo-app/vue-demo-app/app/pages/forum/t/[id].vue + - ../sm-demo-app/vue-demo-app/app/pages/friends.vue + - ../sm-demo-app/vue-demo-app/app/pages/call/[conversation].vue + - .planning/phases/20-demo-instance-with-every-shared-plugin/COVERAGE.md + modified: + - ../sm-demo-app/vue-demo-app/package.json + - ../sm-demo-app/vue-demo-app/pnpm-lock.yaml + - ../sm-demo-app/vue-demo-app/nuxt.config.ts + - ../sm-demo-app/vue-demo-app/app/plugins/auth.ts + - ../sm-demo-app/vue-demo-app/app/pages/chat.vue + - ../sm-demo-app/vue-demo-app/app/pages/index.vue + - ../sm-demo-app/vue-demo-app/app/pages/account/security.vue + - ../sm-demo-app/vue-demo-app/app/layouts/default.vue + - ../sm-demo-app/vue-demo-app/app/components/SiteHeader.vue + - ../sm-demo-app/vue-demo-app/app/components/TourCard.vue + - ../sm-demo-app/vue-demo-app/i18n/locales/en.json + - ../sm-demo-app/vue-demo-app/i18n/locales/pl.json + - ../sm-demo-app/scripts/check-local.sh + - ../sm-demo-app/scripts/smoke.sh + - ../sm-demo-app/scripts/dev.sh + - ../sm-demo-app/deploy/env.example + - ../sm-demo-app/README.md + +key-decisions: + - "Task 3 checkpoint: the user approved livekit-client 2.23.0 (repository github.com/livekit/client-sdk-js, npm maintainer thedavidzhao, no install or postinstall hooks, npm provenance attestations, published 2026-10-16, 15 days old; compatibility with LiveKit server 1.13.1 not checked separately, the local 1.13.1 calls work)" + - "Task 6 checkpoint: release. The subagent's tea repo create was refused by the permission system; the orchestrator, after the user's go-ahead in manual mode, created the three repositories public, pushed main and the annotated tags v0.1.0 (remote tags equal HEAD)" + - "Decline posts video/leave: no chatvideo path publishes video:call:declined; the caller shows a did-not-answer note for a participant who left without joining" + - "The last one out ends the call (video/end instead of video/leave), so the next start rings again" + - "The call button's start answer is handed to the call page in memory (one start per call, no token in the URL)" + - "Forum manage rights come from the host: ForumThread canManage, filled by the demo from the chat bootstrap's moderator flag" + +patterns-established: + - "check-local throwaway_user prints e-mail and JWT; LiveKit join tokens are validated with GET /rtc/validate, the token passed to curl through a config on stdin" + +requirements-completed: [SC-2, D-11, D-12, D-13, D-14, D-15, D-16, D-20, D-21, D-22, D-23] + +coverage: + - id: D1 + description: "Members read the seeded EN/PL forum, create threads, reply, preview, edit and delete own posts; anonymous /forum has the register link to /register?next=/forum and no forum" + requirement: "D-14, D-21, D-22" + verification: + - kind: e2e + ref: "../sm-demo-app/scripts/check-local.sh forum" + status: pass + - kind: unit + ref: "../vue-forum-layer/tests/client.test.ts" + status: pass + - kind: manual + ref: "Playwright probe (scratchpad forum.cjs): categories, thread, preview, reply, edit, delete, new thread, no console error" + status: pass + human_judgment: true + - id: D2 + description: "Friends, requests (accept, reject, cancel), unfriend, groups (create, invite code, join by code, leave); /friends linked from the chat sidebar, the header and /account/security" + requirement: "D-15" + verification: + - kind: e2e + ref: "../sm-demo-app/scripts/check-local.sh friends" + status: pass + - kind: unit + ref: "../vue-userfriends-layer/tests/client.test.ts" + status: pass + - kind: manual + ref: "Playwright probe (scratchpad friends.cjs) with two throwaway members" + status: pass + human_judgment: true + - id: D3 + description: "DM-only call button; start, LiveKit token accepted, end; two members in a call with remote video, mute and camera off seen remotely, decline note, prompt cleared when the caller hangs up, server end ends the page's call" + requirement: "D-11, D-12, D-13, D-23" + verification: + - kind: e2e + ref: "../sm-demo-app/scripts/check-local.sh call" + status: pass + - kind: unit + ref: "../vue-chatvideo-layer/tests/callMachine.test.ts" + status: pass + - kind: manual + ref: "Playwright probe (scratchpad call.cjs) with fake media, against the link pins" + status: pass + human_judgment: true + - id: D4 + description: "Herald VAPID keys in .env without printing them; public-key route 401 without and 200 with a JWT; no /_herald in goPaths" + requirement: "D-16" + verification: + - kind: e2e + ref: "../sm-demo-app/scripts/check-local.sh herald" + status: pass + human_judgment: false + - id: D5 + description: "Ten tour cards in EN and PL; vue-demo-app tests and build against the three tags" + requirement: "D-03, D-20" + verification: + - kind: e2e + ref: "check-local.sh tour; forum friends call chat tour after the tag pin" + status: pass + - kind: build + ref: "pnpm --dir ../sm-demo-app/vue-demo-app test (209) && run build" + status: pass + human_judgment: false + +duration: about 2 h of work (14:13 to 15:15 for Tasks 1 to 5, then the release checkpoint and Task 7 until 20:03) +completed: 2026-10-31 +status: complete +--- + +# Phase 20 Plan 04: forum, friends and video-call layers v0.1.0 on the demo site Summary + +**Three new shared layers are released at v0.1.0: vue-forum-layer (categories, threads, Markdown posts from the server's content_html), vue-userfriends-layer (friends, requests, groups with invite codes) and vue-chatvideo-layer (DM calls on livekit-client 2.23.0, loaded on demand). The demo site has /forum, /friends, /call/{conversation}, a call button in DM headers, an incoming-call prompt on every page, ten tour cards, and herald runs with VAPID keys and no UI.** + +## Performance + +- **Tasks:** 7/7. Task 3 (package legitimacy) and Task 6 (release go-ahead) were checkpoints answered by the user. +- **Files:** vue-forum-layer 22, vue-userfriends-layer 22 and vue-chatvideo-layer 22 tracked files; vue-demo-app 5 commits; sm-demo-app 4 commits. + +## The layers' public API (v0.1.0) + +**vue-forum-layer** +- Runtime config: `forum.apiBase` (`NUXT_FORUM_API_BASE`), `public.forum.apiBase` (`/_forum/api/v1`). +- Hook: `$forumAuth` (token getter). +- Components: `ForumPage` (contextType, contextId, threadPath), `ForumThread` (contextType, contextId, threadId, canManage, backPath), `ForumCategoryList`, `ForumThreadList`, `ForumPostEditor` (submit, preview, withTitle, …). +- Composables: `useForum`, `useForumApi`, `useForumAuth`. Client: `createForumClient`, `ForumApiError`, `forumContextPath`, `forumError`. +- Copy: `forum.*` EN and PL. + +**vue-userfriends-layer** +- Runtime config: `userfriends.apiBase`, `public.userfriends.apiBase` (`/_userfriends/api/v1`). +- Hooks: `$userfriendsAuth` (token getter), optional `$userfriendsDirectory(): Promise<{id, name}[]>`. +- Components: `FriendsPage`, `FriendsList`, `FriendsRequests`, `FriendsAddFriend`, `FriendsGroups`. +- Composables: `useFriends` (useState keys `userfriends:*`), `useFriendsApi`, `useFriendsAuth`, `useFriendsDirectory`. Client: `createFriendsClient`, `FriendsApiError`, `friendTarget`, `friendsError`. +- Copy: `friends.*` EN and PL. + +**vue-chatvideo-layer** +- Peer dependency `vue-chat-layer ^0.1.0` (not extended; the app extends both, chat first). Dependency `livekit-client` 2.23.0 exact. +- Runtime config: `public.chatvideo.apiBase` (`/_chatvideo/api/v1`). Auth: vue-chat-layer's `$chatAuth`. +- Components: `ChatvideoCallButton` (conversationId, callPath), `ChatvideoCall` (conversationId, start, backPath), `ChatvideoTile`, `ChatvideoIncomingCall` (callPath). +- Composables: `useCall`, `useVideoClient`, `useIncomingCalls` (`RING_MS` 45 s). +- Lib: `callMachine` (`initialCallState`, `transition`, `isLive`, `failureReason`), `videoClient` (`createVideoClient`, `VideoApiError`, `videoSessionChannel`, `videoPath`, `videoEvent`, `parseIncomingCall`, `rememberStart`, `takeStart`). +- Copy: `chatvideo.*` EN and PL, reason keys `chatvideo.reason.*`. + +## Task Commits + +| # | Repo | Commit | Message | +|---|------|--------|---------| +| 1 | vue-forum-layer | dcd6549 | feat: forum layer: categories, threads and Markdown posts | +| 1 | vue-demo-app | 2473517 | feat: /forum and /forum/t/{id} on vue-forum-layer | +| 1 | sm-demo-app | c4a9cfc | test: check-local forum, smoke covers /forum | +| 2 | vue-userfriends-layer | bcc8e99 | feat: friends layer: friends, requests and friend groups | +| 2 | vue-demo-app | 4edc81f | feat: /friends on vue-userfriends-layer, linked from the chat and the account | +| 2 | sm-demo-app | c2208e9 | test: check-local friends, smoke covers /friends | +| 3 | — | — | checkpoint: approved livekit-client 2.23.0 | +| 4 | vue-chatvideo-layer | 75e9a40 | feat: video-call layer: DM calls on LiveKit | +| 4 | vue-demo-app | 1db4394 | feat: DM video calls on vue-chatvideo-layer | +| 4 | sm-demo-app | c669717 | test: check-local call (DM start, LiveKit token validation, end) | +| 4 | summercms.go | 0301dc2 | docs(20-04): API coverage of livekit-client, LiveKit, Centrifugo and Web Push | +| 5 | sm-demo-app | fc23b4c | feat: herald VAPID keys without UI, check-local herald and the ten-card tour | +| 5 | vue-demo-app | cc8d330 | feat: Forum, Friends and Calls tour cards | +| 6 | — | — | checkpoint: release | +| 7 | vue-forum-layer, vue-userfriends-layer, vue-chatvideo-layer | tags v0.1.0 | created, pushed and tagged by the orchestrator (below) | +| 7 | vue-demo-app | 9357711 | feat: pin the forum, friends and video-call layers v0.1.0 | + +**Release (Task 7).** The subagent's `tea repo create` was refused by the permission system ("Create Public Surface"), so nothing was created from the subagent. After the user's go-ahead (in manual permission mode), the orchestrator created `golem15/vue-forum-layer`, `golem15/vue-userfriends-layer` and `golem15/vue-chatvideo-layer` public (vue-faq-layer's visibility), pushed `main` and the annotated tags `v0.1.0`. `ls-remote 'refs/tags/v0.1.0^{}'` equals HEAD for each: dcd6549, bcc8e99, 75e9a40. The subagent then pinned the tags in vue-demo-app. + +**Not pushed:** vue-demo-app and sm-demo-app (plan 05). sm-demo-app shows the moved `vue-demo-app` submodule pointer as uncommitted. + +## Checkpoint answers + +- **Task 3:** approved livekit-client 2.23.0. Facts checked by the orchestrator: repository github.com/livekit/client-sdk-js, npm maintainer thedavidzhao, no install or postinstall hooks, npm provenance attestations, published 2026-10-16 (15 days old). Compatibility with LiveKit server 1.13.1 was not checked from the release notes; calls on the local 1.13.1 work. Installed with `--save-exact`; lockfile integrity `sha512-j9Kg86nJ…`. +- **Task 6:** release. + +## Verification + +- **Layers:** forum `pnpm test` 7/7, friends 6/6, chatvideo vitest 10/10; `pnpm build:playground` builds for all three. +- **vue-demo-app:** `pnpm test` 209/209 and `pnpm run build` after every task and against the tags. +- **check-local.sh:** `forum` (Task 1), `friends forum chat` (Task 2), `call chat` (Task 4), `herald tour` (Task 5), `forum friends call chat tour herald` and `back remote realtime quizzes journal` (before the release), `forum friends call chat tour` against the tags (Task 7): all ok. +- **smoke.sh:** `smoke: ok`, with /forum and /friends in the page loop and their anonymous prompts. +- **Browser probes** (Playwright, throwaway members, fake media, against the link pins): the forum, friends and call flows listed in the coverage block passed with no console error. Against the tags, the call probe's ring step failed five times in a row because the stale servers took every incoming-call broadcast (environment notes); the tagged install itself resolves one vue-chat-layer copy (one entry in `node_modules/.pnpm`) and passes check-local call and chat. +- **Task 5 manual check:** `dev.sh up` printed only "added the herald VAPID keys to .env"; no line of the output holds a key value. A second `dev.sh seed` added nothing. The plan's `grep .env` was blocked by the secret-read hook; `check-local herald` (200 with a non-empty publicKey) shows the key is loaded. +- **Acceptance greps:** all print the expected values (no consuming-app names in the layers, v-html only on content_html, one `import('livekit-client')`, no static runtime import, every `subscribe(` line uses `videoSessionChannel`, exact livekit pin, one `conversation-actions`, one `sidebar-footer`, one `to="/friends"` in SiteHeader, no `'/_herald'`, three tour keys, `HERALD__VAPID__PRIVATE_KEY=` once and empty, no OPT-OUT row without a reason, no link pins left). + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Decline has no declined event on the server** +- **Found during:** Task 4 (read video_service.go as the plan asked). +- **Issue:** `Notifier.Declined` exists, but no VideoService path calls it; `Leave` publishes `video:participant:left`. No endpoint publishes `video:call:declined`. +- **Fix:** Decline posts `video/leave`. The caller's page treats `video:participant:left` (and `video:call:declined`, should it ever come) for a participant who never connected as a decline and shows "did not answer". The shared plugin is unchanged (follow-up below). +- **Commit:** 75e9a40. + +**2. [Rule 1 - Bug] A left-open session never rings again** +- **Found during:** Task 4. +- **Issue:** `start` answers the live session without a new `video:call:incoming` when one exists. If both members only posted `leave`, the session stayed open until the reconcile job, and the next call from the DM would not ring. +- **Fix:** Leave posts `video/end` when nobody else is in the room (the last one out ends the call), else `video/leave`. Both use `keepalive` so pagehide still sends them. +- **Commit:** 75e9a40. + +**3. [Rule 2 - Missing critical] One start per call** +- **Issue:** The plan has both the button and the call page start the call; `video-initiate` allows 5 starts a minute. +- **Fix:** The button keeps its start answer in memory for 60 s (`rememberStart`), and the page takes it once (`takeStart`) before starting itself. The join token never goes into a URL. +- **Commit:** 75e9a40. + +**4. Forum manage rights from the host** +- The forum API does not report manage rights. `ForumThread` takes `canManage`; the demo thread page fills it from `GET /_demo/api/v1/chat`'s `moderator` flag (the same group the forum policy checks). A refused action hides itself for the session. +- **Commit:** dcd6549, 2473517. + +**5. [Rule 3 - Blocking] Auto-import collisions** +- Nuxt auto-imports `app/utils` into the app, so the layers' generic helper names would collide. Exports carry the layer's prefix: `forumError`, `forumContextPath`, `friendsError`, `friendTarget`. +- **Commit:** dcd6549, bcc8e99. + +**6. [Rule 3 - Blocking] One vue-chat-layer copy** +- With the development `link:` pin, the linked checkout's own `node_modules/vue-chat-layer` (its dev dependency) would be a second realtime module that never hears the chat's events. vue-chatvideo-layer's nuxt.config sets `vite.resolve.dedupe: ['vue-chat-layer']`. With the link pin the incoming-call prompt works; with the tag install pnpm resolves the peer to the app's copy. +- **Commit:** 75e9a40. + +**7. Smaller additions** +- check-local.sh: `throwaway_user` (e-mail and JWT) with `throwaway_jwt` on top of it; `check_call` reads Ada's id from `/chat/members`. +- smoke.sh: the anonymous prompts of /forum and /friends are checked, not only the page loop. +- `ensure_vapid` runs at the end of `seed()`, so `up` and `seed` both cover it. +- The add-friend picker hides current friends and people with a pending request. +- App copy lives under `forum.*`, `friends.*` and `call.*` next to the layers' trees (distinct keys), plus `header.friends`, `chat.friendsLink` and `security.friendsLink`. +- The plan's draft progress SUMMARY after Task 4 was skipped on the orchestrator's instruction; this SUMMARY is the only one. + +**Total deviations:** 7. **Impact:** none on the plan goal; 1 and 2 make the call flow work with the plugin as it is. + +## Follow-ups + +- **sm-chatvideo-plugin:** `Notifier.Declined` is never called. A decline route (or `Leave` publishing `video:call:declined` for a participant still in status invited) would let the layer drop its participant-left inference. Not needed for this phase. +- **Herald:** the boot warning "PresenceGate is deny-all; the host must SetPresenceGate" remains; it matters only once push is sent (D-16 defers the subscribe UI). + +## Environment notes + +- **Stale demo servers share the local database.** `./bin/demo serve` pid 684528 (user jin, 127.0.0.1:8099, since Oct 22, in sm-demo-app) and pid 2918923 (user golem, 0.0.0.0:8096, since Oct 20) run River workers on `127.0.0.1:55540/summercms_demo` and take `summer.broadcast` jobs. Since the release, the current server ran 7 of about 50 broadcasts; the stale ones ran the rest, and their pushes do not reach the current Centrifugo clients. A Node test without a browser received 4 of 6 incoming-call pushes earlier; later the browser ring step failed 5 runs in a row. They were left running (not approved to stop). Stop them before plan 06's browser tests. +- **Headless Chrome** sometimes hits `ERR_NETWORK_CHANGED` (the host's network interfaces change, likely docker), which breaks page loads in probes. +- **Vite dev re-optimization:** the first `check-local chat` after adding a layer got a 504 "Outdated Optimize Dep" and passed on the rerun (dev server only). +- **Local secrets in a tool log:** while diagnosing, the Centrifugo `PROXY_SECRET`, `TOKEN_SECRET` and `API_KEY` from pid 684528's environment were printed into the session's tool output. They are local development values and were never committed; removing the `SUMMER_REALTIME__CENTRIFUGO__*` lines from `.env` and running `dev.sh up` rotates them. + +## Known Stubs + +None. + +## Threat Flags + +| Flag | File | Description | +|------|------|-------------| +| threat_flag: supply-chain | ../vue-chatvideo-layer/package.json | New third-party dependency livekit-client 2.23.0 (exact), installed after the Task 3 checkpoint as planned in T-20-SC | +| threat_flag: supply-chain | ../sm-demo-app/vue-demo-app/package.json | Three new git dependencies pinned by tag v0.1.0 (commits dcd6549, bcc8e99, 75e9a40 in the lockfile) | + +## Next Phase Readiness + +- **Plan 05:** vue-demo-app pins every layer by tag and has no link pin, so it can be pushed. The sm-demo-app submodule pointer bump is still to do. The deploy needs `/_forum`, `/_userfriends` and `/_chatvideo` routed to Go in nginx (not `/_herald`), the herald VAPID keys in rome's `.env`, and LiveKit reachable at the production `livekit_url`. +- **Plan 06:** the call machine and the three clients have unit tests to extend; browser checks of the call flow need the stale servers stopped. + +## Self-Check: PASSED + +- Layer commits exist: vue-forum-layer dcd6549, vue-userfriends-layer bcc8e99, vue-chatvideo-layer 75e9a40; remote tags v0.1.0 peel to them. +- vue-demo-app: 2473517, 4edc81f, 1db4394, cc8d330, 9357711. sm-demo-app: c4a9cfc, c2208e9, c669717, fc23b4c. summercms.go: 0301dc2. +- The key files listed above exist in their repos.