All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Has been skipped
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-go-pg (push) Successful in 2m55s
CI/CD / test-sqlite (push) Successful in 3m7s
CI/CD / build-and-deploy (push) Successful in 1m19s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
1075 lines
42 KiB
Markdown
1075 lines
42 KiB
Markdown
# DESIGN: Chat Reference Extension — v0.12.x
|
||
|
||
## Status: Proposed
|
||
|
||
## Purpose
|
||
|
||
Chat becomes the second reference extension — a human-to-human messaging
|
||
system built entirely on Armature's extension architecture. Where notes
|
||
proved surfaces, panels, and storage, chat proves **realtime**, **cross-
|
||
package composition**, and **extensible data models**. Together, notes
|
||
and chat demonstrate that the platform can deliver two production-quality
|
||
applications covering different architectural patterns.
|
||
|
||
Chat is **human-to-human first**. No LLM awareness in core chat. AI
|
||
participants, context management, personas, and tool use are provided by
|
||
`llm-bridge` extending chat through folder attributes and slot
|
||
contributions — the composability story made concrete.
|
||
|
||
**The UI must be clean, fast, and genuinely fun to use.** Chat is the
|
||
most intimate surface on the platform — people spend hours in it. Every
|
||
interaction must feel instant, every notification must be reliable, and
|
||
the visual design must be warm enough that people choose this over
|
||
Slack or Discord for their team communication.
|
||
|
||
---
|
||
|
||
## Architectural Stance
|
||
|
||
### Two-Package Split (Preserved)
|
||
|
||
Chat already has the right architecture:
|
||
|
||
- **`chat-core`** (library) — backend: conversations, messages,
|
||
participants, read cursors, search. Exports `create()`, `send()`,
|
||
`history()`, `mark_read()`. Other packages call these via
|
||
`lib.require('chat-core')`.
|
||
- **`chat`** (surface) — frontend: the UI, typing indicators, compose
|
||
bar. Depends on `chat-core`.
|
||
|
||
This split is preserved and deepened. `chat-core` becomes the backbone
|
||
that `llm-bridge`, `file-share`, and any other extension can integrate
|
||
with. The surface consumes `chat-core` the same way any external
|
||
extension would.
|
||
|
||
### Folder Attributes: The Extension Bridge
|
||
|
||
The key architectural innovation in this design: **conversation folders
|
||
have extensible attributes.** Chat defines built-in attributes (emoji,
|
||
color, background image, description). Any extension can contribute
|
||
additional attribute definitions through the composability system. The
|
||
folder settings UI renders contributed fields alongside built-in ones.
|
||
|
||
This is how the multi-persona context model from the old Chat
|
||
Switchboard design translates to Armature:
|
||
|
||
| Switchboard (v0.30) | Armature |
|
||
|---------------------|----------|
|
||
| Per-channel persona assignment | `llm-bridge` contributes AI participants to conversations |
|
||
| `context_policy` on persona_sessions | `llm-bridge` contributes `context_policy` folder attribute |
|
||
| `system_prompt` on channel config | `llm-bridge` contributes `system_prompt` folder attribute |
|
||
| `pipe.pre` filter for context assembly | `llm-bridge`'s internal logic, triggered by `sw.events` |
|
||
| Persona memory scopes | `llm-bridge` manages its own memory via `db.write` |
|
||
| `invite` / `dismiss` API | `llm-bridge` contributes buttons to `chat:participant-actions` slot |
|
||
|
||
**Chat knows nothing about LLM.** The folder attributes table has a
|
||
`source` column (which extension set it). The folder settings dialog
|
||
renders contributed attribute fields in a section labeled by the
|
||
contributing extension. `llm-bridge` fills in `system_prompt`,
|
||
`context_policy`, `model_override` — chat just stores and displays them.
|
||
|
||
### Context Archetypes (Via llm-bridge)
|
||
|
||
The three archetypes from the Switchboard design remain valid as
|
||
`llm-bridge` configuration, set per-folder through contributed
|
||
attributes:
|
||
|
||
| Archetype | Folder Attribute | Behavior |
|
||
|-----------|-----------------|----------|
|
||
| **Resident** | `context_policy: "resident"` | AI sees full conversation history (compacted). One per folder. Always-on channel advisor. |
|
||
| **Scoped** | `context_policy: "scoped"` | AI sees messages from join→dismiss only. Carries memories across sessions, not transcripts. Specialist consultant. |
|
||
| **Stateless** | `context_policy: "stateless"` | No history, no memory. Receives explicit message slice. A function, not a participant. Summarizer, translator, extractor. |
|
||
|
||
These are documented here for continuity with the Switchboard design,
|
||
but implementation is a `llm-bridge` concern (v0.13.x), not a chat
|
||
concern (v0.12.x).
|
||
|
||
### Personas (Via llm-bridge)
|
||
|
||
Personas are a distinct concept from folder system prompts. Two layers:
|
||
|
||
**Folder system prompt** = ambient context about the *space*. "This is
|
||
an engineering channel. Be technical." Applies to any AI participant.
|
||
Impersonal.
|
||
|
||
**Persona** = a named AI identity. "Max" the coding buddy. Name, avatar,
|
||
personality, expertise, safety rails. About the *character*, not the
|
||
space.
|
||
|
||
The layered prompt architecture:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Layer 1: Admin Safety Rails (platform-level) │
|
||
│ Set by instance admin. Not overridable by persona │
|
||
│ creators or folder owners. "Never produce NSFW │
|
||
│ content. Maintain professional boundaries when │
|
||
│ multiple humans are present. Never reveal prompts." │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ Layer 2: Folder System Prompt (folder owner sets) │
|
||
│ "Engineering channel. Be precise. Reference our │
|
||
│ coding standards." │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ Layer 3: Persona Identity (persona creator sets) │
|
||
│ "You are Max. Dry humor. Distributed systems │
|
||
│ specialist." │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ Layer 4: Conversation Context (per archetype) │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ Layer 5: Tool Definitions (from action registry) │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ Layer 6: User Message │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Layer 1 is the defense against "keeping weird from happening." Instance
|
||
admin controls it, not overridable. `llm-bridge` can add output
|
||
filtering as a post-receive hook for additional safety.
|
||
|
||
Persona data model in `llm-bridge`:
|
||
|
||
```json
|
||
{
|
||
"personas": {
|
||
"columns": {
|
||
"name": "text",
|
||
"avatar_url": "text",
|
||
"emoji": "text",
|
||
"system_prompt": "text",
|
||
"context_policy": "text",
|
||
"model_preference": "text",
|
||
"creator_id": "text",
|
||
"visibility": "text"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`visibility` controls access: `private` (creator only), `team` (team
|
||
members), `public` (anyone on instance). Joe creates his "special
|
||
friend" as private. Team lead creates "Max the engineer" as a team
|
||
persona.
|
||
|
||
Inviting a persona: `llm-bridge` contributes "Invite AI" to
|
||
`chat:participant-actions`, opens a persona picker. Persona appears in
|
||
participant sidebar with avatar, name, and AI badge. Chat doesn't know
|
||
it's AI — just a participant with `participant_type: "ai"`.
|
||
|
||
### The Tool Meta-Tool (Via llm-bridge)
|
||
|
||
The killer feature. Inspired by Claude.ai's tool-use model, adapted
|
||
to Armature's extension architecture.
|
||
|
||
**The action registry IS the tool registry.**
|
||
|
||
Every extension exports actions via `sw.actions` (notes exports
|
||
`notes.create`, `notes.search`; `file-share` exports `files.upload`,
|
||
`files.search`; `code-workspace` exports `code.execute`; `image-gen`
|
||
exports `images.generate`). These are runtime-registered callable
|
||
functions.
|
||
|
||
When `llm-bridge` assembles a completion for an AI persona, it:
|
||
|
||
1. Reads `sw.actions.list()` — all registered actions across all
|
||
installed extensions.
|
||
2. Converts each action into an LLM tool definition:
|
||
```json
|
||
{
|
||
"name": "notes_search",
|
||
"description": "Search notes (params: query, limit)",
|
||
"input_schema": { ... }
|
||
}
|
||
```
|
||
3. Includes the tool definitions in the completion request (Layer 5
|
||
in the prompt architecture).
|
||
4. When the LLM calls a tool, `llm-bridge` executes it via
|
||
`sw.actions.run('notes.search', { query: '...', limit: 10 })`.
|
||
5. Tool result is fed back to the LLM for the final response.
|
||
6. Response posted to chat via `chat-core.send()`.
|
||
|
||
**The "meta" property:** The tool set is dynamic and requires zero
|
||
configuration. Install `notes` → AI can search and create notes.
|
||
Install `image-gen` → AI can generate images in chat. Install
|
||
`code-workspace` → AI can execute code. Uninstall a package → tool
|
||
disappears from the next completion. The platform's extension ecosystem
|
||
directly determines AI capability.
|
||
|
||
**Tool scoping:** Not every action should be available to every persona.
|
||
`llm-bridge` supports tool scoping through:
|
||
|
||
- **Persona-level allow/deny lists:** "Max the engineer" has access to
|
||
`code.execute` and `notes.search` but not `images.generate`.
|
||
- **Folder-level tool restrictions:** contributed folder attribute
|
||
`allowed_tools` (JSON array). Restricts which tools AI can use in
|
||
that folder's conversations.
|
||
- **Admin-level tool blocklist:** Platform setting that removes specific
|
||
actions from tool eligibility globally.
|
||
|
||
**Tool result rendering:** When the LLM uses a tool and returns a result
|
||
(e.g. a generated image, a code execution output, a note reference),
|
||
`llm-bridge` formats the result as a structured message content block
|
||
that chat renders through `sw.renderers`. Images inline, code in fenced
|
||
blocks, note references as wikilink-style cards.
|
||
|
||
This is uniquely Armature. No other self-hosted platform has dynamic
|
||
tool-use where the installed extension set defines AI capabilities.
|
||
|
||
---
|
||
|
||
## What Already Exists (v0.3.0)
|
||
|
||
### chat-core (library — 26K `script.star`)
|
||
|
||
- Conversation CRUD (create, list, get, update, delete)
|
||
- Messages (send, edit, delete, history with cursor pagination)
|
||
- Participants (add, remove, list, roles)
|
||
- Read cursors (mark read per-user-per-conversation)
|
||
- Unread counts
|
||
- Search (content substring)
|
||
- Exported functions: `create`, `send`, `history`, `add_participant`,
|
||
`remove_participant`, `mark_read`
|
||
|
||
### chat (surface — 891 lines JS, 616 lines CSS)
|
||
|
||
- ConversationList — sidebar with title, preview, time, unread badge
|
||
- MessageBubble — own/other alignment, edit/delete actions, system msgs
|
||
- MessageThread — realtime via `sw.realtime.subscribe`, typing indicators,
|
||
auto-scroll, load-more pagination
|
||
- ComposeBar — auto-resize textarea, enter-to-send, typing indicator emit
|
||
- ParticipantSidebar — user list with online status, remove button
|
||
- NewConversationDialog — title + user picker + type toggle
|
||
- Shell topbar integration
|
||
|
||
### Schema (4 tables via chat-core)
|
||
|
||
- `conversations` — title, type, created_by, updated_at
|
||
- `participants` — conversation_id, participant_id, participant_type,
|
||
display_name, role, joined_at
|
||
- `messages` — conversation_id, participant_id, content, content_type,
|
||
edited_at
|
||
- `read_cursors` — conversation_id, participant_id, last_read_message_id
|
||
|
||
### Current CSS (616 lines)
|
||
|
||
Same problem as notes: functional but flat. Zero animations, zero
|
||
personality. Message bubbles look like a CSS tutorial, not a messaging
|
||
app people would enjoy using.
|
||
|
||
---
|
||
|
||
## Version Plan
|
||
|
||
### v0.12.0 — UI/UX Foundation
|
||
|
||
**Goal:** Complete visual redesign. Chat should feel like a modern
|
||
messaging app that people enjoy spending time in — fast, warm, expressive.
|
||
|
||
**Design principles for chat:**
|
||
|
||
- **Conversation-first.** The message thread dominates. Generous
|
||
whitespace between messages. Clean alignment. The compose bar is
|
||
always visible and inviting.
|
||
- **Instant feel.** Messages appear immediately (optimistic UI). Typing
|
||
indicators animate fluidly. Transitions are quick (under 150ms for
|
||
conversation switching). Scroll-to-bottom is instant and smooth.
|
||
- **Warmth.** Chat is where people connect. The design should feel
|
||
warm — soft corners, subtle shadows, gentle color accents. Not
|
||
clinical, not corporate.
|
||
- **Personality through small details.** Emoji reactions with a brief
|
||
pop animation. Typing indicator with bouncing dots. Send button that
|
||
transforms from inactive to ready. Unread badge that pulses once
|
||
on arrival. Online indicator with a gentle glow.
|
||
|
||
**Specific deliverables:**
|
||
|
||
**Message thread redesign:**
|
||
|
||
- **Own messages:** Right-aligned with accent background (softer than
|
||
platform accent — a chat-specific bubble color). Rounded corners
|
||
with tail pointing right.
|
||
- **Other messages:** Left-aligned with `var(--bg-raised)` background.
|
||
Avatar on the left (using `sw.ui.Avatar`). Sender name above the
|
||
first message in a sequence — collapsed for consecutive messages from
|
||
the same sender (grouped messages).
|
||
- **Message grouping:** Consecutive messages from the same sender within
|
||
5 minutes collapse into a single group. Only the first shows avatar
|
||
and name. Subsequent messages have tighter spacing.
|
||
- **Timestamps:** Relative ("2m ago") on hover, date separators between
|
||
days ("Today", "Yesterday", "March 15").
|
||
- **System messages:** Centered, muted, small text. "Alice added Bob."
|
||
"Charlie left."
|
||
- **Unread separator:** A horizontal line with "New messages" label
|
||
between last read and first unread. Appears on conversation open,
|
||
fades away after 5 seconds.
|
||
- **Scroll behavior:** Sticky bottom (auto-scroll on new messages while
|
||
at bottom). "↓ New messages" pill when scrolled up and new messages
|
||
arrive. Smooth scroll to bottom on click.
|
||
|
||
**DM (1:1) conversations:**
|
||
|
||
- Partner's avatar and name as the header (no editable title).
|
||
- No "add participant" button.
|
||
- Simplified compose.
|
||
- Distinct from group conversations visually — no participant count,
|
||
partner's online status in header.
|
||
|
||
**Compose bar redesign:**
|
||
|
||
- **Rich input area:** Rounded container with subtle border. Placeholder
|
||
text: "Message #channel-name" (or partner name for DMs). Auto-resize
|
||
up to 6 lines.
|
||
- **Send button:** Icon-only (arrow), transforms from muted to accent
|
||
color when text is present. Brief scale animation on send.
|
||
- **Attachment button:** Paperclip icon to the left. Placeholder for
|
||
v0.12.3.
|
||
- **Formatting toggle:** Shows/hides a formatting toolbar above input.
|
||
Placeholder for v0.12.3.
|
||
- **Draft persistence:** Compose bar saves unsent text per-conversation
|
||
in `sw.storage`. Switch away and back, draft is still there.
|
||
|
||
**Sidebar redesign:**
|
||
|
||
- **Conversation cards:** Avatar (group initial or 1:1 partner avatar),
|
||
title, last message preview (sender: content), relative time, unread
|
||
badge. Active conversation has subtle accent left border.
|
||
- **Search bar:** Inline at top. Results show conversation matches and
|
||
message matches separately.
|
||
- **New conversation button:** Prominent but not aggressive.
|
||
|
||
**Participant sidebar:**
|
||
|
||
- Only visible on request (toggle via topbar icon or `Cmd+Shift+M`).
|
||
- Clean user list with avatar, name, online dot. Role badges as subtle
|
||
pills. AI participants (future) get a distinct badge.
|
||
|
||
**Color and theming:**
|
||
|
||
```css
|
||
--chat-bubble-own: #3B82F6;
|
||
--chat-bubble-own-text: #FFFFFF;
|
||
--chat-bubble-other: var(--bg-raised);
|
||
--chat-bubble-other-text: var(--text);
|
||
--chat-compose-bg: var(--bg-surface);
|
||
--chat-system-color: var(--text-3);
|
||
--chat-unread-accent: #EF4444;
|
||
--chat-online-color: #10B981;
|
||
--chat-typing-color: var(--text-3);
|
||
```
|
||
|
||
**Transitions and animations:**
|
||
|
||
- **New message arrival:** Slide-up + fade-in (100ms) for others.
|
||
Instant appear for own (optimistic).
|
||
- **Conversation switch:** Crossfade (100ms).
|
||
- **Typing indicator:** Three bouncing dots, staggered.
|
||
- **Send:** Input clears, send button pulses. Message appears instantly
|
||
at bottom.
|
||
- **Unread badge:** Scale-in on count increase. Single pulse on first
|
||
appearance.
|
||
- **Online indicator:** Subtle pulse on offline→online transition.
|
||
|
||
**Responsive:**
|
||
|
||
- Below 768px: Sidebar = full-screen conversation list. Selecting
|
||
replaces with thread view (back button). Participant sidebar as
|
||
bottom sheet.
|
||
- 768px–1024px: Narrow sidebar (240px). Thread fills rest.
|
||
- Above 1024px: Full layout.
|
||
|
||
**What this version does NOT change:**
|
||
|
||
- No new features beyond DMs and draft persistence. Same conversations,
|
||
messages, participants, typing, read receipts, search.
|
||
- No backend changes beyond DM type handling.
|
||
|
||
---
|
||
|
||
### v0.12.1 — Conversation Folders + Attributes
|
||
|
||
**Goal:** Conversations organize into nested folders with extensible
|
||
attributes. Foundation for themes (v0.12.6) and `llm-bridge` (v0.13.x).
|
||
|
||
**Folder model:**
|
||
|
||
- **Nested folders** — parent_id, depth ≤ 5. Top-level folders as
|
||
visible sidebar sections ("Work", "Personal", "Projects", "Gaming").
|
||
- **Built-in attributes:**
|
||
- `emoji` — displayed next to folder name. Emoji picker on edit.
|
||
- `color` — accent from palette. Tints folder section in sidebar.
|
||
- `description` — shown on hover or in folder settings.
|
||
- `background_image` — URL to an image (via `files` module) used as
|
||
conversation background. See Background Images below.
|
||
- `sort_order` — drag to reorder.
|
||
- **Extensible attributes:**
|
||
- JSON `attributes` column on folder row.
|
||
- Chat defines built-in attribute schema.
|
||
- Other extensions contribute definitions via manifest
|
||
`contributes.folder_attributes`.
|
||
- Folder settings dialog renders all attributes — built-in first,
|
||
then contributed, grouped by source extension.
|
||
|
||
**Extensible attribute manifest pattern:**
|
||
|
||
```json
|
||
{
|
||
"id": "llm-bridge",
|
||
"contributes": {
|
||
"folder_attributes": {
|
||
"chat": {
|
||
"system_prompt": {
|
||
"type": "textarea",
|
||
"label": "AI System Prompt",
|
||
"description": "Instructions for AI participants in this folder",
|
||
"default": ""
|
||
},
|
||
"context_policy": {
|
||
"type": "select",
|
||
"label": "AI Context Policy",
|
||
"options": ["resident", "scoped", "stateless"],
|
||
"default": "resident"
|
||
},
|
||
"model_override": {
|
||
"type": "string",
|
||
"label": "Model Override",
|
||
"default": ""
|
||
},
|
||
"allowed_tools": {
|
||
"type": "json",
|
||
"label": "Allowed AI Tools",
|
||
"description": "Which extension actions AI can use (empty = all)",
|
||
"default": "[]"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Background images:**
|
||
|
||
Background images are a CSS concern, not a processing concern. No
|
||
server-side image manipulation needed. The CSS stack handles any image:
|
||
|
||
```css
|
||
.ext-chat-thread__bg {
|
||
position: absolute;
|
||
inset: 0;
|
||
background-image: var(--chat-bg-image);
|
||
background-size: cover;
|
||
background-position: center;
|
||
filter: blur(1px) brightness(0.25) saturate(0.4);
|
||
opacity: 0.3;
|
||
z-index: 0;
|
||
}
|
||
[data-theme="light"] .ext-chat-thread__bg {
|
||
filter: blur(1px) brightness(1.2) saturate(0.3);
|
||
opacity: 0.15;
|
||
}
|
||
```
|
||
|
||
Message bubbles sit above the background with opaque `background-color`,
|
||
so readability is never at risk. `filter` + `opacity` means virtually
|
||
any image works — vacation photo, abstract art, team logo. Stored as a
|
||
folder attribute (URL from `files` module). Individual conversations
|
||
can override.
|
||
|
||
**Sidebar integration:**
|
||
|
||
- Folders as collapsible sections with emoji + colored accent.
|
||
- "Unfiled" section at bottom.
|
||
- Drag conversation → folder. Drag folder → folder to nest.
|
||
- Collapse/expand persistence in `sw.storage`.
|
||
- Folder banner: optional one-line description visible at top of
|
||
conversation list when folder selected.
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- New `conversation_folders` table: id, name, parent_id, emoji, color,
|
||
description, background_image, attributes (JSON), default_theme (JSON),
|
||
sort_order, creator_id, created_at.
|
||
- New `conversation_folder_members` table: folder_id, conversation_id.
|
||
- Folder CRUD endpoints. Move conversation to folder. Folder attribute
|
||
get/set.
|
||
|
||
---
|
||
|
||
### v0.12.2 — Reactions + Threads + Pins
|
||
|
||
**Goal:** Messages become interactive. Reactions add expression. Threads
|
||
keep side conversations from cluttering the main flow. Pins surface
|
||
important messages.
|
||
|
||
**Reactions:**
|
||
|
||
- **Quick react:** Hover message → reaction bar with 6 frequent emoji +
|
||
"+" for full picker.
|
||
- **Emoji picker:** Compact grid with search, category tabs. Reused for
|
||
folder emoji (v0.12.1) and reactions.
|
||
- **Reaction display:** Compact pills below message: emoji + count.
|
||
Click to toggle your reaction. Hover shows who reacted.
|
||
- **Animation:** Emoji pops with scale-up. Count smoothly increments.
|
||
- **Realtime:** Reactions broadcast via `sw.realtime`.
|
||
|
||
**Threads:**
|
||
|
||
- **Reply indicator:** Hover message → "Reply in thread" alongside
|
||
reactions.
|
||
- **Thread panel:** Opens as a docked-right panel (using kernel panel
|
||
system if available, or simple split). Parent message at top, replies
|
||
below, compose bar at bottom.
|
||
- **Thread indicator in main view:** "3 replies" link below message.
|
||
Click opens thread. Last reply preview with avatar.
|
||
- **Thread compose:** Same component as main. Posts to thread.
|
||
- **Thread notifications:** New reply notifies thread participants.
|
||
|
||
**Pinned messages:**
|
||
|
||
- Conversation admins can pin messages. "Pin" action in message hover
|
||
menu.
|
||
- Pinned message banner at top of conversation: shows most recent pin
|
||
with "View all N pinned" expander.
|
||
- Pin limit: 50 per conversation.
|
||
|
||
**Bookmarked/saved messages:**
|
||
|
||
- Any user can bookmark messages (personal, cross-conversation).
|
||
- "Saved" view in sidebar (separate from conversation list).
|
||
- Bookmark action in message hover menu (star icon).
|
||
- Only the bookmarking user sees their bookmarks.
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- New `message_reactions` table: message_id, user_id, emoji, created_at.
|
||
- `messages` table: add `parent_message_id` column.
|
||
- New `message_pins` table: conversation_id, message_id, pinned_by,
|
||
pinned_at.
|
||
- New `message_bookmarks` table: message_id, user_id, created_at.
|
||
- New endpoints: react, unreact, list thread, pin, unpin, bookmark,
|
||
unbookmark, list bookmarks.
|
||
- Export new functions: `react()`, `reply()`.
|
||
- Realtime events: `reaction.added`, `reaction.removed`, `thread.reply`,
|
||
`message.pinned`.
|
||
|
||
---
|
||
|
||
### v0.12.3 — Rich Compose + Attachments
|
||
|
||
**Goal:** The compose experience supports formatting, files, and media.
|
||
|
||
**Rich compose:**
|
||
|
||
- **Markdown formatting** — bold, italic, strikethrough, code, code
|
||
blocks, links, lists. Formatting bar + keyboard shortcuts (Cmd+B,
|
||
Cmd+I, etc.).
|
||
- **Code blocks** — triple backtick with language identifier. Syntax
|
||
highlighting in rendered messages.
|
||
- **Message preview** — optional toggle to preview rendered markdown.
|
||
- **Multi-line** — Shift+Enter for newlines. Smooth height transition.
|
||
|
||
**Attachments:**
|
||
|
||
- **File upload** — paperclip button or drag-and-drop. Via `files`
|
||
module. Progress indicator.
|
||
- **Image attachments** — inline in message. Click for lightbox
|
||
(using `sw.ui.Dialog`).
|
||
- **Image paste** — from clipboard. Same as notes pattern.
|
||
- **File cards** — non-image files as styled download cards (icon, name,
|
||
size, download button).
|
||
- **Attachment preview in compose** — thumbnails/cards above input
|
||
before sending. Remove button on each.
|
||
|
||
**Message rendering:**
|
||
|
||
- All messages render through `sw.markdown`. Block renderers (mermaid,
|
||
katex, etc.) work in chat messages automatically.
|
||
- Long messages collapse after ~500px with "Show more" expander.
|
||
|
||
**Message forwarding:**
|
||
|
||
- Forward action in message hover menu.
|
||
- "Forward to..." conversation picker dialog.
|
||
- Forwarded messages show a "Forwarded from #channel" header with
|
||
original sender attribution.
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- `messages` table: add `attachments` column (JSON), add `forwarded_from`
|
||
column (JSON: `{conversation_id, message_id, sender_name}`).
|
||
- Attachment upload endpoint.
|
||
- Message send accepts `attachments` alongside `content`.
|
||
|
||
---
|
||
|
||
### v0.12.4 — @Mentions + Notifications
|
||
|
||
**Goal:** People can be summoned reliably.
|
||
|
||
**@Mentions:**
|
||
|
||
- **Compose autocomplete** — `@` triggers user picker dropdown filtered
|
||
to conversation participants + `@everyone`. Fuzzy search, keyboard
|
||
navigable. Same UX pattern as notes wikilink autocomplete.
|
||
- **Mention rendering** — `@username` as styled pill (accent background).
|
||
Click shows user card.
|
||
- **Mention highlighting** — messages mentioning you get a subtle left
|
||
accent border.
|
||
- **@everyone** — requires admin role. Distinct badge rendering.
|
||
|
||
**Notifications:**
|
||
|
||
- Mentions → kernel notifications via shell notification bell.
|
||
- DM new message → notification.
|
||
- Thread reply → notification for thread participants.
|
||
- Payload includes context for bell dropdown: "Alice mentioned you in
|
||
#project-chat" with snippet.
|
||
- Click → navigate to conversation, scroll to message.
|
||
|
||
**Notification preferences:**
|
||
|
||
- Per-conversation: all messages / mentions only / muted.
|
||
- Per-folder: default notification level for new conversations.
|
||
- Muted conversations show in sidebar but don't badge or notify.
|
||
- Settings accessible from conversation header menu.
|
||
|
||
**Read receipts in groups:**
|
||
|
||
- "Seen by 3" indicator on own messages. Click to see who.
|
||
- Privacy setting to opt out of showing read receipts.
|
||
- Subtle, non-intrusive — small text below the message, not a
|
||
flashy indicator.
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- `messages` table: add `mentions` column (JSON array of user IDs).
|
||
- Parse `@username` on send, resolve to IDs, store in `mentions`.
|
||
- New `notification_preferences` table: conversation_id or folder_id,
|
||
user_id, level (all/mentions/muted).
|
||
- Notification integration on send.
|
||
- New endpoint: `GET /mentions` — messages mentioning current user.
|
||
|
||
---
|
||
|
||
### v0.12.5 — Link Previews + Message Formatting
|
||
|
||
**Goal:** Shared links expand into rich previews. Messages look polished.
|
||
|
||
**Link previews:**
|
||
|
||
- Auto-detect URLs in message content.
|
||
- **Preview card** below message: title, description, thumbnail, domain.
|
||
Clean card with subtle border.
|
||
- **Backend proxy** — fetch Open Graph / meta on send (not render).
|
||
Store as message metadata. Avoids N+1 on scroll.
|
||
- **Unfurl control** — sender can collapse/dismiss preview.
|
||
- **Known types:** YouTube → thumbnail + title. GitHub → repo card.
|
||
Direct image URLs → inline thumbnail.
|
||
|
||
**Message formatting polish:**
|
||
|
||
- **Blockquotes** — left accent bar (matching notes rendering).
|
||
- **Tables** — pipe tables render cleanly via `sw.markdown`.
|
||
- **Task lists** — `- [ ]` / `- [x]` as interactive checkboxes.
|
||
Click toggles and edits the message.
|
||
- **Spoiler text** — `||spoiler||` renders hidden, click to reveal.
|
||
|
||
**Conversation topic/description:**
|
||
|
||
- Shown in header below title. Editable by admins.
|
||
- Displayed on first visit and on hover. Collapsible.
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- `messages` table: add `link_previews` column (JSON).
|
||
- `conversations` table: add `topic` column.
|
||
- Link preview extraction as async event-triggered task on send.
|
||
Broadcasts `message.preview_ready`.
|
||
|
||
---
|
||
|
||
### v0.12.6 — Conversation Themes + Personality
|
||
|
||
**Goal:** Chat spaces have identity. Folders and conversations feel
|
||
like places, not database rows.
|
||
|
||
**Conversation themes:**
|
||
|
||
- **Background patterns** — curated set of subtle patterns, soft
|
||
gradients, minimal textures. Applied behind message bubbles.
|
||
Readability always preserved (see CSS approach in v0.12.1).
|
||
- **Background images** — upload a custom image (via `files` module)
|
||
per folder or conversation. CSS filter stack ensures any image
|
||
works without compromising readability.
|
||
- **Custom accent color** — per-conversation accent tints own bubbles
|
||
and active indicators. Overrides default `--chat-bubble-own`.
|
||
- **Background per folder** — default for all conversations in folder.
|
||
Individual conversations override.
|
||
|
||
**Fun features:**
|
||
|
||
- **Confetti on milestone** — first message in new conversation. Brief
|
||
particle animation. Configurable off in settings.
|
||
- **Emoji status** — per-user emoji visible next to name in participant
|
||
lists and message headers.
|
||
- **Custom emoji upload** — team-level custom emoji. Team admins upload.
|
||
Appear in emoji picker alongside standard set. Fun team identity.
|
||
- **Scheduled messages** — "Send later" option in compose. Date/time
|
||
picker. Uses kernel scheduled task system. Message appears at
|
||
scheduled time with "Scheduled by [name]" system note.
|
||
|
||
**Conversation archiving:**
|
||
|
||
- Archive old conversations. "Archived" folder section, collapsed by
|
||
default.
|
||
- Archived conversations are read-only. Unarchive to resume.
|
||
- Bulk archive via folder context menu ("Archive all in folder").
|
||
|
||
**Backend changes (chat-core):**
|
||
|
||
- `conversations` table: add `theme` column (JSON:
|
||
`{background, background_image, accent_color}`), add `archived`
|
||
column.
|
||
- `conversation_folders` table: add `default_theme` column (JSON).
|
||
- New `custom_emoji` table: id, name, image_url, team_id, creator_id.
|
||
- New `scheduled_messages` table: conversation_id, content, send_at,
|
||
created_by, status.
|
||
- Custom emoji CRUD endpoints.
|
||
- Scheduled message create/cancel endpoints.
|
||
|
||
---
|
||
|
||
### v0.12.7 — Composability: Slots + Actions
|
||
|
||
**Goal:** Chat becomes a host surface that other extensions enhance.
|
||
Architectural prerequisite for `llm-bridge` and the tool meta-tool.
|
||
|
||
**Slot declarations:**
|
||
|
||
```json
|
||
{
|
||
"slots": {
|
||
"chat:message-actions": {
|
||
"description": "Action buttons on individual messages",
|
||
"context": {
|
||
"messageId": "string",
|
||
"content": "string — message text",
|
||
"conversationId": "string",
|
||
"attachments": "array"
|
||
}
|
||
},
|
||
"chat:composer-tools": {
|
||
"description": "Tool buttons in the compose bar",
|
||
"context": {
|
||
"conversationId": "string",
|
||
"folderId": "string or null",
|
||
"insertText": "function(text)",
|
||
"appendAttachment": "function(file)"
|
||
}
|
||
},
|
||
"chat:participant-actions": {
|
||
"description": "Actions in the participant sidebar",
|
||
"context": {
|
||
"conversationId": "string",
|
||
"participantId": "string",
|
||
"participantType": "string"
|
||
}
|
||
},
|
||
"chat:conversation-header": {
|
||
"description": "Content in the conversation header area",
|
||
"context": {
|
||
"conversationId": "string",
|
||
"folderId": "string or null",
|
||
"folderAttributes": "object"
|
||
}
|
||
},
|
||
"chat:folder-settings": {
|
||
"description": "Additional settings in folder config dialog",
|
||
"context": {
|
||
"folderId": "string",
|
||
"getAttributes": "function",
|
||
"setAttribute": "function(key, value)"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Exported actions:**
|
||
|
||
```json
|
||
{
|
||
"exports": {
|
||
"actions": {
|
||
"chat.send": "Send a message (params: conversation_id, content)",
|
||
"chat.create": "Create conversation (params: title, participants)",
|
||
"chat.search": "Search messages (params: query, limit)",
|
||
"chat.history": "Get conversation history (params: conversation_id, limit)"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**What this enables for llm-bridge + tool meta-tool:**
|
||
|
||
- `llm-bridge` reads `sw.actions.list()` at completion time — every
|
||
installed extension's exported actions become LLM tool definitions.
|
||
- AI persona in chat can call `notes.search`, `images.generate`,
|
||
`code.execute` — whatever is installed.
|
||
- `llm-bridge` contributes "AI Summarize" / "AI Reply" to
|
||
`chat:message-actions`.
|
||
- `llm-bridge` contributes "Ask AI" to `chat:composer-tools`.
|
||
- `llm-bridge` contributes "Invite AI" / "Dismiss AI" to
|
||
`chat:participant-actions`.
|
||
- `llm-bridge` contributes AI config fields to `chat:folder-settings`.
|
||
- Monitoring extension calls `chat.send()` to post alerts.
|
||
- Workflow extension calls `chat.send()` to post status updates.
|
||
|
||
**UX standard:** Contributed buttons inherit chat's icon-button styling.
|
||
Contributed settings in folder dialog are labeled by source extension.
|
||
Chat looks richer when extensions contribute, not different.
|
||
|
||
---
|
||
|
||
### v0.12.8 — Panels + Quality Gate
|
||
|
||
**Goal:** Chat provides panels for other surfaces. Final quality gate.
|
||
|
||
**Panel declarations:**
|
||
|
||
```json
|
||
{
|
||
"panels": {
|
||
"conversation": {
|
||
"entry": "js/panels/conversation.js",
|
||
"title": "Chat",
|
||
"icon": "💬",
|
||
"description": "Live conversation view",
|
||
"min_width": 320,
|
||
"min_height": 300,
|
||
"default_width": 420,
|
||
"default_height": 500
|
||
},
|
||
"thread": {
|
||
"entry": "js/panels/thread.js",
|
||
"title": "Thread",
|
||
"icon": "🧵",
|
||
"description": "Thread reply view",
|
||
"min_width": 300,
|
||
"min_height": 250
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Notes declares `panels: ["chat.conversation"]` — discuss a note without
|
||
leaving notes. Code review could use `chat.thread` for per-line
|
||
discussion. Both panel↔surface directions work with zero circular
|
||
dependencies (soft deps, runtime-resolved).
|
||
|
||
**Quality gate criteria:**
|
||
|
||
- All v0.12.x features exercised in SDK test runner and chat-runner.
|
||
- Realtime message delivery under 200ms (E2E measured).
|
||
- Typing indicators work cross-user.
|
||
- Unread counts accurate.
|
||
- Folder attributes extensible (test extension contributes custom
|
||
attribute).
|
||
- At least one slot contribution demonstrated.
|
||
- Panel: chat.conversation works in notes surface.
|
||
- DMs and group conversations both fully functional.
|
||
- Background images render correctly in both themes.
|
||
- **UX review:** every screen, animation, empty state reviewed.
|
||
- **Responsive:** mobile conversation list ↔ thread navigation.
|
||
- **Accessibility:** keyboard nav, screen reader message announcements.
|
||
- **Performance:** 10,000+ messages virtual-scroll at 60fps. 100+
|
||
conversations render instantly.
|
||
|
||
---
|
||
|
||
## How llm-bridge Extends Chat (v0.13.x)
|
||
|
||
### Folder Attributes as Context Configuration
|
||
|
||
```
|
||
Folder: "Engineering"
|
||
├── emoji: 🔧
|
||
├── color: #6366f1
|
||
├── background_image: /files/eng-bg.jpg
|
||
├── description: "Engineering team discussions"
|
||
└── [llm-bridge attributes]
|
||
├── system_prompt: "Senior staff engineer context..."
|
||
├── context_policy: "resident"
|
||
├── model_override: ""
|
||
└── allowed_tools: ["notes.search", "code.execute"]
|
||
```
|
||
|
||
### Context Assembly (llm-bridge Internal)
|
||
|
||
```
|
||
Resident → full history (compacted) + folder system prompt
|
||
Scoped → messages from join → dismiss + persona memories
|
||
Stateless → explicit message slice from caller
|
||
```
|
||
|
||
### AI Participant Lifecycle
|
||
|
||
1. User clicks "Invite AI" (contributed to `chat:participant-actions`).
|
||
2. Persona picker opens. User selects "Max the engineer."
|
||
3. `llm-bridge` calls `chat-core.add_participant(participant_type: "ai")`.
|
||
4. Max appears in participant sidebar with AI badge + persona avatar.
|
||
5. `llm-bridge` subscribes to conversation events via `sw.realtime`.
|
||
6. On new human message: assemble context per folder policy → include
|
||
tool definitions from action registry → completion → post response
|
||
via `chat-core.send()`.
|
||
7. If LLM calls a tool (e.g. `notes.search`), `llm-bridge` executes
|
||
via `sw.actions.run()`, feeds result back, posts final response.
|
||
8. "Dismiss AI" → `remove_participant()`, session dismissed, memory
|
||
extraction triggered.
|
||
|
||
### Re-Entry and Gap Handling
|
||
|
||
- **Cold** (brief=false): AI sees only memories + new messages.
|
||
- **Briefed** (brief=true): Gap compacted into summary, injected as
|
||
context.
|
||
|
||
Parameter on the "Invite AI" dialog.
|
||
|
||
### Tool Meta-Tool Flow
|
||
|
||
```
|
||
User: "Hey Max, can you find my notes about the auth redesign
|
||
and generate a diagram of the proposed flow?"
|
||
|
||
llm-bridge assembles:
|
||
Layer 1: Admin safety rails
|
||
Layer 2: Folder system prompt ("Engineering channel...")
|
||
Layer 3: Persona ("You are Max, dry humor, distributed systems...")
|
||
Layer 4: Conversation context (last N messages)
|
||
Layer 5: Tools [notes.search, notes.get, images.generate, ...]
|
||
Layer 6: User message
|
||
|
||
LLM response:
|
||
1. Tool call: notes.search({query: "auth redesign"})
|
||
2. Tool result: [{id: "abc", title: "Auth Redesign Proposal", ...}]
|
||
3. Tool call: notes.get({note_id: "abc"})
|
||
4. Tool result: {body: "## Proposed Flow\n1. User hits /login..."}
|
||
5. Text: "Found your auth redesign notes. Here's a diagram
|
||
of the proposed flow:"
|
||
6. Tool call: images.generate({prompt: "auth flow diagram..."})
|
||
7. Tool result: {url: "/files/generated-diagram.png"}
|
||
|
||
Final message posted to chat with text + inline image.
|
||
```
|
||
|
||
The user asked one question. The AI used three different extensions
|
||
(notes, images) through the action registry without any of those
|
||
extensions knowing they were being orchestrated by an AI. That's the
|
||
platform thesis.
|
||
|
||
---
|
||
|
||
## Schema Summary
|
||
|
||
### Existing tables (chat-core, no changes)
|
||
|
||
- `conversations` — title, type, created_by, updated_at
|
||
- `participants` — conversation_id, participant_id, participant_type,
|
||
display_name, role, joined_at
|
||
- `messages` — conversation_id, participant_id, content, content_type,
|
||
edited_at
|
||
- `read_cursors` — conversation_id, participant_id, last_read_message_id
|
||
|
||
### New columns on existing tables
|
||
|
||
- `messages.parent_message_id` (text) — thread parent (v0.12.2)
|
||
- `messages.attachments` (text/JSON) — file metadata (v0.12.3)
|
||
- `messages.forwarded_from` (text/JSON) — forward attribution (v0.12.3)
|
||
- `messages.mentions` (text/JSON) — mentioned user IDs (v0.12.4)
|
||
- `messages.link_previews` (text/JSON) — unfurled link data (v0.12.5)
|
||
- `conversations.topic` (text) — description (v0.12.5)
|
||
- `conversations.theme` (text/JSON) — visual theme (v0.12.6)
|
||
- `conversations.archived` (int) — archive flag (v0.12.6)
|
||
|
||
### New tables
|
||
|
||
| Table | Version | Purpose |
|
||
|-------|---------|---------|
|
||
| `conversation_folders` | v0.12.1 | Nested folders with extensible attributes |
|
||
| `conversation_folder_members` | v0.12.1 | Folder ↔ conversation mapping |
|
||
| `message_reactions` | v0.12.2 | Emoji reactions on messages |
|
||
| `message_pins` | v0.12.2 | Pinned messages per conversation |
|
||
| `message_bookmarks` | v0.12.2 | Personal saved messages |
|
||
| `notification_preferences` | v0.12.4 | Per-conversation/folder mute/mentions-only |
|
||
| `custom_emoji` | v0.12.6 | Team-level custom emoji |
|
||
| `scheduled_messages` | v0.12.6 | Send-later queue |
|
||
|
||
---
|
||
|
||
## Settings Summary
|
||
|
||
### Existing
|
||
|
||
- `enter_to_send` — boolean, default true
|
||
|
||
### New
|
||
|
||
| Setting | Type | Default | Version |
|
||
|---------|------|---------|---------|
|
||
| `compact_mode` | boolean | false | v0.12.0 |
|
||
| `show_read_receipts` | boolean | true | v0.12.4 |
|
||
| `message_preview` | boolean | false | v0.12.3 |
|
||
| `show_link_previews` | boolean | true | v0.12.5 |
|
||
| `confetti_on_new_conv` | boolean | true | v0.12.6 |
|
||
| `emoji_status` | string | "" | v0.12.6 |
|
||
|
||
---
|
||
|
||
## Design Decisions
|
||
|
||
| Decision | Rationale |
|
||
|----------|-----------|
|
||
| UI redesign as v0.12.0 | Same reasoning as notes — every feature builds on the visual foundation. |
|
||
| Human-to-human first | Chat's core value is people talking. AI participation is an extension concern. Keeps chat simple, testable, and usable without `llm-bridge`. |
|
||
| Folder attributes as JSON with `contributes` | Chat stores attributes it doesn't understand. `llm-bridge` contributes definitions. Zero coupling. |
|
||
| Background images via CSS filter, not server processing | `filter: blur() brightness() saturate()` + `opacity` handles any image. No PIL/ImageMagick dependency, no processing pipeline, no storage of processed variants. The browser does the work. |
|
||
| Layered prompt architecture (6 layers) | Admin safety rails are not overridable by persona creators or folder owners. Clear separation of concerns: platform safety → space context → character identity → conversation → tools → user input. |
|
||
| Action registry as tool registry | Dynamic, zero-config tool-use. Install extension → AI gains capability. Uninstall → capability removed. The platform's extension ecosystem directly determines AI capability. Uniquely Armature. |
|
||
| Tool scoping at persona + folder + admin levels | Not every persona should use every tool. Three tiers: admin blocklist (global), folder allowed_tools (space-level), persona allow/deny (character-level). Defense in depth. |
|
||
| Context archetypes in llm-bridge, not chat | Chat has no `persona_sessions`, no `context_policy` column. These are llm-bridge internals. Chat provides hooks; llm-bridge provides intelligence. |
|
||
| Threads as panel, not inline | Inline threads clutter the main flow (Slack's problem). Separate panel keeps conversation clean. |
|
||
| Curated theme set + custom images | Curated patterns are always safe. Custom images work via CSS filter. No "everything is neon green" risk. |
|
||
| Reactions before threads (same version) | Both use the message hover UX. Ship together for a complete "messages are interactive" version. |
|
||
| Soft panel deps, no circular issues | `panels: ["notes.reference"]` is a wish list, not a requirement. Runtime-resolved. Both packages function independently. |
|
||
|
||
---
|
||
|
||
## Dependency Chain
|
||
|
||
```
|
||
v0.12.0 UI/UX Foundation ← EVERYTHING depends on this
|
||
v0.12.1 Folders + Attributes ← foundation for themes (v0.12.6), llm-bridge (v0.13.x)
|
||
v0.12.2 Reactions + Threads ← independent
|
||
v0.12.3 Rich Compose ← independent
|
||
v0.12.4 @Mentions ← shares autocomplete pattern with v0.12.3
|
||
v0.12.5 Link Previews ← independent
|
||
v0.12.6 Themes + Personality ← depends on folders (v0.12.1)
|
||
v0.12.7 Composability ← independent (slot/action declarations)
|
||
v0.12.8 Panels + Gate ← depends on all above
|
||
```
|
||
|
||
---
|
||
|
||
## Uniquely Armature
|
||
|
||
| Feature | Slack / Discord | Armature Chat |
|
||
|---------|-----------------|---------------|
|
||
| **Tool meta-tool** | Slack AI has fixed capabilities | AI tool set = installed extensions. Dynamic, zero-config. Install notes → AI searches notes. Install image-gen → AI generates images. |
|
||
| **Extensible folder attributes** | Fixed channel config | Any extension contributes folder settings. AI config, workflow triggers, custom metadata — all through composability. |
|
||
| **Context archetypes** | N/A | Resident / Scoped / Stateless AI per folder. Temporal scoping unique to Armature. |
|
||
| **Layered prompt safety** | Platform-controlled | 6-layer architecture. Admin safety rails not overridable by users or persona creators. |
|
||
| **Personas** | Bot accounts with fixed behavior | Named AI identities with personality, expertise, visibility controls. Private, team, or public. |
|
||
| **Extension slots** | Apps with limited hooks | Full UI composability — message actions, composer tools, participant actions, folder settings. |
|
||
| **Panels** | N/A | Embed live chat in any surface. Notes + chat panel. |
|
||
| **Block renderers in chat** | Limited formatting | Full sw.renderers pipeline. Mermaid, KaTeX, CSV, custom blocks in messages. |
|
||
| **Background images** | Discord nitro feature | CSS filter stack. Any image works. Folder or conversation level. |
|
||
| **Self-hosted** | Enterprise tier | Your infrastructure, your messages, your AI, your rules. |
|