Changeset 0.28.3 (#187)
This commit is contained in:
301
CHANGELOG.md
301
CHANGELOG.md
@@ -1,5 +1,306 @@
|
|||||||
# Changelog
|
# 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
|
## [0.27.5] — 2026-03-11
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ICD-API — Chat Switchboard Backend API Contract
|
# ICD-API — Chat Switchboard Backend API Contract
|
||||||
|
|
||||||
**Version:** 0.28.0
|
**Version:** 0.28.3
|
||||||
**Updated:** 2026-03-11
|
**Updated:** 2026-03-13
|
||||||
**Audience:** Frontend developers, integrators, API consumers
|
**Audience:** Frontend developers, integrators, API consumers
|
||||||
|
|
||||||
> **Organization principle:** Each file is one domain — the complete story.
|
> **Organization principle:** Each file is one domain — the complete story.
|
||||||
|
|||||||
@@ -22,9 +22,15 @@ POST /auth/register
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Gated by `allow_registration` policy. Returns same shape as Login.
|
Gated by `allow_registration` policy. A unique `handle` is
|
||||||
A unique `handle` is auto-generated from `username` (collision-safe
|
auto-generated from `username` (collision-safe with `-2`, `-3` suffixes).
|
||||||
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
|
### Login
|
||||||
|
|
||||||
@@ -36,12 +42,14 @@ POST /auth/login
|
|||||||
{ "username": "jdoe", "password": "..." }
|
{ "username": "jdoe", "password": "..." }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response (200):**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"access_token": "eyJ...",
|
"access_token": "eyJ...",
|
||||||
"refresh_token": "opaque-string",
|
"refresh_token": "opaque-string",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": 900,
|
||||||
"user": {
|
"user": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
"username": "jdoe",
|
"username": "jdoe",
|
||||||
@@ -49,10 +57,7 @@ POST /auth/login
|
|||||||
"display_name": "Jane Doe",
|
"display_name": "Jane Doe",
|
||||||
"handle": "jdoe",
|
"handle": "jdoe",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"auth_source": "builtin",
|
"auth_source": "builtin"
|
||||||
"avatar_url": "/api/v1/profile/avatar?v=1234",
|
|
||||||
"created_at": "...",
|
|
||||||
"last_login": "..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ All enum values used across the API. Definitive source of truth.
|
|||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
|
| `queued` | Waiting to execute |
|
||||||
| `running` | Currently executing |
|
| `running` | Currently executing |
|
||||||
| `completed` | Finished successfully |
|
| `completed` | Finished successfully |
|
||||||
| `failed` | Error during execution |
|
| `failed` | Error during execution |
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# WebSocket Protocol
|
# 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
|
### Connection
|
||||||
|
|
||||||
@@ -9,83 +11,313 @@ ws://{host}{BASE_PATH}/ws?token={access_token}
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Lifecycle:**
|
**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
|
- Client must respond with `pong` within 60 seconds or disconnection
|
||||||
- Max message size: 4 KB
|
- Max inbound message size: 4 KB
|
||||||
- Write deadline: 10 seconds
|
- Write deadline: 10 seconds per frame
|
||||||
|
- Connection ID: `{user_id}-{timestamp}` (internal, not exposed to client)
|
||||||
|
|
||||||
### Event Envelope
|
### Event Envelope
|
||||||
|
|
||||||
All messages are JSON:
|
All messages are JSON. The field name is `event`, not `type`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "event.type",
|
"event": "message.created",
|
||||||
"payload": { ... }
|
"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
|
```json
|
||||||
{ "type": "subscribe", "payload": { "room": "channel:uuid" } }
|
{ "event": "ping" }
|
||||||
{ "type": "unsubscribe", "payload": { "room": "channel:uuid" } }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
### Event Routing Table
|
||||||
|
|
||||||
| Prefix | Direction | Description |
|
Direction is determined by `events.routeTable` in `server/events/types.go`.
|
||||||
|--------|-----------|-------------|
|
Prefix matching: longest matching prefix wins; unmatched labels default
|
||||||
| `channel.created` | → client | New channel created |
|
to `DirLocal` (server-only, never crosses the wire).
|
||||||
| `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: `→ client` = server-to-client only, `← client` = client-to-server
|
| Label | Direction | Delivery | Description |
|
||||||
only, `↔ both` = bidirectional.
|
|-------|-----------|----------|-------------|
|
||||||
|
| `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
|
### 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
|
### Participant Types
|
||||||
|
|
||||||
@@ -93,11 +325,15 @@ only, `↔ both` = bidirectional.
|
|||||||
|
|
||||||
### Participant Roles
|
### Participant Roles
|
||||||
|
|
||||||
`owner`, `member`, `observer`
|
`owner`, `member`, `observer`, `visitor`
|
||||||
|
|
||||||
### Presence Statuses
|
### 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
|
### Message Roles
|
||||||
|
|
||||||
@@ -129,11 +365,11 @@ only, `↔ both` = bidirectional.
|
|||||||
|
|
||||||
### Memory Scopes
|
### Memory Scopes
|
||||||
|
|
||||||
`user`, `persona`
|
`user`, `persona`, `persona_user`
|
||||||
|
|
||||||
### Memory Statuses
|
### Memory Statuses
|
||||||
|
|
||||||
`pending`, `approved`, `rejected`
|
`active`, `pending_review`, `archived`
|
||||||
|
|
||||||
### Workspace Owner Types
|
### Workspace Owner Types
|
||||||
|
|
||||||
@@ -141,11 +377,11 @@ only, `↔ both` = bidirectional.
|
|||||||
|
|
||||||
### Workspace Statuses
|
### Workspace Statuses
|
||||||
|
|
||||||
`active`, `archived`
|
`active`, `archived`, `deleting`
|
||||||
|
|
||||||
### Index Statuses
|
### Index Statuses
|
||||||
|
|
||||||
`pending`, `indexing`, `ready`, `error`
|
`pending`, `indexing`, `ready`, `error`, `skipped`
|
||||||
|
|
||||||
### KB Document Statuses
|
### KB Document Statuses
|
||||||
|
|
||||||
@@ -180,6 +416,18 @@ only, `↔ both` = bidirectional.
|
|||||||
|
|
||||||
See [enums.md](enums.md#notification-types) for full descriptions.
|
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
|
### Git Auth Types
|
||||||
|
|
||||||
`https_pat`, `https_basic`, `ssh_key`
|
`https_pat`, `https_basic`, `ssh_key`
|
||||||
|
|||||||
348
docs/JS-DEPENDENCY-AUDIT.md
Normal file
348
docs/JS-DEPENDENCY-AUDIT.md
Normal file
@@ -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
|
||||||
|
`<script>` tag loaded in dependency order. All 47 files are now wrapped
|
||||||
|
in IIFEs with explicit `window.*` exports:
|
||||||
|
|
||||||
|
```js
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
// ... all code ...
|
||||||
|
window.MyGlobal = MyGlobal;
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 1 complete.** Zero implicit globals remain. ~168 functions
|
||||||
|
privatized. The next phase is onclick → addEventListener migration
|
||||||
|
(143 dynamic handlers in JS innerHTML + ~50 in Go templates), which
|
||||||
|
is the prerequisite for ES module conversion.
|
||||||
|
|
||||||
|
### IIFE Files
|
||||||
|
|
||||||
|
| File | Clean? | Leaks |
|
||||||
|
|------|--------|-------|
|
||||||
|
| `admin-handlers.js` | ✅ | None (all via inline onclick from globals) |
|
||||||
|
| `admin-scaffold.js` | ✅ | None |
|
||||||
|
| `editor-surface.js` | ✅ | None |
|
||||||
|
| `knowledge-ui.js` | ✅ | None |
|
||||||
|
| `pages-splash.js` | ✅ | None |
|
||||||
|
| `task-admin.js` | ❌ | `window._loadAdminTasks` |
|
||||||
|
| `task-settings.js` | ❌ | `window._createFromTemplate`, `window._loadSettingsTasks` |
|
||||||
|
| `task-sidebar.js` | ❌ | `window.TaskSidebar` |
|
||||||
|
| `tools-toggle.js` | ✅ | None |
|
||||||
|
| `workflow-admin.js` | ❌ | 5 functions (`_loadAdminWorkflows`, `_wfCreate`, `_wfOpen`, `_wfEditStage`, `_wfDeleteStage`) |
|
||||||
|
| `workflow-api.js` | ✅ | None (registers on App) |
|
||||||
|
| `workflow-queue.js` | ❌ | `window.WorkflowQueue` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script Load Order
|
||||||
|
|
||||||
|
### base.html (shared — every surface)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. app-state.js → const App = { ... }
|
||||||
|
2. api.js → const API = { ... }
|
||||||
|
3. (inline) → window.__BASE__, auth bootstrap
|
||||||
|
4. events.js → const Events = { ... }
|
||||||
|
5. ui-primitives.js → const UI = { ... }
|
||||||
|
6. vendor/marked.min.js
|
||||||
|
7. vendor/purify.min.js
|
||||||
|
8. ui-format.js → formatMessage(), toggleCodeCollapse(), etc.
|
||||||
|
9. ui-core.js → renderChatList(), renderMessages(), etc.
|
||||||
|
10. pages.js → const Pages = { ... }
|
||||||
|
11. user-menu.js → const UserMenu = { ... }
|
||||||
|
12. model-selector.js → const ModelSelector = { ... }
|
||||||
|
13. file-tree.js → const FileTree = { ... }
|
||||||
|
14. code-editor.js → const CodeEditor = { ... }
|
||||||
|
15. note-editor.js → const NoteEditor = { ... }
|
||||||
|
16. drag-resize.js → const DragResize = { ... }
|
||||||
|
17. pane-container.js → const PaneContainer = { ... }
|
||||||
|
18. (inline) → Theme.init()
|
||||||
|
19. surfaces/{surface}/js/main.js → surface CSS + scaffold
|
||||||
|
```
|
||||||
|
|
||||||
|
### chat.html (25 additional scripts + 1 vendor)
|
||||||
|
|
||||||
|
```
|
||||||
|
vendor/codemirror/codemirror.bundle.js → window.CM (optional, onerror fallback)
|
||||||
|
20. extensions.js → const Extensions = { ... }
|
||||||
|
21. panels.js → const PanelRegistry = { ... }
|
||||||
|
22. ui-settings.js → settings rendering (bare globals)
|
||||||
|
23. ui-admin.js → admin rendering (bare globals)
|
||||||
|
24. tokens.js → token counting (bare globals)
|
||||||
|
25. notes.js → notes UI (bare globals)
|
||||||
|
26. note-graph.js → graph visualization (bare globals)
|
||||||
|
27. files.js → file upload/management (bare globals)
|
||||||
|
28. tools-toggle.js → tool enable/disable (IIFE)
|
||||||
|
29. knowledge-ui.js → KB picker (IIFE)
|
||||||
|
30. memory-ui.js → memory panel (bare globals)
|
||||||
|
31. persona-kb.js → persona KB bindings (bare globals)
|
||||||
|
32. projects-ui.js → project management (bare globals, 78 top-level decls)
|
||||||
|
33. channel-models.js → const ChannelModels = { ... }
|
||||||
|
34. notification-prefs.js → notification preferences (bare globals)
|
||||||
|
35. notifications.js → const Notifications = { ... }
|
||||||
|
36. chat.js → const ChatInput = { ... } + 29 top-level functions
|
||||||
|
37. settings-handlers.js → settings form handlers (bare globals, 24 decls)
|
||||||
|
38. admin-handlers.js → admin form handlers (IIFE, 40 internal functions)
|
||||||
|
39. workflow-api.js → workflow REST helpers (IIFE)
|
||||||
|
40. workflow-admin.js → workflow admin UI (IIFE, 5 window.* leaks)
|
||||||
|
41. workflow-queue.js → workflow queue sidebar (IIFE, window.WorkflowQueue)
|
||||||
|
42. task-sidebar.js → task sidebar (IIFE, window.TaskSidebar)
|
||||||
|
43. app.js → init(), startApp(), event listeners
|
||||||
|
44. debug.js → debug console (bare globals)
|
||||||
|
45. repl.js → REPL (bare globals)
|
||||||
|
```
|
||||||
|
|
||||||
|
### admin.html (14 additional)
|
||||||
|
|
||||||
|
Loads: ui-settings, ui-admin, admin-handlers, settings-handlers,
|
||||||
|
knowledge-ui, persona-kb, memory-ui, notifications, files,
|
||||||
|
admin-scaffold, workflow-api, workflow-admin, task-admin,
|
||||||
|
admin-surfaces.
|
||||||
|
|
||||||
|
### notes.html (14 additional)
|
||||||
|
|
||||||
|
Loads: chat, files, channel-models, tokens, extensions, notes,
|
||||||
|
note-graph, knowledge-ui, persona-kb, memory-ui, notifications,
|
||||||
|
panels, projects-ui, app.
|
||||||
|
|
||||||
|
### settings.html (9 additional)
|
||||||
|
|
||||||
|
Loads: ui-settings, settings-handlers, task-settings, knowledge-ui,
|
||||||
|
persona-kb, memory-ui, notifications, notification-prefs,
|
||||||
|
channel-models.
|
||||||
|
|
||||||
|
### editor.html (4 additional + 1 vendor)
|
||||||
|
|
||||||
|
Loads: vendor/codemirror, chat-pane, editor-surface, app.
|
||||||
|
Lightest surface — fewest dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Globals (Dependency Roots)
|
||||||
|
|
||||||
|
These are referenced by 10+ other files. They form the "root set" that
|
||||||
|
must be extracted first.
|
||||||
|
|
||||||
|
| Global | Defined in | Referenced by | Role |
|
||||||
|
|--------|-----------|---------------|------|
|
||||||
|
| `App` | app-state.js | 20 files | State container (chats, models, user, settings, presence) |
|
||||||
|
| `API` | api.js | 36 files | REST client (all HTTP calls) |
|
||||||
|
| `UI` | ui-core.js | 32 files | DOM helpers (toast, confirm, modal, sidebar, theme) |
|
||||||
|
| `Events` | events.js | 9 files | Pub/sub event bus (WebSocket bridge) |
|
||||||
|
|
||||||
|
### Secondary Globals (3–9 references)
|
||||||
|
|
||||||
|
| Global | Defined in | Referenced by | Role |
|
||||||
|
|--------|-----------|---------------|------|
|
||||||
|
| `Pages` | pages.js | Templates + 3 files | Page-level actions (login, admin forms) |
|
||||||
|
| `ChannelModels` | channel-models.js | 4 files | Model roster per channel |
|
||||||
|
| `ChatInput` | chat.js | 3 files | Message input state + send |
|
||||||
|
| `Notifications` | notifications.js | Templates + 2 files | Bell dropdown, badge count |
|
||||||
|
| `ChatPane` | chat-pane.js | 3 files | Embedded chat component (editor) |
|
||||||
|
| `CM` | vendor/codemirror.bundle.js | 5 files | CodeMirror 6 editor (optional, graceful fallback) |
|
||||||
|
| `PanelRegistry` | panels.js | Templates + 1 file | Side panel management |
|
||||||
|
|
||||||
|
### Leaf Globals (1–2 references, mostly self-contained)
|
||||||
|
|
||||||
|
ModelSelector, FileTree, CodeEditor, NoteEditor, DragResize,
|
||||||
|
PaneContainer, UserMenu, WorkflowQueue, TaskSidebar, Extensions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inline Handler Problem
|
||||||
|
|
||||||
|
143 `onclick=` patterns in JS files (innerHTML-generated HTML) plus
|
||||||
|
~50 in Go templates. These reference bare globals that **must remain
|
||||||
|
on `window`** or the handlers break. This is the primary blocker for
|
||||||
|
ES module migration.
|
||||||
|
|
||||||
|
**Top offenders (dynamic onclick in JS):**
|
||||||
|
|
||||||
|
| File | Count | Examples |
|
||||||
|
|------|-------|---------|
|
||||||
|
| ui-admin.js | 28 | Admin list row actions |
|
||||||
|
| projects-ui.js | 26 | Project CRUD, channel/KB/note associations |
|
||||||
|
| ui-core.js | 25 | Chat list, sidebar, context menus |
|
||||||
|
| ui-format.js | 10 | Code block copy/preview/download |
|
||||||
|
| ui-settings.js | 8 | Settings form toggles |
|
||||||
|
| notifications.js | 8 | Notification row actions |
|
||||||
|
|
||||||
|
**Template onclick (must remain global):**
|
||||||
|
|
||||||
|
UI.toggleSidebar, UI.toggleSidebarSection, UI.switchTeamTab,
|
||||||
|
Notifications.toggleDropdown, PanelRegistry.toggle, PanelRegistry.closeAll,
|
||||||
|
Pages.doLogin, Pages.saveSettings, Pages.saveProvider, Pages.saveTeam,
|
||||||
|
handleLogin, handleRegister, switchAuthTab, newChat, newFolder,
|
||||||
|
newChannelOrDM, sendMessage, createProject, closeLightbox,
|
||||||
|
toggleSidePanelFullscreen, plus ~15 more Pages.* admin form handlers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-File Call Graph (Simplified)
|
||||||
|
|
||||||
|
```
|
||||||
|
app-state.js (App)
|
||||||
|
↑ read/write by 20 files
|
||||||
|
│
|
||||||
|
api.js (API)
|
||||||
|
↑ called by 36 files
|
||||||
|
│
|
||||||
|
events.js (Events)
|
||||||
|
↑ subscribed by 9 files
|
||||||
|
├── chat.js (typing, message.created, presence)
|
||||||
|
├── notifications.js (notification.new/read)
|
||||||
|
├── extensions.js (tool.call/result)
|
||||||
|
├── repl.js (debug events)
|
||||||
|
└── app.js (role.fallback banner)
|
||||||
|
│
|
||||||
|
ui-primitives.js (UI)
|
||||||
|
↑ called by 32 files
|
||||||
|
├── toast(), confirm(), modal()
|
||||||
|
├── toggleSidebar(), toggleSidebarSection()
|
||||||
|
└── theme, kbd shortcuts
|
||||||
|
│
|
||||||
|
ui-format.js (formatMessage, etc.)
|
||||||
|
↑ called by ui-core.js, chat.js, notes.js, projects-ui.js
|
||||||
|
├── depends on: marked.min.js, purify.min.js
|
||||||
|
│
|
||||||
|
ui-core.js (renderChatList, renderMessages, etc.)
|
||||||
|
↑ called by chat.js, app.js, projects-ui.js
|
||||||
|
├── depends on: App, API, UI, ui-format
|
||||||
|
│
|
||||||
|
chat.js (ChatInput, sendMessage, selectChat, loadChats)
|
||||||
|
↑ called by app.js, templates
|
||||||
|
├── depends on: App, API, Events, UI, ui-core, ui-format,
|
||||||
|
│ ChannelModels, tokens, files
|
||||||
|
│
|
||||||
|
app.js (init, startApp)
|
||||||
|
↑ entry point — called by inline <script> in base.html
|
||||||
|
├── depends on: everything above + surface-specific modules
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decomposition Strategy
|
||||||
|
|
||||||
|
### Phase 1: Extract Core Four (no bundler required)
|
||||||
|
|
||||||
|
Move `App`, `API`, `Events`, `UI` to a pattern where they're defined
|
||||||
|
as proper singletons with explicit exports. No ES modules yet — just
|
||||||
|
clean up the global registration so each file has a single `window.X`
|
||||||
|
export with a documented interface.
|
||||||
|
|
||||||
|
**Why no bundler yet:** 143 dynamic onclick handlers + 50 template
|
||||||
|
onclick handlers require globals on `window`. A bundler would need
|
||||||
|
either (a) a global shim layer or (b) converting all handlers to
|
||||||
|
`addEventListener`. Option (b) is the right answer but it's ~200
|
||||||
|
call sites across 16 files + 10 templates — too much churn for one
|
||||||
|
changeset.
|
||||||
|
|
||||||
|
### Phase 2: onclick → addEventListener migration
|
||||||
|
|
||||||
|
Convert dynamic HTML generation from:
|
||||||
|
```js
|
||||||
|
`<button onclick="deleteItem('${id}')">Delete</button>`
|
||||||
|
```
|
||||||
|
to:
|
||||||
|
```js
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.textContent = 'Delete';
|
||||||
|
btn.addEventListener('click', () => deleteItem(id));
|
||||||
|
```
|
||||||
|
|
||||||
|
This eliminates the global requirement. Target the top 6 files first
|
||||||
|
(ui-admin, projects-ui, ui-core, ui-format, ui-settings, notifications)
|
||||||
|
which account for 105 of 143 occurrences.
|
||||||
|
|
||||||
|
Template onclick handlers stay — Go templates can't do addEventListener.
|
||||||
|
These become the only remaining reason for `window.*` exports.
|
||||||
|
|
||||||
|
### Phase 3: ES Module conversion
|
||||||
|
|
||||||
|
Once onclick handlers are gone from JS, files can become ES modules:
|
||||||
|
```html
|
||||||
|
<script type="module" src="js/app.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Import graph follows the dependency order already established by
|
||||||
|
`<script>` tag ordering. Vendor libs (marked, purify, codemirror) stay
|
||||||
|
as classic scripts since they self-register on `window`.
|
||||||
|
|
||||||
|
### Phase 4: Template handler shim
|
||||||
|
|
||||||
|
Create a thin `window.handlers = {}` object that ES modules register
|
||||||
|
into. Template onclick handlers call `handlers.doLogin()` etc. This
|
||||||
|
is the final bridge — once templates move to client-side rendering
|
||||||
|
(extension surface SDK), this shim disappears.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files by Decomposition Priority
|
||||||
|
|
||||||
|
### Tier 1 — Core (extract first, most dependents)
|
||||||
|
|
||||||
|
| File | Lines | Globals | Dependents | Notes |
|
||||||
|
|------|-------|---------|------------|-------|
|
||||||
|
| app-state.js | 96 | 3 | 20 | Clean singleton, easy extract |
|
||||||
|
| api.js | 1,159 | 3 | 36 | Large but self-contained |
|
||||||
|
| events.js | 261 | 1 | 9 | Clean singleton |
|
||||||
|
| ui-primitives.js | 1,316 | 18 | 32 | Large, many utility functions |
|
||||||
|
|
||||||
|
### Tier 2 — Rendering (depends on Tier 1)
|
||||||
|
|
||||||
|
| File | Lines | Globals | Dependents | Notes |
|
||||||
|
|------|-------|---------|------------|-------|
|
||||||
|
| ui-format.js | 573 | 20 | 4 | Markdown + code blocks |
|
||||||
|
| ui-core.js | 1,644 | 6 | 3 | Chat list, messages, sidebar |
|
||||||
|
| pages.js | 382 | 8 | Templates | Page-level orchestration |
|
||||||
|
|
||||||
|
### Tier 3 — Feature modules (leaf nodes, mostly self-contained)
|
||||||
|
|
||||||
|
| File | Lines | Notes |
|
||||||
|
|------|-------|-------|
|
||||||
|
| channel-models.js | 425 | Clean object literal |
|
||||||
|
| chat-pane.js | 143 | Clean object literal |
|
||||||
|
| notifications.js | 390 | Clean singleton, IIFE candidate |
|
||||||
|
| model-selector.js | 191 | Clean object literal |
|
||||||
|
| file-tree.js | 295 | Clean object literal |
|
||||||
|
| tokens.js | 143 | 4 globals, minimal deps |
|
||||||
|
|
||||||
|
### Tier 4 — Heavy pages (most onclick handlers, refactor last)
|
||||||
|
|
||||||
|
| File | Lines | onclick | Notes |
|
||||||
|
|------|-------|---------|-------|
|
||||||
|
| ui-admin.js | 1,922 | 28 | Admin rendering, deeply coupled |
|
||||||
|
| projects-ui.js | 1,794 | 26 | 78 top-level globals (!), biggest mess |
|
||||||
|
| ui-core.js | 1,644 | 25 | Already in Tier 2, but onclick heavy |
|
||||||
|
| ui-settings.js | 1,011 | 8 | Settings rendering |
|
||||||
|
| settings-handlers.js | 1,064 | 0 | But 24 globals called by ui-settings |
|
||||||
|
| admin-handlers.js | 952 | 2 | IIFE but 40 internal functions |
|
||||||
@@ -24,8 +24,8 @@ v0.9.x–v0.27.5 Foundation → Extensions → Surfaces → Auth ✅
|
|||||||
│
|
│
|
||||||
v0.28.0 Platform Polish
|
v0.28.0 Platform Polish
|
||||||
├─ v0.28.1 Surfaces ICD audit ✅
|
├─ v0.28.1 Surfaces ICD audit ✅
|
||||||
├─ v0.28.2 ICD audit — all domains
|
├─ v0.28.2 ICD audit — all domains ✅
|
||||||
├─ v0.28.3 FE decomp + SDK prep
|
├─ v0.28.3 ICD close-out + FE decomp prep
|
||||||
├─ v0.28.4 Security tier (red team)
|
├─ v0.28.4 Security tier (red team)
|
||||||
├─ v0.28.5 Infrastructure
|
├─ v0.28.5 Infrastructure
|
||||||
│ (virtual scroll, KB auto-inject,
|
│ (virtual scroll, KB auto-inject,
|
||||||
@@ -67,7 +67,7 @@ Audit arc, frontend decomposition, security, and infrastructure improvements.
|
|||||||
- [x] 22 handler-level tests + 14 store-level tests (PG + SQLite)
|
- [x] 22 handler-level tests + 14 store-level tests (PG + SQLite)
|
||||||
- [x] CI timeout 8m → 12m for PG integration tests
|
- [x] CI timeout 8m → 12m for PG integration tests
|
||||||
|
|
||||||
### v0.28.2 — ICD Audit: All Domains
|
### v0.28.2 — ICD Audit: All Domains ✅
|
||||||
Full audit of every ICD document against code. Goal: ICD becomes the single
|
Full audit of every ICD document against code. Goal: ICD becomes the single
|
||||||
source of truth. After this version, the workflow is ICD-first — update the
|
source of truth. After this version, the workflow is ICD-first — update the
|
||||||
contract before changing code.
|
contract before changing code.
|
||||||
@@ -77,6 +77,8 @@ contract before changing code.
|
|||||||
ICD → fix code → fix runner → CI green. Final pass picks up straggling
|
ICD → fix code → fix runner → CI green. Final pass picks up straggling
|
||||||
failures across all domains.
|
failures across all domains.
|
||||||
|
|
||||||
|
**Final score: 469/469 (100%)**
|
||||||
|
|
||||||
**Completed audits:**
|
**Completed audits:**
|
||||||
|
|
||||||
*Notifications (cs0):*
|
*Notifications (cs0):*
|
||||||
@@ -128,32 +130,53 @@ failures across all domains.
|
|||||||
not `files`), archive format query param, auth annotations, all response shapes
|
not `files`), archive format query param, auth annotations, all response shapes
|
||||||
- [x] P0 fix: `GET /workspaces` returns bare array → `{"data": [...]}`
|
- [x] P0 fix: `GET /workspaces` returns bare array → `{"data": [...]}`
|
||||||
- [x] P0 fix: `GET /git-credentials` returns bare array → `{"data": [...]}`
|
- [x] P0 fix: `GET /git-credentials` returns bare array → `{"data": [...]}`
|
||||||
- [x] Fix: `GET .../git/log` returns bare array → `{"data": [...]}` + nil slice guard
|
- [x] Fix: `GET .../git/log` returns bare array → `{"data": [...]}` + nil slice guard
|
||||||
- [x] New `workspace_test.go`: List empty envelope, list with data + shape, root_path
|
- [x] New `workspace_test.go`: List empty envelope, list with data + shape, root_path
|
||||||
not exposed, user isolation, GET by ID shape, not found 404, forbidden 403,
|
not exposed, user isolation, GET by ID shape, not found 404, forbidden 403,
|
||||||
git-credentials empty envelope, auth required (11 tests)
|
git-credentials empty envelope, auth required (11 tests)
|
||||||
|
|
||||||
**Remaining audits:**
|
*Projects (cs7):*
|
||||||
|
- [x] `projects.md` full rewrite: 6 categories of drift (ghost fields, missing fields,
|
||||||
|
request shapes, association objects, `omitempty` on computed counts)
|
||||||
|
- [x] 14 new Go tests: CRUD shapes, 201 status, envelope, admin list, auth, isolation
|
||||||
|
- [x] P0 nil guard: `ListByProject` files `null` → `[]`
|
||||||
|
- [x] P0: `ListTeamProviderModels` missing `Type` field — Venice 400 fix
|
||||||
|
- [x] ICD runner projects rewritten 9 → 19 tests
|
||||||
|
- [x] ICD runner tier-providers: SSE parser, model exclusion, ID fallback fixes
|
||||||
|
|
||||||
| Priority | ICD | Known failures | Notes |
|
### v0.28.3 — ICD Close-out + Frontend Decomposition Prep
|
||||||
|----------|-----|----------------|-------|
|
ICD straggler sweep (rolled in from v0.28.2 tail) and frontend
|
||||||
| 1 | `projects.md` | 0 | Passed clean, needs full trace |
|
decomposition groundwork.
|
||||||
| 2 | `websocket.md` | — | Document event shapes only (not testable in runner) |
|
|
||||||
|
|
||||||
- [ ] Notification handler + store tests (PG + SQLite)
|
**ICD close-out (cs0):**
|
||||||
- [ ] WebSocket event contract audit: document `message.created`, `workflow.advanced`,
|
- [x] `websocket.md` full rewrite: event envelope field (`event` not `type`),
|
||||||
`presence.changed`, `typing`, `workflow.assigned` event shapes in ICD
|
payload shapes for 11 event types, routing table from code, room model
|
||||||
- [ ] Final straggler pass: run full ICD runner, fix any remaining failures
|
documented as planned-not-implemented
|
||||||
|
- [x] `auth.md` corrected: login response shape (added `token_type`/`expires_in`,
|
||||||
|
removed phantom fields), register admin-approval path
|
||||||
|
- [x] `enums.md` corrected: added missing `queued` task run status
|
||||||
|
- [x] Presence status gap documented (DB allows `away`, runtime emits only
|
||||||
|
`online`/`offline`)
|
||||||
|
- [x] VERSION, CHANGELOG, ROADMAP updated
|
||||||
|
|
||||||
### v0.28.3 — Frontend Decomposition + ICD Audit SDK Prep
|
**WebSocket delivery fix (cs1):**
|
||||||
Frontend JS decomposition and ICD runner hardening. Prepares the codebase
|
- [x] `workflow.claimed`, `workflow.advanced`, `workflow.completed` use room-scoped
|
||||||
for the SDK layer by untangling the current 15-file global soup.
|
`Bus.Publish()` but rooms are never joined — events never reach WebSocket
|
||||||
|
clients. Convert to `SendToUser()` per channel participant (same pattern as
|
||||||
|
`message.created`, `workflow.assigned`).
|
||||||
|
|
||||||
- [ ] JS dependency audit: map every `window.*` global, cross-file call, init order
|
**Frontend decomposition:**
|
||||||
- [ ] Extract shared primitives: toast, confirm, theme, API client into importable modules
|
- [x] JS dependency audit: `docs/JS-DEPENDENCY-AUDIT.md` — full map of 47 files,
|
||||||
|
431 globals, cross-file call graph, script load order per surface
|
||||||
|
- [x] IIFE extraction: all 47 JS files wrapped, ~168 functions privatized, zero
|
||||||
|
implicit globals remain. Explicit `window.*` exports on every file.
|
||||||
|
- [x] Cross-file coupling fix: `events.js` no longer reads `_storageKey` from
|
||||||
|
`api.js` — uses `API.accessToken` instead
|
||||||
- [ ] ICD runner gains test tiers: `crud`, `envelope`, `security`, `sdk`
|
- [ ] ICD runner gains test tiers: `crud`, `envelope`, `security`, `sdk`
|
||||||
- [ ] Runner coverage target: 100% of ICD-documented endpoints have at least one test
|
- [ ] Runner coverage target: 100% of ICD-documented endpoints have at least one test
|
||||||
- [ ] Document JS module graph and init sequence
|
- [ ] Phase 2: onclick → addEventListener migration (143 dynamic + ~50 template handlers)
|
||||||
|
- [ ] Phase 3: ES module conversion
|
||||||
|
- [ ] Phase 4: Template handler shim
|
||||||
|
|
||||||
### v0.28.4 — Security Tier (ICD Runner Red Team)
|
### v0.28.4 — Security Tier (ICD Runner Red Team)
|
||||||
New `security` tier in ICD test runner. Multi-user fixtures already exist.
|
New `security` tier in ICD test runner. Multi-user fixtures already exist.
|
||||||
|
|||||||
@@ -130,26 +130,43 @@ func (h *WorkflowAssignmentHandler) Claim(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit workflow.claimed WS event
|
// Look up channel for this assignment (needed for WS delivery + notification)
|
||||||
if h.hub != nil {
|
var channelID string
|
||||||
|
_ = database.DB.QueryRowContext(c.Request.Context(),
|
||||||
|
database.Q(`SELECT channel_id FROM workflow_assignments WHERE id = $1`),
|
||||||
|
assignmentID).Scan(&channelID)
|
||||||
|
|
||||||
|
// Emit workflow.claimed WS event to all user participants in the channel.
|
||||||
|
// Uses SendToUser (not room-scoped Bus.Publish) because room subscriptions
|
||||||
|
// are not yet wired on the client side. See websocket.md § Room Model.
|
||||||
|
if h.hub != nil && channelID != "" {
|
||||||
payload, _ := json.Marshal(map[string]any{
|
payload, _ := json.Marshal(map[string]any{
|
||||||
"assignment_id": assignmentID,
|
"assignment_id": assignmentID,
|
||||||
"claimed_by": userID,
|
"claimed_by": userID,
|
||||||
|
"channel_id": channelID,
|
||||||
})
|
})
|
||||||
h.hub.GetBus().Publish(events.Event{
|
evt := events.Event{
|
||||||
Label: "workflow.claimed",
|
Label: "workflow.claimed",
|
||||||
Room: assignmentID,
|
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
Ts: now.UnixMilli(),
|
Ts: now.UnixMilli(),
|
||||||
})
|
}
|
||||||
|
rows, err := database.DB.QueryContext(c.Request.Context(), database.Q(`
|
||||||
|
SELECT participant_id FROM channel_participants
|
||||||
|
WHERE channel_id = $1 AND participant_type = 'user'
|
||||||
|
`), channelID)
|
||||||
|
if err == nil {
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var uid string
|
||||||
|
if rows.Scan(&uid) == nil {
|
||||||
|
h.hub.SendToUser(uid, evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist notification for bell/inbox (v0.28.2)
|
// Persist notification for bell/inbox (v0.28.2)
|
||||||
if svc := notifications.Default(); svc != nil {
|
if svc := notifications.Default(); svc != nil && channelID != "" {
|
||||||
var channelID string
|
|
||||||
_ = database.DB.QueryRowContext(c.Request.Context(),
|
|
||||||
database.Q(`SELECT channel_id FROM workflow_assignments WHERE id = $1`),
|
|
||||||
assignmentID).Scan(&channelID)
|
|
||||||
notifications.NotifyWorkflowClaimed(svc, userID, assignmentID, channelID)
|
notifications.NotifyWorkflowClaimed(svc, userID, assignmentID, channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -338,18 +338,36 @@ func (h *WorkflowInstanceHandler) readWorkflowState(ctx context.Context, channel
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitWorkflowEvent pushes a workflow event to all connected clients in the channel's room.
|
// emitWorkflowEvent pushes a workflow event to all user participants in the channel.
|
||||||
|
// Uses SendToUser (not room-scoped Bus.Publish) because room subscriptions are
|
||||||
|
// not yet wired on the client side. See websocket.md § Room Model.
|
||||||
func (h *WorkflowInstanceHandler) emitWorkflowEvent(label, channelID string, data map[string]any) {
|
func (h *WorkflowInstanceHandler) emitWorkflowEvent(label, channelID string, data map[string]any) {
|
||||||
if h.hub == nil {
|
if h.hub == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload, _ := json.Marshal(data)
|
payload, _ := json.Marshal(data)
|
||||||
h.hub.GetBus().Publish(events.Event{
|
evt := events.Event{
|
||||||
Label: label,
|
Label: label,
|
||||||
Room: channelID,
|
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
Ts: time.Now().UnixMilli(),
|
Ts: time.Now().UnixMilli(),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Send to all user participants in the channel
|
||||||
|
rows, err := database.DB.Query(database.Q(`
|
||||||
|
SELECT participant_id FROM channel_participants
|
||||||
|
WHERE channel_id = $1 AND participant_type = 'user'
|
||||||
|
`), channelID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ws] %s: failed to query participants for channel %s: %v", label, channelID[:min(8, len(channelID))], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var uid string
|
||||||
|
if rows.Scan(&uid) == nil {
|
||||||
|
h.hub.SendToUser(uid, evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggerOnComplete checks if the workflow has an on_complete chain config
|
// triggerOnComplete checks if the workflow has an on_complete chain config
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
// Admin actions: user management, roles, model visibility,
|
// Admin actions: user management, roles, model visibility,
|
||||||
// personas, team management, global providers.
|
// personas, team management, global providers.
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Admin Actions ────────────────────────────
|
// ── Admin Actions ────────────────────────────
|
||||||
|
|
||||||
function _adminScroll() { return document.getElementById('adminMain'); }
|
function _adminScroll() { return document.getElementById('adminMain'); }
|
||||||
@@ -883,3 +886,36 @@ async function saveAdminExtension(id) {
|
|||||||
UI.toast(e.message, 'error');
|
UI.toast(e.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window._adminPersonaForm = _adminPersonaForm;
|
||||||
|
window._initAdminListeners = _initAdminListeners;
|
||||||
|
window.adminResetUserPassword = adminResetUserPassword;
|
||||||
|
window.bulkSetUserModelVisibility = bulkSetUserModelVisibility;
|
||||||
|
window.cycleModelVisibility = cycleModelVisibility;
|
||||||
|
window.deleteAdminExtension = deleteAdminExtension;
|
||||||
|
window.deleteAdminPersona = deleteAdminPersona;
|
||||||
|
window.deleteGroup = deleteGroup;
|
||||||
|
window.deleteTeam = deleteTeam;
|
||||||
|
window.deleteUser = deleteUser;
|
||||||
|
window.deleteUserPersona = deleteUserPersona;
|
||||||
|
window.editAdminExtension = editAdminExtension;
|
||||||
|
window.editAdminPersona = editAdminPersona;
|
||||||
|
window.ensureAdminPersonaForm = ensureAdminPersonaForm;
|
||||||
|
window.removeGroupMember = removeGroupMember;
|
||||||
|
window.removeTeamMember = removeTeamMember;
|
||||||
|
window.saveGrant = saveGrant;
|
||||||
|
window.settingsDeleteTeamPersona = settingsDeleteTeamPersona;
|
||||||
|
window.settingsRemoveTeamMember = settingsRemoveTeamMember;
|
||||||
|
window.settingsUpdateTeamMember = settingsUpdateTeamMember;
|
||||||
|
window.showApproveForm = showApproveForm;
|
||||||
|
window.toggleAdminExtension = toggleAdminExtension;
|
||||||
|
window.toggleAdminPersona = toggleAdminPersona;
|
||||||
|
window.toggleTeamActive = toggleTeamActive;
|
||||||
|
window.toggleUserActive = toggleUserActive;
|
||||||
|
window.toggleUserModelVisibility = toggleUserModelVisibility;
|
||||||
|
window.toggleUserRole = toggleUserRole;
|
||||||
|
window.updateTeamMember = updateTeamMember;
|
||||||
|
window._closeExtEditForm = _closeExtEditForm;
|
||||||
|
window.saveAdminExtension = saveAdminExtension;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// v0.25.0: Surface lifecycle management.
|
// v0.25.0: Surface lifecycle management.
|
||||||
// Renders in the "Surfaces" admin section via ADMIN_LOADERS.
|
// Renders in the "Surfaces" admin section via ADMIN_LOADERS.
|
||||||
|
//
|
||||||
|
// Exports: window._loadAdminSurfaces
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
async function _loadAdminSurfaces() {
|
async function _loadAdminSurfaces() {
|
||||||
const container = document.getElementById('adminSurfacesContent');
|
const container = document.getElementById('adminSurfacesContent');
|
||||||
@@ -191,3 +196,7 @@ async function _loadSurfaceList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window._loadAdminSurfaces = _loadAdminSurfaces;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -7,8 +7,13 @@
|
|||||||
// BASE_PATH: injected via index.html <script>
|
// BASE_PATH: injected via index.html <script>
|
||||||
// at container startup. All API paths are
|
// at container startup. All API paths are
|
||||||
// prefixed automatically.
|
// prefixed automatically.
|
||||||
|
//
|
||||||
|
// Exports: window.API
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const BASE = window.__BASE__ || '';
|
const BASE = window.__BASE__ || '';
|
||||||
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';
|
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';
|
||||||
|
|
||||||
@@ -987,3 +992,6 @@ const API = {
|
|||||||
return this._parseJSON(resp, path);
|
return this._parseJSON(resp, path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.API = API;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
// Loaded by base.html for ALL surfaces. Contains the App state
|
// Loaded by base.html for ALL surfaces. Contains the App state
|
||||||
// object and universal data-loading functions (models, settings).
|
// object and universal data-loading functions (models, settings).
|
||||||
// Surface-specific logic lives in app.js (chat), admin-scaffold.js, etc.
|
// Surface-specific logic lives in app.js (chat), admin-scaffold.js, etc.
|
||||||
|
//
|
||||||
|
// Exports: window.App, window.fetchModels
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const App = {
|
const App = {
|
||||||
chats: [],
|
chats: [],
|
||||||
@@ -130,3 +136,7 @@ async function fetchModels() {
|
|||||||
console.log(`📋 Loaded ${App.models.length} models (${App.models.filter(m => m.isPersona).length} personas, ${App.models.filter(m => m.hidden).length} hidden)`);
|
console.log(`📋 Loaded ${App.models.length} models (${App.models.filter(m => m.isPersona).length} personas, ${App.models.filter(m => m.hidden).length} hidden)`);
|
||||||
} catch (e) { console.warn('Model fetch failed:', e.message); }
|
} catch (e) { console.warn('Model fetch failed:', e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.App = App;
|
||||||
|
window.fetchModels = fetchModels;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
// (loaded by base.html for all surfaces).
|
// (loaded by base.html for all surfaces).
|
||||||
// Domain logic lives in chat.js, notes.js, settings-handlers.js,
|
// Domain logic lives in chat.js, notes.js, settings-handlers.js,
|
||||||
// admin-handlers.js. UI rendering in ui-*.js files.
|
// admin-handlers.js. UI rendering in ui-*.js files.
|
||||||
|
//
|
||||||
|
// Exports: handleLogin, handleRegister, handleLogout, switchAuthTab,
|
||||||
|
// startApp, initBanners
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
console.log('🔀 Chat Switchboard initializing...');
|
console.log('🔀 Chat Switchboard initializing...');
|
||||||
@@ -574,3 +580,12 @@ async function initBanners() {
|
|||||||
|
|
||||||
// ── Boot ─────────────────────────────────────
|
// ── Boot ─────────────────────────────────────
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.handleLogin = handleLogin;
|
||||||
|
window.handleRegister = handleRegister;
|
||||||
|
window.handleLogout = handleLogout;
|
||||||
|
window.switchAuthTab = switchAuthTab;
|
||||||
|
window.startApp = startApp;
|
||||||
|
window.initBanners = initBanners;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
// - @mention autocomplete in chat input
|
// - @mention autocomplete in chat input
|
||||||
// - Model attribution on assistant messages
|
// - Model attribution on assistant messages
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.ChannelModels
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const ChannelModels = {
|
const ChannelModels = {
|
||||||
|
|
||||||
@@ -429,3 +434,7 @@ function _shortName(fullName) {
|
|||||||
.trim()
|
.trim()
|
||||||
.replace(/\s+/g, '-'); // spaces → hyphens
|
.replace(/\s+/g, '-'); // spaces → hyphens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.ChannelModels = ChannelModels;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
// v0.22.7: Mountable, self-contained chat pane instances.
|
// v0.22.7: Mountable, self-contained chat pane instances.
|
||||||
// Replaces the hardcoded singleton pattern.
|
// Replaces the hardcoded singleton pattern.
|
||||||
// ChatPane.primary is the backward-compat bridge.
|
// ChatPane.primary is the backward-compat bridge.
|
||||||
|
//
|
||||||
|
// Exports: window.ChatPane
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const ChatPane = {
|
const ChatPane = {
|
||||||
...createComponentRegistry('ChatPane'),
|
...createComponentRegistry('ChatPane'),
|
||||||
@@ -145,3 +150,7 @@ const ChatPane = {
|
|||||||
active() { return this.primary; },
|
active() { return this.primary; },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.ChatPane = ChatPane;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Chat management, send, stream, regenerate, edit, branch,
|
// Chat management, send, stream, regenerate, edit, branch,
|
||||||
// summarize, per-chat model persistence.
|
// summarize, per-chat model persistence.
|
||||||
|
//
|
||||||
|
// Exports: ChatInput, loadChats, selectChat, newChat, newGroupChat,
|
||||||
|
// deleteChat, startRenameChat, sendMessage, stopGeneration,
|
||||||
|
// reloadActivePath, regenerateMessage, regenerate, editMessage,
|
||||||
|
// submitEdit, cancelEdit, switchSibling, advanceWorkflowStage,
|
||||||
|
// rejectWorkflowStage, _initChatListeners,
|
||||||
|
// summarizeAndContinue, toggleChannelAutoCompact
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Chat Input Abstraction ──────────────────
|
// ── Chat Input Abstraction ──────────────────
|
||||||
// Wraps CM6 editor (when available) or fallback textarea.
|
// Wraps CM6 editor (when available) or fallback textarea.
|
||||||
@@ -1246,3 +1256,27 @@ async function rejectWorkflowStage() {
|
|||||||
UI.toast('Reject failed: ' + (e.message || e), 'error');
|
UI.toast('Reject failed: ' + (e.message || e), 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.ChatInput = ChatInput;
|
||||||
|
window.loadChats = loadChats;
|
||||||
|
window.selectChat = selectChat;
|
||||||
|
window.newChat = newChat;
|
||||||
|
window.newGroupChat = newGroupChat;
|
||||||
|
window.deleteChat = deleteChat;
|
||||||
|
window.startRenameChat = startRenameChat;
|
||||||
|
window.sendMessage = sendMessage;
|
||||||
|
window.stopGeneration = stopGeneration;
|
||||||
|
window.reloadActivePath = reloadActivePath;
|
||||||
|
window.regenerateMessage = regenerateMessage;
|
||||||
|
window.regenerate = regenerate;
|
||||||
|
window.editMessage = editMessage;
|
||||||
|
window.submitEdit = submitEdit;
|
||||||
|
window.cancelEdit = cancelEdit;
|
||||||
|
window.switchSibling = switchSibling;
|
||||||
|
window.advanceWorkflowStage = advanceWorkflowStage;
|
||||||
|
window.rejectWorkflowStage = rejectWorkflowStage;
|
||||||
|
window._initChatListeners = _initChatListeners;
|
||||||
|
window.summarizeAndContinue = summarizeAndContinue;
|
||||||
|
window.toggleChannelAutoCompact = toggleChannelAutoCompact;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
// editor.isModified('src/main.go'); // → false
|
// editor.isModified('src/main.go'); // → false
|
||||||
// await editor.saveAll();
|
// await editor.saveAll();
|
||||||
// editor.destroy();
|
// editor.destroy();
|
||||||
|
//
|
||||||
|
// Exports: window.CodeEditor
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const CodeEditor = {
|
const CodeEditor = {
|
||||||
...createComponentRegistry('CodeEditor'),
|
...createComponentRegistry('CodeEditor'),
|
||||||
@@ -324,3 +329,7 @@ function _ceDetectLanguage(path) {
|
|||||||
};
|
};
|
||||||
return map[ext] || 'text';
|
return map[ext] || 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.CodeEditor = CodeEditor;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
//
|
//
|
||||||
// Activate: Ctrl+K → "debug" or tap the 🐛 badge (appears after init)
|
// Activate: Ctrl+K → "debug" or tap the 🐛 badge (appears after init)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.DebugLog,window.openDebugModal,window.switchDebugTab,window.clearDebugLog,window.copyDebugLog,window.exportDebugLog,window.runDebugDiagnostics,window.purgeCache
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const DebugLog = {
|
const DebugLog = {
|
||||||
entries: [],
|
entries: [],
|
||||||
@@ -661,3 +666,14 @@ async function purgeCache() {
|
|||||||
|
|
||||||
// Initialize ASAP — before app.js init() so we capture everything
|
// Initialize ASAP — before app.js init() so we capture everything
|
||||||
DebugLog.init();
|
DebugLog.init();
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.DebugLog = DebugLog;
|
||||||
|
window.openDebugModal = openDebugModal;
|
||||||
|
window.switchDebugTab = switchDebugTab;
|
||||||
|
window.clearDebugLog = clearDebugLog;
|
||||||
|
window.copyDebugLog = copyDebugLog;
|
||||||
|
window.exportDebugLog = exportDebugLog;
|
||||||
|
window.runDebugDiagnostics = runDebugDiagnostics;
|
||||||
|
window.purgeCache = purgeCache;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
// onMove(delta, ctx), // delta = px from start (signed)
|
// onMove(delta, ctx), // delta = px from start (signed)
|
||||||
// onEnd(ctx), // cleanup / persist
|
// onEnd(ctx), // cleanup / persist
|
||||||
// });
|
// });
|
||||||
|
//
|
||||||
|
// Exports: window.initDragResize
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach drag-resize behavior to a handle element.
|
* Attach drag-resize behavior to a handle element.
|
||||||
@@ -92,3 +97,7 @@ function _clientPos(e, isHoriz) {
|
|||||||
}
|
}
|
||||||
return isHoriz ? e.clientX : e.clientY;
|
return isHoriz ? e.clientX : e.clientY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.initDragResize = initDragResize;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -9,8 +9,13 @@
|
|||||||
// Events.on('chat.message.*', (payload, meta) => { ... });
|
// Events.on('chat.message.*', (payload, meta) => { ... });
|
||||||
// Events.emit('chat.typing.abc123', { user: 'jeff' });
|
// Events.emit('chat.typing.abc123', { user: 'jeff' });
|
||||||
// Events.connect('/ws');
|
// Events.connect('/ws');
|
||||||
|
//
|
||||||
|
// Exports: window.Events
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const Events = {
|
const Events = {
|
||||||
|
|
||||||
_handlers: new Map(), // label → Set<{fn, once}>
|
_handlers: new Map(), // label → Set<{fn, once}>
|
||||||
@@ -191,9 +196,9 @@ const Events = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add auth token as query param (WebSocket doesn't support headers)
|
// Add auth token as query param (WebSocket doesn't support headers)
|
||||||
const tokens = JSON.parse(localStorage.getItem(typeof _storageKey !== 'undefined' ? _storageKey : 'sb_auth') || '{}');
|
const token = (typeof API !== 'undefined' && API.accessToken) ? API.accessToken : null;
|
||||||
if (tokens.accessToken) {
|
if (token) {
|
||||||
url += (url.includes('?') ? '&' : '?') + `token=${encodeURIComponent(tokens.accessToken)}`;
|
url += (url.includes('?') ? '&' : '?') + `token=${encodeURIComponent(token)}`;
|
||||||
} else {
|
} else {
|
||||||
console.warn('[EventBus] No auth token — skipping WebSocket');
|
console.warn('[EventBus] No auth token — skipping WebSocket');
|
||||||
return;
|
return;
|
||||||
@@ -325,3 +330,6 @@ const Events = {
|
|||||||
return subs;
|
return subs;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.Events = Events;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
//
|
//
|
||||||
// Load order: events.js → extensions.js → [ext scripts] → api.js → app.js
|
// Load order: events.js → extensions.js → [ext scripts] → api.js → app.js
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.Extensions
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const Extensions = {
|
const Extensions = {
|
||||||
|
|
||||||
@@ -481,3 +486,7 @@ const Extensions = {
|
|||||||
return { extensions: exts, rendererCount: this._renderers.length };
|
return { extensions: exts, rendererCount: this._renderers.length };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.Extensions = Extensions;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
// await tree.refresh();
|
// await tree.refresh();
|
||||||
// tree.setActiveFile('src/main.go');
|
// tree.setActiveFile('src/main.go');
|
||||||
// tree.destroy();
|
// tree.destroy();
|
||||||
|
//
|
||||||
|
// Exports: window.FileTree
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const FileTree = {
|
const FileTree = {
|
||||||
...createComponentRegistry('FileTree'),
|
...createComponentRegistry('FileTree'),
|
||||||
@@ -261,3 +266,7 @@ function _ftFileIcon(name) {
|
|||||||
};
|
};
|
||||||
return icons[ext] || '📄';
|
return icons[ext] || '📄';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.FileTree = FileTree;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
// Consumed by: chat.js (send flow), ui-core.js (message rendering),
|
// Consumed by: chat.js (send flow), ui-core.js (message rendering),
|
||||||
// ui-admin.js (storage tab)
|
// ui-admin.js (storage tab)
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Constants ───────────────────────────────
|
// ── Constants ───────────────────────────────
|
||||||
|
|
||||||
const FILE_POLL_INTERVAL = 2000; // ms between extraction status checks
|
const FILE_POLL_INTERVAL = 2000; // ms between extraction status checks
|
||||||
@@ -834,3 +837,24 @@ function _hasDragFiles(e) {
|
|||||||
if (!e.dataTransfer?.types) return false;
|
if (!e.dataTransfer?.types) return false;
|
||||||
return e.dataTransfer.types.includes('Files');
|
return e.dataTransfer.types.includes('Files');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window._initFileListeners = _initFileListeners;
|
||||||
|
window._pollTimers = _pollTimers;
|
||||||
|
window._startPolling = _startPolling;
|
||||||
|
window._stopPolling = _stopPolling;
|
||||||
|
window.clearStaged = clearStaged;
|
||||||
|
window.closeLightbox = closeLightbox;
|
||||||
|
window.consumeStaged = consumeStaged;
|
||||||
|
window.getStagedFileIds = getStagedFileIds;
|
||||||
|
window.hasStagedFiles = hasStagedFiles;
|
||||||
|
window.initFiles = initFiles;
|
||||||
|
window.isSendBlocked = isSendBlocked;
|
||||||
|
window.loadAdminStorage = loadAdminStorage;
|
||||||
|
window.loadAuthImages = loadAuthImages;
|
||||||
|
window.loadChannelFiles = loadChannelFiles;
|
||||||
|
window.renderMessageFiles = renderMessageFiles;
|
||||||
|
window.unstageFile = unstageFile;
|
||||||
|
window.openLightbox = openLightbox;
|
||||||
|
window.downloadFile = downloadFile;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
// User-facing memory management in Settings,
|
// User-facing memory management in Settings,
|
||||||
// admin memory review in Admin Panel,
|
// admin memory review in Admin Panel,
|
||||||
// and persona memory config in persona forms.
|
// and persona memory config in persona forms.
|
||||||
|
//
|
||||||
|
// Exports: window.MemoryUI
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const MemoryUI = {
|
const MemoryUI = {
|
||||||
|
|
||||||
@@ -362,3 +367,7 @@ function _debounce(fn, ms) {
|
|||||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.MemoryUI = MemoryUI;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
// sel.select(modelId);
|
// sel.select(modelId);
|
||||||
// sel.getSelected(); // → current model ID
|
// sel.getSelected(); // → current model ID
|
||||||
// sel.destroy();
|
// sel.destroy();
|
||||||
|
//
|
||||||
|
// Exports: window.ModelSelector
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const ModelSelector = {
|
const ModelSelector = {
|
||||||
...createComponentRegistry('ModelSelector'),
|
...createComponentRegistry('ModelSelector'),
|
||||||
@@ -167,3 +172,7 @@ const ModelSelector = {
|
|||||||
return instance;
|
return instance;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.ModelSelector = ModelSelector;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
// The existing notes.js + PanelRegistry integration continues to
|
// The existing notes.js + PanelRegistry integration continues to
|
||||||
// work on the chat surface. This component is for NEW mount points
|
// work on the chat surface. This component is for NEW mount points
|
||||||
// (editor pane, future notes-studio layout).
|
// (editor pane, future notes-studio layout).
|
||||||
|
//
|
||||||
|
// Exports: window.NoteEditor
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const NoteEditor = {
|
const NoteEditor = {
|
||||||
...createComponentRegistry('NoteEditor'),
|
...createComponentRegistry('NoteEditor'),
|
||||||
@@ -433,3 +438,7 @@ const NoteEditor = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.NoteEditor = NoteEditor;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
// connections. Lazy-loaded — initializes only
|
// connections. Lazy-loaded — initializes only
|
||||||
// when the graph view is opened.
|
// when the graph view is opened.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.openNoteGraph,window.closeNoteGraph,window._render,window._graphResetZoom,window._graphToggleOrphans,window.invalidateNoteGraph
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var _graphData = null; // cached { nodes, edges, unresolved }
|
var _graphData = null; // cached { nodes, edges, unresolved }
|
||||||
var _graphState = null; // { panX, panY, zoom, animId, canvas, ctx }
|
var _graphState = null; // { panX, panY, zoom, animId, canvas, ctx }
|
||||||
@@ -488,3 +493,12 @@ function invalidateNoteGraph() {
|
|||||||
_graphDirty = true;
|
_graphDirty = true;
|
||||||
_folderColorMap = {};
|
_folderColorMap = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.openNoteGraph = openNoteGraph;
|
||||||
|
window.closeNoteGraph = closeNoteGraph;
|
||||||
|
window._render = _render;
|
||||||
|
window._graphResetZoom = _graphResetZoom;
|
||||||
|
window._graphToggleOrphans = _graphToggleOrphans;
|
||||||
|
window.invalidateNoteGraph = invalidateNoteGraph;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Notes panel: editor, multi-select, folders, CRUD.
|
// Notes panel: editor, multi-select, folders, CRUD.
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var _editingNoteId = null;
|
var _editingNoteId = null;
|
||||||
var _notesSelectMode = false;
|
var _notesSelectMode = false;
|
||||||
var _selectedNoteIds = new Set();
|
var _selectedNoteIds = new Set();
|
||||||
@@ -940,3 +943,18 @@ function _initNotesListeners() {
|
|||||||
// No-op: kept for backward compatibility with app.js calling sequence.
|
// No-op: kept for backward compatibility with app.js calling sequence.
|
||||||
// All wiring moved into _registerNotesPanel().
|
// All wiring moved into _registerNotesPanel().
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window._currentNote = _currentNote;
|
||||||
|
window._editingNoteId = _editingNoteId;
|
||||||
|
window._initNotesListeners = _initNotesListeners;
|
||||||
|
window._registerNotesPanel = _registerNotesPanel;
|
||||||
|
window.deleteNote = deleteNote;
|
||||||
|
window.openNoteEditor = openNoteEditor;
|
||||||
|
window.openNotes = openNotes;
|
||||||
|
window.saveMessageToNote = saveMessageToNote;
|
||||||
|
window.saveNote = saveNote;
|
||||||
|
window._toggleNoteSelect = _toggleNoteSelect;
|
||||||
|
window._navigateToLinkedNote = _navigateToLinkedNote;
|
||||||
|
window.toggleBacklinks = toggleBacklinks;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
───────────────────────────────────────────── */
|
───────────────────────────────────────────── */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
//
|
||||||
|
// Exports: window.NotifPrefs
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
const NotifPrefs = {
|
const NotifPrefs = {
|
||||||
_loaded: false,
|
_loaded: false,
|
||||||
_prefs: [],
|
_prefs: [],
|
||||||
@@ -161,3 +166,7 @@ NotifPrefs._getAdminSmtp = function() {
|
|||||||
if (pw) config.smtp_password = pw;
|
if (pw) config.smtp_password = pw;
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.NotifPrefs = NotifPrefs;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
//
|
//
|
||||||
// Dependencies: API, Events, PanelRegistry, UI
|
// Dependencies: API, Events, PanelRegistry, UI
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.Notifications
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const Notifications = {
|
const Notifications = {
|
||||||
|
|
||||||
@@ -423,3 +428,7 @@ function _timeAgo(isoStr) {
|
|||||||
if (sec < 604800) return `${Math.floor(sec / 86400)}d ago`;
|
if (sec < 604800) return `${Math.floor(sec / 86400)}d ago`;
|
||||||
return d.toLocaleDateString();
|
return d.toLocaleDateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.Notifications = Notifications;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -9,8 +9,13 @@
|
|||||||
// - Cascading model select (provider change → filter models)
|
// - Cascading model select (provider change → filter models)
|
||||||
// - Admin save operations (roles, routing, providers, teams, users, settings)
|
// - Admin save operations (roles, routing, providers, teams, users, settings)
|
||||||
// - Table filtering
|
// - Table filtering
|
||||||
|
//
|
||||||
|
// Exports: window.Pages, window._val (used by pages-splash.js)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const Pages = {
|
const Pages = {
|
||||||
|
|
||||||
// ── Model Select Cascading ───────────────
|
// ── Model Select Cascading ───────────────
|
||||||
@@ -340,3 +345,8 @@ async function _api(method, path, body) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.Pages = Pages;
|
||||||
|
window._val = _val; // used by pages-splash.js
|
||||||
|
})();
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
// const fileTree = layout.getPane('files');
|
// const fileTree = layout.getPane('files');
|
||||||
// layout.resize('files', 250);
|
// layout.resize('files', 250);
|
||||||
// layout.destroy();
|
// layout.destroy();
|
||||||
|
//
|
||||||
|
// Exports: window.PaneContainer
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const PaneContainer = {
|
const PaneContainer = {
|
||||||
_presets: {},
|
_presets: {},
|
||||||
@@ -581,3 +586,7 @@ PaneContainer.registerPreset('split', {
|
|||||||
{ type: 'leaf', id: 'secondary', component: null },
|
{ type: 'leaf', id: 'secondary', component: null },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.PaneContainer = PaneContainer;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
// .workspace-primary ← main content (chat / notes / editor)
|
// .workspace-primary ← main content (chat / notes / editor)
|
||||||
// .workspace-handle ← drag-resize between panes
|
// .workspace-handle ← drag-resize between panes
|
||||||
// .workspace-secondary ← panel pages (preview / notes / project)
|
// .workspace-secondary ← panel pages (preview / notes / project)
|
||||||
|
//
|
||||||
|
// Exports: window.PanelRegistry,window.toggleSidePanelFullscreen,window._initWorkspaceResize,window._initPanelSwipe,window._initPanelResponsive,window._initPanelOverlay
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Panel Registry ──────────────────────────
|
// ── Panel Registry ──────────────────────────
|
||||||
|
|
||||||
@@ -441,3 +446,12 @@ function closeSidePanel() {
|
|||||||
function switchSidePanelTab(tab) {
|
function switchSidePanelTab(tab) {
|
||||||
PanelRegistry.open(tab);
|
PanelRegistry.open(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.PanelRegistry = PanelRegistry;
|
||||||
|
window.toggleSidePanelFullscreen = toggleSidePanelFullscreen;
|
||||||
|
window._initWorkspaceResize = _initWorkspaceResize;
|
||||||
|
window._initPanelSwipe = _initPanelSwipe;
|
||||||
|
window._initPanelResponsive = _initPanelResponsive;
|
||||||
|
window._initPanelOverlay = _initPanelOverlay;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
// Provides renderPersonaKBPicker(container, opts) which renders a multi-select
|
// Provides renderPersonaKBPicker(container, opts) which renders a multi-select
|
||||||
// KB picker with auto_search toggles for use in persona create/edit forms.
|
// KB picker with auto_search toggles for use in persona create/edit forms.
|
||||||
// Pattern: (container, options) → control object with getValues/setValues/clear.
|
// Pattern: (container, options) → control object with getValues/setValues/clear.
|
||||||
|
//
|
||||||
|
// Exports: window.renderPersonaKBPicker,window.loadPersonaKBs,window.savePersonaKBs
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@@ -175,3 +180,9 @@ async function savePersonaKBs(personaId, kbPicker, apiPrefix) {
|
|||||||
console.warn('Failed to save persona KBs:', e.message);
|
console.warn('Failed to save persona KBs:', e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.renderPersonaKBPicker = renderPersonaKBPicker;
|
||||||
|
window.loadPersonaKBs = loadPersonaKBs;
|
||||||
|
window.savePersonaKBs = savePersonaKBs;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
// and drag-and-drop.
|
// and drag-and-drop.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
async function loadProjects() {
|
async function loadProjects() {
|
||||||
try {
|
try {
|
||||||
const resp = await API.listProjects();
|
const resp = await API.listProjects();
|
||||||
@@ -2003,3 +2006,54 @@ async function _removeParticipant(channelId, recordId) {
|
|||||||
UI.toast('Failed to remove: ' + e.message, 'error');
|
UI.toast('Failed to remove: ' + e.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window._registerProjectPanel = _registerProjectPanel;
|
||||||
|
window.createProject = createProject;
|
||||||
|
window.cycleChannelAiMode = cycleChannelAiMode;
|
||||||
|
window.deleteChannel = deleteChannel;
|
||||||
|
window.deleteFolder = deleteFolder;
|
||||||
|
window.deleteProject = deleteProject;
|
||||||
|
window.loadChannels = loadChannels;
|
||||||
|
window.loadFolders = loadFolders;
|
||||||
|
window.loadProjectChannelPositions = loadProjectChannelPositions;
|
||||||
|
window.loadProjects = loadProjects;
|
||||||
|
window.loadUsers = loadUsers;
|
||||||
|
window.newChannelOrDM = newChannelOrDM;
|
||||||
|
window.newFolder = newFolder;
|
||||||
|
window.onChatDragEnd = onChatDragEnd;
|
||||||
|
window.onChatDragStart = onChatDragStart;
|
||||||
|
window.onChatSectionDrop = onChatSectionDrop;
|
||||||
|
window.onFolderDrop = onFolderDrop;
|
||||||
|
window.onProjectDragLeave = onProjectDragLeave;
|
||||||
|
window.onProjectDragOver = onProjectDragOver;
|
||||||
|
window.onProjectDrop = onProjectDrop;
|
||||||
|
window.onUnfiledDrop = onUnfiledDrop;
|
||||||
|
window.openParticipantsPanel = openParticipantsPanel;
|
||||||
|
window.openProjectPanel = openProjectPanel;
|
||||||
|
window.selectChannel = selectChannel;
|
||||||
|
window.showChannelContextMenu = showChannelContextMenu;
|
||||||
|
window.showChatContextMenu = showChatContextMenu;
|
||||||
|
window.showFolderMenu = showFolderMenu;
|
||||||
|
window.showProjectMenu = showProjectMenu;
|
||||||
|
window.toggleProjectCollapse = toggleProjectCollapse;
|
||||||
|
window.renameProject = renameProject;
|
||||||
|
window.setProjectColor = setProjectColor;
|
||||||
|
window.toggleActiveProject = toggleActiveProject;
|
||||||
|
window.moveChatToProject = moveChatToProject;
|
||||||
|
window.dismissChatContextMenu = dismissChatContextMenu;
|
||||||
|
window.createProjectAndMove = createProjectAndMove;
|
||||||
|
window.moveChatToFolder = moveChatToFolder;
|
||||||
|
window._removeProjectKB = _removeProjectKB;
|
||||||
|
window._addProjectKB = _addProjectKB;
|
||||||
|
window._removeProjectNote = _removeProjectNote;
|
||||||
|
window.showWorkspacePicker = showWorkspacePicker;
|
||||||
|
window._moveChatInProject = _moveChatInProject;
|
||||||
|
window.renameChannel = renameChannel;
|
||||||
|
window.editChannelTopic = editChannelTopic;
|
||||||
|
window.archiveChannel = archiveChannel;
|
||||||
|
window.renameFolder = renameFolder;
|
||||||
|
window._showAddParticipantSearch = _showAddParticipantSearch;
|
||||||
|
window._addParticipant = _addParticipant;
|
||||||
|
window._removeParticipant = _removeParticipant;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
// - Multi-line: Shift+Enter for newline, Enter to run
|
// - Multi-line: Shift+Enter for newline, Enter to run
|
||||||
// - Admin-gated OR ?debug=1 URL param
|
// - Admin-gated OR ?debug=1 URL param
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
//
|
||||||
|
// Exports: window.REPL
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const REPL = {
|
const REPL = {
|
||||||
|
|
||||||
@@ -549,3 +554,7 @@ const REPL = {
|
|||||||
|
|
||||||
// Initialized from app.js startApp() after auth is confirmed,
|
// Initialized from app.js startApp() after auth is confirmed,
|
||||||
// ensuring API.user.role is available for admin gate check.
|
// ensuring API.user.role is available for admin gate check.
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.REPL = REPL;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Settings save, provider CRUD, avatar, command palette,
|
// Settings save, provider CRUD, avatar, command palette,
|
||||||
// user model roles.
|
// user model roles.
|
||||||
|
//
|
||||||
|
// Exports: window.loadSettings,window.saveSettings,window.updateAvatarPreview,window._userProvForm,window._userProvList,window.handleSaveAdminSettings,window._userRoleConfig,window.toggleCmdPalette,window.closeCmdPalette,window._initSettingsListeners,window._initAdminSettingsToggles
|
||||||
|
// (also: window.toggleUserModelVisibility, window.bulkSetUserModelVisibility,
|
||||||
|
// window.deleteUserPersona, window.loadTeamWorkflows — self-assigned)
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Settings ─────────────────────────────────
|
// ── Settings ─────────────────────────────────
|
||||||
|
|
||||||
@@ -892,3 +899,17 @@ window.loadTeamWorkflows = async function() {
|
|||||||
el.innerHTML = '<div class="settings-placeholder">Failed to load workflows: ' + esc(e.message) + '</div>';
|
el.innerHTML = '<div class="settings-placeholder">Failed to load workflows: ' + esc(e.message) + '</div>';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.loadSettings = loadSettings;
|
||||||
|
window.saveSettings = saveSettings;
|
||||||
|
window.updateAvatarPreview = updateAvatarPreview;
|
||||||
|
window._userProvForm = _userProvForm;
|
||||||
|
window._userProvList = _userProvList;
|
||||||
|
window.handleSaveAdminSettings = handleSaveAdminSettings;
|
||||||
|
window._userRoleConfig = _userRoleConfig;
|
||||||
|
window.toggleCmdPalette = toggleCmdPalette;
|
||||||
|
window.closeCmdPalette = closeCmdPalette;
|
||||||
|
window._initSettingsListeners = _initSettingsListeners;
|
||||||
|
window._initAdminSettingsToggles = _initAdminSettingsToggles;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
// Chat Switchboard – Token Estimation
|
// Chat Switchboard – Token Estimation
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Context tracking, token estimation, and context warning.
|
// Context tracking, token estimation, and context warning.
|
||||||
|
//
|
||||||
|
// Exports: window.Tokens,window.updateInputTokens,window.updateContextWarning
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Token Estimation + Context Tracking ─────
|
// ── Token Estimation + Context Tracking ─────
|
||||||
|
|
||||||
@@ -157,3 +162,9 @@ function dismissContextWarning() {
|
|||||||
const el = document.getElementById('contextWarning');
|
const el = document.getElementById('contextWarning');
|
||||||
if (el) el.style.display = 'none';
|
if (el) el.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.Tokens = Tokens;
|
||||||
|
window.updateInputTokens = updateInputTokens;
|
||||||
|
window.updateContextWarning = updateContextWarning;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
// Fullscreen admin panel with category + section navigation.
|
// Fullscreen admin panel with category + section navigation.
|
||||||
// All loadAdmin*() functions unchanged — same endpoints, same DOM IDs.
|
// All loadAdmin*() functions unchanged — same endpoints, same DOM IDs.
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Category → Section mapping ────────────
|
// ── Category → Section mapping ────────────
|
||||||
const ADMIN_SECTIONS = {
|
const ADMIN_SECTIONS = {
|
||||||
people: ['users', 'teams', 'groups'],
|
people: ['users', 'teams', 'groups'],
|
||||||
@@ -1565,3 +1568,9 @@ Object.assign(UI, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.ADMIN_SECTIONS = ADMIN_SECTIONS;
|
||||||
|
window.ADMIN_LABELS = ADMIN_LABELS;
|
||||||
|
window.ADMIN_LOADERS = ADMIN_LOADERS;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
// model selector, capability badges, user, generating state, toast,
|
// model selector, capability badges, user, generating state, toast,
|
||||||
// settings modal dispatcher, export, message actions, scroll.
|
// settings modal dispatcher, export, message actions, scroll.
|
||||||
// Extended by ui-settings.js and ui-admin.js via Object.assign.
|
// Extended by ui-settings.js and ui-admin.js via Object.assign.
|
||||||
|
//
|
||||||
|
// Exports: window.UI, window.renderPersonaForm,
|
||||||
|
// window.toggleSummarizedHistory (onclick handler)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Avatar Helper ────────────────────────────
|
// ── Avatar Helper ────────────────────────────
|
||||||
// Returns HTML for a message avatar: <img> if data URI available, emoji fallback.
|
// Returns HTML for a message avatar: <img> if data URI available, emoji fallback.
|
||||||
@@ -1596,3 +1603,10 @@ const UI = {
|
|||||||
} catch (_) { /* corrupt localStorage — ignore */ }
|
} catch (_) { /* corrupt localStorage — ignore */ }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.UI = UI;
|
||||||
|
window.renderPersonaForm = renderPersonaForm;
|
||||||
|
// onclick handler (called from innerHTML)
|
||||||
|
window.toggleSummarizedHistory = toggleSummarizedHistory;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Markdown rendering, code blocks, preview panel, time formatting.
|
// Markdown rendering, code blocks, preview panel, time formatting.
|
||||||
// esc() is in ui-primitives.js (loaded globally in base.html).
|
// esc() is in ui-primitives.js (loaded globally in base.html).
|
||||||
|
//
|
||||||
|
// Exports: formatMessage, clearPreview, runExtensionPostRender,
|
||||||
|
// _livePreviewUpdate, _relativeTime, _renderToolCallsHTML,
|
||||||
|
// _registerPreviewPanel,
|
||||||
|
// toggleCodeCollapse, toggleHTMLPreview, downloadCode,
|
||||||
|
// popOutExtBlock (onclick handlers)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
// ── Message Formatting ──────────────────────
|
// ── Message Formatting ──────────────────────
|
||||||
|
|
||||||
@@ -573,3 +583,19 @@ function runExtensionPostRender(container) {
|
|||||||
Extensions.runPostRenderers(container);
|
Extensions.runPostRenderers(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
// Cross-file function calls
|
||||||
|
window.formatMessage = formatMessage;
|
||||||
|
window.clearPreview = clearPreview;
|
||||||
|
window.runExtensionPostRender = runExtensionPostRender;
|
||||||
|
window._livePreviewUpdate = _livePreviewUpdate;
|
||||||
|
window._relativeTime = _relativeTime;
|
||||||
|
window._renderToolCallsHTML = _renderToolCallsHTML;
|
||||||
|
window._registerPreviewPanel = _registerPreviewPanel;
|
||||||
|
// onclick handlers (called from innerHTML)
|
||||||
|
window.toggleCodeCollapse = toggleCodeCollapse;
|
||||||
|
window.toggleHTMLPreview = toggleHTMLPreview;
|
||||||
|
window.downloadCode = downloadCode;
|
||||||
|
window.popOutExtBlock = popOutExtBlock;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
// Follows the renderPersonaForm() pattern: (container, options) → control object.
|
// Follows the renderPersonaForm() pattern: (container, options) → control object.
|
||||||
// Designed for extension hooks: registries are open for .add(), primitives
|
// Designed for extension hooks: registries are open for .add(), primitives
|
||||||
// accept options for customization, and return handles for programmatic control.
|
// accept options for customization, and return handles for programmatic control.
|
||||||
|
//
|
||||||
|
// Exports: esc, createComponentRegistry, componentMixin, Providers, Roles,
|
||||||
|
// renderCapBadges, renderProviderForm, renderProviderList,
|
||||||
|
// renderRoleConfig, renderUsageDashboard, showConfirm, showPrompt,
|
||||||
|
// openModal, closeModal, checkTabsOverflow, Theme, mountAvatarUpload
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -1108,3 +1114,23 @@ function mountAvatarUpload(containerEl, opts = {}) {
|
|||||||
|
|
||||||
return { setImage: render };
|
return { setImage: render };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.esc = esc;
|
||||||
|
window.createComponentRegistry = createComponentRegistry;
|
||||||
|
window.componentMixin = componentMixin;
|
||||||
|
window.Providers = Providers;
|
||||||
|
window.Roles = Roles;
|
||||||
|
window.renderCapBadges = renderCapBadges;
|
||||||
|
window.renderProviderForm = renderProviderForm;
|
||||||
|
window.renderProviderList = renderProviderList;
|
||||||
|
window.renderRoleConfig = renderRoleConfig;
|
||||||
|
window.renderUsageDashboard = renderUsageDashboard;
|
||||||
|
window.showConfirm = showConfirm;
|
||||||
|
window.showPrompt = showPrompt;
|
||||||
|
window.openModal = openModal;
|
||||||
|
window.closeModal = closeModal;
|
||||||
|
window.checkTabsOverflow = checkTabsOverflow;
|
||||||
|
window.Theme = Theme;
|
||||||
|
window.mountAvatarUpload = mountAvatarUpload;
|
||||||
|
})();
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Extends UI with settings modal tabs, team management,
|
// Extends UI with settings modal tabs, team management,
|
||||||
// providers, usage, model roles, and user preferences.
|
// providers, usage, model roles, and user preferences.
|
||||||
|
//
|
||||||
|
// Exports: none (extends window.UI via Object.assign)
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
Object.assign(UI, {
|
Object.assign(UI, {
|
||||||
// ── General Settings (Settings Surface) ──
|
// ── General Settings (Settings Surface) ──
|
||||||
@@ -861,3 +866,5 @@ Object.assign(UI, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
// menu.setUser({ display_name: 'Jeff', username: 'jeff', avatar: null });
|
// menu.setUser({ display_name: 'Jeff', username: 'jeff', avatar: null });
|
||||||
// menu.showAdmin(true);
|
// menu.showAdmin(true);
|
||||||
// menu.destroy();
|
// menu.destroy();
|
||||||
|
//
|
||||||
|
// Exports: window.UserMenu
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const UserMenu = {
|
const UserMenu = {
|
||||||
...createComponentRegistry('UserMenu'),
|
...createComponentRegistry('UserMenu'),
|
||||||
@@ -131,3 +136,7 @@ const UserMenu = {
|
|||||||
return instance;
|
return instance;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Exports ─────────────────────────────────
|
||||||
|
window.UserMenu = UserMenu;
|
||||||
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user