From f68a58b07303ad3cc57c765f6e3bcb3f49ff561c Mon Sep 17 00:00:00 2001 From: xcaliber Date: Sat, 14 Mar 2026 12:30:57 +0000 Subject: [PATCH] Changeset 0.28.3 (#187) --- CHANGELOG.md | 301 +++++++++++++++++++ VERSION | 2 +- docs/ICD/README.md | 4 +- docs/ICD/auth.md | 21 +- docs/ICD/enums.md | 1 + docs/ICD/websocket.md | 368 ++++++++++++++++++++---- docs/JS-DEPENDENCY-AUDIT.md | 348 ++++++++++++++++++++++ docs/ROADMAP.md | 61 ++-- server/handlers/workflow_assignments.go | 37 ++- server/handlers/workflow_instances.go | 26 +- src/js/admin-handlers.js | 36 +++ src/js/admin-surfaces.js | 9 + src/js/api.js | 8 + src/js/app-state.js | 10 + src/js/app.js | 15 + src/js/channel-models.js | 9 + src/js/chat-pane.js | 9 + src/js/chat.js | 34 +++ src/js/code-editor.js | 9 + src/js/debug.js | 16 ++ src/js/drag-resize.js | 9 + src/js/events.js | 14 +- src/js/extensions.js | 9 + src/js/file-tree.js | 9 + src/js/files.js | 24 ++ src/js/memory-ui.js | 9 + src/js/model-selector.js | 9 + src/js/note-editor.js | 9 + src/js/note-graph.js | 14 + src/js/notes.js | 18 ++ src/js/notification-prefs.js | 9 + src/js/notifications.js | 9 + src/js/pages.js | 10 + src/js/pane-container.js | 9 + src/js/panels.js | 14 + src/js/persona-kb.js | 11 + src/js/projects-ui.js | 54 ++++ src/js/repl.js | 9 + src/js/settings-handlers.js | 21 ++ src/js/tokens.js | 11 + src/js/ui-admin.js | 9 + src/js/ui-core.js | 14 + src/js/ui-format.js | 26 ++ src/js/ui-primitives.js | 26 ++ src/js/ui-settings.js | 7 + src/js/user-menu.js | 9 + 46 files changed, 1589 insertions(+), 107 deletions(-) create mode 100644 docs/JS-DEPENDENCY-AUDIT.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f65411..34c0e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,306 @@ # Changelog +## [0.28.3] — 2026-03-13 + +### Summary + +ICD audit close-out and straggler sweep. Rolls in the remaining v0.28.2 +items (websocket doc, auth/enums reconciliation) since the 0.28.3 branch +was cut before the final v0.28.2 changeset landed. + +### Changed + +#### WebSocket ICD (`websocket.md`) — Full Rewrite +- Event envelope field corrected: `"event"` not `"type"` (matches Go + struct json tag `json:"event"`). +- Room subscribe/unsubscribe documented as **planned, not implemented**. + `JoinRoom`/`LeaveRoom` exist as stubs but no client wiring exists; + all delivery uses `Hub.SendToUser()`. +- Added payload shape documentation for 11 event types: + `message.created` (user + assistant variants), `typing.start`, + `typing.stop`, `typing.user`, `user.presence`, `user.mentioned`, + `notification.new`, `notification.read`, `workflow.assigned`, + `workflow.claimed`, `workflow.advanced`, `workflow.completed`, + `role.fallback`, `tool.call.*`. +- Routing table rewritten with accurate Direction, Delivery mechanism, + and labels matching `events.routeTable` in code. +- Noted that `workflow.claimed`, `workflow.advanced`, `workflow.completed` + use room-scoped `Bus.Publish()` which does not reach WebSocket clients + (rooms not wired). Forward-looking plumbing only. + +#### Auth ICD (`auth.md`) +- Login/Register response shape corrected: added `token_type` ("Bearer") + and `expires_in` (900), removed phantom fields (`avatar_url`, + `created_at`, `last_login`) that `generateTokens` does not emit. +- Register documents the admin-approval path (201 with message when + `is_active` is false). + +#### Enums ICD (`enums.md`) +- Task run statuses: added missing `queued` status (present in DB CHECK + constraint, was omitted from enum doc). + +#### Presence Status Reconciliation +- DB CHECK constraint: `online`, `away`, `offline`. +- Runtime behavior: hub emits only `online` and `offline`. `away` is + reserved for future idle detection. +- Documented this gap in both `websocket.md` and `enums.md` rather than + adding dead code or removing the DB constraint. + +#### Housekeeping +- `VERSION` bumped to 0.28.3. +- `CHANGELOG.md` updated with entries for 0.28.0 through 0.28.3. +- `ROADMAP.md` updated: v0.28.2 marked complete, v0.28.3 scope adjusted. + +### ICD Runner + +No runner changes — websocket events are not testable via HTTP. All +existing tests remain at 469/469 (100%). + +### Fixed + +#### WebSocket Workflow Event Delivery (cs1) +- `workflow.advanced`, `workflow.completed`, `workflow.claimed` were + emitted via room-scoped `Bus.Publish()`, but the room subscription + system is not wired (no client-side subscribe/unsubscribe). These + events **never reached WebSocket clients**. +- Converted all three to `SendToUser()` per channel participant, + matching the pattern used by `message.created` and + `workflow.assigned`. +- `workflow.claimed` payload gains `channel_id` field (needed to look + up participants; also useful for frontend navigation). +- `workflow_assignments.go`: consolidated the duplicate + `channel_id` query (was queried separately for WS and notification). + +### Added + +#### JS Dependency Audit (cs2) +- `docs/JS-DEPENDENCY-AUDIT.md`: full map of the frontend codebase. + 47 files, 25K lines, 431 top-level globals, 143 dynamic onclick + handlers. Documents the complete script load order per surface, + core global dependency graph (`App` → 20 files, `API` → 36 files, + `UI` → 32 files, `Events` → 9 files), IIFE vs bare global + classification, inline handler inventory, cross-file call graph, + and a 4-phase decomposition strategy (core extract → onclick + migration → ES modules → template handler shim). + +#### Core Four IIFE Extraction (cs3) +- `api.js`: Wrapped in IIFE. `BASE` and `_storageKey` are now private + (were leaking as implicit globals). Single export: `window.API`. +- `events.js`: Wrapped in IIFE. Replaced `_storageKey` localStorage + coupling with `API.accessToken` (eliminates cross-file private + dependency). Single export: `window.Events`. +- `app-state.js`: Wrapped in IIFE. `resolveCapabilities()` now private. + Exports: `window.App`, `window.fetchModels`. +- `ui-primitives.js`: Wrapped in IIFE. `updateTabArrows()` now private + (zero external callers). 17 explicit `window.*` exports with manifest + comment. Previously all 18 declarations leaked as implicit globals. + +#### Tier 2 IIFE Extraction (cs4) +- `ui-format.js`: Wrapped in IIFE. 11 explicit exports (7 cross-file + + 4 onclick handlers), 9 privatized (`_highlightMentions`, `_formatMarked`, + `_formatBasic`, `_unwrapMarkdownFence`, `_decodeHTML`, `_looksLikeHTML`, + `_hasPreviewContent`, `_extractLastHTMLBlock`, `_livePreviewTimer`). +- `ui-core.js`: Wrapped in IIFE. 3 exports (`UI`, `renderPersonaForm`, + `toggleSummarizedHistory`), 3 privatized (`avatarHTML`, + `assistantAvatarURI`, `_isSummaryMessage`). +- `pages.js`: Wrapped in IIFE. 2 exports (`Pages`, `_val`), 6 privatized + (`_pageData`, `_show`, `_hide`, `_toast`, `_parseJSON`, `_api`). + +#### Tier 3 IIFE Extraction — Leaf Modules (cs5) +- 11 files wrapped: `channel-models.js`, `chat-pane.js`, `notifications.js`, + `model-selector.js`, `file-tree.js`, `tokens.js`, `code-editor.js`, + `note-editor.js`, `drag-resize.js`, `pane-container.js`, `user-menu.js`. +- 13 total explicit exports across all 11 files. +- 17 functions privatized (e.g. `_shortName`, `_timeAgo`, `_ftFileIcon`, + `_ceFileIcon`, `_ceDetectLanguage`, `dismissContextWarning`, `_clientPos`, + 10 PaneContainer internals). + +#### Small File IIFE Wrap (cs6) +- 7 files: `admin-surfaces.js`, `extensions.js`, `memory-ui.js`, + `notification-prefs.js`, `persona-kb.js`, `repl.js`, `ui-settings.js`. +- `ui-settings.js` has no standalone exports (extends `UI` via + `Object.assign`); IIFE scopes its closure only. +- 3 functions privatized (`_loadSurfaceList`, `_debounce`, + `savePersonaKBs` — zero external callers). + +#### Chat + App Entry Point IIFE Wrap (cs7) +- `chat.js`: Wrapped in IIFE. 22 explicit exports (ChatInput + 21 + functions), 7 privatized (`_typingTimer`, `_emitTyping`, + `_saveChatModel`, `_restoreChatModel`, `_cleanStaleChatModel`, + `updateChatTokenCount`, `_autoNamePending`). +- `app.js`: Wrapped in IIFE. 6 exports (`handleLogin`, `handleRegister`, + `handleLogout`, `switchAuthTab`, `startApp`, `initBanners`), + 13 privatized. `init()` self-invokes via `DOMContentLoaded` — not + exported. `handleLogout` overrides the base.html fallback on chat, + editor, and notes surfaces. + +#### Clean Tier 4 Wraps (cs8) +- `settings-handlers.js`: Wrapped in IIFE. 11 explicit exports + 4 + self-assigned `window.*` functions (preserved from original). 13 + privatized. +- `note-graph.js`: Wrapped in IIFE. 6 exports, 17 privatized. +- `debug.js`: Wrapped in IIFE. 8 exports (all onclick-referenced from + base.html template), 1 privatized. +- `panels.js`: Wrapped in IIFE. 6 exports, 4 privatized. + +#### Heavy Page IIFE Wraps — All Remaining (cs9) +- `files.js`: 18 exports (15 cross-file + 3 onclick), 18 privatized. +- `admin-handlers.js`: 30 exports (28 cross-file + 2 onclick), 10 + privatized. Previously misclassified as IIFE — was actually bare + globals. +- `notes.js`: 12 exports (9 cross-file + 3 onclick), 31 privatized. +- `ui-admin.js`: 3 exports (`ADMIN_SECTIONS`, `ADMIN_LABELS`, + `ADMIN_LOADERS`), 1 privatized. Also extends `UI` via + `Object.assign` (28 onclick handlers are UI methods, not standalone). +- `projects-ui.js`: 48 exports (29 cross-file + 19 onclick), 30 + privatized. Largest file in the codebase (78 top-level globals + reduced to 48 explicit exports). + +**Frontend decomposition Phase 1 complete.** All 47 JS files are now +IIFE-wrapped with explicit `window.*` exports. Zero implicit globals +remain. The `_storageKey` cross-file coupling between `api.js` and +`events.js` is eliminated. ~168 functions privatized across the +codebase. + + +## [0.28.2] — 2026-03-13 + +### Summary + +Full ICD audit across all remaining domains. Every ICD document traced +against Go handlers, stores (PG + SQLite), and the ICD test runner. +Eight changesets (cs0–cs7), 469/469 runner tests passing. + +### Added + +#### Go Integration Tests +- `projects_test.go`: 14 tests — CRUD shapes, 201 status, envelope, + admin list, auth, isolation. +- `workspace_test.go`: 11 tests — list empty/data envelope, shape, + root_path not exposed, user isolation, GET by ID, 404, 403, git + credentials empty envelope, auth required. +- `profile_test.go`: GET profile shape, PUT profile, duplicate email + 409, settings envelope, password change (success, wrong current 401, + too short 400), avatar delete. +- Knowledge tests: document status polling, document delete, update + empty body 400, permission denial. + +#### ICD Runner Tests (v0.28.2.4) +- Projects: rewritten from 9 → 19 tests (full CRUD shapes, + channel/KB/note association lifecycles, files shape, isolation, + admin list). +- Knowledge: document status, document delete, permission, team-scoped + KB creation. +- Profile: all 7 endpoints exercised. +- Workspaces: envelope, shape, isolation, git operations. +- Notifications: envelope, preferences, type enum. + +### Fixed + +#### P0 — Security +- `SetDiscoverable` on knowledge bases had no authorization check. + Added `loadAndAuthorize` + owner/admin gate + audit log (cs2). + +#### P0 — Response Shape +- `GET /settings` returned bare object → wrapped in + `{"settings": {...}}` (cs5). +- `GET /workspaces` returned bare array → `{"data": [...]}` (cs6). +- `GET /git-credentials` returned bare array → `{"data": [...]}` (cs6). +- `GET .../git/log` returned bare array → `{"data": [...]}` + nil + slice guard (cs6). +- `ListByProject` files returned `{"files": null}` → `{"files": []}` (cs7). +- `GET /notifications/preferences` missing `"preferences"` key in + envelope (cs0). + +#### P0 — Data Integrity +- `ListTeamProviderModels` response struct missing `Type` field — + embedding models indistinguishable from chat models, causing Venice + 400 on completion (cs7). + +#### Notification System +- Implemented `memory.extracted` notification (was missing hook in + memory extractor) (cs0). +- Implemented `user.mentioned` persisted notification (was WS-only) (cs0). +- Implemented `workflow.claimed` persisted notification (was WS-only) (cs0). +- Removed dead `NotifTypeProjectInvite` constant (cs0). + +#### ICD Runner (v0.28.2.4) +- SSE parser: extract content from OpenAI-format + `choices[0].delta.content` — was checking top-level `evt.content`. + All three provider tiers (global, team, BYOK) were affected (cs7). +- `pickCheapestChat` excludes embedding/image models by type and ID + pattern (cs7). +- Model ID references use `(m.model_id || m.id)` fallback for team + provider models (cs7). + +### Changed + +#### ICD Documents Corrected +- `notifications.md`: object shape, query params, response envelopes, + WS events. Type enum synced (removed aspirational, added implemented). +- `knowledge.md`: KB object shape (6 field mismatches), search envelope + (`data` not `results`), search result fields, status progression + (`extracting` step), file type support, auth annotations. +- `profile.md`: avatar API (multipart → JSON base64), response shapes + for all 7 endpoints, auth annotations, field table. Added + `last_login_at` to `profileResponse` + dialect-safe time scan. +- `workspaces.md`: workspace object shape (`indexing_enabled`, `git_*`, + `total_bytes` not `storage_bytes`, `owner_type` full enum), file + entry shape, git status/log/commit shapes, archive format, auth + annotations. +- `projects.md`: full rewrite — 6 categories of drift fixed (ghost + fields removed, 6 missing fields added, create/update shapes + corrected, association objects documented, `omitempty` on computed + counts noted). +- `notes.md`: verified clean. +- `memory.md`: verified clean (audited v0.28.0). + +#### Dead Code Removal +- `ListGlobal`/`ListForTeam` on KnowledgeBaseStore — interface + PG + + SQLite (cs4). +- `CreateKB` moved team role check from raw `database.DB.QueryRow` to + `stores.Teams.IsTeamAdmin`; removed `database` import (cs4). +- Stale `/avatar` route aliases removed from integration test harness (cs5). +- `ListDiscoverableKBs` normalized to use `toKBResponse()` (cs3). + + +## [0.28.1] — 2026-03-12 + +### Summary + +Surfaces ICD audit — first domain audit pass. Corrected 6 ICD +discrepancies and hardened the surface install path. + +### Fixed +- ICD `surfaces.md` corrected: field name, archive format, response + shape (6 discrepancies). +- Surface ID slug validation + `extractableRelPath` install hardening + (path traversal defense). + +### Added +- 19 E2E surface CRUD tests in ICD runner (install, enable/disable, + delete, error paths). +- 22 handler-level tests + 14 store-level tests (PG + SQLite). + +### Changed +- CI timeout increased 8m → 12m for PG integration tests. + + +## [0.28.0] — 2026-03-12 + +### Summary + +Platform polish arc kickoff. Memory ICD audit — first domain to complete +the full trace-and-test cycle. + +### Changed +- `memory.md` ICD audited and verified: object shape, scopes + (`user`/`persona`/`persona_user`), statuses + (`active`/`pending_review`/`archived`), all endpoints, admin review, + AI tools, background extraction, memory injection. +- 13/13 ICD runner tests passing. + + ## [0.27.5] — 2026-03-11 ### Summary diff --git a/VERSION b/VERSION index 5fe1ecc..0ba165c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.28.2 \ No newline at end of file +0.28.3 \ No newline at end of file diff --git a/docs/ICD/README.md b/docs/ICD/README.md index e81f173..6220dd5 100644 --- a/docs/ICD/README.md +++ b/docs/ICD/README.md @@ -1,7 +1,7 @@ # ICD-API — Chat Switchboard Backend API Contract -**Version:** 0.28.0 -**Updated:** 2026-03-11 +**Version:** 0.28.3 +**Updated:** 2026-03-13 **Audience:** Frontend developers, integrators, API consumers > **Organization principle:** Each file is one domain — the complete story. diff --git a/docs/ICD/auth.md b/docs/ICD/auth.md index bfcc2af..45cc96b 100644 --- a/docs/ICD/auth.md +++ b/docs/ICD/auth.md @@ -22,9 +22,15 @@ POST /auth/register } ``` -Gated by `allow_registration` policy. Returns same shape as Login. -A unique `handle` is auto-generated from `username` (collision-safe -with `-2`, `-3` suffixes). +Gated by `allow_registration` policy. A unique `handle` is +auto-generated from `username` (collision-safe with `-2`, `-3` suffixes). + +**Response (201):** Same token shape as Login. If the user requires admin +approval (`is_active` = false), returns `201` with: + +```json +{ "message": "Account created but requires admin approval", "user_id": "uuid" } +``` ### Login @@ -36,12 +42,14 @@ POST /auth/login { "username": "jdoe", "password": "..." } ``` -**Response:** +**Response (200):** ```json { "access_token": "eyJ...", "refresh_token": "opaque-string", + "token_type": "Bearer", + "expires_in": 900, "user": { "id": "uuid", "username": "jdoe", @@ -49,10 +57,7 @@ POST /auth/login "display_name": "Jane Doe", "handle": "jdoe", "role": "user", - "auth_source": "builtin", - "avatar_url": "/api/v1/profile/avatar?v=1234", - "created_at": "...", - "last_login": "..." + "auth_source": "builtin" } } ``` diff --git a/docs/ICD/enums.md b/docs/ICD/enums.md index 5826f10..c714755 100644 --- a/docs/ICD/enums.md +++ b/docs/ICD/enums.md @@ -169,6 +169,7 @@ All enum values used across the API. Definitive source of truth. | Value | Description | |-------|-------------| +| `queued` | Waiting to execute | | `running` | Currently executing | | `completed` | Finished successfully | | `failed` | Error during execution | diff --git a/docs/ICD/websocket.md b/docs/ICD/websocket.md index 7f2d8b6..95a4fda 100644 --- a/docs/ICD/websocket.md +++ b/docs/ICD/websocket.md @@ -1,6 +1,8 @@ # WebSocket Protocol -Real-time bidirectional event bus. Single connection per client. +Real-time event delivery. Single connection per client. All event +delivery uses targeted `SendToUser` — events are pushed to every +WebSocket connection belonging to the recipient user. ### Connection @@ -9,83 +11,313 @@ ws://{host}{BASE_PATH}/ws?token={access_token} ``` **Lifecycle:** -- Server sends `ping` every 54 seconds +- Server sends WebSocket-level `ping` frames every 54 seconds - Client must respond with `pong` within 60 seconds or disconnection -- Max message size: 4 KB -- Write deadline: 10 seconds +- Max inbound message size: 4 KB +- Write deadline: 10 seconds per frame +- Connection ID: `{user_id}-{timestamp}` (internal, not exposed to client) ### Event Envelope -All messages are JSON: +All messages are JSON. The field name is `event`, not `type`: ```json { - "type": "event.type", - "payload": { ... } + "event": "message.created", + "payload": { ... }, + "ts": 1710000000000 } ``` -### Room Subscription +| Field | Type | Description | +|-------|------|-------------| +| `event` | string | Dot-delimited event label | +| `room` | string | Optional — room scope (currently unused client-side, see § Room Model) | +| `payload` | object | Event-specific data | +| `ts` | int64 | Unix milliseconds | -Clients subscribe to rooms for scoped event delivery: +### Client → Server Events + +The client can send JSON events to the server. Only events with +`DirFromClient` or `DirBoth` routing are accepted; all others are +silently dropped. + +**Application-level ping:** ```json -{ "type": "subscribe", "payload": { "room": "channel:uuid" } } -{ "type": "unsubscribe", "payload": { "room": "channel:uuid" } } +{ "event": "ping" } ``` +Server responds with `{ "event": "pong", "ts": ... }`. This is +separate from the WebSocket-level ping/pong frames. + +**Typing (human → other participants):** + +Client sends a `chat.typing.{channelID}` event. The server tags it +with `SenderID` and `ConnID`, then rebroadcasts to other participants +via `SendToUser` per channel membership. + +### Room Model (Planned) + +`JoinRoom` and `LeaveRoom` methods exist on the connection struct but +are **not yet wired** — no client-side subscribe/unsubscribe mechanism +is implemented. The `subscribeToBus` handler has room filtering logic +(`if e.Room != "" && !c.rooms[e.Room]`) but since connections never +join rooms, all room-scoped `Bus.Publish` calls are effectively +no-ops for WebSocket delivery. + +Current workaround: all user-facing events use `Hub.SendToUser()` +which bypasses room filtering. Room-scoped `Bus.Publish()` calls +(e.g. `workflow.claimed` with `Room: assignmentID`) exist as +forward-looking plumbing but do not reach WebSocket clients today. + ### Event Routing Table -| Prefix | Direction | Description | -|--------|-----------|-------------| -| `channel.created` | → client | New channel created | -| `channel.updated` | → client | Channel metadata changed | -| `channel.deleted` | → client | Channel removed | -| `message.created` | → client | New message in subscribed channel | -| `message.updated` | → client | Message content changed | -| `message.deleted` | → client | Message removed | -| `cursor.updated` | → client | Branch cursor moved | -| `typing.start` | ↔ both | Participant started typing (payload includes `participant_id`, `participant_type`) | -| `typing.stop` | ↔ both | Typing ended | -| `participant.joined` | → client | Participant added to channel | -| `participant.left` | → client | Participant removed from channel | -| `participant.updated` | → client | Participant role changed | -| `presence.update` | → client | Participant online/idle/offline status change | -| `model.changed` | → client | Channel model roster changed | -| `persona.updated` | → client | Persona metadata changed | -| `kb.updated` | → client | Knowledge base changed | -| `kb.document.status` | → client | Document processing status update | -| `note.created` | → client | Note created | -| `note.updated` | → client | Note content changed | -| `note.deleted` | → client | Note removed | -| `notification.new` | → client | New notification | -| `notification.read` | → client | Notification marked read | -| `memory.extracted` | → client | New memory extracted | -| `memory.status` | → client | Memory approved/rejected | -| `role.fallback` | → client | Role fallback triggered | -| `user.mentioned` | → client | User was @mentioned in a channel | -| `workflow.assigned` | → client | Workflow stage assigned to team/user | -| `workflow.claimed` | → client | Workflow assignment claimed | -| `workflow.advanced` | → client | Workflow stage advanced | -| `workflow.completed` | → client | Workflow instance completed | -| `workspace.updated` | → client | Workspace state changed | -| `project.updated` | → client | Project metadata changed | -| `extension.updated` | → client | Extension config changed | -| `settings.updated` | → client | Global settings changed | -| `user.updated` | → client | User profile changed | -| `file.created` | → client | File uploaded or generated (§17b.4) | -| `file.deleted` | → client | File removed | +Direction is determined by `events.routeTable` in `server/events/types.go`. +Prefix matching: longest matching prefix wins; unmatched labels default +to `DirLocal` (server-only, never crosses the wire). -Direction: `→ client` = server-to-client only, `← client` = client-to-server -only, `↔ both` = bidirectional. +| Label | Direction | Delivery | Description | +|-------|-----------|----------|-------------| +| `message.created` | → client | `SendToUser` per channel | New message in channel | +| `message.updated` | → client | `SendToUser` | Message content changed | +| `message.deleted` | → client | `SendToUser` | Message removed | +| `channel.created` | → client | `SendToUser` | New channel created | +| `channel.updated` | → client | `SendToUser` | Channel metadata changed | +| `channel.deleted` | → client | `SendToUser` | Channel removed | +| `channel.member.*` | → client | `SendToUser` | Participant added/removed/role changed | +| `typing.start` | → client | `SendToUser` | AI persona started generating | +| `typing.stop` | → client | `SendToUser` | AI persona finished generating | +| `typing.user` | → client | `SendToUser` per channel | Human typing indicator | +| `chat.typing.*` | ↔ both | Bus (prefix match) | Legacy typing relay | +| `user.presence` | → client | `SendToUser` | Online/offline status change | +| `user.mentioned` | → client | `SendToUser` | User @mentioned in a channel | +| `notification.new` | → client | `SendToUser` | New persisted notification | +| `notification.read` | → client | `SendToUser` | Badge sync across tabs | +| `role.fallback` | → client | Bus (Room: `admin`) | Role fallback activated (admin-targeted) | +| `workflow.assigned` | → client | `SendToUser` per team | New assignment for team members | +| `workflow.claimed` | → client | `SendToUser` per channel | Assignment claimed | +| `workflow.advanced` | → client | `SendToUser` per channel | Stage advanced | +| `workflow.completed` | → client | `SendToUser` per channel | Workflow finished | +| `workspace.file.*` | → client | Bus | Workspace file changed | +| `tool.call.*` | → client | `SendToUser` | Browser tool invocation | +| `tool.result.*` | ← client | Bus | Browser tool result | +| `system.notify` | → client | Bus | System broadcast | +| `ping` | ← client | Direct | Application-level keepalive | +| `pong` | → client | Direct | Response to application ping | --- -## 19. Appendix: Enums +## Event Payload Shapes + +### `message.created` + +User message (mention_only / DM relay): + +```json +{ + "id": "uuid", + "channel_id": "uuid", + "role": "user", + "content": "message text", + "user_id": "uuid" +} +``` + +Assistant message (completion chain): + +```json +{ + "id": "uuid", + "channel_id": "uuid", + "role": "assistant", + "content": "response text", + "model": "model-id", + "participant_type": "persona", + "participant_id": "uuid", + "display_name": "Persona Name", + "avatar": "/path/to/avatar", + "tokens_used": 1234, + "chain_depth": 1 +} +``` + +### `typing.start` / `typing.stop` + +AI typing indicator (targeted via `SendToUser`): + +```json +{ + "channel_id": "uuid", + "participant_id": "uuid", + "participant_type": "persona", + "display_name": "Persona Name" +} +``` + +### `typing.user` + +Human typing indicator (relayed to other channel participants): + +```json +{ + "channel_id": "uuid", + "user_id": "uuid", + "display_name": "Jane Doe" +} +``` + +### `user.presence` + +Emitted on WebSocket connect/disconnect: + +```json +{ + "user_id": "uuid", + "status": "online|offline" +} +``` + +Note: the DB CHECK constraint allows `online`, `away`, `offline` but +the WebSocket hub currently only emits `online` (on connect) and +`offline` (on last connection closed). `away` is reserved for future +idle detection. + +### `user.mentioned` + +Targeted to the mentioned user: + +```json +{ + "channel_id": "uuid", + "from_user": "uuid", + "content": "truncated to 120 chars..." +} +``` + +### `notification.new` + +Full notification object (same shape as REST `GET /notifications`): + +```json +{ + "id": "uuid", + "user_id": "uuid", + "type": "kb.ready", + "title": "Knowledge base ready", + "body": "optional detail", + "resource_type": "knowledge_base", + "resource_id": "uuid", + "is_read": false, + "created_at": "..." +} +``` + +### `notification.read` + +Single notification marked read: + +```json +{ "id": "uuid" } +``` + +Mark-all-read: + +```json +{ "action": "mark_all_read" } +``` + +### `workflow.assigned` + +Targeted via `SendToUser` to each team member: + +```json +{ + "channel_id": "uuid", + "team_id": "uuid", + "stage_name": "Review", + "assigned_to": "uuid|empty" +} +``` + +### `workflow.claimed` + +Targeted via `SendToUser` to all user participants in the channel: + +```json +{ + "assignment_id": "uuid", + "claimed_by": "uuid", + "channel_id": "uuid" +} +``` + +### `workflow.advanced` + +Targeted via `SendToUser` to all user participants in the channel: + +```json +{ + "channel_id": "uuid", + "workflow_id": "uuid", + "stage": "stage_key", + "stage_name": "Stage Name" +} +``` + +### `workflow.completed` + +Targeted via `SendToUser` to all user participants in the channel: + +```json +{ + "channel_id": "uuid", + "workflow_id": "uuid", + "stage": "final_stage_key" +} +``` + +### `role.fallback` + +Published to bus with `Room: "admin"`: + +```json +{ + "role": "primary", + "operation": "chat|embedding", + "primary_model": "model-a", + "fallback_model": "model-b", + "message": "Role \"primary\" primary (model-a) failed — using fallback (model-b)" +} +``` + +### `tool.call.*` + +Browser tool invocation (targeted via `SendToUser`): + +```json +{ + "tool_name": "...", + "call_id": "...", + "input": { ... } +} +``` + +Client responds with `tool.result.*` containing the execution result. + +--- + +## Appendix: Enums ### Channel Types -`direct` (single-user), `group` (multi-user/multi-model), `workflow` (staged) +`direct` (single-user), `dm` (human-to-human, AI mention-only), +`group` (multi-user/multi-model), `channel` (named persistent), +`workflow` (staged), `service` (autonomous task execution) + +### Channel AI Modes + +`auto` (default), `mention_only` (default for `dm`), `off` ### Participant Types @@ -93,11 +325,15 @@ only, `↔ both` = bidirectional. ### Participant Roles -`owner`, `member`, `observer` +`owner`, `member`, `observer`, `visitor` ### Presence Statuses -`online`, `idle`, `offline` +`online`, `away`, `offline` + +DB CHECK constraint: `online`, `away`, `offline`. Runtime: only +`online` and `offline` are emitted by the WebSocket hub. `away` is +reserved for future idle-timeout detection. ### Message Roles @@ -129,11 +365,11 @@ only, `↔ both` = bidirectional. ### Memory Scopes -`user`, `persona` +`user`, `persona`, `persona_user` ### Memory Statuses -`pending`, `approved`, `rejected` +`active`, `pending_review`, `archived` ### Workspace Owner Types @@ -141,11 +377,11 @@ only, `↔ both` = bidirectional. ### Workspace Statuses -`active`, `archived` +`active`, `archived`, `deleting` ### Index Statuses -`pending`, `indexing`, `ready`, `error` +`pending`, `indexing`, `ready`, `error`, `skipped` ### KB Document Statuses @@ -180,6 +416,18 @@ only, `↔ both` = bidirectional. See [enums.md](enums.md#notification-types) for full descriptions. +### Workflow Assignment Statuses + +`unassigned`, `claimed`, `completed` + +### Task Run Statuses + +`queued`, `running`, `completed`, `failed`, `budget_exceeded`, `cancelled` + +### Workflow Instance Statuses + +`active`, `completed`, `stale`, `cancelled` + ### Git Auth Types `https_pat`, `https_basic`, `ssh_key` diff --git a/docs/JS-DEPENDENCY-AUDIT.md b/docs/JS-DEPENDENCY-AUDIT.md new file mode 100644 index 0000000..34a336a --- /dev/null +++ b/docs/JS-DEPENDENCY-AUDIT.md @@ -0,0 +1,348 @@ +# Frontend JS Dependency Audit + +**Version:** v0.28.3 cs2 +**Date:** 2026-03-13 +**Purpose:** Map every global, cross-file dependency, and init sequence +to prepare for module extraction. + +--- + +## Summary + +| Metric | Value | +|--------|-------| +| JS files | 47 | +| Total lines | 25,056 | +| Total bytes | 1,103,572 (~1.1 MB) | +| Top-level globals (original) | 431 | +| IIFE-wrapped files | 47 (all) | +| Explicit `window.*` exports | ~215 | +| Functions privatized | ~168 | +| Implicit globals remaining | 0 | +| Vendor libs | 3 (marked.min.js, purify.min.js, codemirror.bundle.js) | +| Inline `onclick` handlers (templates) | ~50 unique functions | +| Dynamic `onclick` in JS (innerHTML) | 143 total occurrences | + +--- + +## Architecture: IIFE Modules (Post-Decomposition) + +No module system (no ES modules, no bundler, no AMD). Every file is a +` +``` + +Import graph follows the dependency order already established by +`