# Changelog All notable changes to Chat Switchboard. ## [0.21.5] — 2026-03-01 ### Added - **Editor Surface (Development Mode).** IDE-like experience built as a v0.21.3 surface consuming workspace primitives. Activates automatically when a channel or project has a bound workspace. Three-pane layout: file tree (sidebar), code editor (CM6), and AI chat panel with resizable split. - **`editor-mode.js`** — Full editor surface module. File tree backed by `workspace_ls` API with nested directory expansion, file icons by extension, and context menus. Tab bar with modified indicators, close buttons, and multi-file editing. CM6 integration with language auto-detection (20+ languages) and textarea fallback when CM6 bundle unavailable. - **Split pane layout** with draggable resize handle. Editor (left) and AI chat (right) share the main region. Chat DOM borrowed from Surfaces saved fragments — real conversation history and input preserved across mode switches. - **Quick Open** (`Ctrl/Cmd+P`): Fuzzy file finder overlay using workspace file index. `Ctrl/Cmd+S` save, `Ctrl/Cmd+W` close tab. - **Live editor updates**: When AI tool calls modify workspace files (`workspace_write`, `workspace_patch`), the server emits `workspace.file.changed` via WebSocket. If the file is open and unmodified in the editor, content refreshes automatically. - **Status bar**: Current file path, language mode, git branch display. - **`editor-mode.css`**: Complete styling for all editor components with dark theme support, mobile responsive (stacked layout at ≤768px). - **Workspace API methods** on frontend `API` module: `getWorkspace`, `listWorkspaceFiles`, `readWorkspaceFile`, `writeWorkspaceFile`, `deleteWorkspaceFile`, `mkdirWorkspace`, `getWorkspaceGitStatus`, `getWorkspaceGitBranches`. - **`Surfaces.getSavedFragment()` / `putSavedFragment()`** — Cross-surface DOM sharing. Allows the editor surface to borrow chat DOM into its split pane without cloning. - **`workspace.file.` event routing** added to server event route table (`DirToClient`). - **Workspace management UI**: Create, list, and bind workspaces from the frontend. Project settings panel gains a Workspace section with dropdown picker and "New" button. Chat context menu gains "Set workspace…" option for per-channel override. Both paths support creating new workspaces inline. - **`GET /workspaces`** — List all workspaces accessible to the current user (user-owned + team-owned). - **`workspace_id` in channel API**: `updateChannelRequest` accepts `workspace_id` for binding/unbinding. `channelResponse` and both list/get queries now include `workspace_id`. ### Changed - `stream_loop.go`: Emits `workspace.file.changed` event after successful `workspace_write`/`workspace_patch` tool calls. - `index.html`: Added `editor-mode.css` stylesheet and `editor-mode.js` script. - `channels.go`: `channelResponse` includes `workspace_id`, list/get queries select it, update accepts it. - `workspaces.go`: Added `List` handler for `GET /workspaces`. - `main.go`: Added `GET /workspaces` route. - `projects-ui.js`: Workspace section in project panel, workspace picker in chat context menu. - `styles.css`: Workspace row layout in project panel. ## [0.21.4] — 2026-03-01 ### Added - **Git Integration.** Workspaces can now track a git remote. Operations use `os/exec` calling the `git` binary (no CGO, no Go git libraries). Credentials are vault-encrypted (AES-256-GCM) using the same pattern as BYOK provider configs. - **`git_credentials` table.** Postgres + SQLite migrations. Stores encrypted PAT tokens, HTTP basic auth, and SSH private keys. Columns: id, user_id, name, auth_type, encrypted_data, nonce, created_at. - **Workspace git columns.** `git_remote_url`, `git_branch`, `git_credential_id`, `git_last_sync` added to `workspaces` table via migration. - **`workspace.GitOps`** — exec-based git operations: Clone, Pull, Push, Status, Diff, Commit, Log, BranchList, Checkout. Credential injection via temporary `GIT_ASKPASS` scripts (PAT), `.git-credentials` files (basic auth), or `GIT_SSH_COMMAND` (SSH keys). Temp files securely wiped after each operation. - **Post-operation hooks.** Clone, pull, and checkout automatically trigger workspace reconcile (filesystem → DB sync) and re-index changed files through the v0.21.2 indexing pipeline. - **5 git tools** for LLM use: `git_status`, `git_diff`, `git_commit`, `git_log`, `git_branch`. Filtered out when no workspace is bound. Each validates git remote configuration before execution. - **9 git API endpoints:** `POST clone`, `POST pull`, `POST push`, `GET status`, `GET diff`, `POST commit`, `GET log`, `GET branches`, `POST checkout` — all under `/api/v1/workspaces/:id/git/`. - **3 credential endpoints:** `POST /api/v1/git-credentials` (create), `GET /api/v1/git-credentials` (list user-scoped summaries), `DELETE /api/v1/git-credentials/:id` (delete). Encrypted data never exposed in API responses. - **`GitCredentialStore` interface** + Postgres/SQLite implementations. Create, GetByID, ListByUser, Delete with user-scoped authorization. - **Security.** `file://` and local path URLs rejected in clone. Error output sanitized to strip credential-containing lines. `GIT_TERMINAL_PROMPT=0` set on all operations to prevent interactive prompts. ### Changed - `WorkspaceStore` interface: added `SetGitLastSync()` method + both implementations. - `models.Workspace`: added git tracking fields. `models.WorkspacePatch`: added git patch fields. - Completion handler: git tools disabled alongside workspace tools when no workspace bound. - `store.Stores` struct: added `GitCredentials` field. - Postgres/SQLite `NewStores()`: wires `GitCredentialStore`. ## [0.21.3] — 2026-03-01 ### Added - **Surface Registry (`surfaces.js`).** Mode-switching system that allows extensions to register "surfaces" (UI modes) that take over named regions of the interface. DOM nodes are detached and preserved in `DocumentFragment`s during mode switches — not destroyed — ensuring CM6 editor instances and other stateful components survive transitions. - **Surface region attributes.** Four `data-surface-region` attributes on `index.html` containers: `surface-header` (model bar), `surface-main` (chat messages), `surface-footer` (input area), `sidebar-content` (search + chat history). Extensions swap these regions via `ctx.ui.replace()` / `ctx.ui.restore()`. - **Mode selector.** Appears in the sidebar (`#modeSelectorWrap`) when ≥1 extension surface is registered beyond the default chat. Shows icon buttons for each mode with active state highlighting. - **Extension context API.** `ctx.surfaces.register()`, `ctx.surfaces.unregister()`, `ctx.surfaces.activate()`, `ctx.surfaces.getCurrent()` on the scoped extension context. `ctx.ui.replace()` and `ctx.ui.restore()` for region DOM management. - **Surface events.** `surface.activated`, `surface.deactivated`, `surface.registered`, `surface.unregistered` on the EventBus (all `localOnly`). - **REPL console (`repl.js`).** Fourth tab in the debug modal (Ctrl+Shift+L). `AsyncFunction` wrapper for top-level `await`. Injected globals: `API`, `Events`, `Extensions`, `Surfaces`, `DebugLog`, `Panels`, `UI`, plus `$()`, `$$()`, `sleep()` helpers. Features: collapsible JSON pretty-printing, red errors with expandable stack traces, command history via ↑/↓ (persisted to sessionStorage), tab-completion on live object graphs and EventBus labels, multi-line input (Shift+Enter), admin-gated (admin role OR `?debug=1` URL param). - **CSS.** Mode selector styles (`.mode-selector`, `.mode-btn`, active/hover states). REPL styles (output formatting, value-type color coding, collapsible JSON, input prompt). ### Changed - `extensions.js` context builder: added `ctx.surfaces` namespace and `ctx.ui.replace()` / `ctx.ui.restore()` methods. - `app.js` startup: initializes `Surfaces.init()` before `Extensions.loadAll()`, and `REPL.init()` after auth confirmation. - `index.html`: added `data-surface-region` attributes, mode selector container, REPL tab in debug modal, script tags for `surfaces.js` and `repl.js`. - `EXTENSIONS.md` §6: updated with implementation details, actual API signatures, and event catalog. ## [0.21.2] — 2026-03-01 ### Added - **Workspace Indexing Pipeline.** Background text chunking and embedding for workspace files, reusing the existing `knowledge.SplitText` chunker and `knowledge.Embedder` for vector generation. Indexable file types include 40+ source code extensions (Go, Rust, Python, TypeScript, Perl, etc.) plus all `text/*` MIME types. Code-aware chunking uses 1500-character chunks with function/class/type boundary separators (`\n\nfunc `, `\n\nfn `, `\n\nclass `, `\n\ndef `, `\n\ntype `, `\n\nsub `, `\n\npub `, `\n\nimpl `), falling back to paragraph and line breaks. - **Content-addressed skip.** SHA256 computed during `WriteFile` is compared against the prior hash before triggering re-indexing. Unchanged files skip the chunk + embed pipeline entirely, making archive extracts and bulk writes efficient. - **`workspace_chunks` table.** New table (Postgres + SQLite migrations) storing text chunks with vector embeddings per workspace file. Postgres uses sequential scan with pgvector `<=>` cosine operator (no IVFFlat/HNSW index — 3072-dim embeddings exceed the 2000-dim index limit; sequential scan is adequate at workspace scale). SQLite uses app-level cosine in Go. Columns: workspace_id, file_id, chunk_index, content, token_count, embedding, metadata (JSONB with line_start, language). - **`workspace_files` additions.** `index_status` column (`pending`, `indexing`, `ready`, `error`, `skipped`) and `chunk_count` for tracking indexing progress per file. `workspaces` table gains `indexing_enabled` boolean (default true). - **`WorkspaceStore` chunk methods.** Four new store interface methods implemented for both Postgres and SQLite: `InsertChunks` (batch insert with pgvector cast or JSON text), `DeleteChunksByFile`, `SimilaritySearch` (pgvector `<=>` operator or app-level cosine), `UpdateFileIndexStatus`. - **`workspace.Indexer`.** Background indexing orchestrator with configurable concurrency semaphore. Single-file indexing (`IndexFile`) triggered on `WriteFile` when content changes. Batch indexing (`IndexBatch`) triggered after `ExtractArchive`. Async goroutines with 5-minute timeout per file. Embedding usage tracked via `Embedder.LogUsage`. - **`workspace_search` tool.** Semantic search across workspace files via natural language query. Embeds the query, runs cosine similarity against workspace chunks, returns file paths, content snippets, relevance scores, and approximate line numbers. Optional `file_pattern` glob filtering (e.g. `*.go`, `src/*.ts`) applied post-search. Top-K capped at 20. - **Indexer integration.** `workspace.FS.SetIndexer()` hooks the indexer into the write path. `WriteFile` triggers single-file indexing on content change. `ExtractArchive` triggers batch indexing for all extracted files. Both use the workspace owner's identity for embedding provider resolution. - **Configuration.** `WORKSPACE_INDEXING_ENABLED` env var (global kill switch, default true). `WORKSPACE_INDEX_CONCURRENCY` env var (default 2). Per-workspace `indexing_enabled` toggle via `WorkspacePatch`. Indexing gracefully degrades when no embedding role is configured. - **Index status endpoint.** `GET /api/v1/workspaces/:id/index-status` returns aggregated indexing progress: per-status file counts (pending, indexing, ready, error, skipped), total chunk count, and workspace indexing_enabled flag. ### Changed - `workspace_files` scanner updated in both Postgres and SQLite stores to include `index_status` and `chunk_count` columns. - `workspaces` scanner updated to include `indexing_enabled` column. - `WorkspacePatch` model extended with `IndexingEnabled` field. - `WorkspaceToolNames()` now includes `workspace_search` in the disabled set when no workspace is bound. - `RegisterWorkspaceSearchTool()` added for late registration of the search tool (requires embedder). ## [0.21.1] — 2026-03-01 ### Added - **Workspace Tools.** Six AI-callable tools in the `workspace` category: `workspace_ls` (list files with content type/size), `workspace_read` (text content with 50KB soft limit, binary detection), `workspace_write` (create/overwrite with auto-mkdir), `workspace_rm` (delete with recursive guard), `workspace_mv` (move/rename with auto-mkdir), `workspace_patch` (find/replace operations, each match must be unique, 1MB file limit). Late registration via `RegisterWorkspaceTools()` after stores + FS init. `WorkspaceToolNames()` returns the full list for filtering. - **Channel Workspace Binding.** `channels.workspace_id` nullable FK column (Postgres + SQLite migration 010/009). Channels with a bound workspace get workspace tools auto-injected into the completion tool set. - **Project Workspace Binding.** `projects.workspace_id` nullable FK column. Projects can bind a workspace shared by all channels in the project. - **Workspace Resolution Chain.** `ChannelStore.ResolveWorkspaceID()` resolves the effective workspace for a channel: channel `workspace_id` takes priority, falling back to the project's `workspace_id` via `project_channels` join. Both Postgres and SQLite implementations. Returns empty string when no workspace is bound. - **Tool Injection in Completion Handler.** `buildToolDefs()` accepts `workspaceID` parameter; when empty, all workspace tool names are added to the disabled set. `ExecutionContext.WorkspaceID` field carries the resolved workspace ID through tool execution and streaming. - **Workspace resolution wired into streaming.** Both `CompletionHandler` and `streamWithToolLoop` resolve the workspace ID from the channel and pass it through to tool execution contexts. ### Deferred - Frontend workspace UI (channel settings toggle, project Files tab, chat bar workspace indicator) deferred to a future release. - Archive-to-workspace upload flow (detect archive MIME + workspace binding → extract to workspace) deferred. ## [0.21.0] — 2026-03-01 ### Added - **Workspace Storage Primitive.** Platform-level file storage bound to users, projects, channels, or teams via polymorphic owner model. Dual-layer architecture: PVC filesystem (source of truth) with DB metadata index (queryable cache). Workspaces support configurable quotas, status lifecycle (active/archived/deleting), and owner-based authorization inheritance. - **Workspace data model.** Two new tables: `workspaces` (polymorphic owner_type/owner_id, root_path, max_bytes quota, status) and `workspace_files` (path, MIME type, size, sha256, is_directory). Unique index on (workspace_id, path) enables upsert-on-conflict for file metadata sync. Migrations for both Postgres and SQLite. - **`workspace.FS` package.** Filesystem operations layer with security-first design: atomic writes (temp file + rename with SHA256 computed via tee reader), path traversal guards (cleanPath normalization + absPath containment validation), symlink rejection on write targets. Operations: ReadFile, WriteFile, DeleteFile (with recursive guard), Mkdir, Stat, ListDir, Tree, Reconcile (filesystem→DB drift sync). - **Archive operations.** Extract zip and tar.gz archives into workspaces with bomb protection: 10K file limit, 100MB single file cap, workspace quota enforcement during extraction. Common-prefix stripping handles GitHub-style `project-name/` wrapper directories. CreateArchive packages workspaces into downloadable zip or tar.gz. - **Content type detection.** Extension-based MIME detection covering 40+ source code types (Go, Rust, Python, TypeScript, etc.) with `http.DetectContentType` sniffing fallback for unknown extensions. - **`WorkspaceStore` interface.** Full CRUD for workspaces, file index operations (upsert, delete, delete-by-prefix, get, list with recursive/non-recursive modes), ownership lookup (GetByOwner, ListByOwner), and aggregate stats. Postgres and SQLite implementations. - **Workspace API.** 15 new endpoints under `/api/v1/workspaces`: workspace CRUD (create, get, update, delete), file operations (list, read, write, delete, mkdir), archive management (upload with extraction, download), reconcile (FS→DB sync), stats. Owner-based authorization: user workspaces require self, channel workspaces require channel owner, project workspaces require project member, team workspaces require team member. - **Unit tests.** Path cleaning (13 cases including traversal, dotfiles, whitespace), absPath traversal detection, MIME detection (14 extensions), unsafe path filtering (7 cases), common prefix detection (5 cases), write/read round-trip with mock store, delete verification, mkdir with index sync. ## [0.20.0] — 2026-03-01 ### Added - **Notifications Core (Phase 1)**: Persistent, user-targeted notification infrastructure with real-time WebSocket delivery. `notifications` table (Postgres + SQLite), `NotificationStore` with paginated queries, `Service.Notify()` / `NotifyMany()` for centralized creation and dispatch. Five API endpoints: list (paginated, filterable), unread count, mark read, mark all read, delete. Bell icon in header bar with unread count badge (capped at 9+). Notification dropdown (latest 10, grouped, click-to-navigate via `resource_type`/`resource_id`). Full notification panel registered with `PanelRegistry`. WebSocket push via `notification.new` event with toast for high-priority types (`kb.error`, `role.fallback`). Background cleanup goroutine (configurable retention, default 90 days). Initial sources: `role.fallback` (via EventBus subscription), `kb.ready`/`kb.error` (knowledge base processing), `grant.changed` (group membership). - **@mention Parsing + Multi-model Routing (Phase 2)**: Channels support multiple AI models with @mention-based routing. `mentions.Parse()` extracts @mentions from message content, resolves against channel model roster (case-insensitive, longest-match-first, trailing punctuation tolerant). Completion handler fans out sequentially to mentioned model(s), producing one assistant response per target. Without @mention, default channel model responds (fully backward compatible). Channel model CRUD: 4 new endpoints for add/remove/update/list. Frontend: model pills in chat header, @mention autocomplete (CM6 `mentionCompletion` extension with roster-backed suggestions), model attribution labels on multi-model responses. Message `model_display` field for human-readable attribution. SSE streaming tagged with model info per response. - **Email Transport + Notification Preferences (Phase 3)**: SMTP email delivery via `EmailTransport` supporting implicit TLS (port 465) and STARTTLS (port 587). Multipart MIME messages (HTML + plaintext) with branded templates using Go `html/template`. `notification_preferences` table with three-tier resolution: specific type → user wildcard `*` → system default (in_app=true, email=false). Three preference API endpoints (list, set with partial patch, delete). Admin SMTP configuration in settings (host, port, user, password, from address, TLS mode) with test email endpoint. User notification preferences UI in Settings → Notifications tab with per-type in-app/email checkboxes. Async email delivery (goroutine with 30s timeout, failures logged non-blocking). - **Gin Release Mode**: Backend now automatically sets `gin.SetMode(gin.ReleaseMode)` when `ENVIRONMENT=production`, suppressing per-request access logs for health checks and reducing log noise. `GIN_MODE` env var also added to K8s backend deployment as explicit override. ### Fixed - **Model preferences 500 on every page load**: `GET /api/v1/models/preferences` returned HTTP 500 due to `NULL` values in `user_model_settings.hidden` and `sort_order` columns failing Go `sql.Scan()` into non-pointer types. Root cause: the `Set` upsert passed `NULL` for unset patch fields, bypassing `DEFAULT false`/`DEFAULT 0` on INSERT. Fixed with `COALESCE` in both SELECT (read path) and INSERT VALUES (write path) for Postgres and SQLite. Includes data-fix SQL for existing NULL rows. ## [0.19.2] — 2026-02-28 ### Added - **Project Persona Default**: Bind a persona to a project via the detail panel. All chats in the project inherit the persona's model, parameters, and system prompt as a fallback when no explicit preset is selected. Resolution chain: explicit request → project persona → none. - **Project Archive Toggle**: Archive/unarchive projects from the detail panel. Archived projects hidden from sidebar by default with "Show archived (N)" toggle. Dimmed visual treatment when shown. - **Channel Reorder**: Right-click a chat within a project for "Move up" / "Move down" options. Server-persisted positions via `project_channels.position`, loaded on startup, maintained across moves. ## [0.19.1] — 2026-02-28 ### Added - **Active Project**: Pin a project as active; new chats auto-assign to it. Persists across reloads via localStorage. Visual indicator (📌 + accent border) in sidebar. - **Project System Prompt**: Per-project instructions stored in `projects.settings` JSONB. Injected between persona/channel prompt and KB hint during completion. Merge semantics on update (preserves other settings keys). - **Project Detail Panel**: Side panel (via PanelRegistry) for managing project system prompt, KB bindings, and notes. Accessible from project ⋯ menu → "Project settings". - Enriched `ListKBs` and `ListNotes` responses with JOIN-sourced `name`/`title` fields for display in the project panel. ### Changed - `ProjectPatch` model now accepts `settings` field for partial settings merge. - Project context menu expanded: "Pin as active", "Project settings", plus existing rename/color/delete. ## [0.19.0] — 2026-02-28 ### Added - **Projects / Workspaces.** Organizational containers that group related conversations, knowledge bases, and notes into a single workspace. Projects provide a scope-aware organizational layer above individual channels, with support for personal, team, and global visibility. - **Project data model.** Four new tables: `projects` (with scope, owner, team, color, icon, settings JSONB), `project_channels` (ordered membership with UNIQUE constraint), `project_knowledge_bases` (with auto_search flag), and `project_notes`. Channels gain a denormalized `project_id` FK for efficient filtering. - **Project API.** 17 new endpoints under `/api/v1/projects`: full CRUD, channel add/remove/list/reorder, KB add/remove/list, note add/remove/list. Access checks enforce owner-or-team-member visibility with owner-only delete. - **Project KB resolution.** Knowledge bases bound to a project are automatically available to all channels within that project. Resolution chain extended: Persona KBs → **Project KBs** → Channel KBs → Personal KBs. Both `BuildKBHint` (system prompt injection) and `kbsearch` (tool-time retrieval) updated. - **Note auto-association.** Notes created from a channel that belongs to a project are automatically added to that project's note collection. - **Sidebar project groups.** Projects appear as collapsible groups in the sidebar above the existing time-based "Recent" section. Each group shows a color dot, chat count, and an options menu (⋯) for rename, color picker, and delete. - **Drag-and-drop.** Drag chat items between project groups or back to Recent. Visual feedback with outline and background highlight on valid drop targets. - **Right-click context menu.** Right-click any chat to move it to a project, remove it from its current project, or create a new project and assign in one step. - **New Project button.** Added to the New Chat split-button dropdown for quick project creation. - **Channel project_id filter.** `GET /api/v1/channels` accepts `?project_id=` to filter by project, or `?project_id=none` for unassigned channels. Response includes `project_id` field. ### Changed - `renderChatList()` rewritten to support project grouping while preserving the original time-based layout when no projects exist. Chat items now include `draggable`, `oncontextmenu`, and drag event handlers. - Channel response struct, SELECT queries, and scan calls updated across `ListChannels`, `GetChannel`, and `CreateChannel` (both Postgres and SQLite paths) to include `project_id`. - `resource_grants` CHECK constraint extended to include `'project'` as a valid resource type. - `db-validate.sh` updated: former "Dropped tables" assertions for `projects` and `project_channels` replaced with positive checks for all four project tables plus `channels.project_id` column check. - Test helper truncation list updated with project junction tables. ### Fixed - **JSON corruption defense** (hotfix carry-forward from 0.18.2): `SafeJSON` wrapper with `scanJSON` and `scanTags` hardened helpers that replace bare `json.RawMessage` / `pq.Array` scanning. Prevents `encoding/json: invalid character` panics from NULL or empty columns. - **Service worker** (hotfix carry-forward): `chrome-extension://` URL filtering to avoid opaque response cache errors. ## [0.18.1] — 2026-02-28 ### Added - **Side panel architecture.** Complete rewrite of the side panel system from shared-tab layout to independent single-slot panels. Any action (preview, notes, diagram pop-out) fills the slot, replacing whatever was there — no tabs, no association between panel types. - **Panel registry** (`PanelRegistry`): named panels with independent open/close state, scroll/state preservation across switches, keyboard shortcuts (Ctrl+\ cycle, Ctrl+Shift+\ toggle dual-view). - **Dual-view mode**: two panels side-by-side via CSS grid with a drag-adjustable split ratio (0.2–0.8 range, 6px divider handle). Toggle button in header actions area alongside fullscreen and close. - **Live HTML preview**: streaming responses update the preview iframe in real-time (500ms debounce). Extracts the last fenced HTML block from partial content and renders it as the response streams in. - **Extension pop-out**: `⧉` button on rendered extension blocks (mermaid, KaTeX, etc.) clones the content into the preview iframe and opens the side panel. - **Extension UI primitives** (`ctx.ui`): seven methods exposed to browser extensions through the scoped extension context: - `toast(msg, type)` — toast notifications via `UI.toast()` - `openPreview(html)` — load HTML into side panel preview iframe - `isDark()` — theme detection without DOM sniffing - `isMobile()` — viewport width check (≤768px) - `isPanelOpen()` — side panel container visibility - `confirm(msg, opts)` — modal confirm dialog (Promise\) - `createMenu(anchor, opts)` — popup menu (unchanged from stub) - **Mermaid context-aware expand**: single `⛶` button replaces the previous two-button (pop-out + fullscreen) approach. When the side panel is open, expand pops the diagram into it; when closed, expand goes fullscreen. - **Mermaid fullscreen close button**: 40px circular close button overlaid top-right in fullscreen mode, 48px on mobile. Fixes the previous Escape-key-only exit which was unusable on touch devices. - **Mobile side panel**: swipe navigation between panels (80px threshold, 1.5× horizontal-to-vertical ratio), tap-to-close overlay, responsive auto-collapse of dual mode on narrow viewports, enlarged touch targets for all panel controls. - **Side panel header label**: simple text label showing the active panel name, replacing the previous tab bar UI. ### Changed - Side panel model changed from tabbed (Preview + Notes tabs visible simultaneously) to single-slot (one panel fills the space, actions replace it). No user-selectable tabs — content is entirely action-driven. - Dual-view toggle button moved from tab bar (dynamically injected) to static header actions area next to fullscreen and close. - Mermaid extension refactored to use `ctx.ui` primitives exclusively. Zero direct references to `UI.*`, `PanelRegistry.*`, or DOM class sniffing for theme detection. Extension remains fully self-contained in `extensions/builtin/mermaid-renderer/`. - Mermaid source copy uses `ctx.ui.toast()` instead of inline button text swap. Theme detection uses `ctx.ui.isDark()` instead of manual `document.body.classList.contains('dark-theme')` check. - Side panel outer resize minimum bumped to 480px in dual mode (vs 280px single). ### Removed - Tab bar UI (`.side-panel-tabs`, `.side-panel-tab`, tab rendering logic). Panels no longer have user-selectable tabs. - Separate pop-out and fullscreen buttons in mermaid toolbar (collapsed into single context-aware expand button). ## [0.18.0] — 2026-02-28 ### Added - **Memory system.** Long-term memory across conversations with scope-aware isolation. Three memory scopes: `user` (personal facts/preferences), `persona` (shared across all users of a Persona), and `persona_user` (per-user within a Persona context, e.g. tutoring progress per student). Memories persist across channels and are injected into the system prompt at completion time with scope priority (persona_user > persona > user). - **Memory tools.** Two new LLM-callable tools: - `memory_save` — LLM explicitly stores a fact with key/value/confidence. Scope-aware: saves to the active scope for the current Persona context. - `memory_recall` — LLM queries stored facts relevant to current context. Merges results from applicable scopes with semantic search via embeddings and keyword fallback. - **Automatic memory extraction.** Background scanner finds conversations with sufficient new activity, sends them to the utility model role for fact extraction, and stores results as `pending_review` memories. Configurable extraction prompt per Persona (e.g. "extract FAQ-worthy Q&A pairs"). Scanner runs on a configurable interval with concurrency control. Global kill switch via admin settings. - **Memory review pipeline.** Extracted memories start in `pending_review` status. Admin panel shows pending review queue with bulk approve. Users can approve/reject/edit their own pending memories from the Settings → Memory tab. - **User memory management.** Settings → Memory tab shows active/pending memory counts, filterable/searchable memory list with inline edit and delete. Status filter (active, pending_review, archived). Approve All button for batch operations on pending memories. - **Admin memory controls.** Admin panel → Memory section shows system-wide pending review queue. Memory extraction toggle in admin Settings panel (`memory_extraction_enabled`). Bulk approve endpoint for batch review. - **Hybrid semantic recall.** Memory recall supports both keyword search and vector similarity (cosine distance). Postgres uses `<=>` operator with pgvector; SQLite computes cosine similarity in Go with app-level vector loading. Results are merged and deduplicated. - **Memory injection at completion time.** `BuildMemoryHint()` loads relevant memories and formats them as a system prompt section, injected after the knowledge base hint. Context-budget aware with configurable character limit. Supports embedding-based relevance filtering when the user's latest message is available. - **Persona memory configuration.** Personas gain `memory_enabled` (bool) and `memory_extraction_prompt` (text) fields. When memory is enabled on a Persona, conversations with that Persona contribute to persona-scoped memory extraction. Custom extraction prompts allow specialization (e.g. helpdesk FAQ extraction vs. tutoring progress tracking). - **Database migrations.** Four new migration files (Postgres + SQLite): - `004_v0180_memories.sql` / `sqlite/003_v0180_memories.sql` — `memories` table with composite unique index, full-text search index (GIN/keyword), `memory_extraction_log` tracking table. - `005_v0180_memory_phase2.sql` / `sqlite/004_v0180_memory_phase2.sql` — Persona memory columns, extraction log unique constraint. - **API endpoints.** Eight new authenticated endpoints: - `GET /memories` — list user's memories (filterable by status/query) - `GET /memories/count` — active + pending counts - `PUT /memories/:id` — edit a memory's key/value/confidence - `DELETE /memories/:id` — delete a memory - `POST /memories/:id/approve` — approve a pending memory - `POST /memories/:id/reject` — reject (archive) a pending memory - `GET /admin/memories/pending` — admin pending review queue - `POST /admin/memories/bulk-approve` — admin bulk approve ### Changed - `CompletionHandler` now accepts an `*knowledge.Embedder` parameter for memory injection with semantic relevance filtering. - Persona create/update forms include memory configuration fields (enabled toggle, extraction prompt textarea). - Admin settings save handler includes `memory_extraction` and `memory_extraction_enabled` keys. - `store.Stores` struct includes `Memories MemoryStore` field. - Admin panel sections include Memory with pending review loader. ### Technical Notes - Memory embeddings use `vector(3072)` matching the existing KB/notes schema. HNSW index is not used (pgvector limits HNSW to 2000 dims); filtered sequential scans are performant for per-user memory tables. IVFFlat or dimension reduction available as future optimizations. - SQLite hybrid recall loads embeddings into Go and computes cosine similarity at the application level, reusing the existing `cosineSimilarity()` function from the knowledge base store. - Memory tools use late registration (like KB search and note tools) because they require stores and embedder dependencies initialized in main.go. ## [0.17.3] — 2026-02-28 ### Added - **Wikilink bi-directional linking.** Notes support `[[Title]]` and `[[Title|display text]]` syntax. Links are extracted on save via regex, resolved to target note IDs by case-insensitive title match, and stored in a `note_links` junction table. Dangling links (references to notes that don't exist yet) are preserved and automatically resolved when a matching note is created later. - **Transclusion embeds.** `![[Title]]` syntax renders embedded note content inline in read mode. Content is fetched asynchronously with a recursion guard (max depth 1 — nested transclusions render as plain text references). Transclusion links are visually distinct in both the editor (border-left, italic) and the graph (dashed edges). - **Backlinks panel.** Read mode shows a collapsible "Linked mentions" panel below the note content listing all notes that link to the current note. Each backlink is clickable to navigate directly. Count badge updates on every note open. - **Knowledge graph visualization.** Canvas-based force-directed graph with no external dependencies (~480 lines). Physics: O(n²) Coulomb repulsion, Hooke spring attraction on edges, center gravity. Interaction: pan, zoom (0.15–4.0x toward cursor), drag nodes, hover highlights node + neighbors. Click opens note editor. Nodes sized by √(link_count), colored by folder path (10-color palette). Ghost nodes for unresolved `[[links]]` with toggle button. Energy-based pause (stops rAF when total kinetic energy falls below threshold). ResizeObserver for responsive canvas sizing. - **CM6 note editor.** New `CM.noteEditor()` factory function with live markdown preview (heading sizes for h1–h3, blockquote styling, fenced code block decorations), `[[wikilink]]` autocomplete triggered by `[[` with async title search, and clickable wikilink chip rendering. Falls back to plain `