docs(20-04): forum, friends and video-call layers v0.1.0 on the demo site summary

This commit is contained in:
Jakub Zych
2026-10-31 20:11:26 +01:00
parent 0301dc2ae4
commit d50a1575ca

View File

@@ -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.