Changeset 0.22.7 (#149)

This commit is contained in:
2026-03-04 10:44:42 +00:00
parent d8e0664fa3
commit 389e47b0f9
62 changed files with 6820 additions and 1476 deletions

746
docs/DESIGN-SURFACES.md Normal file
View File

@@ -0,0 +1,746 @@
# DESIGN — Surface & Extension Architecture
**Status:** Accepted
**Scope:** Primitives, Components, Surfaces, Extension hooks, Themes
**Depends on:** v0.22.5 (Go template engine), v0.22.7 (ChatPane)
**Informs:** v0.22.8+, EXTENSIONS.md rewrite, ARCHITECTURE.md update, ICD-API, ICD-SURFACE
---
## Overview
The UI is a four-layer architecture: Primitives, Components, Surfaces,
and Banners. Each layer has a single responsibility and a strict
dependency direction — primitives know nothing, components compose
primitives, surfaces compose components, banners are platform chrome
outside the surface boundary.
Extensions participate at every layer through six defined hooks.
Admin-installed extensions are privileged (full DOM, direct API access,
own routes). Themes are pure CSS custom property overrides that
propagate through every layer uniformly.
This document defines the layer contracts, extension hook system,
display content model, trust boundaries, and theme architecture.
---
## Definitions
A **surface** is a full-page layout — a composition of components with
a route, a data loader, and a boot script. Chat is a surface. The
editor is a surface. A custom triage intake form is a surface.
An **extension** is a package — a manifest plus code plus assets that
participates in the platform through hooks. An extension can do one,
some, or all of:
- Register tools the LLM can call (Hook 5: Tool Bridge)
- Transform content during streaming (Hook 2: Stream Processing)
- Enhance rendered messages (Hook 3: Post-Render)
- Attach visual content to messages (Hook 4: Display Content)
- Inject panels or sections into existing surfaces (Hook 6: Surface Injection)
- Create entirely new surfaces with their own routes (Surface Registration)
The five **core surfaces** (Chat, Editor, Notes, Admin, Settings) exist
without extensions. They are built from the same primitive and component
layers that extensions use, but they are registered directly in Go
rather than through a manifest.
An extension that creates a surface gets its own route (`/s/:slug`),
its own data loader, and the full primitive/component library. An
extension that only registers a post-render hook (like a block
renderer) doesn't create any surfaces at all.
**Examples:**
| Extension | Creates Surface? | Hooks Used |
|-----------|-----------------|------------|
| Mermaid renderer | No | Post-Render |
| KaTeX renderer | No | Post-Render |
| Calculator tool | No | Tool Bridge |
| Image Generator | Yes (`/s/gallery`) | Tool Bridge, Stream Processing, Post-Render, Display Content, Surface |
| Custom Dashboard | Yes (`/s/dashboard`) | Surface, Surface Injection (admin section) |
---
## Existing Extension Mapping
The current extension mechanisms map directly into this architecture:
| Current Mechanism | Architecture Equivalent |
|-------------------|------------------------|
| Block renderers (`ctx.renderers.register()`) | Hook 3: Post-Render |
| Tool bridge (`ctx.tools.register()`) | Hook 5: Tool Bridge |
| `ctx.ui.toast()`, `ctx.ui.openPreview()` | Primitive layer (unchanged API) |
| `ctx.ui.isDark()`, `ctx.ui.isMobile()` | Theme layer queries |
| `ctx.surfaces.getCurrent()` | Returns `window.__SURFACE__` |
The manifest schema gains optional new fields (`hooks`, `surfaces`,
`surface_injections`, `theme`). Existing fields retain their meaning.
`Extensions.boot()` is the single entry point for extension
initialization on every surface. It loads manifests, registers
renderers, and initializes tool bridges — same sequence, available
on every surface rather than only chat.
---
## Layer Model
```
┌──────────────────────────────────────────────┐
│ Banner (top) │ ← Platform config
├──────────────────────────────────────────────┤
│ │
│ Surface │ ← Layout + lifecycle
│ ┌─────────────┬──────────────────────────┐ │
│ │ Component │ Component │ │ ← Domain-aware
│ │ (ChatPane) │ (NoteEditor) │ │
│ │ ┌─────────┐ │ ┌────────┐ ┌──────────┐ │ │
│ │ │Primitive│ │ │Primitv.│ │Primitive │ │ │ ← Atomic UI
│ │ │ (input) │ │ │ (menu) │ │ (toggle) │ │ │
│ │ └─────────┘ │ └────────┘ └──────────┘ │ │
│ └─────────────┴──────────────────────────┘ │
│ │
├──────────────────────────────────────────────┤
│ Banner (bottom) │ ← Platform config
└──────────────────────────────────────────────┘
```
Four layers, strict dependency direction: Primitives know nothing.
Components use Primitives. Surfaces compose Components. Banners are
global chrome outside the surface boundary.
---
## Layer 1: Primitives
Atomic UI elements. No domain logic. A toggle doesn't know if it's
toggling a KB or a theme — it takes a label, a state, and a callback.
Styled entirely via CSS custom properties (the theme contract). Every
primitive reads from the same property namespace, so theme changes
propagate instantly.
### Catalog
| Primitive | Description |
|-----------|-------------|
| `Input` | Text, textarea, number, password, with label + validation |
| `Select` | Dropdown, single or multi |
| `Toggle` | Boolean switch with label |
| `Checkbox` | Checkbox with label |
| `Button` | Text button, variants: primary, secondary, danger, ghost |
| `IconButton` | Icon-only button with tooltip |
| `ColorPicker` | Color input with hex text field |
| `Menu` | Dropdown or context menu, item list with icons + shortcuts |
| `Dialog` | Modal: confirm, prompt, or custom form content |
| `Toast` | Transient notification: success, error, warning, info |
| `Badge` | Inline label: accent, success, danger, warning, muted |
| `Avatar` | Image circle with upload affordance |
| `Tabs` | Tab bar with content switching |
| `FormGroup` | Label + input + validation message + help text |
| `SectionHeader` | Titled section divider |
| `EmptyState` | Placeholder with icon + message + action |
| `Spinner` | Loading indicator |
| `Table` | Sortable, paginated data table |
### Implementation
Each primitive is a factory function that returns a DOM element (or
attaches to an existing one). No classes, no inheritance — just
functions.
```js
// Example — not prescriptive API, just the pattern:
Primitives.toggle({ label: 'Auto-search', value: true, onChange: fn })
Primitives.menu({ anchor: el, items: [...], onSelect: fn })
Primitives.dialog({ title: 'Confirm', body: el, onConfirm: fn })
```
Current locations: `ui-primitives.js`, `ui-primitives-additions.js`,
and Go template components (`model-select.html`, `team-select.html`,
`file-upload.html`). These converge into one coherent set.
### Go Template Primitives
Some primitives need server-rendered initial state (e.g., model-select
needs the model list, team-select needs the team list). These are Go
template partials that render the HTML + initial data, then JS hydrates
interactivity on load.
```html
{{template "model-select" dict "ID" "channelModel" "Models" .Models "Type" "chat"}}
```
The JS primitive attaches to the server-rendered DOM by ID, not by
replacing it.
---
## Layer 2: Components
Domain-aware compositions of primitives. Each component owns its DOM
subtree, manages its own state, and exposes a clean API for the surface
to interact with.
### Catalog
| Component | Primitives Used | Domain |
|-----------|----------------|--------|
| `ChatPane` | Input, Button, Menu, Spinner, Toast | Channels, Messages, Streaming |
| `NoteEditor` | Input (CM6), Menu, Tabs, Badge | Notes, Wikilinks, Folders |
| `FileTree` | Menu (context), Button, Spinner | Workspaces, Files |
| `ModelSelector` | Select, Badge, Spinner | Models, Capabilities, Health |
| `PersonaPicker` | Select, Badge, Avatar | Personas, Scopes |
| `KBPicker` | Toggle, Badge, Select | Knowledge Bases, Discoverability |
| `ProjectSidebar` | Menu, Tabs, Badge, EmptyState | Projects, Channels, DnD |
| `AuditLog` | Table, Select, Badge | Audit, Filtering, Pagination |
| `CodeEditor` | Input (CM6), Select, Tabs | Workspaces, Languages |
| `SettingsForm` | FormGroup, Toggle, Select, Button | User/Admin Settings |
### Instance Pattern
Components use the factory pattern established by ChatPane:
```js
const pane = ChatPane.create({
messagesEl: document.getElementById('editorChatMessages'),
inputEl: document.getElementById('editorChatInput'),
sendBtnEl: document.getElementById('editorSendBtn'),
channelId: workspaceChatId,
standalone: true,
});
// Lifecycle
pane.renderMessages(msgs);
pane.streamResponse(resp, msgs);
pane.destroy();
```
Components can be instantiated multiple times on the same page (editor
has a ChatPane, notes has a ChatPane — independent instances).
### Server-Rendered Shell + JS Hydration
Components have a Go template partial for the server-rendered scaffold:
```html
{{template "chat-pane" dict "ID" "editor"}}
```
This renders the DOM structure with predictable IDs. The JS component
attaches to these IDs on DOMContentLoaded. No client-side DOM
construction for the initial layout.
---
## Layer 3: Surfaces
A surface is a full-page layout that composes components. Each surface
declares:
1. **What components it uses** (ChatPane, NoteEditor, FileTree, etc.)
2. **How they're arranged** (CSS grid/flex layout)
3. **What data it needs on load** (Go data loader)
4. **What JS runs on boot** (script block or module)
### Current Surfaces
| Surface | Components | Data Loader |
|---------|-----------|-------------|
| Chat | ChatPane, ProjectSidebar, ModelSelector, PersonaPicker | channels, personas, models, projects |
| Editor | FileTree, CodeEditor, ChatPane (assist) | workspace, files, models |
| Notes | NoteEditor, NoteGraph, ChatPane (assist) | notes, folders, graph |
| Admin | AuditLog, SettingsForm, Table (various) | users, configs, models, health |
| Settings | SettingsForm, KBPicker, PersonaPicker | profile, preferences, policies |
### Surface Registration
Today: hardcoded in Go (`pageEngine.RenderSurface("chat")`).
Future: manifest-driven. An admin-installed extension declares a
surface in its manifest:
```json
{
"surfaces": [{
"id": "triage-intake",
"route": "/s/triage",
"title": "Triage Intake",
"components": ["chat-pane", "settings-form"],
"data_requires": ["personas", "models"],
"script": "surfaces/triage.js",
"auth": "authenticated"
}]
}
```
The page engine reads registered surfaces, generates routes, assembles
data loaders from the declared requirements, and renders a shell
template that loads the surface's script.
**Route namespace:** Admin-created surfaces live under `/s/:slug` to
avoid collision with core routes.
### Data Loaders
Each surface declares what data it needs. The page engine has a
registry of data providers:
```
"personas" → loads personas for the current user
"models" → loads enabled models with health status
"channels" → loads user's channels
"workspace" → loads workspace by :wsId param
"notes" → loads notes list
...
```
The surface's `data_requires` field pulls from this registry. Data is
injected into the page as `window.__PAGE_DATA__` (existing pattern).
The surface's JS reads from there on boot — no waterfall of API calls
on page load.
---
## Layer 4: Banners
The banner is global chrome outside the surface boundary. It exists
at the top, bottom, or both — configured by the platform admin via
`PUT /admin/settings/banner`.
```json
{
"enabled": true,
"text": "DEVELOPMENT",
"position": "both|top|bottom",
"bg": "#007a33",
"fg": "#ffffff"
}
```
The Go template base layout renders banners before and after the
surface content area. CSS custom properties (`--banner-top-height`,
`--banner-bottom-height`, `--banner-bg`, `--banner-fg`) allow surfaces
to account for banner space without knowing banner state.
Banners are not configurable by extensions or themes. They are a
platform-level trust signal.
---
## Themes
A theme is a set of CSS custom property overrides applied to `<html>`.
```css
[data-theme="corporate"] {
--bg: #f5f5f5;
--text: #1a1a1a;
--accent: #0066cc;
--border: #d1d5db;
--font-ui: 'Inter', sans-serif;
--font-code: 'Fira Code', monospace;
--radius: 4px;
/* ... full property set */
}
```
### What a Theme Can Do
- Override any CSS custom property in the theme contract
- Change colors, fonts, border radii, spacing scale
- Switch between light and dark base palettes
- Apply to every primitive, component, and surface uniformly
### What a Theme Cannot Do
- Add or remove DOM elements
- Execute JavaScript
- Modify component behavior or layout
- Override banner appearance (platform chrome)
- Access APIs or user data
### Theme Contract
The set of CSS custom properties that all primitives read from. This
is the stable API between themes and the UI. Properties are namespaced:
```
--bg, --bg-secondary, --bg-tertiary (backgrounds)
--text, --text-secondary, --text-muted (typography)
--accent, --accent-hover, --accent-muted (interactive)
--border, --border-strong (edges)
--success, --warning, --danger (semantic)
--font-ui, --font-code (typefaces)
--radius, --radius-lg (shapes)
--shadow, --shadow-lg (elevation)
--banner-bg, --banner-fg (read-only, set by platform)
```
Themes are admin-installed. An admin uploads a CSS file that declares
a `[data-theme="name"]` rule set. Users can select from installed
themes in Settings → Appearance.
Built-in themes: `dark` (default), `light`. The system preference
auto-detection (`prefers-color-scheme`) continues to work.
---
## Extension Hooks
Extensions interact with the application through a defined set of
hooks. Each hook has a specific trigger point in the lifecycle and
a clear contract for what the extension receives and returns.
### Hook 1: Pre-Completion
**When:** After the user sends a message, before the API request fires.
**Receives:** The completion request object (model, messages, tools, etc.)
**Returns:** Modified request object (or unmodified to pass through).
**Use case:** Inject additional tools, modify system prompt, add context.
```js
ctx.hooks.preCompletion(request => {
request.tools.push(myCustomTool);
return request;
});
```
### Hook 2: Stream Processing
**When:** On each SSE chunk during streaming.
**Receives:** The chunk (content delta, tool_use, tool_result, etc.)
**Returns:** Modified chunk (or null to suppress).
**Use case:** Transform content, intercept tool calls, accumulate data.
```js
ctx.hooks.streamChunk((chunk, context) => {
if (chunk.type === 'tool_result' && chunk.name === 'image_gen') {
context.displayContent.push({ type: 'image', src: chunk.data.url });
return null; // suppress from text content
}
return chunk;
});
```
### Hook 3: Post-Render
**When:** After a message is rendered into the DOM.
**Receives:** The message container element, the message object.
**Returns:** Nothing (mutates DOM in place).
**Use case:** Add action buttons, wrap elements, enhance display.
This is how extensions compose with each other's output. Extension A
produces an image via display content. Extension B's post-render hook
finds `<img>` elements and wraps them with action buttons. They don't
know about each other — they agree on the DOM contract.
```js
ctx.hooks.postRender((containerEl, message) => {
containerEl.querySelectorAll('img[data-display-content]').forEach(img => {
const actions = document.createElement('div');
actions.className = 'image-actions';
actions.innerHTML = '<button data-action="upscale">Upscale</button>';
img.parentElement.appendChild(actions);
});
});
```
### Hook 4: Display Content
**When:** After a completion finishes (all chunks received).
**Receives:** The assistant message object, accumulated display content.
**Returns:** Display content items to attach to the message.
Display content is **message-scoped, rendered inline, but excluded from
the LLM context window**. It's not in `messages.content` and not sent
back on the next turn.
```
Message
├── content (text — goes to LLM)
├── attachments (files — go to LLM via multimodal assembly)
└── display_content[] ← rendered inline, NOT sent to LLM
├── { type: "image", src: "...", extension_id: "img-gen" }
└── { type: "html", content: "<div>...", extension_id: "editor" }
```
**Storage:** Display content is persisted in a `display_content` JSONB
column on the message (or a junction table). It survives page reload
and is included in `GET /channels/:id/path` responses.
### Hook 5: Tool Bridge
**When:** The LLM invokes a tool registered by the extension.
**Receives:** Tool name and input parameters.
**Returns:** Tool result (string or structured).
Existing mechanism — the WebSocket tool bridge from EXTENSIONS.md.
Tool is registered via manifest, exposed to the LLM through the
tools list, and executed client-side (browser tier) or server-side
(starlark/sidecar tiers).
### Hook 6: Surface Injection
**When:** Surface boot (DOMContentLoaded).
**Receives:** The surface ID and available mount points.
**Returns:** Nothing (attaches to mount points).
Extensions declare which surfaces they target and which mount points
they use:
```json
{
"surface_injections": [{
"surface": "chat",
"mount_point": "side-panel",
"component": "my-panel.js"
}, {
"surface": "admin",
"mount_point": "section",
"section_id": "my-admin-section",
"label": "Image Gen Settings"
}]
}
```
Mount points are declared by each surface template:
```html
<div data-mount="side-panel"></div>
<div data-mount="section" data-section-id="..."></div>
```
---
## Trust Model
Two tiers. The admin is the trust boundary.
### Privileged (Admin-Installed)
| Capability | Allowed |
|-----------|---------|
| Full DOM access | Yes |
| Direct API calls (user's JWT) | Yes |
| Modify other extensions' output (post-render) | Yes |
| Create surfaces (own routes) | Yes |
| Register tools | Yes |
| Access all hook types | Yes |
| Go template data loader | Yes |
| Read `window.__PAGE_DATA__` | Yes |
The admin chose to install it. Same trust model as a VS Code extension
or a WordPress plugin — you trust the publisher.
### Sandboxed (User-Installed, Future)
| Capability | Allowed |
|-----------|---------|
| Shadow DOM / iframe only | Yes |
| API calls through message bridge | Yes (proxied, scoped) |
| Modify other extensions' output | No |
| Create surfaces | No |
| Register tools | Limited (user-scoped) |
| Access hooks | Post-render own output only |
| Go template data loader | No |
| Read `window.__PAGE_DATA__` | No |
Post-1.0 scope. Documented here to confirm the architecture
accommodates it without redesign.
---
## Extension Lifecycle
### Boot Sequence
```
1. Page loads (Go template renders surface shell + banner)
2. Primitives initialize (CSS loaded, JS factories available)
3. Components hydrate (attach to server-rendered DOM)
4. Extensions.boot() — idempotent, runs on every surface
a. Load extension manifests
b. Register hooks (pre-completion, post-render, etc.)
c. Register surface injections for current surface
d. Initialize tool bridges
5. Surface-specific JS runs (app.js for chat, editor-mode.js, etc.)
```
`Extensions.boot()` replaces the current chat-specific
`Extensions.loadAll()` / `Extensions.initAll()` pair and runs on
every surface.
### Manifest Declaration
```json
{
"id": "image-generator",
"name": "AI Image Generator",
"version": "1.0.0",
"tier": "browser",
"hooks": {
"pre_completion": "hooks/pre-completion.js",
"stream_chunk": "hooks/stream.js",
"post_render": "hooks/post-render.js"
},
"tools": [{
"name": "generate_image",
"description": "Generate an image from a text description",
"parameters": { ... }
}],
"surfaces": [{
"id": "image-gallery",
"route": "/s/gallery",
"title": "Image Gallery",
"components": ["chat-pane"],
"data_requires": ["channels"],
"script": "surfaces/gallery.js"
}],
"surface_injections": [{
"surface": "chat",
"mount_point": "message-actions",
"script": "injections/image-actions.js"
}],
"theme": null,
"settings_schema": { ... }
}
```
---
## Display Content — Data Model
### Message Extension
```sql
ALTER TABLE messages ADD COLUMN display_content JSONB DEFAULT '[]';
```
Array of display items:
```json
[
{
"type": "image",
"src": "data:image/png;base64,...",
"alt": "A cat in a spacesuit",
"extension_id": "image-generator",
"metadata": { "model": "dall-e-3", "size": "1024x1024" }
},
{
"type": "html",
"content": "<div class='chart'>...</div>",
"extension_id": "data-viz",
"metadata": {}
}
]
```
### API Contract
Display content is included in message objects returned by
`GET /channels/:id/path` and `GET /channels/:id/messages`.
It is **excluded** from the message array sent to the LLM provider
in `POST /chat/completions`. The completion handler strips
`display_content` when assembling the provider request.
### Rendering
During `renderMessages()`, after the message content is rendered,
each `display_content` item is rendered below the text content:
```html
<div class="message-content">
<p>Here's the image you requested:</p>
</div>
<div class="message-display-content">
<img src="..." alt="..." data-display-content data-extension="image-generator">
</div>
```
The `data-display-content` attribute is the DOM contract that
post-render hooks use to discover display content from any extension.
---
## Migration Path
| Current State | Target State |
|---------------|-------------|
| `ui-primitives.js` + `ui-primitives-additions.js` | Primitive catalog with stable factory API |
| `ChatPane.create()` (v0.22.7) | Component catalog with shared instance pattern |
| 5 Go template routes | Surface registry (hardcoded core, manifest-driven extensions) |
| `ctx.ui.replace()` / `ctx.ui.inject()` | Hook 6: Surface Injection with declared mount points |
| `ctx.surfaces.register()` | Manifest `surfaces` field → page engine route registration |
| Block renderers in `Extensions.initAll()` | `Extensions.boot()` on every surface |
| Message content only (text + attachments) | `display_content` column for render-only visual content |
| Block renderer pipeline (mermaid, katex) | Post-render hook + DOM contract (`data-display-content`) |
| `[data-theme]` with CSS variables | Theme contract: stable CSS custom property namespace |
---
## Implementation Sequence
This does not prescribe version numbers. It describes dependency order.
**Phase 1: Primitive consolidation**
- Audit `ui-primitives.js`, `ui-primitives-additions.js`, and Go
template components
- Define the primitive catalog and factory API
- Define the CSS custom property contract (theme API)
- Migrate existing callers to the consolidated API
**Phase 2: Component formalization**
- ChatPane already exists (v0.22.7). Formalize the instance pattern.
- Extract NoteEditor, FileTree, ModelSelector as components
- Each component gets a Go template partial + JS hydration
**Phase 3: Extension hooks**
- `Extensions.boot()` on every surface (v0.22.8)
- Pre-completion and post-render hooks
- Display content data model (message column + render pipeline)
- Stream processing hook
**Phase 4: Surface registry**
- Manifest-driven surface registration
- Dynamic route generation in page engine
- Data loader registry
- Mount point declaration and injection
**Phase 5: Theme system**
- Stable CSS custom property contract
- Admin theme upload
- User theme selection in Settings → Appearance
---
## Open Questions
1. **Display content storage:** JSONB column on messages vs. separate
`message_display_content` table. Column is simpler; table allows
independent lifecycle (delete display content without touching
message). Leaning column for KISS.
2. **Display content size limits:** Base64 images in JSONB could get
large. May need blob storage for display content with URL references
instead of inline data. Deferred until real usage patterns emerge.
3. **Extension ordering:** Post-render hooks from multiple extensions
run in what order? Manifest priority field? Installation order?
Alphabetical? Matters when Extension B adds buttons to Extension A's
images — A must render first.
4. **Surface layout persistence:** If a surface supports resizable
panes (editor: file tree width, chat pane width), where is that
persisted? User settings? Per-workspace? Per-surface?
5. **Extension settings storage:** Extensions need per-user config
(API keys for image gen, preferences for behavior). Current
`UpdateUserExtensionSettings` endpoint exists. Is the schema
sufficient or does it need structured validation from
`settings_schema` in the manifest?

2225
docs/ICD-API.md Normal file

File diff suppressed because it is too large Load Diff

265
docs/ICD-AUDIT.md Normal file
View File

@@ -0,0 +1,265 @@
# ICD Audit: API Contract vs ROADMAP / ARCHITECTURE
**Date:** 2026-03-03 | **Baseline:** v0.22.7
**Scope:** Cross-reference ICD-API.md against ROADMAP.md, ARCHITECTURE.md, and live codebase.
**Status:** Items §2 (naming), §3 (cleanup) resolved in v0.22.8 naming cleanup pass. See ICD-API.md (domain-organized v2) and DESIGN-SURFACES.md.
---
## 1. ARCHITECTURE.md — Staleness Report
ARCHITECTURE.md is stamped **v0.20** — six minor versions behind. The following sections are materially stale:
### 1.1 Frontend File Structure (§ Frontend Architecture)
ARCHITECTURE lists 19 JS files. Codebase has **34**. Fifteen files are undocumented:
| Missing from ARCHITECTURE | Added In | Purpose |
|---------------------------|----------|---------|
| `admin-scaffold.js` | v0.22.7 | Admin section DOM scaffolding |
| `channel-models.js` | v0.20.0 | Multi-model channel roster UI |
| `chat-pane.js` | v0.22.7 | Reusable ChatPane component |
| `editor-mode.js` | v0.21.5 | Editor surface (listed but not in file tree) |
| `extensions.js` | v0.11.0 | Extension loader |
| `notification-prefs.js` | v0.20.0 | Notification preference UI |
| `notifications.js` | v0.20.0 | Bell dropdown + badge |
| `pages.js` | v0.22.5 | Page-level auth + routing |
| `pages-splash.js` | v0.22.7 | Login/register surface |
| `panels.js` | v0.18.1 | Side panel architecture |
| `persona-kb.js` | v0.17.0 | Persona-KB binding UI |
| `repl.js` | v0.21.3 | REPL surface |
| `surface-nav.js` | v0.22.8 | Surface navigation overrides |
| `tools-toggle.js` | v0.13.1 | Per-tool enable/disable UI |
| `ui-format.js` | v0.22.7 | Message formatting helpers |
| `ui-primitives.js` | v0.10.5 | Shared render primitives |
| `ui-primitives-additions.js` | v0.22.7 | Toast, badge, icon extensions |
ARCHITECTURE also lists `knowledge.js`**renamed** to `knowledge-ui.js`.
### 1.2 SPA Shell References
ARCHITECTURE §Deployment Modes still says:
> "The frontend entrypoint (`docker-entrypoint-fe.sh`) handles `BASE_PATH` injection into `index.html`"
And the file tree lists:
> `├── index.html # Single-page app shell`
Both are wrong since v0.22.6. `index.html` is now a 16-line redirect stub. The real entry points are Go-rendered surface templates. The entrypoint now generates `proxy_pass` blocks to the backend for surface routes.
### 1.3 Deployment Diagram
The split-deployment description is functionally correct but the mechanism is wrong. Frontend no longer serves an SPA — it reverse-proxies page routes to the backend, which renders Go templates. The diagram should show the backend serving both API and HTML.
### 1.4 Backward Compatibility Section
The section says:
> "JSON field rename: `api_config_id` → `provider_config_id`... Frontend updated to match."
This is now ancient history (v0.9). The rename is complete everywhere. This section should either be removed or reduced to a single sentence noting historical context.
### 1.5 Missing Sections
ARCHITECTURE has **no documentation** for:
- Go template engine (`server/pages/`)
- Surface architecture (5 surfaces: chat, editor, notes, admin, settings)
- Template components (`model-select`, `team-select`, `file-upload`, `chat-pane`)
- Page data loaders (`loaders.go`)
- Provider Extensions / hook system (v0.22.1)
- Routing policies (v0.22.2)
- Export system (v0.22.4)
- Git integration (v0.21.4)
- The `treepath/` package (cursor, path, siblings)
**Recommendation:** Bump to v0.22 and rewrite §Frontend Architecture, §Deployment Modes, and §Backward Compatibility. Add §Template Engine, §Surface Architecture, §Provider Extensions sections.
---
## 2. ROADMAP.md — Housekeeping
### 2.1 Naming Inconsistency: Presets vs Personas
The ROADMAP uses "Persona" consistently. The API routes use `/personas`. The Go handler type is `PersonaHandler`. The JSON response sends **both** `"personas"` and `"presets"` keys (backward compat). The frontend reads the `presets` key exclusively.
This is a naming barnacle that bleeds into every layer:
| Layer | Name Used |
|-------|-----------|
| ROADMAP / ARCHITECTURE | Persona |
| Go types | `Persona`, `PersonaPatch` |
| Go handler | `PersonaHandler` |
| API routes | `/personas`, `/admin/personas` |
| JSON response keys | `"personas"` AND `"presets"` (dual) |
| Frontend JS | `presets` (reads `data.presets`) |
| UI labels | Mixed ("Preset" in some places, "Persona" in others) |
**Recommendation:** Pick one. The domain concept is "Persona" (it carries identity, system prompt, model, KB bindings — it's not just a preset). Two options:
- **Option A — Route rename**: `/personas``/personas`. Update FE. Remove dual JSON keys. This is a clean break and pre-1.0 is the time to do it.
- **Option B — Accept "presets" everywhere**: Rename Go types to `Preset`, update docs. Simpler but loses semantic precision.
### 2.2 `chat_id` Backward Compat Alias
`completionRequest` has:
```go
ChatID string `json:"chat_id"` // deprecated alias — maps to channel_id
```
The frontend doesn't send `chat_id` anymore. This is dead code in the handler that adds 3 lines of aliasing logic. Safe to remove.
### 2.3 Go Field Name Barnacle: `APIConfigID`
Throughout `channels.go` and `completion.go`, the Go struct field is named `APIConfigID` with json tag `"provider_config_id"`. The JSON tag is correct, so this is invisible to the API — but it's a source of confusion for anyone reading the Go code. Rename the Go field to `ProviderConfigID` for consistency.
### 2.4 Dual JSON Keys on Persona List
Three `PersonaHandler` methods return:
```go
gin.H{"personas": personas, "presets": personas}
```
The `"presets"` key exists solely for backward compat that was needed in v0.9. The FE only reads `"presets"`. If we go with Option A (§2.1), remove the `"presets"` key. If Option B, remove the `"personas"` key. Either way, stop sending both.
### 2.5 Route Alias: `/models` → `/models/enabled`
`main.go` has:
```go
protected.GET("/models/enabled", modelH.ListEnabledModels)
protected.GET("/models", modelH.ListEnabledModels) // alias
```
Plus the admin has its own `/admin/models`. Having `/models` as an alias for `/models/enabled` is confusing when `/admin/models` is a completely different thing. The ICD documents both, but one should go away. The FE uses `listEnabledModels()``/models/enabled`. The alias is unused.
### 2.6 ROADMAP v0.22.8 — Status vs Reality
The ROADMAP shows v0.22.8 as the next incomplete version with checkboxes for ChatPane Bridge, Theme Integration, Admin Surface, and Settings Surface. The last two sessions partially implemented some of these (admin scaffold, surface-nav, prototype overrides) but things broke and were rolled back/lost. The ROADMAP doesn't reflect the partial state — it still shows all items as unchecked.
**Recommendation:** After the ICD is finalized and the FE rebuild begins properly, update v0.22.8 checkboxes to reflect actual completion. Consider splitting v0.22.8 into smaller increments since it's become a catch-all.
---
## 3. ICD Gaps (things the backend implements but ICD doesn't fully cover)
### 3.1 Admin Models — Specific Endpoints
The ICD §24.5 lists admin model routes but doesn't document request/response shapes for:
- `POST /admin/models/fetch` — triggers provider sync, returns updated catalog
- `PUT /admin/models/bulk` — bulk visibility updates (request shape)
- `PUT /admin/models/:id` — single model update (what fields?)
**Fix:** Extract shapes from `admin.go` handler methods and add to ICD.
### 3.2 Admin Stats Response Shape
ICD §24.3 says "returns aggregate counts" but doesn't specify the shape. Should document:
```json
{
"users": 42,
"channels": 156,
"messages": 12847,
...
}
```
### 3.3 Admin Global Config — configWithKey Shape
ICD §24.4 says admin configs include `has_key` flag but doesn't show the full shape. The actual `configWithKey` response differs from the user-facing `safeConfig` — it includes `config`, `headers`, `settings` objects.
### 3.4 Team Provider Models
`GET /teams/:teamId/providers/:id/models` is in `main.go` but not clearly called out in the ICD §18.2 team admin section.
### 3.5 Team Audit Endpoints
`GET /teams/:teamId/audit` and `GET /teams/:teamId/audit/actions` are registered but not in the ICD team section.
### 3.6 Knowledge Base — Missing Detail
ICD §14 doesn't document:
- `GET /channels/:id/knowledge-bases` response shape (array of `ChannelKB`)
- `PUT /channels/:id/knowledge-bases` request shape
- `GET /knowledge-bases-discoverable` vs `GET /knowledge-bases` distinction
- KB document status progression: `pending → chunking → embedding → ready | error`
### 3.7 Export Endpoint Shape
ICD §21.2 has the right route but should note that `pandoc` must be available in the container. Also missing: supported format list beyond pdf/docx (the handler may accept more).
---
## 4. ICD Barnacles (things in the ICD that should be cleaned up)
### 4.1 `chat_id` Alias
The ICD §5.1 notes `chat_id` as a deprecated alias in the completion request. Once the dead code is removed from the handler, remove from ICD.
### 4.2 Legacy ListMessages
ICD §4.2 documents `GET /channels/:id/messages` as "Legacy/Debug." If it's truly not for frontend use, consider either removing the route or moving it to the admin namespace. Currently it's in the protected (user) namespace, which means any user can dump all messages (including branches) for their channels.
---
## 5. ROADMAP/ARCHITECTURE Reorganization Suggestions
### 5.1 ARCHITECTURE Should Reference the ICD
Instead of maintaining API route tables in multiple places, ARCHITECTURE should say "See ICD-API.md for the complete endpoint reference" and focus on:
- Design principles, scope model, resolution chains (what it does well today)
- Package structure (update to v0.22)
- Frontend architecture (rewrite for surfaces/templates)
- Security model (mostly current)
- New: Template engine, provider extensions, routing
### 5.2 ROADMAP Completed Versions — Collapse
The ROADMAP has 900+ lines of completed release details (v0.9 through v0.22.7). These are duplicated in CHANGELOG.md. The ROADMAP should collapse completed versions to one-liners (as it does for v0.9.x through v0.16.0) and push detailed history to CHANGELOG.
Currently v0.17.0 through v0.22.7 have full checkbox-level detail in the ROADMAP — that's useful while in-progress but noise once shipped. Collapse them.
### 5.3 Three-Document Model
Post-audit, the documentation should be:
| Document | Audience | Content |
|----------|----------|---------|
| `ARCHITECTURE.md` | Developers | Design principles, package structure, data model, resolution chains, security, deployment. **No API details.** |
| `ICD-API.md` | FE developers, integrators | Complete API contract: routes, shapes, auth, streaming, WebSocket. |
| `ROADMAP.md` | Stakeholders, contributors | Version plan, dependency graph, future features. Completed versions collapsed to one-liners with CHANGELOG refs. |
Future additions:
| Document | Audience | Content |
|----------|----------|---------|
| `ICD-SURFACE.md` | FE developers | Surface contract: templates, PageData, DOM IDs, JS globals, lifecycle. |
| `ICD-EXTENSION.md` | Extension authors | Extension contract: manifest, tiers, hooks, tool bridge, asset serving. |
### 5.4 ROADMAP Dependency Graph — Update
The ASCII dependency graph stops at v0.22.7. The v0.22.8 → v0.23.0 → v0.24.0 path should be added.
---
## 6. Cleanup Punch List
Priority-ordered actions that could all happen in a single commit:
| # | Action | Risk | Lines Changed |
|---|--------|------|---------------|
| 1 | Remove `chat_id` alias from `completionRequest` | None — FE doesn't use it | ~5 |
| 2 | Rename Go field `APIConfigID``ProviderConfigID` in channels.go + completion.go | None — json tags unchanged | ~30 |
| 3 | Pick persona vs preset naming, remove dual JSON keys | Low — FE reads one key | ~10 |
| 4 | Remove `/models` alias (keep `/models/enabled` only) | Low — check FE uses | ~1 |
| 5 | Bump ARCHITECTURE.md to v0.22, rewrite §Frontend | Medium — documentation | ~100 |
| 6 | Collapse ROADMAP completed versions (v0.17v0.22.7) to one-liners | None — CHANGELOG has detail | ~600 removed |
| 7 | Add missing ICD shapes (§3 above) | None — documentation | ~80 added |
| 8 | Update ICD to remove chat_id and document any cleanup from #1#4 | None | ~10 |

View File

@@ -109,6 +109,11 @@ v0.22.5 Surfaces + Go Templates + Admin Bug Fixes ✅
v0.22.6 Split Deployment Fix + CI Timeout + Code Pruning ✅
|
v0.22.7 Surface Prototypes (Theme + ChatPane + Splash) ✅
|
v0.22.8 Surface Integration (Wire into existing JS)
|
v0.23.0 Multi-Participant Channels + Presence
v0.24.0 Auth Strategy (mTLS/OIDC) + Full RBAC + Group permissions
@@ -1102,6 +1107,97 @@ replaced by server-rendered surfaces.
---
---
## v0.22.7 — Surface Prototypes (Theme + ChatPane + Splash) ✅
Foundation CSS/JS for the v0.22.5 surface architecture: theme system,
reusable chat pane component, splash/login redesign, and UI primitive
extensions. All files target the correct tree locations established in
v0.22.5. Settings surface gains BYOK and User Personas feature gates.
Depends on: surfaces + Go templates (v0.22.5), code pruning (v0.22.6).
**Theme System:**
- [x] `theme.css`: CSS custom properties for light/dark themes via `[data-theme]` attribute
- [x] System preference auto-detection (`prefers-color-scheme` media query)
- [x] Variable bridge: new vars (`--bg`, `--text`) alias old vars (`--bg-primary`, `--text-primary`)
- [x] Shared component styles: toast stack, badges, buttons, icon buttons, form fields, confirm dialog, theme toggle, avatar upload, section headers
- [x] Surface layout styles: splash, chat-pane, admin, settings, editor
- [x] Google Fonts: DM Sans (UI) + JetBrains Mono (code)
- [x] `base.html`: `data-theme` on `<html>`, `theme.css` before `styles.css`
**ChatPane Component:**
- [x] `chat-pane.js`: factory pattern — `ChatPane.create(opts)` returns self-contained instance
- [x] Instance API: renderMessages, appendChunk, finalizeStream, appendTyping, removeTyping, scrollToBottom, showWelcome, clear, getInputValue, setInputValue, focusInput, destroy
- [x] Lookup: `ChatPane.get(id)`, `ChatPane.forChannel(channelId)`, `ChatPane.active()`
- [x] `components/chat-pane.html`: Go template partial for server-rendered mount points
- [x] Editor surface: `chat-pane` template in assist pane with `ChatPane.create()` boot
**Splash / Login:**
- [x] `login.html` rewritten: split hero + auth card layout
- [x] Animated grid canvas (40px grid, scrolling offset, `--border` color)
- [x] Auth tabs (Login / Register) with server-gated registration
- [x] Register form: username, display name, email, password, avatar upload
- [x] `pages-splash.js`: `Pages.initSplash()`, grid animation, POST login, POST register + PUT avatar
- [x] `PageData` gains `InstanceName`, `LogoURL`, `Tagline`, `RegistrationOpen`
- [x] `loadBranding()` + `isRegistrationOpen()` from GlobalConfig
**UI Primitives Additions:**
- [x] `Toast`: show/success/error/warning/info with auto-dismiss, stacking
- [x] `renderBadge()`: 6 color variants (accent, success, danger, warning, purple, muted)
- [x] `renderIcon()`: 20+ SVG icon set
- [x] `renderIconBtn()`: icon button factory with active state
- [x] `Theme`: init/set/get/resolved/renderToggle with localStorage
- [x] `showConfirmDialog()`: enhanced with variant, onConfirm/onCancel
- [x] `mountAvatarUpload()`: file input with preview, onUpload callback
**Settings Surface Gates:**
- [x] BYOK gate: My Providers, Model Roles, Usage tabs shown when `user_providers.enabled`
- [x] User Personas gate: My Personas tab shown when `user_presets.enabled`
- [x] Models tab added to base navigation
- [x] `SettingsPageData` gains `BYOKEnabled`, `UserPersonasEnabled` from GlobalConfig
- [x] BYOK status indicator in nav footer
**Go Changes:**
- [x] `PageData`: Theme, SurfaceSettings, InstanceName, LogoURL, Tagline, RegistrationOpen
- [x] `Render()` defaults Theme to "dark"
- [x] `RenderLogin()` populates splash fields from GlobalConfig
- [x] `settingsLoader()` reads feature gates
---
## v0.22.8 — Surface Integration (Wire into Existing JS)
Connect v0.22.7 prototypes to the existing frontend JS modules.
ChatPane.primary replaces singleton UI.* calls. Theme system wired
into appearance settings. Admin surface hybrid loader completed.
Depends on: surface prototypes (v0.22.7).
**ChatPane Bridge:**
- [ ] `ChatPane.primary` creation in `startApp()` from server-rendered mount points
- [ ] `ui-core.js` delegation: `UI.renderMessages``ChatPane.primary.renderMessages`, etc.
- [ ] `editor-mode.js` ChatPane creation for workspace chat
- [ ] Branding init from `window.__SETTINGS__` (instanceName, logoURL)
**Theme Integration:**
- [ ] Wire `Theme` into appearance settings save/load cycle
- [ ] CM6 theme sync on theme change
- [ ] Persist theme preference to user settings API
**Admin Surface:**
- [ ] Complete hybrid section loaders for all JS-loaded sections
- [ ] Admin surface stats/overview server-rendered content
**Settings Surface:**
- [ ] Models visibility toggles (API.saveModelPreferences)
- [ ] User Personas CRUD (add/edit/delete with confirm dialogs)
- [ ] BYOK provider CRUD in settings context
- [ ] Teams tab content
---
## v0.23.0 — Multi-Participant Channels + Presence
The channel foundation for workflows and real-time collaboration. Today