ELNOR REPO READER TEXT MIRROR Original path: Current Specs/DOC20/DOC20_R4_3.md Source repo: /Users/OpenClaw1/Elnor/Elnor Specs Git branch: main Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331 Generated: 2026-06-09T01:23:58.539Z --- # DOC20 — Project, Browser, Notes, and Document Viewer Addendum **Version:** R4.3 **Date:** 2026-04-09 **Status:** Consolidated implementation-ready revision — R4.3 supersedes all prior versions on all subjects **Scope:** Unified Workspace shell, Browser, projects, DOC7 project integration, notes, To-Do system, Calendar module, Floating Palette, agent awareness, AI wiring contract, data schemas, command contracts, Document Viewer, Embedded Web Browser, Session System, Icon System, Split View, Notification System, Skills & Connectors page **Intent:** Implementation-complete spec for Q/EC/OpenClaw coordination — a coding agent can build from this document without guessing **Applies to:** Q Dashboard, EC durable state, DOC7 integration surfaces, project-scoped work, note editing and export, document viewing and review, unified tab system, navigation architecture, floating palette, to-do lists, calendar events --- ## CHANGELOG | Date | Inputs | Areas Updated | Summary | |---|---|---|---| | 2026-04-09 | Skills & Connectors page design (proposal V1, mockup iteration, OpenClaw ecosystem research) | §6.29 (new — 30+ subsections), §6.18.2 (content registry), §8 (commands), §12 (cross-doc) | R4.3: Skills & Connectors Page — new Q content surface for unified capability management. 6 tabs: My Capabilities (unified browse of all capability types with CapabilityCardSchema read-model, 7-section detail drawer with confidence history / usage timeline / provenance / linked entities, inline edit for learned procedures via BundleEditOperation), Connectors & Accounts (MCP server management, OAuth account connections, multi-account, health monitoring), OpenClaw Tools (ClawHub catalog with 13,700+ skills, plugin management, install/update/uninstall), Expose Elnor (reverse MCP server with exposure scope / knowledge scope / action permissions / audit trail), Learn & Teach (DOC3 learning flow surfaces), Hub (Phase 2 placeholder). Backend split: DOC16 Entry 16.7 owns connector lifecycle / credential management / reverse MCP config; DOC11 owns gateway tool catalog routes and reverse MCP runtime; DOC24 owns capability registry queries and MCP registration; DOC72 owns procedure graph queries. Action matrix defines per-type CRUD (learned procedures: full edit/promote/archive/export; native skills: edit/disable/uninstall; MCP tools: read-only with connector link; system tools: read-only with settings link). Usage data from ProcedureExecutionOutcomeEvent records. | | 2026-03-12 | Design discussion, DOC7, existing project specs | All | Initial draft R1 | | 2026-03-13 | Design review, DOC10 R11, DOC15 R7.1, DOC18 R2, Ableton Live browser reference, notes architecture discussion | §3-§6, §10 | R1.1 comprehensive revision | | 2026-03-14 | Design iteration session (Claude), mockup artifacts | §3 Browser, §4 Projects, §6 Comments/Tracked Changes, new §6.15 AI Wiring | R1.2: visual design pass — single-line browser, Home tab, highlight-to-comment, Review dropdown, author-colored changes, AI payload schemas, multiple overlays, archive/delete semantics | | 2026-03-15 | Implementation completion redline (ChatGPT), folders feature design, self-audit | All sections | R1.3: implementation-complete — exact TypeScript schemas for all data contracts, browser state model + query contract, change-set tracked changes, comment anchor remapping, note storage layout + autosave, project schema + creation flow + membership table, Places/linked-folder wiring, Folders scope, command envelopes, deep-link routing, acceptance criteria additions | | 2026-03-16 | Multi-session mockup iteration (Claude + Will), Browser V4, Notes V5, Document Viewer V6 | §3 Browser, §6 Notes, §6.16 Document Viewer, §7-§8 Contracts, §9-§13 | R1.4: Artifact Viewer → Document Viewer (any file type). Tabbed right panel (Comments + Send to Agent). Two-tier Send to Agent (Respond in Chat / Send with Instructions + result formats including Respond in Comments). Select-comments scope. Per-comment send icon. Folder overlay toggle. Browser: scope deselection, 5 sort options, 14 type chips, collections filter, archive/delete, footer. Notes: search/sort, archive/delete, deep subfolders, user-colored tracked changes, +Folder/+Note buttons, Send to Agent drawer with chat selector. Full comment interaction wiring (Reply/Edit/Resolve/Reopen/Delete). Immutable versioning model. Reference ID in all send payloads. Responsive toolbar collapse. | | 2026-04-05 | Multi-session Q Unified Workspace mockup evolution (V4→V7.3, 9 font variants), 37-item instruction document, 44-item change audit | §0, §1, §3.4, §3.6, §6.15.10, §6.19.9, §6.20 (new — 28 subsections), §7, §8, §9, §13 | R3: Q Unified Workspace Shell — the workspace evolves from a "three-view" (notes, documents, web) to the primary Q app interface. New §6.20 defines: unified tab system supporting 8 content types (note, doc, web, clips, chat, room, task, utility) with type-specific colors and icons; Nav tab as 4th browser mode replacing the left sidebar menu (conversations, activity, pages, open tabs); transient utility tabs with blue outline border, leftmost insertion, one-at-a-time replacement, persist-on-navigate (no auto-close), right-click Pin; duplicate tab prevention; left nav rail simplified to 6 items (Q logo, browser toggle, bell with badge, settings, chat column toggle); right chat column (320px, independent of Ask panel, at app right edge); Ask Agent panel: context card, Include checkboxes, attachment button, inline agent response with "Continue in full chat →"; Chats management page (search, filters, star toggle); session system (auto-create on launch, "Clips: {M.D}-{N}" naming, close on quit/idle/manual); expanded [+] dropdown with Create/Open/Saved Groups sections. Browser: Folders as scope (not overlay), resizable splitter (150px default, 400px max, 6px handle), Panel and Forum type chips added (17 total), scope chips reduced to 5 (Folders button separate). Notes browser: folders-at-top + flat-all-notes-below layout, independent sort dropdowns, single-line note rows, draggable folders, folder rename/delete. Bookmarks: full Chrome-like CRUD with favicon squares. Clips unified with notes (same renderer, toolbar, blocks). IBM Plex Sans as working font. Web tab favicons. Tab bar design tokens codified. | | 2026-04-03 | Q Browser v2 design session — tabs, privacy, credentials, downloads, reader mode, clippings, context pinning, DOC72 signal architecture | §6.19 (major expansion, 14 new subsections) | R2.3: Q Browser Full Feature Spec — tabs and tab groups, URL-as-search-bar, cookie/privacy controls with domain exclusion list, incognito mode, downloads manager, credential vault with macOS Keychain encryption and Elnor permission model, personal info autofill, Chrome extension loading, native ad blocking, reader mode, Send to Chat, clippings-to-note, pin-as-context (bucket or session), screenshot/full-page PDF capture, PDF interception to Document Viewer, custom context menus, zoom controls, DOC72 signal emission (BrowserPageVisitSchema + M365PageMetadataSchema) with exclusion-list filtering, WebBrowserReadSkill for Elnor awareness, browser settings page, profile export/import. Bookmark folders. Audio handling. All Chrome-equivalent behaviors for standard browser operations. | | 2026-03-28 | Embedded browser concept exploration, Electron feasibility analysis, mockup (Q_BROWSER_VIEW_V1.jsx) | §6.19 (new section) | R2.2: Embedded Workspace Browser — §6.19 added (11 subsections, ~220 lines). Full web browser within Q using Electron ``. Reuses existing interaction surfaces (bubble menu, comment rail, Ask Elnor panel, Save as Artifact/Note). Content extraction via mozilla/readability for Elnor context fusion. Electron-dependent — hidden in web app mode. WebBookmark and WebPageSave commands. Bookmarks, passwords, tabs, agent-driven browsing explicitly deferred to v2. Dual capability model: web app (default) + Electron desktop app (optional). | | 2026-03-19 | 5-reviewer red-team cycle + self-audit + E2E wiring audit | All sections | R2: Contract Closure — full enum canonicalization (context_bucket→bucket, generated_artifact→artifact across all tables/maps/routes). AttachmentSchema split to blob+ref. 20+ missing commands added (ModulePreset CRUD, FeedRefresh, DocumentSelectionAI, NoteCommentReanchor, TrackedChangeOverlapResolve, NoteAIPreviewAccept/Reject, NoteAICancel, NoteTemplateSave, ProjectDuplicate, PlaceRename, TodayNoteResolve, Recently Deleted restore/permanent-delete). InlineThread dispatch clarified (compound command). Agent feed response format contract. move_to_folder phantom fixed. RendererCapabilities reconciled with docx entry. Context menu routing table rewritten with exact command names. Export format matrix. DnD state machine. ArtifactVersionSchema + read contract. Iframe sandbox. Tracked-change flush-first rule. DOC20Settings command. 8 E2E gaps closed. | | 2026-03-19 | Feed architecture redesign, +Module toolbar, block spacing, mockup V5–V5.2 iteration | §6.2A.3 ActivityFeed, §6.2A.6 Block insertion, §6.2C ModulePreset, §6.2B Today note, §6.3.1 Storage, §7.6A–B Schemas | R1.8: Feed Architecture + Module Presets — ActivityFeed blocks rewritten from cron/subscription model to lazy-evaluation (refresh-on-view, zero background cost). Two source types: system events (filtered from EC activity_log.jsonl via WebSocket) and agent feeds (single agent call on stale interval). ModulePreset schema and library (§6.2C). Pre-built presets: System Activity, System Notices, Gate Approvals, Active Operations (system), Morning Summary, Email Watch, Deadline Tracker (agent). +Module toolbar button replaces + inserter. Block spacing: 8px margin above/below, no auto-inserted spacers. Feed dormancy after configurable expiry (default 15 days). Feed cache at notes/{note_id}/feed_cache/. | | 2026-03-19 | Workspace mockup iteration (Claude + Will), block architecture design session | §3.4–3.5 Browser scopes, §6.1–6.2 Notes model, §6.15 Notes AI, §7 Schemas, §8 Commands, §11–§13 | R1.7: Block-Based Workspace Architecture — notes become block-based modular documents. Block types: Text (implicit), TaskList, ActivityFeed, InlineThread, ConfigurableBar. + inserter between blocks, /slash commands, drag reorder, collapse/expand. Today note as workspace home page with rollover behavior. Notes scope added to browser with dedicated folder tree (replaces separate notes sidebar). Inline threads as durable comments with display_mode. @mention agent invocation. Unified note model: to-do lists are notes with TaskList blocks, no separate to-do schema. New commands: BlockInsertCommand, BlockDeleteCommand, BlockReorderCommand, TodayNoteRolloverCommand, InlineThreadCreateCommand. | | 2026-03-19 | 5-reviewer red-team cycle (ChatGPT, Grok, Gemini, Codex, Claude Code) + self-audit | §3 Browser, §6.6 Comments, §6.10 Tracked Changes, §6.15 Notes AI, §6.16 Document Viewer, §6.18 Content Map, §7 Schemas, §8 Commands, §12 Cross-doc | R1.6: Contract Hardening — Canonical Enum Lock (§7.0A), ECCommandResult response envelope + idempotency, per-command error tables, Browser Context Menu Routing Table (§8.8), CommentAnchor discriminated union, RendererCapabilities, NoteReviewRequest + NoteReviewCommand, note write serialization, BrowserResolver SLOs, archive/delete/trash model, orphaned comment display, tracked-change overlap policy, stale reference UX, artifact creation heuristics, version major/minor rules, Save as Prompt modal, full-text note search, table support, NoteDuplicateCommand, 4 ArtifactDiff commands, empty states, cross-doc obligations expanded. | | 2026-03-16 | Cross-doc analysis (EC Core, DOC7, DOC12, DOC15, DOC17, DOC3, DOC4, DOC18, DOC16) | §6.18 Unified Content Map, §7.18-7.19 Contracts, §8.6B-8.6C Commands, §12-§13 | R1.5: Unified Content Map (§6.18) — complete registry of 16 browsable + 20 system-internal content types cross-referenced from 9 specs. Artifact creation pipeline (chat output → durable artifact → DocIndex). Attachment model (reference-not-copy, dedup by hash). Browser discovery aggregation (BrowserResolver reads 13+ sources). Elnor content awareness (4-layer: DocIndex → QMD → DOC18 → DOC15). Lifecycle rules (delete/archive/orphan). Save as Prompt (→DOC17) and Save as Artifact from chat flows. Content Registration check added to cross-doc review process. | | 2026-04-06 | Complete spec delta V7.4→V7.11 (527-line delta covering 8 mockup versions), DOC72 R5.6 entity mapping discussion, DOC24 R2.4 delivery architecture | §0.4, §1.6, §6.1 (nav rail), §6.3 (tabs), §6.4 (browser column), §6.12 (doc title), §6.14 (note canvas modules), §6.15 (chat tab), §6.16 (ask panel), §6.17 (status bar), §6.19.9 (bookmarks bar), §6.20 (major expansion — 20+ subsections), §6.21 (new — To-Do System), §6.22 (new — Calendar Module), §6.23 (new — Icon System), §6.24 (new — Split View), §6.25 (new — Notification System), §6.18.2 (content registry), §7 (schemas), §8 (commands) | R4: Floating Palette V2 + To-Do + Calendar + Icon System + Split View + Notifications — Floating palette redesigned from 3 tabs to sidebar architecture (☰ sidebar with Command bar, Notices, Activity, Quick Actions + 3 persistent content tabs Chat/Note/To Do). Unified To-Do system with shared `fpTodoLists` data pool across 3 view surfaces (palette, note modules, standalone tabs). Calendar module (Month/Week/Day/List views, Outlook integration via DOC16, event editor, agent instructions). Two-layer data architecture: EC application tables (Layer 1) + DOC72 entity graph extraction (Layer 2) for to-do items → `obligation` nodes, calendar events → `obligation` nodes, lists → `work_product` nodes. Complete SVG icon system replacing all emoji (Heroicons primary, Lucide/Tabler selective). Chrome-style tab bar (white active tabs, proportional shrinking, `display:contents` groups, colored outlines). Split view (independent dual panes, draggable divider). Linear-style notification inbox with source-colored cards, snooze, silent mode, delivery matrix. Pin (always-on-top) toggle. Document title right-click menu. Zoom slider in status bar. Per-tab right panel state. Tab icon format migration (emoji → string IDs). Module block type `tasks` → `todo`. Browser scope expansion (Notes tab scope toggles: Notes/To Do/Cal). Ask button simplification (per-task removed, list-level kept). Naming: "Conversations" → "Chats", "Knowledge Manager" → "Knowledge". Tab colors: web → slate gray, chat → dark blue, todo → teal (new). | | 2026-04-07 | Architecture discussion — centralized data model, multi-surface rendering, networking readiness, agent contention, DOC72 intake contracts | §2.4 (new), §2.5 (new), §2.6 (new), §6.21.2 (schema expanded), §6.21.7 (new), §6.21.8 (new), §6.22.4 (schema expanded), §6.22.8 (new), §6.22.9 (new), §12 (updated) | R4.1: Centralized Data, Multi-Client Architecture, and Surface Intake Contracts — Establishes the surface independence principle: every Q rendering surface is a stateless viewport subscribing to EC-owned durable state. Defines three-tier content sync matrix. Specifies EC subscription contract (surface registration, push channel, optimistic updates, note edit lock, agent busy indicators). Expands TodoList/TodoTask schemas with `project_id`, `tags`, `attachments`, `done_at`, `created_at`, `updated_at` and TodoAttachment schema. Expands CalendarEvent schema with `event_type` (10 categories), `participants`, `attachments`, `project_id`, `tags`, `source`, `external_id`. Adds signal emission and DOC72 integration subsections for To-Do (§6.21.7, deterministic + entity resolution) and Calendar (§6.22.8, deterministic + entity resolution). Identifies Notes intake gap (§6.22.9 — LLM-assisted extraction needed, cross-doc to DOC72). Produces separate DOC72 Proposal: Surface Intake Contracts specifying full intake contracts for all four surfaces. Flags DOC25 (proposed) for server-side sync. | | 2026-04-07 | Entity resolution hierarchy clarification, project_id vs matter distinction | §6.21.2 (updated), §6.21.7 (updated) | R4.2: Entity Resolution Hierarchy — Establishes that individual task `text` is the primary entity resolution signal (not the list name). Each task item ("Prepare expert report in Paramount") is resolved independently for matter/case, people, document types, and actions. List `name` is contextual — provides temporal framing ("February 8") or matter framing ("Henderson MTD Prep") that boosts confidence on task text matches. Subtask text inherits parent task's resolved entity context. Explicit separation: `project_id` is organizational grouping; matter/case association is independent via DOC72 entity resolution. Extraction table reordered by priority. DOC72 Proposal updated to V2 with matching hierarchy. | --- ## 0. Preamble ### 0.1 Purpose This addendum defines the visible UX and implementation contract for fourteen closely related surfaces: 1. the **Q Unified Workspace** shell — tab system, navigation, layout, session system (§6.20, R3); 2. the **Browser** column in Q — including the Nav tab (R3); 3. **Projects** and the Project page; 4. **DOC7 Context Buckets as used by projects**; 5. the first-class **Notes** system — including clips (unified with notes, R3); 6. **Places**, **Linked Folders**, and **Folders** as organizational and file-access surfaces; 7. the **Document Viewer**; 8. the **Embedded Web Browser** (§6.19); 9. the **Floating Palette** — always-on-top mini workspace with Chat, Note, To Do tabs and sidebar (§6.20.30, R4); 10. the **To-Do System** — unified data pool with 3 view surfaces (§6.21, R4); 11. the **Calendar Module** — embeddable calendar with Outlook integration (§6.22, R4); 12. the **Icon System** — SVG icon components replacing all emoji (§6.23, R4); 13. the **Split View** — independent dual panes with draggable divider (§6.24, R4); and 14. the **Notification System** — Linear-style inbox, delivery matrix, silent mode (§6.25, R4); and 15. the **Skills & Connectors** page — unified capability management, connectors, OpenClaw tools, reverse MCP, learning flows (§6.29, R4.3). This document is intentionally implementation-heavy. Its purpose is to make these surfaces concrete enough that a coding agent can build them without guessing at schemas, state shapes, command payloads, or interaction sequences. ### 0.2 Non-negotiable architecture rules The following existing suite rules remain in force: - **EC is the single durable writer.** Q never writes durable project, bucket, note, collection, saved view, folder, or file-link state directly. - **OpenClaw autonomy must be preserved.** OpenClaw continues to own native tool execution, browser/desktop/file actions, and terminal execution. DOC20 may guide and trigger those actions, but must not over-micromanage them. - **Memory and learning must be bounded, observable, and defeatable.** Projects and notes must not create hidden background learning or silent long-term memory mutation. - **Single-writer discipline is sacred.** All new durable write paths introduced here must go through EC commands and append-only JSONL + atomic JSON patterns where practical. - **No ambient hidden focus mode.** Merely viewing or selecting a project in Q must not silently alter unrelated chats, tasks, rooms, panels, or scheduled automations. ### 0.3 Relationship to prior specs This addendum is **additive**. It does not replace DOC7. It reuses DOC7's bucket model, assignment semantics, indexing, manifests, background storage, and picker concepts, but allows the project UI to present those capabilities differently. This addendum also narrows the visible UX role of "projects" relative to older project-loading concepts. DOC20 treats projects as: - an organizational grouping; - a durable default context container for project-scoped work; and - a filtered operating surface. If older internal project-loading behavior remains elsewhere in the suite, it must not create hidden ambient context beyond what is visible and explicit in this addendum. ### 0.4 Core design choices in DOC20 DOC20 adopts the following decisions from the design discussion: - Keep the word **Scope** in the Browser. - Permit **Project**, **Context Bucket**, and **Folder** to exist both as browser scope values and as item types. - Solve overlap by list behavior rules, not by inventing a different abstraction. - Keep **Configure** as the canonical place to add/remove items to/from a project. - Keep **Chats** and **Rooms** together in the visible UX where appropriate. - Use **Tiptap OSS** as the notes editor base. - Add **custom comments** and **custom Elnor/AI editing actions** on top of Tiptap OSS. - Use **Pandoc** for DOCX/PDF export. - Keep **Notes** as their own main menu page while also allowing notes to belong to a project. - Use **Folders** as a virtual hierarchical organizer (scope family in the Browser) that does not change storage layout. **R3 additions:** - The **Q Unified Workspace** is the primary interface for the entire Q app. The tab system holds any content type — not just notes, documents, and web pages. - The **Nav tab** in the browser column is the app's main navigation surface, replacing the traditional left sidebar menu. - **Clips are notes.** Same renderer, same toolbar, same block modules. Only difference: purple accent color and auto-populated clip blocks. No separate code path. - **Agent name is always dynamic.** All UI text references `agent.name` — never hardcoded "Elnor." Users may change the agent name. - **Utility tabs are transient but persistent.** They persist when navigating away (no auto-close), but only one exists at a time — opening a new utility page replaces the current one. Blue outline border distinguishes from working tabs. - **IBM Plex Sans** is the working UI font. Production may use Söhne (licensed) or final selection TBD. - **Content fills available width.** No artificial maxWidth constraints. User controls width via resizable browser column and right panel. - **Folders button is separate from scope chips.** Visually distinct (dotted border, tinted background) but triggers the same scope mechanism. Not duplicated in the scope chip list. - **Bookmark favicons are colored squares** with domain initial letter (14px, 3px border-radius). Simulates Chrome favicon behavior. - The **right chat column** is independent of the Ask Agent panel. Chat column at app edge; Ask panel within workspace content area. **R4 additions:** - **Two-layer data architecture** for To-Do and Calendar data: Layer 1 = EC application tables (`ELNOR_MEMORY/todo_items`, `ELNOR_MEMORY/todo_lists`, `ELNOR_MEMORY/cal_events`) for fast CRUD; Layer 2 = DOC72 entity graph for semantic queries. DOC20 describes the UI; EC owns the writes; DOC72 owns the extracted knowledge. - **Floating Palette V2** is a sidebar-based control surface. Three persistent content tabs (Chat, Note, To Do) with state preservation. ☰ sidebar slides out left with Command bar, Notices (notification inbox), Activity feed, Quick Actions. Pin (always-on-top) toggle. Designed as a standalone micro-workspace — press one hotkey, check notifications, fire a chat, dismiss. Eventually becomes the mobile view. - **Unified To-Do system.** Single `fpTodoLists` data pool shared across three view surfaces: (1) palette To Do tab, (2) note canvas to-do modules, (3) standalone to-do tabs. Changes propagate instantly. Each list: `{id, name, noteId, tasks:[{id, text, done, sub:[{id, text, done}]}]}`. - **Calendar module** — embeddable in notes or as standalone tabs. Month/Week/Day/List views. Event editor with reminders. Settings with agent instructions, Outlook sources (DOC16 Entry 16.7), sync rules, notification preferences. - **Chrome-style tab bar.** Active tabs white, seamlessly connected to content. Inactive slightly darker with separators. Tab groups use `display:contents` for uniform proportional shrinking. Group labels as colored pills. Active grouped tabs get continuous colored border outline. - **SVG icon system.** All emoji icons replaced. Heroicons (outline) primary. Lucide for selective swaps. Tabler for two specialized icons. Components: `Ic` → `I` → `TabIcon` → `NavIcon`. - **Split view.** Two independent panes with draggable vertical divider. Each pane: own tab bar, active tab, content area. Left pane keeps browser column toggle. - **Linear-style notification inbox.** Source-colored cards (Calendar amber, Agent purple, Email blue, System gray). Filter by All/Unread/Snoozed. Snooze options. Silent mode suppresses sounds/glow/auto-show but not badges. Delivery matrix configurable per source. - **Document title** — clickable (opens in default app), right-click context menu (Show in Finder, Open in Default App, Copy Ref, Copy Path, Copy File Name). - **Zoom slider** in status bar — Word-style minus/range/plus/percentage, 50%–200%, CSS `transform: scale()`. - **Per-tab right panel state.** Panel resets on tab switch. Utility tabs auto-close panel. - **Tab icon format migration.** Tab `icon` field changed from emoji strings to string IDs consumed by `TabIcon` component. Affects tab serialization. - **Module block type rename.** `"tasks"` → `"todo"` for consistency. Backwards-compatible rendering. - **Naming changes:** "Conversations" → "Chats" (nav section), "Knowledge Manager" → "Knowledge". - **Tab color changes:** web → `#64748B` (slate gray), chat → `#1a5276` (dark blue), todo → `#0891B2` (teal, new). - **Ask button simplification.** Per-task and per-subtask spark icons removed. Single list-level "Ask" button sends full list as context. - **Bookmarks bar** — thin horizontal bar below web browser toolbar, populated from special "Bookmarks Bar" folder, toggle via settings. **R4.1 additions:** - **Surface independence principle (§2.4).** Every Q rendering surface is a stateless viewport. No surface owns data. All content is EC-owned durable state delivered via subscription. `fp*` variables are Q-local UI selection state, not data ownership. This enables mobile clients, multi-user collaboration, and multi-device sync without data model changes. - **Content sync matrix (§2.5).** Three-tier classification: Tier 1 (multi-surface + networking) = chats, rooms, notes, to-do lists, calendar events, panels, forums; Tier 2 (networking) = projects, context buckets; Tier 3 (config, pull-on-open) = tasks, skills, agents, overlays. - **Surface-side sync contract (§2.6).** Surface registration, EC push channel, optimistic updates with reconciliation, note edit lock (Phase 1) with CRDT readiness (Phase 2), rename propagation, selector population from EC state, agent busy indicators. - **DOC25 proposed** as new spec for server-side sync transport, conflict resolution, networking layer, auth, and offline queue. DOC20 owns the surface contract; DOC25 owns the server contract. - **Expanded To-Do schema (§6.21.2).** `TodoList` gains `project_id`, `tags`, `created_at`, `updated_at`. `TodoTask` gains `done_at`, `attachments`, `created_at`, `updated_at`. New `TodoAttachment` schema for document references. List `name` documented as semantically significant for DOC72 entity resolution. `project_id` is organizational; matter/case association is independent via entity resolution. - **Expanded Calendar schema (§6.22.4).** `CalendarEvent` gains `event_type` (10 categories from `hearing` to `personal`), `participants`, `attachments`, `project_id`, `tags`, `source`, `external_id`. Event type enables differentiated reasoning (hearings are hard deadlines; vacations block availability). - **Signal emission contracts (§6.21.7, §6.22.8).** To-Do and Calendar surfaces now specify exactly when and how they emit signals to DOC72 — deterministic extraction for structured fields, entity resolution for text fields, optional LLM for inference. Triggered at EC on every CRUD command regardless of originating surface. - **Notes intake gap identified (§6.22.9).** Notes need LLM-assisted extraction with a significance gate. Cross-doc obligation flagged for DOC72 to design the `intake.notes` contract. - **DOC72 Proposal: Surface Intake Contracts** produced as separate document with full specifications for `intake.todo`, `intake.calendar`, `intake.notes`, and `intake.browser`. --- ## 1. High-level product model ### 1.1 Browser The Browser is a second-column browse/filter/launch surface immediately to the right of the main menu column. It is not the command palette and not the owner of most objects. It is a universal finder, launcher, organizer, and attach surface. ### 1.2 Project A Project is a durable grouping and default-context container. A project has two jobs: 1. **Grouping:** show all relevant notes, chats, rooms, tasks, panels/forums, generated artifacts, and project-scoped context in one filtered view; 2. **Default project context:** apply the project's DOC7-backed context to project-scoped work in a visible, durable, and defeatable way. A project is **not** a temporary global focus mode. ### 1.3 Project context Project context is implemented using DOC7 buckets, not a second context engine. A project may have: - exactly one **primary project bucket** (auto-created with the project); - zero or more **attached shared buckets** (existing DOC7 buckets linked to the project); - zero or more **linked folder aliases** (filesystem references with optional project-bucket sync). ### 1.4 Notes A Note is a rich-text editing surface with: - Tiptap/ProseMirror-based editor with AI agent integration; - comment rail with anchored threaded comments; - tracked changes / redlining with per-author colors; - import (DOCX, Markdown) and export (DOCX, PDF, Markdown); - cross-note linking and backlinks; - task list items; - version history and autosave. Notes belong to zero or one projects. ### 1.5 Folders A Folder is a virtual hierarchical organizer. Folders are purely for the user's visual organization in the Browser — they do not change how items are stored. A folder can contain items and subfolders. Any browser item type can be placed in a folder. Folders are only visible when the Folders scope is selected in the Browser. ### 1.6 Unified Workspace Shell (R3, updated R4) The Q Unified Workspace is the primary interface for the entire Q app. The workspace shell provides: - a **universal tab system** supporting 9 content types (note, doc, web, clips, chat, room, task, todo, utility) with Chrome-style tab bar (R4); - a **4-mode browser column** (Nav, Browser, Notes, Web) where the Nav tab replaces the traditional left sidebar menu; - a **simplified left nav rail** (6 items — Q logo, browser toggle, split view toggle, quick command/palette toggle, chat column toggle, settings) (R4 — updated); - a **right chat column** (320px, independent of the Ask Agent panel, at the app's right edge); - a **session system** for temporal workspace organization; - a **split view** for side-by-side panes (R4); - a **floating palette** (always-on-top mini workspace, see §1.8) (R4). The workspace shell is defined in §6.20. ### 1.7 Sessions (R3) A Session is a temporal container for workspace activity. Sessions auto-create on Q launch and produce a clips note ("Clips: 4.4-1") in the Session Notes folder. Sessions close on quit, manual action, or 2-hour idle. The clips note persists forever as a regular note. See §6.20.22–6.20.24. ### 1.8 Floating Palette (R4) The floating palette is a dark-themed, always-on-top overlay window that functions as a complete mini control surface. Toggled via ⌥Space or the ⚡ icon in the left nav rail. In Electron, it is a separate `BrowserWindow` with `alwaysOnTop` capability and `frame:false`. The palette has three persistent content tabs (Chat, Note, To Do) whose state is preserved across tab switches, and a sidebar (☰) that slides out from the left edge with a Command bar, Notices (notification inbox), Activity feed, Quick Actions list, mute toggle, and hotkey indicator. Pin (thumbtack) toggles `alwaysOnTop`. See §6.20.30. ### 1.9 To-Do System (R4) To-do lists are a first-class data type with a unified data pool (`fpTodoLists`) shared across three view surfaces: the floating palette To Do tab, note canvas to-do modules (embedded in notes), and standalone to-do tabs (opened from browser). Changes in any surface propagate to all others immediately. Each list has a `noteId` linking back to the note it was created in. Lists support tasks with subtasks, due dates, reminders, and agent assignment. A single list-level "Ask" button sends the full list as context to the agent. See §6.21. ### 1.10 Calendar Module (R4) The calendar module is insertable as a note canvas module or as a standalone tab. It supports Month, Week, Day, and List views. Events include title, date/time range, calendar source, location, notes, and reminders. Calendar sources connect to Outlook via DOC16 Entry 16.7. Settings cover agent instructions, source management, sync rules, and notification preferences. See §6.22. ### 1.11 Two-Layer Data Architecture (R4) To-Do and Calendar data follow a two-layer architecture: - **Layer 1: EC application tables** — `ELNOR_MEMORY/todo_items`, `ELNOR_MEMORY/todo_lists`, `ELNOR_MEMORY/cal_events`. Operational data stores for fast CRUD. Q reads and writes via EC commands. - **Layer 2: DOC72 entity graph** — extracted knowledge nodes for semantic matching. When items are created or modified, EC extracts entities and creates nodes in DOC72's graph. This enables semantic queries ("what deadlines relate to the Paramount case?") without polluting the operational store. DOC72 node mapping: to-do items → `obligation` nodes, to-do lists → `work_product` with `entity_subtype: "todo_list"`, calendar events → `obligation` with `source_type: "calendar_event"`, calendars → `world_entity` nodes. New edge types: `subtask_of`, `belongs_to_list`, `synced_to_calendar`. See DOC72 for entity graph details. --- ## 2. Ownership split and durable state rules ### 2.1 Ownership split | Surface | Durable state owner | Read-model consumer | |---|---|---| | Browser column | EC (catalog read model) | Q | | Projects | EC | Q | | DOC7 buckets | EC (via DOC7 commands) | Q | | Notes | EC | Q | | Collections | EC | Q | | Saved Views | EC | Q | | Places | EC | Q | | Folders | EC | Q | | Linked Folder Aliases | EC | Q | | To-Do lists and items | EC (`ELNOR_MEMORY/todo_lists`, `ELNOR_MEMORY/todo_items`) | Q (palette, note modules, standalone tabs) | | Calendar events | EC (`ELNOR_MEMORY/cal_events`) | Q (calendar modules, standalone tabs) | | Notifications | EC (notification store) | Q (palette inbox) | | DOC72 extracted knowledge | EC → DOC72 entity graph | Q (semantic queries via EC) | ### 2.2 Single-writer discipline All creates, updates, and deletes for browser-visible objects go through EC commands. Q reads from EC-materialized read models and session overlays. ### 2.3 Read-model principle Q renders from read models, not from raw event logs. EC materializes current-state projections from append-only event logs. Q may cache read-model snapshots locally for responsiveness but must not treat local cache as source of truth. ### 2.4 Surface independence principle (R4.1) **Every Q rendering surface is a stateless viewport.** No surface owns data. All content — chats, notes, to-do lists, calendar events, rooms, panels, forums — is EC-owned durable state. Surfaces subscribe to EC state and render it. Mutations always flow through EC commands. This principle applies uniformly to: - Main workspace content tabs - Floating palette content tabs (Chat, Note, To Do) - Right chat column - Note canvas embedded modules (to-do modules, calendar modules, inline threads) - Split view panes (both independently) - Browser column selectors and lists - Future: mobile clients, shared/collaborative views, third-party participant views **What this means for `fp*` state variables:** Variables like `fpTodoLists`, `fpActiveTodoList`, `fpSelectedChatId`, and `calEvents` as described in §6.20–6.22 are **Q-local UI state** — they track which item is selected, which tab is active, which list is expanded. They are NOT data ownership. The underlying data (the actual to-do items, calendar events, chat messages, note content) lives in EC application tables and is delivered to surfaces via subscription. The `fp*` variables select which slice of EC state to render. **Consequence:** If the same to-do list is open in the palette, a note canvas module, and a standalone tab, all three surfaces subscribe to the same EC state for that list. Checking an item in one surface sends a command to EC; EC writes the change; EC pushes the updated state to all subscribed surfaces; all three re-render. No surface-to-surface communication. No shared React state. EC is the sole synchronization point. **Rationale:** This architecture enables three capabilities that would otherwise require data model changes: 1. **Mobile clients** — a mobile app is just another surface subscribing to the same EC state. No data migration, no separate API. 2. **Multi-user collaboration** — another human joining a chat, editing a shared note, or participating in a panel is just another surface subscribing to the same EC state, plus an auth/permissions layer. 3. **Multi-device sync** — the user's Mac, phone, and any future device all see the same state because they all subscribe to the same EC truth. ### 2.5 Content sync matrix (R4.1) Not all content types have the same sync requirements. This matrix defines three tiers based on current multi-surface rendering needs and future networking needs. #### Tier 1 — Multi-surface rendering + networking These content types are rendered on multiple surfaces simultaneously within a single client AND will need real-time sync across network participants (other humans, remote devices). | Content type | Current rendering surfaces | Future surfaces | Networking scenario | Owner spec | |---|---|---|---|---| | **Chats** | Main chat tabs, palette Chat tab, right chat column | Mobile, shared chats | Other humans join a chat with user + AI agents. Agent streaming responses must fan out to all surfaces and all participants. | DOC12 | | **Rooms** | Main room tabs | Palette (future), mobile | Same as chats — multi-human rooms with AI agents. | DOC12 | | **Notes** | Main note tabs, palette Note tab, note canvas modules (embedded) | Mobile, shared/collaborative notes | Third parties can view and edit shared notes. Requires conflict resolution for concurrent edits. | DOC20 §6 | | **To-Do Lists** | Palette To Do tab, note canvas to-do modules, standalone to-do tabs | Mobile | Shared to-do lists where multiple users check items. Lower conflict risk than notes (discrete operations, not continuous text). | DOC20 §6.21 | | **Calendar Events** | Note canvas calendar modules, standalone calendar tabs, notification system | Mobile | Shared calendars, meeting invitations. External sync via Outlook (DOC16). | DOC20 §6.22 | | **Panels** | Main panel tabs | Palette (future), mobile, shared | Human participants joining AI panel reviews (e.g., CANDOR output review, research panels). Same real-time requirements as rooms. | DOC12 | | **Forums** | Main forum tabs | Palette (future), mobile, shared | Forum posts from multiple humans + agents. Lower real-time requirement than chats (async discussion). | DOC12 | #### Tier 2 — Networking required, single surface per client These content types currently render on one surface at a time but need sync for multi-device and collaboration. | Content type | Networking scenario | Owner spec | |---|---|---| | **Projects** | Project state, membership, configuration shared across team members and devices. Project-attached content (chats, notes, tasks, buckets) all reference back to the project — project sync is foundational for everything else. | DOC20 §4 | | **Context Buckets** | In a networked environment: firm-shared buckets vs personal buckets. Bucket contents and file references must sync across nodes. Critical because buckets feed context assembly — stale bucket contents on a remote node means the agent gets wrong context. | DOC7 | #### Tier 3 — Verify alignment, low sync urgency These content types are likely already architecturally sound (EC-owned, surface just renders) but should be verified during the DOC25 design process. | Content type | Notes | Owner spec | |---|---|---| | **Tasks (DOC23)** | Task system's modular canvas references task state — doesn't own a separate copy. EC ownership model is sound. Gap: task status updates (completion, gate approvals) need to push to any surface displaying that task. | DOC23 | | **Skills, Agents, Overlays** | Configuration/registry data. Changes rarely. Pull-on-open is sufficient — real-time push is overkill. Must be correct across devices but doesn't need streaming. | DOC3, EC Core | ### 2.6 Surface-side sync contract (R4.1) This section defines what every Q surface must implement to participate in the centralized data model. The server-side implementation (transport, conflict resolution, networking) is owned by DOC25 (proposed). DOC20 owns only the surface contract. #### 2.6.1 Surface registration When a surface opens a content item (a chat, note, to-do list, etc.), it registers with EC: ```ts interface SurfaceRegistration { surface_id: string // unique per rendering surface instance surface_type: "main_tab" | "palette_tab" | "chat_column" | "note_module" | "calendar_module" | "todo_module" | "split_pane" | "mobile" content_type: "chat" | "room" | "note" | "todo_list" | "calendar" | "panel" | "forum" content_id: string // the specific item being rendered capabilities: SurfaceCapabilities } interface SurfaceCapabilities { can_edit: boolean // false for read-only views supports_streaming: boolean // true for chat/room surfaces that show token-by-token agent responses supports_optimistic: boolean // true for surfaces that update locally before EC confirms } ``` When the surface closes (tab closed, palette switched, module unmounted), it deregisters. EC uses the registration table to know which surfaces need push updates for which content items. #### 2.6.2 EC push channel EC pushes state updates to registered surfaces via WebSocket (or equivalent local IPC for Electron). The push payload is: ```ts interface ECStateUpdate { content_type: string content_id: string update_type: "full_state" | "delta" | "stream_token" | "metadata_only" payload: any // content-type-specific version: number // monotonic — surfaces reject updates older than their current version source_surface_id: string | null // which surface originated the mutation (null for EC-internal changes) } ``` **Fan-out rule:** When EC processes a mutation, it pushes the resulting state update to ALL registered surfaces for that content item EXCEPT the source surface (which already applied the optimistic update). If the optimistic update was wrong (EC rejected the command), EC pushes a correction to the source surface too. **Chat streaming:** When an agent generates a response, EC pushes `update_type: "stream_token"` to all surfaces registered for that chat. Each token is fanned out independently. Surfaces append tokens to the in-progress message. When the response completes, EC pushes a `"delta"` update with the final message. #### 2.6.3 Optimistic updates For responsive UI, surfaces apply mutations locally before EC confirms: 1. User checks a to-do item in the palette. 2. Palette immediately renders the item as checked (optimistic local state). 3. Palette sends `TodoItemUpdateCommand` to EC. 4. EC writes the change to `ELNOR_MEMORY/todo_items/`. 5. EC pushes `ECStateUpdate` to all OTHER registered surfaces for that to-do list. 6. Those surfaces re-render with the checked item. 7. If EC rejected the command (validation error, conflict), EC pushes a rollback to the source surface. The palette unchecks the item and shows an error toast. **Optimistic update scope:** Optimistic updates are appropriate for discrete, low-conflict operations: checking to-do items, renaming, starring, archiving, adding tasks, creating events. They are NOT appropriate for continuous text editing (notes) — see §2.6.4. #### 2.6.4 Note editing conflict strategy Notes present a unique challenge because they involve continuous rich-text editing, not discrete operations. If the same note is open in the palette and a main tab, two Tiptap editors have independent local state against the same document. **Phase 1 (single-user, multi-surface):** Single active editor with a lock indicator. - When a surface opens a note for editing, it acquires an edit lock from EC. - If another surface already holds the lock, the new surface opens in **read-only mode** with a banner: "This note is being edited in [palette / tab name]. Edits here will be disabled until the other editor closes." - The read-only surface still receives live push updates as the editing surface auto-saves, so the user sees changes in near-real-time. - Lock releases on: surface close, tab switch away, blur after idle (30s), explicit release. - The existing auto-save contract (1200ms debounce, 15s max unsaved, flush on blur/navigation) continues to govern when the editing surface pushes to EC. **Phase 2 (multi-user collaboration):** CRDT/Yjs integration for real-time concurrent editing. This is deferred but the Phase 1 lock model is designed to be replaceable — the lock is an EC-side concept, not baked into Tiptap. When CRDT is ready, the lock is removed and Yjs handles merge. The surface contract (subscribe, render, push edits) does not change. #### 2.6.5 Rename and metadata propagation When any content item is renamed (chat title, note title, to-do list name, project name), the rename propagates to: - All rendering surfaces showing that item (via ECStateUpdate) - Tab titles in the tab bar (the tab bar subscribes to title updates for all open tabs) - Browser column lists (Nav tab chats section, Notes browser list, selector dropdowns) - Selector dropdowns (palette selectors, +Module Link Existing selectors, standalone tab header selectors) - History entries and search indexes (EC rebuilds these on rename) This is not special handling — it's a consequence of the surface independence principle. Surfaces subscribe to EC state; EC state includes the title; title changes push to all subscribers. #### 2.6.6 Selector population Content selectors (§6.20.30J) show recent items from EC state. When a new item is created (new chat, new note, new to-do list) from any surface, it appears in all selectors across all surfaces because: 1. The creation command goes to EC. 2. EC writes the new item. 3. EC pushes a catalog update to all surfaces. 4. Selector dropdowns re-query the catalog on open (or subscribe to catalog changes if performance requires). The selectors do NOT maintain their own item lists. They query EC's read models. #### 2.6.7 Agent busy indicators When an agent is engaged on a task (running a DOC23 pipeline, generating a long response, processing a review), every surface that could invoke that agent should show its availability status: - **Palette chat tab:** If the selected agent is busy, show status below the input: "{Agent} is working on [task name]… [Interrupt] [Wait]" - **Ask button (notes, documents, to-do lists, calendars):** If the agent is busy, the Ask button shows a busy indicator. Click still works but queues the request with a toast: "Request queued — {Agent} is currently engaged." - **Chat input in main tabs:** Same busy indicator as palette. - **Inline thread @mention:** Same queuing behavior. Agent availability is EC state (the dispatch queue / active task registry). Surfaces subscribe to agent status the same way they subscribe to content state. The interrupt/queue/wait logic is owned by DOC11 (gateway routing) and EC Core (dispatch coordination). DOC20 owns only the surface indicators. **Cross-doc obligation:** DOC11 must define the agent contention protocol: what happens when a second request arrives while the agent is busy. Options include: queue with priority, interrupt current task (with user confirmation), spawn a parallel session (if the model supports it), or reject with "busy" status. DOC20 surfaces will render whatever DOC11 decides. #### 2.6.8 Networking readiness — what changes, what doesn't The surface independence principle means the data model does NOT change for networking. What changes is the transport and auth layers, owned by DOC25 (proposed): | Concern | Single-node (current) | Networked (future) | What changes | |---|---|---|---| | **Transport** | WebSocket to localhost EC | WebSocket to EC via Cloudflare Tunnel or relay server | Transport layer only — surface contract unchanged | | **Auth** | None (single user, single machine) | Per-user identity, session tokens, per-item permissions | New auth middleware between surface and EC | | **Permissions** | Implicit full access | Read/write/admin per content item per user | EC enforces; surfaces show/hide edit controls based on permission grants | | **Presence** | Not needed | "Will is typing…", "3 participants", online/offline indicators | New subscription channel for presence state | | **Conflict resolution** | Optimistic + lock (Phase 1) | CRDT for notes (Phase 2), last-write-wins for discrete ops, merge for concurrent discrete ops on same item | DOC25 owns resolution strategy per content type | | **Offline queue** | Not needed | Mobile clients queue mutations when disconnected, reconcile on reconnect | New client-side queue with idempotent command replay | | **Invitation flow** | Not needed | Invite user to chat/note/project, accept/decline, permission grant | New EC commands + UI surfaces in DOC20 | **Key architectural point:** Because surfaces are stateless viewports subscribing to EC state, adding a mobile client or a third-party participant is "just" adding another subscriber. The data model, command contract, and push channel are the same. The complexity is in transport (reaching the subscriber), auth (verifying they're allowed), and conflict resolution (merging concurrent edits) — all of which belong in DOC25, not DOC20. --- ## 3. Browser ## 3.1 Browser layout **Browser column toggle shortcut:** `⌘+B` (Mac) / `Ctrl+B` (Windows). Also available via sidebar icon. (R1.6) ### 3.1.1 Browser shell - Entire browser column can be shown/hidden. - Toggle button lives near the top of the main menu column, aligned right. - Browser width is adjustable by dragging a **4px drag handle on the right edge**. - Browser width persists between sessions via EC user preferences. - Default width: **260px**. Minimum: **200px**. Maximum: **450px**. ### 3.1.2 Browser vertical structure Top to bottom: 1. Search bar 2. Collections quick row 3. Scope chips 4. Scope detail list (context-sensitive based on selected scope family) 5. Draggable vertical splitter 6. Type chips (two rows) 7. Filter/sort bar 8. Results list ### 3.1.2A Browser wireframe ```text ┌──────────────────────────────────────────────┐ │ [ Search… ][This View▼] │ │ ● ● ● ● ● ● ● ● ● ● (+) │ │ [Collection] [Project] [Bucket] [Places] │ │ [Folders] [Saved Views] │ ├──────────────────────────────────────────────┤ │ Scope Detail │ │ Henderson │ │ Adams │ │ Global │ │ No Project │ │ │ ├────────────── draggable vertical splitter ───┤ │ [Document] [Chat] [Preset] [Skill] [Task] │ │ [Agent] [Panel] [Forum] [Overlay] [Note] │ │ [Bucket] [Project] │ │ [Filter▼] [Sort: Modified▼] │ ├──────────────────────────────────────────────┤ │ pin • title … status TYPE 2h │ │ pin • title … status TYPE 4h │ │ pin • title … status TYPE 1d │ │ … │ └──────────────────────────────────────────────┘ ``` ### 3.1.2B Browser state object Use the following in-memory browser state shape in Q: ```ts type BrowserPrimaryScopeFamily = "project" | "bucket" | "places" | "folders" | "notes" | "saved_view" | null // R1.7: added "notes" interface BrowserViewState { q_session_id: string follow_current_page: boolean search_mode: "this_view" | "everywhere" search_query: string selected_collection_ids: string[] primary_scope_family: BrowserPrimaryScopeFamily primary_scope_detail_id: string | null selected_type_ids: BrowserItemType[] active_filter_state: Partial> sort_key: BrowserSortKey sort_dir: "asc" | "desc" include_archived: boolean current_page_hint: BrowserPageHint | null folder_overlay_open: boolean // R1.6 — tracks folder overlay panel visibility show_nested: boolean // R1.6 — recursive results for folder/place scopes place_smart_filter: boolean // R1.6 — noise-file filtering for Places (default: true) } ``` ### 3.1.2C Collection quick row and Collection scope relationship The Collections quick row and the Collection scope detail list MUST operate on the same `selected_collection_ids[]` state. Rules: - clicking a collection circle in the quick row toggles that collection id in `selected_collection_ids[]`; - selecting or deselecting collections in the Collection scope detail list updates the same `selected_collection_ids[]`; - the Collection quick row shows only collections where `show_in_quick_row = true`; - the Collection scope detail list shows **all** collections, including archived collections when `include_archived = true`; - the Browser may have zero or more selected collections at any time; - selected collections are always applied as an **intersection** filter: - an item matches if it contains **every** selected collection id; - if zero collections are selected, no collection filtering is applied. ### 3.1.2D Primary scope exclusivity The Browser may have **at most one** selected primary scope at a time. The primary scope families are: - `project` - `bucket` - `places` - `folders` - `saved_view` Selecting a new primary scope family clears the previous primary scope family and its detail id. The Collection quick row and Collection scope detail list do **not** count as the primary scope. Collections are additive filters that combine with any selected primary scope. ### 3.1.3 Vertical splitter A draggable horizontal bar separates the scope detail area from the type chip area. - Default position: 35% of browser column height. - Min: enough room for at least 3 scope detail entries. - Max: enough room for at least the type chip rows plus filter bar. - Grab handle: 4px tall, centered 20px wide grip indicator at 25% opacity. ### 3.1.4 Sticky behavior When the user scrolls the results list, the search bar, collections row, scope chips, scope detail, splitter, type chips, and filter bar all remain fixed. Only the results list scrolls. ### 3.1.5 Vertical height budget guidance In a 900px-tall viewport with the browser at default width: - search bar: ~36px - collections row: ~30px - scope chips: ~26px - scope detail: ~110px (adjustable via splitter) - splitter: 4px - type chips: ~44px (two rows) - filter/sort bar: ~26px - results list: remaining (~624px, approximately 19 rows at 32px) ## 3.2 Browser search ### 3.2.1 Separation from command palette Browser search searches the browser's current universe. It does not replace the global command palette. ### 3.2.2 Search behavior Browser search has two explicit modes: - **This View** — search inside the current browser universe after applying current page hint, selected collections, selected primary scope, selected types, and active filters. - **Everywhere** — search across the full Browser catalog read model, ignoring current page hint but still respecting: - selected collections; - selected primary scope, if one is selected; - selected types, if any are selected; - `include_archived`. Search behavior rules: 1. Empty query string returns the current filtered list without title/content scoring. 2. Non-empty query performs ranked search over: - title; - aliases, where supported; - excerpt/subtitle, where supported; - path basename, for path-backed items; - note plain text, where the type is `note`; - bucket file title and docmeta summary, where the type is `document`. 3. Ranking order: - exact title match; - title prefix match; - title token match; - alias exact or prefix match; - subtitle/excerpt match; - body/path match. 4. Search scoring is stable and deterministic. 5. Search results always return through the same `BrowserItemSchema` read model used by the Browser UI. 6. Search never mutates durable state. ### 3.2.3 Search actions The search control contains: - a left dropdown/chevron button with: - **Save current view/search** - **Open saved view** - **Manage saved views** - **Clear search** - a segmented mode control on the right side of the field: - **This View** - **Everywhere** ### 3.2.4 Save current view/search flow Clicking **Save current view/search** opens a compact modal with: - **Name** (required text input) - **Pin in Saved Views** (checkbox) - **Overwrite existing** (checkbox + target selector shown only if a name collision exists) The saved payload MUST persist: - `selected_collection_ids` - `primary_scope_family` - `primary_scope_detail_id` - `selected_type_ids` - `active_filter_state` - `sort_key` - `sort_dir` - `search_query` - `search_mode` - `include_archived` ## 3.3 Collections quick row ### 3.3.1 Purpose Collections are additive tag-based groupings. The quick row provides fast toggle access to collections the user uses frequently. ### 3.3.2 UI - Row of colored circular collection chips renders **between the search bar and the scope chips**. - Each collection appears as an **18px colored circle** with its assigned color. Circles render at 70% opacity by default, brightening to 100% on hover. - A **dashed circle with + icon** (18px, dashed border in textTer color) appears first in the row as the create/edit entry point. - Clicking a collection circle toggles that collection id in `selected_collection_ids[]`. It does not set a primary scope. - The row scrolls horizontally if collections exceed the browser width. - There are **10 color options**. ### 3.3.3 Create/edit collection popup Clicking the `+` button opens a compact popup with: - name text input - color picker (10 dots) - show in quick row checkbox - save/cancel/delete actions ### 3.3.4 Selection rules Collections act as additive filters and may combine with one selected primary scope (Project, Bucket, Places, Folders, or Saved View). ## 3.4 Scope block ### 3.4.1 Scope families The Browser has **seven** scope families. Five appear as chips in the scope row; Folders and Notes are accessed via dedicated UI elements. 1. **Collection** — multi-select tag filter (handled by collections quick row + detail list, not a primary scope) 2. **Project** — show items in a specific project, or "No Project" 3. **Bucket** — show items in a specific context bucket 4. **Places** — show saved filesystem folder aliases and their browseable contents 5. **Saved Views** — activate a saved browser configuration 6. **Folders** — show the user's virtual folder hierarchy (see §3.15). Accessed via a dedicated dotted-border button inline with scope chips but visually distinct (1.5px dashed border, tinted background, marginLeft:4). Not rendered as a scope chip. Activates the same scope mechanism as other scopes. (R3) 7. **Notes** — dedicated note folder tree with structured organization. Auto-filters results to notes only. Accessed via the Notes browser mode tab. (R1.7) ### 3.4.2 Scope philosophy Scoping answers: "What universe am I looking at?" A scope narrows the visible items. It does not mutate them. Selecting a project scope does not attach items to that project. ### 3.4.3 Scope selection rules - **Project**, **Bucket**, **Places**, **Folders**, **Notes**, and **Saved Views** are mutually exclusive as primary scopes. - **Collection** is an overlay and combines with any primary scope. - At most one primary scope may be active. - Within a primary scope, one detail entry may be selected (e.g., a specific project, a specific bucket, a specific folder). ### 3.4.4 Scope chip UI Scope chips render as a row of compact buttons below the collections row: - padding: 2px 8px, height: 20px, borderRadius: 3, fontSize: 10 - active: border accentBtn+"60", bg accentBtn+"10", color accentBtn, fontWeight 600 - inactive: border borderLight, bg transparent, color textTer, fontWeight 400 ## 3.5 Scope detail list ### 3.5.1 Purpose When a scope family is selected, the scope detail list shows the entries within that family. The user clicks an entry to narrow results to that specific scope. ### 3.5.2 Project scope detail entries Project scope detail list includes all non-archived projects plus a "No Project" entry. Each row shows: - project color dot (7px) - project title - active state (selected row highlighted) A `+ New Project` action is available from the Project detail header. ### 3.5.3 Places scope detail entries Places scope detail list includes saved place aliases. **Collection filter dropdown (R4):** When Places scope is active, a collection filter dropdown appears in the scope detail header area. Shows a colored dot per collection, folder icon for "All." Dropdown trigger displays the selected collection's dot or folder icon. Filtering by collection restricts the Places list to aliases tagged with that collection. X (remove) button works on ALL place items including those with `missing` status. Collection data example: `[{name:"All",color:null},{name:"Active Cases",color:"#31588c"},{name:"Research",color:"#2E8B57"},{name:"Templates",color:"#D97706"}]` Each row shows at minimum: - alias title - path preview (truncated, e.g. `~/Documents/Henderson/…`) - availability badge (`ok`, `missing`, `needs access`) - file count (immediate children) A `+ Add Place` action is available from the Places detail header. ### 3.5.3A Places browsing behavior When a Place is selected in the scope detail list, the results area shows the **browseable contents** of that folder: **What shows up:** - All files in the immediate directory (not recursive by default). - Subdirectories show as navigable rows with a folder icon. Clicking a subdirectory row navigates into it (the scope detail list updates to show the current path with a "← Back" breadcrumb). - A **"Show nested"** toggle in the filter bar enables recursive display of all files in the selected folder and its descendants (flattened list, not tree). **File type filtering:** - All files are visible by default regardless of type. - Unsupported files (binary, system files, etc.) are visually dimmed with a "Not previewable" badge but still listed. - Type chips apply on top — selecting "Document" shows only document-type files from the Place. - The same include/exclude globs from §3.12.9 can optionally be applied as a "Smart filter" toggle to hide noise files (.git, node_modules, .DS_Store, etc.). **File row metadata:** - file name (title) - file extension / type badge - file size - modified date - availability badge (if file has been moved or deleted since last refresh) **Drag from Place:** - Files from a Place can be dragged onto: - A project's "Add to Project" drop zone → creates a DOC7 local-path ref in the project's primary bucket - A context bucket → creates a DOC7 local-path ref - The note editor → creates a `[[link]]` to the file - The chat composer → attaches the file to the chat message - Dragging a file from a Place does **not** copy the file. It creates a reference. **Supported path types:** - Local filesystem paths (default, fully supported) - External drives / USB volumes (supported if mounted and readable) - iCloud Drive paths (supported if synced locally; `needs_access` if download-on-demand and not yet downloaded) - Network paths / SMB mounts (supported if mounted; `missing` if unmounted) - Symlinks are followed; the resolved path is used **Refresh behavior:** - Right-click a Place → "Refresh" rescans the directory for changes (new files, removed files, updated metadata). - Refresh is metadata-only — no file content is read or imported. - If a Place's path no longer exists, the Place shows `missing` badge and its contents are empty until the path becomes available again. ### 3.5.4 Folders scope detail entries Folders scope detail list shows the user's virtual folder hierarchy as an expandable tree. Each row shows: - expand/collapse chevron (if folder has subfolders) - folder title - item count badge - indentation indicating depth A `+ New Folder` action is available from the Folders detail header. Right-clicking a folder shows: New Subfolder, Rename, Delete. When a folder is selected in the detail list, the results area shows only items assigned to that folder (not recursively — only direct children unless explicitly toggled). ### 3.5.5 Saved Views detail entries (R4 — updated) Saved Views scope detail list includes all saved views. **Built-in views (R4 — updated):** The "Current" and "No Project" built-in views have been **removed**. Default views: - **Recent** (built-in) — cannot be edited or deleted - **Deadlines this week** (user-created default) - **Active matters** (user-created default) **User views:** Each row shows: - title - Edit (pencil icon) on hover — opens inline rename - Delete (X icon) on hover — removes view **"Save current view" (R4):** A subtle text link with save icon (not a prominent button). Click opens a save popover: text input for view name, Enter to save, Escape to cancel. ### 3.5.6 Bucket scope detail entries Bucket scope detail list includes browseable DOC7 buckets. Each row shows: - bucket title - health badge - item count ### 3.5.7 Overlap rule If a specific project/bucket/place/folder is selected in the scope detail list, the item list shows items **within** that selected scope and suppresses the selected scope object itself by default. ### 3.5.8 Notes scope detail entries (R4 — updated from R1.7) When Notes scope is active, the scope detail area shows a dedicated note folder tree with scope toggles. **Scope toggles (R4):** Three toggle buttons at the top of the Notes scope area: **Notes** | **To Do** | **Cal**. State tracked in `noteBrowserScope: Set<"notes"|"todo"|"cal">` — multiple scopes can be active simultaneously. When "To Do" is active, to-do lists appear in the results with a teal "To Do" type badge. When "Cal" is active, calendar items appear with an amber "Cal" type badge. Type badges have transparent background with text colored by type. See also §6.20.8 for the browser column Notes mode. **Folder tree:** 1. **"All Notes"** entry at top — shows all notes regardless of folder. Always present. 2. **User-created folders** — hierarchical folder tree for organizing notes. Supports subfolder creation (+ button on hover), rename (click title), delete (trash icon on hover with inline confirmation). Same interaction patterns as the Folders scope (§3.5.4). 3. **Special folders:** - **Daily Notes** — auto-populated. Contains yesterday's Today notes renamed to their date (see §6.2B). - **Templates** — contains note templates (see §6.15.15B). **Draggable splitter (R4):** Between the folder tree and the results list. State: `noteSplitterPos` (default 140px). User drags to resize. Selecting a folder filters the results list to notes in that folder and its subfolders. The scope detail area height is adjustable via the standard splitter (§3.1.3). **Results auto-filter:** When Notes scope is active, the results list automatically filters to `item_type: "note"` (plus to-do lists and calendar items when those scope toggles are active). Type chips are hidden (redundant). The results list shows: pin icon, Today badge (sun icon for the Today note), title, modified time, tracked change count (✏), task count (☐ N tasks). Comment count indicators have been **removed** from note list items (R4). **Note folder tree read derivation (R2):** The Notes scope folder tree is derived from `FolderSchema` records in `notes_folders_current.json` (stored under `ELNOR_MEMORY/notes/folders/`). Q reads this file to build the hierarchical tree. The browser results list is still a flat query from `browser_catalog_current.json` filtered by `folder_id`. The tree structure is a Q-side derivation from the folder registry — no separate hierarchical read contract is needed. **Note folder tree replaces the separate Notes sidebar.** There is no standalone Notes sidebar panel. The browser's Notes scope provides the same functionality (folder organization, search, sort) within the existing browser framework. ## 3.6 Type block ### 3.6.1 Purpose Type chips filter the results list to show only items of the selected types. ### 3.6.2 Visible type categories ```ts type BrowserItemType = | "document" | "chat" | "preset" | "skill" | "task" | "agent" | "panel" | "forum" | "overlay" | "note" | "bucket" // R2: renamed from context_bucket | "project" | "automation" | "artifact" // R2: renamed from generated_artifact | "place" ``` ### 3.6.3 Chat and room treatment Chats, rooms, and room subtypes all appear under a single `chat` type chip. Multi-agent rooms, red-team rooms, and standard rooms are filtered via subtab or the filter dropdown, not by separate type chips. ### 3.6.4 Adaptive type rules If the current scope makes certain types impossible (e.g., a Places scope will never contain tasks), those type chips should be visually dimmed or hidden. The adaptive resolver uses the filter support map from §3.7.4. ### 3.6.5 Layout **Responsive collapse rules (R1.6):** - ≥ 260px: All chips visible in wrapping rows (default behavior) - < 260px: Chips hidden behind a "Types…" toggle button. Click to expand a dropdown showing all chips. Active chip count shown on the toggle: "Types (3)" - Collapsible behavior: Double-click the splitter or click a chevron above the type chip area to toggle chip visibility Type chips render in **two rows** below the vertical splitter: - padding: 1px 6px, height: 18px, borderRadius: 3, fontSize: 9.5 - active: border accentBtn+"50", bg accentBtn+"10", color accentBtn, fontWeight 600 - inactive: border borderLight, bg transparent, color textTer, fontWeight 400 ## 3.7 Adaptive filters and sort ### 3.7.1 Filter bar UI The Browser renders a **single compact toolbar row** between the type chips and the results list. Layout: ```text [Filter ▼] [Sort: Modified ▼] [Include archived □] ``` Rules: - `Filter ▼` opens a popover listing only filters supported by at least one currently visible selected type. - `Sort` is a dropdown. - `Include archived` is a checkbox shown for all views except when the selected Saved View hardcodes archived visibility. ### 3.7.2 Browser filter key enum ```ts // R1.6: UI label "Running" renamed to "Last Used" everywhere. No enum change — last_used_at already sorts by recency. type BrowserItemStatus = "active" | "running" | "waiting" | "paused" | "completed" | "failed" | "archived" | "deleted" // R1.6: Replaces loose status: string | null across all schemas. type BrowserFilterKey = | "open" | "in_progress" | "scheduled" | "completed" | "failed" | "unread" | "recent" | "generated" | "pinned" | "errors" | "preset" | "archived" | "todo" | "unread_comments" | "system" | "has_context" | "has_unread" ``` ### 3.7.3 Filter semantics | Filter key | Meaning | |---|---| | `open` | item is currently open in the active Q session | | `in_progress` | task status is `queued`, `running`, or `waiting_human`; automation status is `running` | | `scheduled` | task status is `scheduled` or automation has `next_run_at != null` and `enabled = true` | | `completed` | task status is `completed` | | `failed` | task status is `failed` | | `unread` | item has unseen output or unseen updates | | `recent` | `updated_at >= now - 7 days` | | `generated` | item_type is `artifact` or document row has `is_generated = true` | | `pinned` | `pinned = true` | | `errors` | bucket health is `degraded` or place availability is not `ok` | | `preset` | row subtype is a preset subtype | | `archived` | row or owning record is archived | | `todo` | note has TaskList blocks with open (undone) items. R1.7: was note_kind = "todo", now checks block content. | | `unread_comments` | note has `comment_unread_count > 0` | | `system` | bucket is `system_managed = true` | | `has_context` | project has primary bucket or at least one attached bucket | | `has_unread` | project has at least one child row with `unread = true` | ### 3.7.4 Filter support by visible type ```ts const FILTER_SUPPORT_BY_TYPE: Record = { document: ["generated", "recent", "pinned", "archived", "errors"], chat: ["open", "unread", "recent", "pinned", "archived"], preset: ["recent", "pinned", "archived", "preset"], skill: ["recent", "pinned", "archived"], task: ["in_progress", "scheduled", "completed", "failed", "pinned", "archived", "preset", "recent"], agent: ["recent", "pinned", "archived"], panel: ["open", "unread", "recent", "pinned", "archived"], forum: ["open", "unread", "recent", "pinned", "archived"], overlay: ["recent", "pinned", "archived"], note: ["recent", "pinned", "todo", "unread_comments", "archived"], bucket: ["pinned", "archived", "errors", "system", "recent"], project: ["archived", "has_context", "has_unread", "recent"], automation: ["in_progress", "scheduled", "recent", "pinned", "archived"], artifact: ["generated", "recent", "pinned", "archived"], place: ["errors", "pinned", "recent", "archived"], } ``` ### 3.7.5 Adaptive filter resolver ```ts function resolveVisibleFilterKeys(selectedTypes: BrowserItemType[]): BrowserFilterKey[] { const types = selectedTypes.length ? selectedTypes : ALL_VISIBLE_BROWSER_ITEM_TYPES const keys = new Set() for (const t of types) { for (const key of FILTER_SUPPORT_BY_TYPE[t] ?? []) keys.add(key) } return FILTER_DISPLAY_ORDER.filter(key => keys.has(key)) } const FILTER_DISPLAY_ORDER: BrowserFilterKey[] = [ "open", "in_progress", "scheduled", "completed", "failed", "unread", "recent", "generated", "pinned", "todo", "unread_comments", "errors", "preset", "system", "has_context", "has_unread", "archived", ] ``` ### 3.7.6 Sort key enum ```ts type BrowserSortKey = "updated_at" | "title" | "item_type" | "last_used_at" | "created_at" ``` Sort dropdown labels: - `updated_at` → Date Modified - `title` → Alphabetical - `item_type` → Type - `last_used_at` → Last Used - `created_at` → Created Date Default sort: `updated_at desc` for all views except project lists and saved views (which default to `title asc` unless overridden). ### 3.7.7 Filter application order Browser filtering applies in this exact order: 1. current page hint, if `follow_current_page = true` and `search_mode = "this_view"` 2. primary scope filter 3. folder filter (if Folders scope is active) 4. collection intersection filter 5. type filter 6. filter predicates 7. search scoring / search match pruning 8. sort 9. pagination / virtualization window ## 3.8 Follow Current Page ### 3.8.1 Behavior When Follow Current Page is on and search_mode is "this_view", the browser automatically adjusts its results based on the active main content page: - Notes page → hints toward notes - Tasks page → hints toward tasks - Agents page → hints toward agents - etc. The hint narrows the default results but does not override explicit type chip selections. ### 3.8.2 Configuration Follow Current Page can be toggled via a small icon in the search bar or in browser settings. ### 3.8.3 Elnor browser awareness Elnor can query the browser and present results directly in chat using BrowserQuerySkill. When Elnor performs a browser query, the results include clickable deep links so the user can navigate directly to any result. ## 3.9 Browser results list ### 3.9.1 General behavior The results area is the main item list. It is virtualized for large lists and supports mixed-type lists. **Each item renders as a single line** at **32px row height** with the following inline layout: ``` [pin icon?] [unread dot?] [title — truncated with ellipsis] ... [status?] [TYPE label] [time] ``` Field details: - **Pin icon**: 8px pin icon in warn color. Only shown for pinned items. Flush left. - **Unread dot**: 4px dot in accentBtn color. Only shown for unread items. - **Title**: flex:1, minWidth:0, fontSize 11.5, fontWeight 600 for unread / 450 for read. Overflow: hidden, text-overflow ellipsis, white-space nowrap. - **Status badge** (optional): 8.5px uppercase text. Color varies by status (running=accentBtn, waiting=warn, complete=green, scheduled=neutral). Only shown for task-type items with an active status. - **Type label**: 8.5px uppercase, fontWeight 600, letterSpacing 0.03em. Flush right before time. Color is type-specific: Note=accentBtn, Chat=green, Doc=textTer, Task=warn, Agent=#5B5F97, Panel=accent, Forum=accent, Preset=neutral, Skill=neutral, Bucket=textTer, Project=accentBtn, Overlay=neutral. minWidth 28px, textAlign right. - **Time**: 9px, color textTer. Relative format (2m, 4h, 1d, 1w). flexShrink:0, minWidth 18px, textAlign right. **Project color dots and project names are NOT shown on individual rows.** Project context is established by the scope selection above, not repeated per-row. ### 3.9.2 Selection and open behavior Use standard Mac selection behavior: - **single click** = select row - **double click** = default open action - **Cmd-click** = toggle multi-selection - **Shift-click** = range selection - **Enter/Return** = open selected item - **Right click** = context menu (see §3.10) No checkbox gutter is required for standard browser multi-select. ### 3.9.3 Default open behavior by type - Document → open preview/viewer if supported; otherwise reveal/open in Finder/default system app - Note → open note editor - Context Bucket → open DOC7 bucket page - Project → open project page - Chat (including room subtypes) → open conversation surface - Panel / Forum → open that surface - Task → open task configure/detail page - Agent → open agent config page - Skill → open skill config page - Saved View → activate saved view - Collection → activate filter view ### 3.9.4 Multi-select action bar When `selected_row_count >= 2`, Q MUST render a compact bulk action bar above the results list. ```text [12 selected] [Add to Project…] [Add to Collection…] [Pin] [Unpin] [Remove from Project] [Clear] ``` Rules: - actions shown are the intersection of capabilities across all selected rows; - destructive actions require explicit confirmation; - `Add to Project…` opens the same selector used elsewhere; - `Remove from Project` is shown only if every selected row supports project membership removal in the current scope; - `Clear` clears current selection; - the bulk action bar disappears when selection count returns to 0 or 1. ### 3.9.5 Keyboard navigation - Arrow keys move selection up/down through the results list. - Enter opens the selected item. - Escape clears selection and returns focus to search bar. - Type-ahead filtering: typing while the results list is focused narrows visible results by title prefix. The filter resets after 500ms idle. - Tab moves focus between browser sections (search → scope → type → results). ### 3.9.6 Pinned items Pinned items sort to the top of the results list within their current sort order. Pin/unpin is available via right-click context menu (§3.10). ### 3.9.7 Scrollbar The browser results list uses a **4px thin auto-hide scrollbar**. The scrollbar thumb is invisible by default and fades in when the user hovers over the scrollable container. Thumb color: #d1d5db. Track: transparent. This scrollbar style applies globally within the browser column, the notes editor, and the comment rail. ```css ::-webkit-scrollbar { width: 4px } ::-webkit-scrollbar-track { background: transparent } ::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px } *:hover::-webkit-scrollbar-thumb { background: #d1d5db } ``` ### 3.9.8 Empty states (R1.6) | State | Message | Action | |---|---|---| | No results (filtered) | "No items match your filters." | "Try removing some filters or searching everywhere." Link: "Clear all filters" | | No results (search) | "No results for '{query}'" | "Try different keywords or search everywhere." | | No results (empty scope) | "No {type} items in {scope_name}" | Context-appropriate action: "Create a note" / "Add a document" | | Loading | Skeleton rows (3–5 shimmer rows matching BrowserItemSchema layout) | — | ### 3.9.9 BrowserResolver performance contract (R2 — expanded from R1.6) **Canonical filename:** `browser_catalog_current.json` everywhere. This is the single materialized read model for all browser queries. **Schema envelope:** ```ts interface BrowserCatalogEnvelope { materialized_at: string // ISO timestamp of last materialization source_count: number // number of sources read item_count: number // total items in catalog degraded_sources: string[] // source names that failed on last build items: BrowserItemSchema[] } ``` **Cold-start:** On first launch or if `browser_catalog_current.json` is missing/corrupt, EC rebuilds synchronously from all 13+ sources. Q shows loading skeleton (§3.9.8) during rebuild. On subsequent launches, EC loads from cache and incrementally updates via file watchers. **Update notification:** EC emits `browser.catalog_updated` via the Q Backend WebSocket (§Q Dashboard 1.4) when the materialized read model changes. Q re-reads the file on receipt. 500ms debounce to coalesce rapid changes. **Rebuild command:** `BrowserCatalogRebuildCommand` (§8) for manual recovery. Available via Settings or command palette. **Integrity check:** On load, EC validates `item_count` matches actual array length. Mismatch triggers automatic rebuild. EC maintains `browser_catalog_current.json` as a materialized read model. This file contains the pre-computed `BrowserItemSchema[]` array for all items. Q reads this file for browser queries instead of computing results from raw data files. **SLOs:** | Operation | Target | Degraded threshold | |---|---|---| | Cached query (no filter change) | < 200ms | > 500ms | | Delta update (incremental) | < 250ms | > 600ms | | Full rebuild | < 3s | > 8s | **Degraded-source behavior:** If any data source (notes, chats, artifacts, etc.) is unreachable or stale, the browser still renders with available data. A warning badge appears in the browser footer: "⚠ Some sources unavailable — results may be incomplete." The badge tooltip lists which sources are degraded. ## 3.10 Right-click context menu Browser rows expose actions by **capability**, not by hardcoded global menu. ```ts type BrowserCapability = | "open" | "open_in_new_tab" | "rename" | "duplicate" | "delete" | "add_to_collection" | "assign_project" | "remove_from_project" | "move_to_project" | "show_in_finder" | "open_with_default_app" | "reveal_source_bucket" | "attach_to_bucket" | "export" | "pin" | "archive" | "cancel" | "copy_link" | "copy_reference" | "move_to_folder" ``` Capability map: ```ts const CAPABILITIES_BY_TYPE: Record = { document: ["open", "open_in_new_tab", "show_in_finder", "open_with_default_app", "assign_project", "attach_to_bucket", "pin", "copy_link", "copy_reference"], chat: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "archive", "pin", "copy_link", "copy_reference"], preset: ["open", "open_in_new_tab", "duplicate", "delete", "pin", "copy_link", "copy_reference"], skill: ["open", "open_in_new_tab", "pin", "copy_link", "copy_reference"], task: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "cancel", "pin", "copy_link", "copy_reference"], agent: ["open", "open_in_new_tab", "duplicate", "pin", "copy_link", "copy_reference"], panel: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "pin", "copy_link", "copy_reference"], forum: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "pin", "copy_link", "copy_reference"], overlay: ["open", "open_in_new_tab", "pin", "copy_link", "copy_reference"], note: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "duplicate", "delete", "export", "pin", "copy_link", "copy_reference", "move_to_folder"], bucket: ["open", "open_in_new_tab", "attach_to_bucket", "pin", "copy_link", "copy_reference"], project: ["open", "open_in_new_tab", "archive", "delete", "pin", "copy_link", "copy_reference"], automation: ["open", "open_in_new_tab", "assign_project", "move_to_project", "remove_from_project", "cancel", "pin", "copy_link", "copy_reference"], artifact: ["open", "open_in_new_tab", "show_in_finder", "assign_project", "move_to_project", "remove_from_project", "export", "pin", "copy_link", "copy_reference"], place: ["open", "show_in_finder", "rename", "delete", "pin", "copy_link", "copy_reference"], } ``` Context menu rendering order: 1. Open 2. Open in New Tab 3. separator 4. Add to Project… 5. Move to Project… 6. Remove from Project 7. Move to Folder… 8. Add to Collection… 9. Attach to Bucket… 10. separator 11. Pin / Unpin 12. Copy Link 13. Copy Reference 14. Reveal in Finder / Show in Finder 15. Open with Default App 16. Export 17. Duplicate 18. Rename 19. separator 20. Archive 21. Cancel 22. separator 23. Delete ### 3.10.2 Copy Reference "Copy Reference" copies a structured reference string to the clipboard that can be pasted into the chat composer, note editor, or any text field. Format: ``` @[{title}]({item_type}:{item_id}) ``` Examples: - `@[Henderson_Complaint.pdf](document:d1)` - `@[Henderson Discovery Priorities](note:n1)` - `@[Review batch #4](task:t1)` When pasted into the **chat composer**, the reference renders as a clickable chip showing the item's title and type icon. Elnor receives the reference in the message context with the item's type and ID, allowing him to resolve it via NoteReadSkill, BrowserQuerySkill, or other appropriate skill without the user needing to explain what the item is or where to find it. When pasted into the **note editor**, the reference renders as a `[[link]]` to the item (consistent with §6.13). Copy Reference is available on **every item type** in both the main browser and the notes list. ### 3.10.1 Show in Finder `Show in Finder` / `Reveal in Finder` is only available for **path-backed items**: - local file document rows - linked folder alias rows - place rows - generated artifacts with `path` - exported note files - project output folder It is not available for rows that have no meaningful filesystem target. ## 3.11 Drag and drop ### 3.11.1 Priority DOC20 prioritizes drag/drop for **documents first**. Supported first-wave document drops: - document → chat composer / attachment well - document → project context docs - document → context bucket - document → panel/forum config - document → task config if task surface supports it - **any browser item → note editor** (creates a `[[link]]` to the item at the drop position — see §6.13) - **any browser item → folder** in Folders scope detail (assigns item to that folder) ### 3.11.2 Project membership drop A dedicated drag/drop surface exists in Project Configure and in the persistent project header to add eligible items to a project. Eligible first-wave dragged item types: - chat / room - panel - forum - task / automation - note - document / artifact - bucket If a dragged item type is not supported for a target surface, Q must show a clear no-drop state. ### 3.11.3 Move confirmation If an item can belong to only one project and already belongs to another, drop must prompt: - Move to this project - Cancel ### 3.11.4 Drop payload schema ```ts interface ProjectDropPayload { item_type: ProjectMemberType | "document" | "bucket" // R2: renamed item_id: string title: string current_project_id: string | null } ``` If the payload is unsupported for the target, show no-drop state and do not submit a command. ## 3.12 File handling, Places, and aliasing ### 3.12.1 Core principle The browser must prefer **references/aliases over copies**. Adding a Place, linking a folder into a project, or showing a generated artifact in a list must not duplicate the underlying file by default. ### 3.12.2 Place aliases A **Place** is a saved filesystem alias/reference for browsing. Initial place support is folder-first. ```ts interface PlaceAliasSchema { place_id: string title: string path: string kind: "folder" availability_status: "ok" | "missing" | "needs_access" last_checked_at: string | null pinned: boolean created_at: string updated_at: string } ``` ### 3.12.3 Linked folder aliases in projects A **linked folder alias** is a project-level reference to a Place, allowing project documents to include filesystem-backed files. ### 3.12.4 Unsupported files Files in Places or linked folders that are not natively previewable or not directly usable should still be visible when possible, but visually marked. ### 3.12.5 Add-to-context behavior Adding a file from Places or a linked folder to context does **not** copy the file content into EC durable storage by default. It creates the appropriate DOC7/local-path reference, after which DOC7 indexing/caching rules apply. ### 3.12.6 Rename/delete behavior for external files Browser rename/delete for external files from Places and linked folders: - rename should rename the filesystem file where possible; - delete should remove the alias/reference; filesystem deletion requires additional confirmation; - external path-backed files should generally use **Show in Finder**; - generated artifacts with known paths may allow direct rename and Trash-move. ### 3.12.7 Place add / refresh / remove flow Availability detection rules: - `ok` = path exists and is readable by the running app process - `missing` = path does not exist - `needs_access` = path exists but the running app process does not currently have read permission Place add flow: 1. User clicks `+ Add Place`. 2. OpenClaw opens a native folder picker. 3. Q submits `place_add`. 4. EC validates path against allowed roots policy. 5. EC writes the PlaceAlias record. 6. Browser refreshes. Place refresh flow: - right-click Place row → `Refresh` - Q submits `place_refresh` - EC rescans availability and immediate children metadata only - no file content is imported during refresh Place remove flow: - removing a Place deletes the alias record only - it does not delete the external folder or any files inside it - if a linked folder alias references the removed place, that linked folder alias becomes `broken` until reassigned or removed ### 3.12.8 Linked folder alias semantics A linked folder alias is a project-level reference to a Place. ```ts interface LinkedFolderAliasSchema { linked_folder_id: string project_id: string place_id: string mode: LinkedFolderMode sync_status: LinkedFolderSyncStatus include_globs: string[] exclude_globs: string[] last_scan_at: string | null last_sync_at: string | null created_at: string updated_at: string } type LinkedFolderMode = "browse_only" | "sync_to_project_bucket" type LinkedFolderSyncStatus = "idle" | "syncing" | "ok" | "warning" | "error" ``` Rules: - one `LinkedFolderAlias` points to exactly one `PlaceAlias` - multiple projects may link the same Place - linked folder aliases do not create DOC7 file rows unless explicit sync or add-to-context occurs - linked folder aliases may be `browse_only` or `sync_to_project_bucket` ### 3.12.9 sync_to_project_bucket semantics `sync_to_project_bucket` is **opt-in** and is defined as: - source of truth remains the filesystem folder; - EC creates or updates DOC7 local-path file refs in the project's primary bucket for files matching include rules; - EC never copies file bytes into project storage solely because sync is enabled; - EC stores refs and indexing metadata only, consistent with DOC7 local-path refs; - sync job is manual by default (`Sync now` button); - optional watch mode may be enabled later but is out of scope for v1. Default inclusion/exclusion globs: ```ts const LINKED_FOLDER_DEFAULT_INCLUDE_GLOBS = [ "**/*.md", "**/*.txt", "**/*.docx", "**/*.pdf", "**/*.rtf", "**/*.html", "**/*.json", ] const LINKED_FOLDER_DEFAULT_EXCLUDE_GLOBS = [ "**/.git/**", "**/node_modules/**", "**/.DS_Store", "**/~$*", ] ``` Dedupe rule: if a candidate file path already exists as a DOC7 file ref in the primary project bucket, update/reindex that row instead of creating a duplicate. ### 3.12.10 Linked folder sync status A linked folder row in project documents should show: - alias title - path - mode - last scan at - last sync at (if sync_to_project_bucket) - sync status badge ## 3.13 Saved Views ### 3.13.1 Definition A Saved View is a persisted snapshot of a browser filter/scope configuration that can be restored with one click. ### 3.13.2 Save flow See §3.2.4 for the save current view flow. ### 3.13.3 Labels Saved views appear in the scope detail list when the Saved Views scope is selected, and in the search dropdown. ### 3.13.4 Built-in Saved Views (R4 — updated) DOC20 defines these built-in saved views. The "Current" and "No Project" built-in views have been **removed** (R4). Only "Recent" remains as a system view. Default user views are seeded on first launch but can be edited or deleted. ```ts const BUILT_IN_SAVED_VIEWS: SavedViewSchema[] = [ { saved_view_id: "sv_recent", title: "Recent", scope_family: null, scope_detail_ids: [], collection_ids: [], type_ids: [], filters: { recent: true }, sort_key: "updated_at", sort_dir: "desc", search_query: "", search_mode: "this_view", include_archived: false, pinned: true, system_view: true, system_view_key: "recent", }, ] // Default user-created views (seeded on first launch, editable/deletable): const DEFAULT_USER_VIEWS: SavedViewSchema[] = [ { saved_view_id: "sv_deadlines_week", title: "Deadlines this week", scope_family: null, scope_detail_ids: [], collection_ids: [], type_ids: [], filters: { due_this_week: true }, sort_key: "updated_at", sort_dir: "asc", search_query: "", search_mode: "this_view", include_archived: false, pinned: false, system_view: false, system_view_key: null, }, { saved_view_id: "sv_active_matters", title: "Active matters", scope_family: "project", scope_detail_ids: [], collection_ids: [], type_ids: [], filters: { open: true, in_progress: true }, sort_key: "updated_at", sort_dir: "desc", search_query: "", search_mode: "this_view", include_archived: false, pinned: false, system_view: false, system_view_key: null, }, ] ``` ### 3.13.5 BrowserQuery read contract The Browser UI and BrowserQuerySkill MUST use the same normalized read contract. ```ts interface BrowserQueryRequest { q_session_id?: string follow_current_page: boolean current_page_hint: BrowserPageHint | null search_mode: "this_view" | "everywhere" search_query: string selected_collection_ids: string[] primary_scope_family: BrowserPrimaryScopeFamily primary_scope_detail_id: string | null selected_type_ids: BrowserItemType[] active_filter_state: Partial> sort_key: BrowserSortKey sort_dir: "asc" | "desc" include_archived: boolean limit: number cursor?: string | null } interface BrowserQueryResponse { items: BrowserItemSchema[] total_count: number next_cursor: string | null available_type_ids: BrowserItemType[] available_filter_keys: BrowserFilterKey[] applied_state: BrowserViewState warnings: string[] } ``` ### 3.13.6 BrowserQuery execution rules 1. `open` and `unread` may depend on Q session overlay state. 2. If `q_session_id` is absent: - `open` is ignored; - `unread` falls back to durable unread/has_unseen flags from owner read models only; - response `warnings[]` includes `"SESSION_OVERLAY_UNAVAILABLE"`. 3. BrowserQuerySkill MUST accept `q_session_id` when invoked from the active Q chat session. 4. BrowserQuerySkill MUST return `deep_link` metadata on every result row. 5. BrowserQuerySkill MUST never write durable state. ### 3.13.7 Browser resolver pseudocode ```ts function runBrowserQuery(req: BrowserQueryRequest, catalog: BrowserItemSchema[], session?: BrowserSessionOverlay): BrowserQueryResponse { let rows = catalog if (req.follow_current_page && req.search_mode === "this_view" && req.current_page_hint) { rows = rows.filter(row => pageHintMatches(req.current_page_hint!, row)) } rows = applyPrimaryScope(rows, req.primary_scope_family, req.primary_scope_detail_id) rows = applyCollectionIntersection(rows, req.selected_collection_ids) rows = applyTypeFilter(rows, req.selected_type_ids) rows = applyPredicateFilters(rows, req.active_filter_state, session, req.include_archived) rows = applySearch(rows, req.search_query, req.search_mode) rows = sortRows(rows, req.sort_key, req.sort_dir) const total = rows.length const paged = paginate(rows, req.limit, req.cursor) return { items: paged.items, total_count: total, next_cursor: paged.next_cursor, available_type_ids: deriveAvailableTypes(rows), available_filter_keys: resolveVisibleFilterKeys(req.selected_type_ids), applied_state: deriveBrowserViewState(req), warnings: deriveBrowserWarnings(req, session), } } ``` ## 3.14 Project color usage Each project has a configurable color from the 10-color palette shared with Collections. This color appears in: - the project header dot; - the project switch dropdown; - the project scope detail list in the Browser; - project rows when the Browser is listing projects as items; - notes lists and other project-specific lists where the spec explicitly says a project color dot is shown. This color does **not** appear on general mixed browser result rows when the Browser is showing non-project items. Project context is carried by the selected scope and by the item's deep link metadata, not by repeating the project color on every row. ## 3.15 Folders ### 3.15.1 Purpose Folders provide virtual hierarchical organization for the user's items. They are purely for visual organization — they do not change how items are stored, which project they belong to, or any durable metadata beyond the folder assignment. ### 3.15.2 Data model ```ts interface BrowserFolderSchema { folder_id: string title: string parent_folder_id: string | null // null = root folder order_index: number created_at: string updated_at: string } interface BrowserFolderMembershipSchema { folder_id: string item_type: BrowserItemType item_id: string created_at: string } ``` ### 3.15.3 Folder hierarchy rules - Folders support unlimited nesting depth (but UI should practically discourage more than 3-4 levels). - An item can belong to **zero or one** folder. Folder membership is optional — items without a folder assignment simply don't appear when the Folders scope is filtered to a specific folder. - An item's folder assignment is independent of its project membership. You can have a note in Project Henderson AND in Folder "Privilege Issues" — these are orthogonal organizational axes. - Deleting a folder detaches all items from it (items survive). Subfolders are also deleted, and their items are detached. - Renaming a folder does not affect its items. ### 3.15.4 Folder scope behavior When the Folders scope is selected in the Browser: 1. The scope detail list shows the folder tree (expandable/collapsible, indented by depth). 2. Selecting a specific folder shows only items directly in that folder (not recursive by default). 3. A "Show nested" toggle in the filter bar enables recursive display of all items in the selected folder and its descendants. 4. Items without any folder assignment are shown when no specific folder is selected (i.e., the Folders scope root shows "Unfiled" items). 5. Type chips and filters still apply on top of the folder filter. ### 3.15.5 Folder management - **Create folder**: "+" button in Folders scope detail header. Prompts for name only. - **Create subfolder**: Right-click parent folder → "New Subfolder". Or drag a folder onto another folder. - **Rename**: Right-click → Rename. Inline edit in scope detail list. - **Delete**: Right-click → Delete. Confirms with "This will remove the folder. Items in this folder will become unfiled." Subfolders are also deleted. - **Move folder**: Drag a folder to a new parent in the scope detail tree. ### 3.15.6 Assigning items to folders - **Drag and drop**: Drag any browser item onto a folder in the Folders scope detail list. - **Right-click → Move to Folder…**: Opens a folder picker tree. Includes "Remove from folder" option. - **Bulk action**: Multi-select items → "Move to Folder…" in bulk action bar. ### 3.15.7 Folder visibility rules Folders are **only visible when the Folders scope is selected**. When the user is in Project scope, Bucket scope, Places scope, or Saved Views scope, folder hierarchy is completely hidden. This avoids visual clutter. The exception: if a Saved View was created while the Folders scope was active with a specific folder selected, restoring that Saved View activates the Folders scope and selects that folder. ### 3.15.8 Interaction with other scopes Folders are orthogonal to projects and collections: - An item can be in a project AND in a folder AND tagged with collections simultaneously. - Filtering by project scope shows all project items regardless of folder assignment. - Filtering by folder scope shows all items in that folder regardless of project assignment. - Collections always act as intersection filters on top of whatever scope is active. ## 3.16 Scope deselection (R1.4) **Persistence rule (R1.6):** When a scope chip is deselected, only the scope-related fields are cleared (`primary_scope_family` → null, `primary_scope_detail_id` → null). All other filter state is preserved: `selected_collection_ids`, `selected_type_ids`, `active_filter_state`, `sort_key`, `sort_dir`, `search_query`. Clicking an already-active scope chip **deselects** it, returning to "no scope" mode where all items are shown unfiltered by scope. The scope detail area shows "No scope — showing all items." ## 3.17 Extended sort options (R1.4) The sort control is a dropdown with five options: - **Modified** (default) — most recently modified first - **Alphabetical** — A-Z by title - **Type** — grouped by entity type - **Created** — most recently created first - **Running** — running/active items sorted to top, then by modified The sort actually re-orders the results list. Pinned items always float to top. ## 3.18 Collections filter behavior (R1.4) Clicking a collection dot toggles it as an active filter. Multiple collections active simultaneously use AND logic (item must be in all selected collections). Selected dots: dark border, 1.1x scale. "clear" button when any are active. ## 3.19 Extended type chip set (R1.4) 14 types: Document, Chat, Preset, Skill, Task, Agent, Panel, Forum, Overlay, Note, Bucket, Project, Artifact, Prompt. Each toggles independently. Multiple active: OR logic. "clear" button when any active. ## 3.20 Folder overlay toggle (R1.4) A persistent folder overlay accessible from any scope via a **folder icon button** (dashed border, no label) at the end of the scope chip row. When active: - Compact folder tree strip (max-height 90px, scrollable) appears between scope detail and type chips - Header: "Drag items to folders" + X close - Full folder hierarchy with expand/collapse - Results items become **draggable** (grab cursor) - Drag onto folder → dashed blue border drop target → toast confirms assignment - Current scope, filters, search, sort all remain active - Auto-hides when switching to Folders scope (already visible) - Browser footer shows "📁 Folders" when active Solves: assigning items to folders while in a filtered/scoped view. ### 3.20.1 Folder overlay DnD implementation contract (R2 — expanded) **State machine:** 1. `idle` — no drag in progress 2. `dragging` — user has picked up item(s). Source items show reduced opacity. Drop targets highlight on hover. 3. `drop_pending` — user released on a valid target. Optimistic move: Q immediately moves the item(s) in the browser results list. EC command in flight. 4. `committed` — EC accepted. No further action. 5. `reverted` — EC rejected (e.g., folder doesn't exist, permission error). Q reverts the optimistic move. Error toast shown. **Optimistic update:** On drop, Q immediately updates the browser results list and folder tree to reflect the move. If EC rejects, Q reverts to the previous state. This provides instant visual feedback. **Cross-scope edge cases:** - Drag from "No Project" scope into a folder: item moves to folder, no project change. - Drag while a filter is active: item may disappear from the filtered results after moving (expected — filter still applies). Toast: "Moved to {folder_name}." - Drag while Places scope is active: Places items are filesystem references and cannot be moved to virtual folders. Show no-drop cursor on all folder targets. - Drag multiple selected items: all move together. If any single item fails, the entire batch reverts. - Multi-select drag: All selected items move to the target folder. Payload: `{ item_ids: string[], target_folder_id: string }` - Auto-expand on hover: If a collapsed folder is hovered for > 500ms during drag, it expands - Cancel: Dropping outside any folder cancels the move - Folder delete/rename during active drag: Blocked (folder actions disabled while drag is in progress) ## 3.21 Browser footer (R1.4) Compact bar inside browser column (bottom) showing: - Active scope name (or "All" if none) - Project name / "No Project" when applicable - Active collection names - Active type chip names - "📁 Folders" when overlay active - Sort key (right-aligned) ## 3.22 Archive/Delete browser items (R1.4) Right-click context menu on any result item gains: - **Archive** — sets status "archived." Hidden by default. Toggle in filter bar to show. - **Delete** — inline "Delete permanently? Yes / No" confirmation. Soft-delete (preserved in event log). ## 3.23 Save view from any scope (R1.4) A save/bookmark icon next to the sort dropdown in the filter bar. Click → inline input below filter bar with name field, Save button, Cancel button (Escape also dismisses). Saved views appear in the Saved Views scope. In Saved Views scope: user-created views show Edit (pencil) and Delete (X) icons on hover. The built-in "Recent" view is not editable or deletable. "Save current view" appears as a subtle text link with save icon — click opens a popover with name input (R4). ## 3.24 Remove places (R1.4) Hover a place in Places scope → X icon appears. Click → inline "Remove? Yes / No" confirmation. Files on disk NOT deleted; only the alias is removed. EC: `PlaceRemoveCommand`. ## 3.25 Delete folders (R1.4) Hover a folder in Folders scope → trash icon appears (alongside + subfolder icon). Click → inline "Delete? Yes / No" confirmation. Items inside become unfiled. Subfolders cascade-deleted. EC: `FolderDeleteCommand`. ## 3.26 "No Project" clickable (R1.4) In Project scope, "No Project" is a clickable item filtering to items with project_id = null. Visual: dashed circle border, italic when unselected, accent color when selected. --- ## 4. Projects ## 4.1 Project purpose A Project is a durable grouping and default-context container for related work. ### 4.1.1 What selecting a project does - filters visible items in the Browser to project members and project-scoped content; - makes the project's DOC7 buckets available as default context for new chats, tasks, and other operations; - scopes the Home tab notes workspace to project notes; - provides a persistent surface for organizing chats, documents, tasks, panels, forums, and automations. ### 4.1.2 What selecting a project does not do - does not silently alter unrelated chats, tasks, rooms, panels, or scheduled automations; - does not create hidden background context injection; - does not activate a global focus mode; - does not prevent access to items outside the project. ### 4.1.3 Project membership model DOC20 uses **single-primary-project membership** for workflow objects. The following entity types support **0 or 1** primary project association in v1: - chat / room - panel - forum - task - automation - note - generated artifact Membership for these entity types is represented durably as a project membership edge plus the owning object's current `project_id` field/read-model projection. The following do **not** use the same membership mechanism: - context buckets → attached to projects through project-bucket attachment records; - direct context documents in a project's primary bucket → represented by DOC7 file refs in the primary bucket; - bucket-inherited documents → represented by attached bucket membership, not direct project membership; - linked folders → represented by `LinkedFolderAlias` records; - places, saved views, collections, folders → not project members. ### 4.1.3A Entity-by-entity membership table | Entity type | Association model | Max projects | Remove from Project means | Move to Project means | |---|---|---:|---|---| | Chat / Room | `project_id` + membership edge | 1 | clear `project_id` → item becomes No Project | set `project_id` to new project | | Panel | `project_id` + membership edge | 1 | clear `project_id` | set `project_id` to new project | | Forum | `project_id` + membership edge | 1 | clear `project_id` | set `project_id` to new project | | Task | `project_id` + membership edge | 1 | clear `project_id` | set `project_id` to new project | | Automation | `project_id` + membership edge | 1 | clear `project_id` | set `project_id` to new project | | Note | `project_id` + membership edge | 1 | clear `project_id` | set `project_id` to new project | | Generated artifact | `project_id` on artifact record | 1 | clear artifact `project_id` only; origin stays unchanged | set artifact `project_id` to new project | | Direct project context document | DOC7 file ref in primary project bucket | n/a | remove file ref from primary project bucket | re-add file ref to another project's primary bucket | | Bucket-inherited document | attached bucket membership | n/a | detach bucket from project | attach bucket to another project | | Linked folder alias | `LinkedFolderAlias` record | n/a | remove linked folder alias from project | add linked folder alias to another project | ### 4.1.4 Global vs No Project "Global" means no project filter is active — the user sees everything. "No Project" is a filter that shows only items with `project_id = null`. ### 4.1.5 Project schema ```ts type ProjectStatus = "active" | "paused" | "archived" type ProjectKind = "standard" | "global" interface ProjectSchema { project_id: string project_kind: ProjectKind title: string slug: string description: string color_token: string status: ProjectStatus primary_project_bucket_id: string default_agent_id: string | null default_model_id: string | null fallback_chain_id: string | null think_level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" default_overlay_ids: string[] output_folder_path: string | null budget_enabled: boolean budget_amount_usd: number | null budget_period: "day" | "week" | "month" | "quarter" | null budget_alert_threshold_pct: number | null show_budget_in_header: boolean created_at: string updated_at: string created_by: string updated_by: string background_instructions: string | null // R2: project-level instructions and context notes current_spend_usd: number // R2: read-only projection from EC cost tracking current_spend_updated_at: string | null // R2 archived_at: string | null deleted: boolean deleted_at: string | null } ``` ### 4.1.6 Project bucket attachment schema ```ts type ProjectBucketAttachmentRole = "primary" | "attached" interface ProjectBucketAttachmentSchema { project_id: string bucket_id: string role: ProjectBucketAttachmentRole order_index: number created_at: string created_by: string } ``` ### 4.1.7 Project creation flow Project creation MUST be atomic from the user's point of view. Flow: 1. User submits `project_create`. 2. EC allocates `project_id`. 3. EC creates the primary DOC7 bucket first: - title = `{project.title} — Project Bucket` - summary = `Primary project bucket for {project.title}` - system_managed = false - pinned = false 4. EC allocates `primary_project_bucket_id`. 5. EC writes: - project create event - primary bucket create event (via DOC7 command) - project-bucket attachment row with `role = "primary"` 6. EC materializes `ProjectSchema` current state. 7. Q opens `/projects/:project_id/home`. Failure rule: if primary bucket creation fails, project creation fails and no project record is materialized. ### 4.1.8 Project page route contract ```ts type ProjectTabId = "home" | "documents" | "activity" | "project_context" | "configure" type ProjectDocumentsSubtabId = "generated" | "context_docs" | "notes" | "all" type ProjectActivitySubtabId = "chats" | "panels_forums" | "tasks" | "automations" | "all" ``` Routes: - `/projects/:project_id` - `/projects/:project_id/:tab` - query param `documents_subtab` - query param `activity_subtab` Defaults: missing `:tab` → `home`; invalid subtab → first valid subtab for that tab. ### 4.1.9 Paused project semantics `paused` means: - the project remains visible and openable; - creating new items from outside the project does not auto-suggest it; - creating new items **from inside** the project page still defaults to that project; - existing project members continue to retain their project membership; - existing project members continue to inherit project context on their normal project-scoped turns/dispatches; - automated scheduled work already attached to the project is not cancelled solely because the project is paused. `paused` is therefore a **creation-default / discovery** state, not a context-removal state. ## 4.2 Project page header The project page header contains: **First row** (flex, align center): - Project color dot (12px circle) - Project name (h1, 20px, fontWeight 700) - **Switch project button**: subtle bordered button with "Switch ▾" label. Opens a dropdown listing all projects with color dots. Current project shows a check. Clicking a project switches the page. - flex spacer - **Persistent "Add to Project / Drop here" surface** (right-aligned): 2px dashed border in accentBtn at 40% opacity, padding 10px 24px, borderRadius md, minWidth 260px, fontSize 13.5, fontWeight 600. Persists across all tabs. Valid drag-and-drop target. On hover, border becomes solid accentBtn. On drop, triggers standard project membership add flow. **Below the add-to-project surface** (right-aligned): - Cost indicator: subtle pill (fontSize 10, textTer color, bgInput background, borderRadius full). Shows "$XX.XX / $YYY" format. Clicking hides it for the session. Only visible when budget tracking is enabled and "Show budget in project header" is on (see §4.7.7). **Second row**: - Project description (fontSize 13, textSec color, left margin 22px). Editable in Configure tab (§4.7.2). ### 4.2.1 Autosave Project metadata edits autosave. A separate save button is not required for project metadata. ## 4.3 Project tabs Project page tabs: 1. **Home** (full notes workspace as default view — see §4.3.2) 2. **Documents** 3. **Activity** 4. **Project Context** 5. **Configure** Default tab: **Home**. ### 4.3.1 Tab visual style Tabs use **connected box style**: - Tabs share borders: each tab has a 1.5px border on all sides, with `borderRight: none` between adjacent tabs (except the last). - **Active tab**: backgroundColor bgPanel (white), color accentBtn, borderBottom 2px solid accentBtn, zIndex 1, marginBottom -1. Font weight 700 for Home/Documents/Activity, 650 for Project Context/Configure. - **Inactive tabs**: backgroundColor **#E8EAEE**, color **#2A2D35**, border 1.5px solid #CDD1D8. Font weight 450. - First tab: borderRadius "6px 0 0 0". Last tab: borderRadius "0 6px 0 0". ### 4.3.2 Home tab The Home tab renders the full notes workspace within the project page. This is functionally identical to the standalone Notes page (§6) but scoped to the current project's notes. Layout: three-pane (note list | editor | comment rail) consuming the full content area below the tabs. The note list defaults to showing only notes belonging to the current project. Filter pills (All, Pinned, Todo) filter within the project scope. The "New" button creates a note automatically associated with the current project. The note list is **collapsible** — a collapse button (SplitV icon) appears in the note list header next to "New." When collapsed, the note list shrinks to a 28px vertical bar with a chevron and rotated "Notes" label. Click to expand. Note list font sizes: title 12.5px, excerpt 11px, metadata 9.5px. ### 4.3.2A Project page wireframe ```text ┌────────────────────────────────────────────────────────────────────────────┐ │ ● Henderson [Switch ▼] [ Add to Project / Drop here ]│ │ [$42.15 / $200] │ │ Securities fraud / deposition / motion work │ ├────────────────────────────────────────────────────────────────────────────┤ │ [Home][Documents][Activity][Project Context][Configure] │ ├────────────────────────────────────────────────────────────────────────────┤ │ Home = project-scoped notes workspace │ │ ┌──────────────┬───────────────────────────────────────┬─────────────────┐ │ │ │ Notes list │ Editor │ Comment rail │ │ │ │ │ │ │ │ │ └──────────────┴───────────────────────────────────────┴─────────────────┘ │ └────────────────────────────────────────────────────────────────────────────┘ ``` ## 4.4 Documents tab ### 4.4.1 Subtabs Subtab order: **Generated · Context Docs · Notes · All** Subtabs use the same connected box style as main tabs but smaller (padding 7px 16px, fontSize 12.5). Active subtab: accentBtn color, accentBtn+"06" bg. Inactive: textTer color, bgPanel bg. ### 4.4.2 Shared list behavior across Documents subtabs Each subtab shows a list of items with consistent row layout: - icon (14px, type-colored) - title (13px, fontWeight 550) + subtitle (11px, textTer) - source badge (Pill) - modified time (10px, textTer) - Remove button (where applicable) Hover: bgPanelAlt. ### 4.4.3 Notes subtab Shows project notes with title, excerpt, modified time, comment count, pin indicator, and "Remove" button that detaches the note from the project (does not delete it). ### 4.4.4 Context Docs subtab Shows project-relevant context documents using DOC7-backed project context plus linked folders. Each document row shows a **source badge** as a Pill: - Directly added documents: badge shows **"Project Bucket"** in accentBtn color. Shows a **"Remove" button**. - Documents inherited from an attached bucket: badge shows **"Bucket: {bucket_name}"** in accent color with italic **"via bucket"** label. Do **NOT** show individual Remove button. User must detach bucket from Project Context tab. ### 4.4.5 Generated subtab Shows generated artifacts associated with this project. Each row shows: origin source ("from Chat: Henderson discovery"), type badge (Artifact, Document, etc.), modified time, Remove button. ### 4.4.6 All subtab Union of Generated + Context Docs + Notes, with type grouping headers. ## 4.5 Activity tab ### 4.5.1 Purpose Activity shows chats, panels/forums, tasks, and automations associated with the project. ### 4.5.2 Subtabs Subtab order: **Chats · Panels & Forums · Tasks · Automations · All** Connected box style matching Documents subtabs. ### 4.5.3 Chats subtab Shows project chats with: title, message count, last active time, subtype badge (Chat / Multi-Agent / Red-Team), unread indicator, Remove button. ### 4.5.4 Panels & Forums subtab Shows project panels and forums with: title, type badge, status, participant count, Remove button. ### 4.5.5 Tasks subtab Shows project tasks with: title, status pill (running/waiting/complete/scheduled/failed), assigned agent, cost, Remove button. ### 4.5.6 Automations subtab Shows project automations with: title, enabled/disabled badge, next run time, last run status, Remove button. ### 4.5.7 All subtab Union of all activity items with type grouping. ## 4.6 Project Context tab ### 4.6.1 Purpose Project Context shows the DOC7 bucket configuration that provides default context for project-scoped work. ### 4.6.2 Core model A project's context is built from: 1. **Primary project bucket** — auto-created with the project; the project's "own" document store. 2. **Attached shared buckets** — existing DOC7 buckets linked to the project for additional context. 3. **Linked folder aliases** — filesystem folder references optionally synced to the primary bucket. ### 4.6.3 UI sections The Project Context tab shows: - **Primary Bucket card**: title, health badge, document count, "Open Full Bucket" action. - **Attached Buckets card**: list of attached buckets with health badges, "Detach" action per bucket, "Attach Bucket" action. - **Linked Folders card** (if any): list of linked folder aliases with mode, sync status, "Sync Now" / "Remove" actions. - **Project Background / Instructions**: editable text area for project-level instructions and context notes. ### 4.6.4 Inheritance behavior Project context is durable through project membership and is applied at **operation assembly time**, not because a page is merely visible. Project context candidate buckets are: 1. primary project bucket 2. attached project buckets in `order_index` order 3. surface-specific buckets already attached to the target 4. global buckets from DOC7 The project bucket candidate set MUST be de-duplicated by `bucket_id` before materialization. ### 4.6.5 Operation assembly timing by surface | Surface | When project context is assembled | |---|---| | Chat / Room | on every new outbound user turn and on every agent turn | | Task | at dispatch start and each resume after waiting_human / retry / scheduled wake | | Panel | when a panel run is started and at each agent sub-dispatch | | Forum | when a forum thread turn is dispatched | | Automation | at execution start | | Note AI request | when EC handles `note_ai_request` for a project-scoped note | ### 4.6.6 Project-context assembly pseudocode ```ts function resolveProjectBucketsForTarget(target: { target_type: "chat" | "room" | "task" | "panel" | "forum" | "automation" | "note_ai" target_id: string project_id: string | null surface_bucket_ids: string[] per_run_excluded_bucket_ids: string[] }): string[] { const buckets: string[] = [] if (target.project_id) { const project = readProject(target.project_id) buckets.push(project.primary_project_bucket_id) for (const attached of readProjectAttachedBuckets(target.project_id)) { buckets.push(attached.bucket_id) } } for (const surfaceBucketId of target.surface_bucket_ids) buckets.push(surfaceBucketId) for (const globalBucketId of readGlobalBucketIds()) buckets.push(globalBucketId) return dedupePreserveOrder(buckets) .filter(bucketId => !target.per_run_excluded_bucket_ids.includes(bucketId)) .filter(bucketId => !isArchivedOrDeletedBucket(bucketId)) } ``` ### 4.6.7 Explicit bulk actions Project Context MUST offer these explicit actions: - **Apply Project Context to Open Now** — opens a review modal with checkboxes for current chat, open chats/rooms, open panels/forums, in-progress tasks, scheduled tasks. Nothing applied until user clicks **Apply**. - **Attach Project Buckets to Selected Items** ### 4.6.8 Per-run exclude model Per-run excludes remain DOC7-native: - per-run excludes are ephemeral; - they are stored in the dispatch/request object, not in project durable state; - excluding a project bucket for one run does not detach it from the project; - the UI must show excluded buckets visibly in the run/dispatch review UI. ### 4.6.9 Project Context vs Context Docs split Project Context tab manages bucket relationships. Context Docs subtab (under Documents) shows the resulting visible files. They are not duplicate surfaces — one manages the plumbing, the other shows the visible content. ## 4.7 Configure tab ### 4.7.1 Purpose Configure is the canonical place to manage project settings, membership, defaults, overlays, budget, and lifecycle. ### 4.7.2 Required sections Configure contains the following sections in order: 1. **General** — Project name (editable input), description (editable textarea), color picker (10 preset dots, 20px each, selected has 2px border in textPri), status dropdown (Active / Paused / Archived — see §4.7.8). 2. **Project Content** — Table listing all content types (Chats, Notes, Tasks, Panels, Forums, Documents, Generated, Automations) with icon, count, **"View in Browser →"** link per row, and **"Manage Members →"** link per row. An **"Add to Project / Drop items here"** surface appears immediately after the "Project Content" title, spanning flex:1 maxWidth 300px. 3. **Defaults** — Default agent (dropdown), default model (dropdown), fallback chain (dropdown with chain preview), think level (dropdown: off/minimal/low/medium/high/xhigh). 4. **Prompt & Overlay (DOC17)** — Toggle: "Apply overlays to all project chats" (on/off). **Multiple overlays supported.** Each active overlay listed with remove button (×). "Add Overlay" button. Overlays set the default overlay set for new chats. 5. **Output & Cost** — Output folder (path + Change button). Budget tracking toggle (on/off). Budget amount (editable input). Budget period (dropdown: Daily / Weekly / Monthly / Quarterly). Current spend (display + percentage). Alert threshold toggle. "Show budget in project header" toggle. See §4.7.7. 6. **Project Management** — Standard border, neutral header color. Contains: Duplicate, Archive, Delete. See §4.7.9. ### 4.7.3 Add to Project selector modal The selector modal has: - title: "Add to Project: {project name}" - search field - type filter chips (Chat, Note, Task, Panel, Forum, All) - multi-select item list (defaults to showing "No Project" items first) - toggle: "Show items already in another project" - if an item already has another project and is single-project-only, confirm move on submit - Cmd-click and Shift-click selection supported - Cancel / Add Selected buttons ### 4.7.3A Manage Members drawer Clicking **Manage Members →** on a content type row opens an in-page drawer for that type: - title: `Manage {Type} in {Project}` - search field - item list filtered to items already in the project - per-item "Remove from Project" action - "Add…" button opens the Add to Project selector modal pre-filtered to that type - "Close" button ### 4.7.4 Single-project move rule If an item that supports only one project association is added here and already belongs to another project, Q must prompt before moving it. Text: `Move "{title}" from "{old_project}" to "{new_project}"?` ### 4.7.5 Project defaults Defaults configurable in Configure > Defaults: default agent, default model, fallback chain, think level. Project context defaults live in Project Context, not Configure. ### 4.7.6 Individual item removal The Remove button on item rows **detaches** the item from the project. It does not delete the item. Exact detach behavior: - workflow objects with single-primary-project membership → set `project_id = null` - direct context documents from the project's primary bucket → remove the DOC7 file ref from the primary bucket - bucket-inherited documents → `Remove from Project` is **not shown**; user must detach bucket from Project Context tab - linked folder rows → remove the `LinkedFolderAlias` from the project ### 4.7.7 Budget visibility and configurability - When budget tracking is **off**: no budget pill in header, no spend tracking, no alerts. - When budget tracking is **on**: cost pill appears in header (unless "Show budget in project header" is off). Budget amount and period are configurable. Alert threshold is configurable. - Budget period options: Daily, Weekly, Monthly, Quarterly. Spend counter resets at start of each period. - "Hide" behavior: clicking cost pill in header hides it for the session. "Show budget in project header" toggle in Configure controls persistent default. ### 4.7.8 Project status semantics - **Active**: Fully operational. Visible in sidebar, browser, project dropdown. Context auto-applies. Appears in project switcher. - **Paused**: Visible and accessible but dormant. New chats/tasks do not default to it. Context continues to apply to existing members. Appears with "Paused" badge. See §4.1.9. - **Archived**: Hidden from normal views (sidebar, browser defaults, project dropdown). All content preserved with associations intact. Recoverable by changing status to Active. Accessible via direct URL or browser with "include archived" filter. ### 4.7.9 Archive vs Delete **Archive**: Sets `project.status = "archived"`. Preserves everything — primary bucket, attached buckets, membership edges, notes, chats, tasks, panels/forums, generated artifacts, linked folders. Reversible. **Delete**: Permanently removes project record and settings. Delete flow presents two choices: 1. **Detach all content**: Clear project membership on all workflow objects. Delete linked folder alias rows. Delete primary project bucket. Delete project-bucket attachment rows. Preserve underlying shared buckets. Preserve workflow objects themselves. 2. **Delete project-owned content**: Delete notes whose `project_id` equals this project. Clear `project_id` on chats, rooms, panels, forums, tasks, automations, and artifacts. Delete primary project bucket. Delete linked folder alias rows. Delete project-bucket attachment rows. Preserve shared buckets. Delete confirmation modal shows count summary for all content types. Copy: `Delete project "{title}"? This permanently removes the project record and its project-level settings. Choose whether project-associated content is detached or deleted. Shared attached buckets are detached, not deleted.` --- ## 5. DOC7 bucket integration in DOC20 ## 5.1 Reuse rule DOC20 reuses DOC7's existing bucket model. It does not create a second context engine. Project context is implemented as DOC7 bucket references, not a parallel system. ## 5.2 Primary project bucket Every project has exactly one primary project bucket, auto-created at project creation time. ## 5.3 Additional attached buckets Projects may attach existing DOC7 shared buckets for additional context. Attaching a bucket does not move or copy the bucket — it creates a project-bucket attachment record. ## 5.4 Project-scoped DOC7 behavior When a bucket is attached to a project, that bucket enters the project's candidate set according to §4.6.4 and is subject to normal DOC7 de-duplication, health checks, cap/order rules, materialization mode, and per-run excludes. The primary project bucket MUST always be ordered before attached buckets unless explicitly excluded. ## 5.5 Project-specific presentation of DOC7 The project's Documents tab and Project Context tab show DOC7 bucket contents in project-specific views. These views do not replace the full DOC7 bucket management surface — they provide convenient project-scoped access. ## 5.6 Assign to open now `Assign to open now` is an explicit batch convenience command. It resolves concrete target ids from the active Q session and submits normal DOC7 bucket attach or project-context apply commands. It never creates a hidden ongoing mode. ## 5.7 Linked folders and bucket files Linked folder aliases connect filesystem Places to projects. Files from linked folders can be browsed inline or synced to the project's primary bucket as DOC7 local-path refs. See §3.12.8 for semantics. ## 5.8 Open full DOC7 surface Each bucket card and bucket row in DOC20 surfaces should include an "Open Full Bucket" action that navigates to the canonical DOC7 bucket management page. --- ## 6. Notes ### 5.9 Project Home empty state (R2) If a project has 0 notes, the Home tab displays: - Project details summary (title, description, color badge, budget if enabled) - Prominent [Create Project Note] button - Quick links: "Drop files into Documents" and "Start a Chat" - If the project has documents but no notes: "This project has {N} documents. Create a note to organize your work." --- ## 6.1 Notes as a first-class page Notes are block-based modular documents. Every note is a sequence of blocks: free-form text interspersed with structured modules (task lists, activity feeds, inline threads, configurable bars). Notes appear in the Browser as a type and are organized via the Browser's Notes scope (§3.5.8). There is no separate Notes sidebar — the browser provides all note navigation, folder organization, search, and sort. **The Today note is the default workspace home page.** On first launch each day, Q opens (or generates) the Today note — a pinned daily workspace with configurable block sections. See §6.2B for lifecycle. Users can navigate to any other note from the browser; the Today note is just the starting point. **Unified note model (R1.7):** To-do lists are notes with TaskList blocks. Daily briefs are notes with ActivityFeed blocks. There is no separate to-do schema or to-do surface. All notes support all block types. A "to-do list" is simply a note whose primary content is a TaskList block, filed in whatever folder the user chooses. ## 6.2 Note model ```ts type NoteKind = "standard" | "today" // R1.7: "todo" removed (tasks are blocks), "today" added type NoteStatus = "active" | "archived" | "deleted" // R2: SavedView system_view_key extended // system_view_key?: "current" | "recent" | "no_project" | "recently_deleted" interface NoteMetadataSchema { note_id: string title: string note_kind: NoteKind status: NoteStatus project_id: string | null folder_id: string | null pinned: boolean excerpt: string word_count: number task_count: number // R1.7: total open tasks across all TaskList blocks comment_count: number comment_unread_count: number inline_thread_count: number // R1.7: count of inline thread blocks pending_change_count: number last_opened_at: string // R2: for feed dormancy sweep block_type_summary: string[] // R1.7: list of block types present, e.g. ["text","tasks","feed"] created_at: string updated_at: string created_by: string updated_by: string } ``` ## 6.2A Block architecture (R4 — updated from R1.7) Notes are composed of an ordered sequence of blocks. The document structure is: title → metadata bar → block[0] → block[1] → ... → block[N]. Between any two blocks (and before block[0] / after block[N]), the user can type free-form text or insert a new block. **Editable canvas with module islands (R4):** The entire note content area is `contentEditable` — all space between modules is typeable. Modules render as bordered "island" components embedded in flowing text. Editable paragraph gaps are enforced between modules. A final editable area at the bottom ensures typing below the last module. In TipTap: modules = custom NodeView extensions; ambient text = standard paragraphs. ### 6.2A.1 Block types (R4 — updated) ```ts type NoteBlockType = "text" | "task_list" | "todo" | "activity_feed" | "inline_thread" | "bar" | "note_block" | "calendar" interface NoteBlockBase { block_id: string block_type: NoteBlockType position: number // 0-indexed order in document collapsed: boolean title: string | null // editable title for non-text blocks created_at: string updated_at: string } ``` **Block type rename (R4):** The `"tasks"` block type has been renamed to `"todo"` for consistency with the tab type and UI labeling. `renderBlock` handles both `b.type==="tasks"` and `b.type==="todo"` for backwards compatibility. New blocks are created with `"todo"` type via `insertBlock("todo")`. The `"task_list"` type in the schema remains valid for existing data. **Text blocks** are implicit — free-form Tiptap content between module blocks. They do not have a `NoteBlockBase` record. Text is stored in the Tiptap JSON document as standard ProseMirror nodes. Module blocks are custom Tiptap node extensions that render as structured UI within the document flow. **Note Block (R4):** A new module type — a boxed, movable note distinct from ambient canvas text. Renders as a bordered island with drag handle, collapse/expand, and delete button. ### 6.2A.2 TaskList block ```ts interface TaskListBlock extends NoteBlockBase { block_type: "task_list" title: string // editable, e.g. "To Do", "Henderson Tasks" tasks: TaskItem[] } interface TaskItem { task_id: string text: string done: boolean due_date: string | null linked_note_id: string | null subtasks: SubTaskItem[] created_at: string updated_at: string } interface SubTaskItem { subtask_id: string text: string done: boolean } ``` **Rendering:** Collapsible module with header bar showing title + open task count. Each task: checkbox + text + optional due date + optional linked note badge + subtask count. Expandable to show subtasks with individual checkboxes, add-subtask input, @Elnor button, Link Note button. "Add task…" input at the bottom. Done section collapsible via `
`. **Elnor interaction:** Elnor can add tasks as tracked changes (so user sees what was added). Elnor can check off tasks. Elnor can add subtasks. All via NoteWriteSkill operating on the Tiptap JSON. ### 6.2A.3 ActivityFeed block (R1.8 — rewritten) An ActivityFeed block is a passive display surface configured by a ModulePreset (§6.2C). It displays items from one of two source types and updates only when viewed — no background processes, no cron jobs, no subscriptions. ```ts interface ActivityFeedBlock extends NoteBlockBase { block_type: "activity_feed" title: string // editable, e.g. "Activity Brief", "Henderson Emails" preset_id: string | null // links to a ModulePreset (§6.2C), null for inline config source: SystemEventSource | AgentFeedSource max_items: number // default 25. Display cap. Older items scroll off. max_visible_height: number // px, default 280. Internal scroll beyond this. dormancy_days: number // default 15. Feed goes dormant if note not opened for this many days. 0 = never. last_updated_at: string // ISO timestamp of last refresh dormant: boolean // true if expired. Shows "[Refresh now]" instead of auto-refreshing. sections: FeedSection[] // cached display data } interface SystemEventSource { source_type: "system_events" event_filters: { event_types: string[] // e.g. ["agent.complete", "chain.complete", "watcher.event"] agent_id?: string // filter to specific agent chain_id?: string // filter to specific chain watcher_id?: string // filter to specific watcher forum_id?: string // filter to specific forum/panel project_id?: string // filter to specific project } } interface AgentFeedSource { source_type: "agent_instruction" instruction: string // natural language instruction agent_id: string // which agent processes this feed refresh_interval_minutes: number // minimum minutes between refreshes. Default 60. } // R2: FeedSections are display-only groupings. For system event feeds, EC assigns events to sections // by matching event_type against section labels. For agent feeds, the agent's structured response // specifies which section each item belongs to via an optional "section" field in the JSON array. interface FeedSection { section_id: string label: string icon: string collapsed: boolean items: FeedItem[] } interface FeedItem { item_id: string text: string time: string accent_color: string | null created_at: string } ``` **Refresh lifecycle (lazy evaluation — R1.8):** 1. **On note open:** Q checks `last_updated_at` on each feed block. If the configured interval has elapsed AND `dormant` is `false`, Q fires one refresh. 2. **System event feeds:** Q reads from EC's `system/activity_log.jsonl` with the block's `event_filters` applied. Returns the last `max_items` matching events. While the note is open, new WebSocket events matching the filter append in real-time. Navigate away → display freezes. Navigate back → backfill from log. Zero agent cost. 3. **Agent feeds:** Q sends one request to EC with the feed's `instruction` and `agent_id`. Agent returns items. Items are cached in `feed_cache/{block_id}.json` (see §6.3.1). Next open, if interval hasn't elapsed, show cache. If it has, one fresh call. Zero background cost. 4. **While note is open:** If another `refresh_interval_minutes` passes, Q fires one more refresh. No batching of missed intervals — if 3 hours pass, one refresh, not three. 5. **Navigate away:** All refreshes stop. No background activity. **Dormancy (R1.8):** If the note has not been opened for `dormancy_days` (default 15), the feed block is marked `dormant: true`. On next note open, instead of auto-refreshing, the block displays: "Feed dormant — last updated {date}. [Refresh now]". Clicking Refresh fires one update and resets `dormant: false` and `last_updated_at`. This prevents accidental agent cost when opening old notes. **Dormancy sweep mechanism (R2):** EC runs a daily sweep (on heartbeat at midnight or on EC restart) checking all notes' `last_opened_at` (from `NoteMetadataSchema`) against each feed block's `dormancy_days`. Notes where `last_opened_at + dormancy_days < now` get their feed blocks marked `dormant: true` in the Tiptap JSON. This means when the user opens the note, the dormancy flag is already set. Q sends a `NoteMetadataUpdateCommand` with `last_opened_at: now` on every note open to keep the timestamp current. Setting `dormancy_days: 0` disables dormancy (the Today note uses this — it's opened daily and should always auto-refresh). **Rendering:** Collapsible module with header bar showing title + total item count + gear config. Each section: collapsible sub-header with icon + label + count. Items: ▹ indicator + optional accent dot + text + timestamp. Internal scroll at `max_visible_height`. **Configuration:** Gear icon on the block header opens the Module Preset picker (§6.2C) in edit mode. User can change the preset, adjust `max_items`, `dormancy_days`, `refresh_interval_minutes`, or switch source type. **Agent feed response format (R2):** For agent feeds, EC sends the instruction to the agent with a system prompt appendix: "Respond with a JSON array of items. Each item: `{ text: string, time: string, accent_color: string | null }`. Return only the JSON array." EC parses the response, assigns `item_id` and `created_at` to each item, and stores as `FeedItem[]` in `feed_cache/{block_id}.json`. If the agent response is not parseable JSON, EC wraps the entire response text as a single FeedItem with `time` = now. **No cron. No subscriptions. No background processes. No Mode 2.** If you need background alerts (e.g., "notify me when opposing counsel emails"), use a watcher (EC Core §15A) which triggers a notification via the notification drawer — that's independent of any note or feed block. ### 6.2A.4 InlineThread block ```ts interface InlineThreadBlock extends NoteBlockBase { block_type: "inline_thread" context_quote: string | null // text that prompted the thread (from selection) display_mode: "inline" // always "inline" for this block type messages: ThreadMessage[] } interface ThreadMessage { message_id: string author: string body: string created_at: string edited: boolean edited_at: string | null } ``` **Rendering:** Collapsible. Expanded: header bar ("Thread · N" + optional "re: {context_quote}"), chronological message list (flat, not nested), reply input at bottom. Collapsed: single line "[E] Elnor · N messages · time". **Invocation (see §6.2D for full @mention spec):** 1. **@mention on a line:** User types `@Elnor {question}` on any line. Tiptap converts the line into an InlineThread block and sends the question to the agent. Agent response appears as a reply. 2. **Bubble menu → "Ask inline":** User selects text, clicks "Ask inline" in bubble menu. Creates an InlineThread block below the selection with the selected text as `context_quote`. 3. **/ask slash command:** User types `/ask` → prompt appears → type question → Enter creates InlineThread block. **Async lifecycle (R2):** While an agent is processing an inline thread request, the reply input shows a loading indicator (spinner + "Agent thinking..."). The user can continue typing but cannot submit another @mention until the current request completes or is cancelled (via NoteAICancelCommand). On error, the thread shows the error as a system message with [Retry] and [Dismiss] actions. **Editing:** User can edit or delete their own messages. User can delete agent messages (with confirmation). The whole thread is deletable via block delete. **Durable storage:** InlineThread blocks are stored both in the Tiptap JSON (for rendering position) and in `comments_current.json` with `display_mode: "inline"`. This means inline threads appear in the global comments index (§8.10.2) and can be queried cross-surface. ### 6.2A.5 ConfigurableBar block ```ts interface BarBlock extends NoteBlockBase { block_type: "bar" title: string // the notice/alert text accent_color: string | null time: string | null elnor_ref: string | null // chat reference for Elnor configuration } ``` **Rendering:** Single-line bar with drag handle + text + optional timestamp + "Elnor" button + dismiss X. No icons next to the text. The "Elnor" button opens a chat with a reference to this block, so the user can instruct Elnor on how to update or manage it. **Use cases:** Email arrival notices, deadline reminders, status alerts. Elnor inserts these at a configured position (default: top of document) when trigger conditions are met. User can dismiss (removes block) or click Elnor to configure the trigger. ### 6.2A.6 Block insertion (R4 — updated from R1.8) **+Module toolbar button:** The note editor toolbar includes a "+Module" button (grid icon + label). Click opens a **two-step dropdown picker** (R4): - **Step 1 ("choose"):** List of available module types: Note Block, To Do, Link Existing To Do, Activity Feed (→ step 2), @{Agent} Thread, Notice Bar, Calendar. - **Step 2 ("feed_presets"):** When "Activity Feed" is selected, shows the ModulePreset picker (§6.2C) with tabs: **system** | **agent**. Feed presets: System Activity, System Notices, Gate Approvals, Active Operations (system tab); Morning Summary, Email Watch, Deadline Tracker (agent tab). The selected block is inserted at the current Tiptap cursor position via `editor.commands.insertContentAt(editor.state.selection.from, blockNode)`. **"Link Existing To Do" (R4):** Instead of creating a new empty to-do block, this option opens the **universal content selector** (§6.20.30J) showing all existing to-do lists from the unified `fpTodoLists` pool (§6.21): up to 8 recent lists, "Create new list" at top, "Browse all…" at bottom (opens Notes browser with To Do scope active). Selecting a list embeds it as a to-do module that reads/writes the same shared data. Changes in the module propagate to the palette and standalone tab views. **Each module (R4):** Drag handle, collapse/expand, delete button, title bar. **No + inserters between blocks.** The hover-gap inserter from R1.7 is removed. The toolbar button and /slash commands are the only insertion mechanisms. **/slash commands:** On any empty line, typing `/` triggers a command palette: - `/todo` or `/tasks` → insert To Do block - `/feed` → insert ActivityFeed block (opens preset picker inline) - `/ask` → insert InlineThread block (with prompt for initial message) - `/bar` or `/notice` → insert ConfigurableBar block - `/table` → insert table (§6.15.15A) - `/cal` or `/calendar` → insert Calendar block (R4) - `/note` → insert Note Block (R4) **Block spacing (R1.8):** Module blocks have 8px margin above and below. This provides a compact visual separation. Blocks do not auto-insert text spacers between themselves — if two modules are adjacent, they render with 16px total gap (8px bottom of first + 8px top of second). The user can press Enter between blocks to add a text line for more space if desired. **Drag reorder:** Every non-text block has a drag handle (grip icon) in its header row. Drag to reorder blocks within the document. Text between blocks is user content and stays in place — dragging a module does not move adjacent text. If a drag results in two modules becoming adjacent (no text between them), no spacer is auto-inserted. **Block deletion:** Every block has a delete button (X icon). Text blocks show the X on hover. Module blocks show the X in their header bar. Deletion is immediate with no confirmation (undo via standard Ctrl+Z). Deleting a feed block also deletes its cached items in `feed_cache/{block_id}.json`. **Collapse/expand:** Every non-text block collapses to a single-line header bar showing: chevron + icon + title + summary count. Click to expand. Collapsed state persists across sessions. **Editable titles:** TaskList, ActivityFeed, and Calendar block titles are editable. Click the title text to enter edit mode (inline input). Enter to save, Escape to cancel. ## 6.2B Today note lifecycle (R1.7) ### 6.2B.1 Purpose The Today note is the default workspace surface. It opens automatically when Q launches. It provides a daily workspace combining tasks, agent briefings, alerts, and scratch notes in a single editable surface using the block architecture (§6.2A). ### 6.2B.2 Generation On first open each day (or at a configurable time, default: midnight), EC generates a new Today note: 1. Create a note with `note_kind: "today"`, `title: "Today — {weekday}, {month} {day}"`, `pinned: true`. 2. Apply the user's Today template (stored in Settings or as a template note). Default template: - TaskList block titled "To Do" (populated with carried-over tasks from yesterday — see §6.2B.3) - ActivityFeed block configured with the "System Activity" preset (§6.2C.3), `dormancy_days: 0` - Text block (empty — "Working Notes" area) 3. All feed blocks in the new Today note start with `dormant: false`, `last_updated_at` = now. They refresh on first view. The Today template does not hardcode specific feed sections (e.g., "Elnor", "Deadlines", "Email"). Feed content is determined by the preset configuration. Users can customize the Today template to include any combination of presets — for example, adding a "Morning Summary" agent preset and a "Gate Approvals" system preset alongside the default "System Activity". ### 6.2B.3 Rollover When a new Today note is generated: 1. **Unchecked tasks carry forward:** All tasks in yesterday's TaskList blocks with `done: false` are copied to today's "To Do" block. They appear as regular task items (not tracked changes — they're the user's tasks). 2. **Checked tasks stay:** Completed tasks remain in yesterday's note as a record. 3. **Yesterday's note renamed:** Title changes from "Today — ..." to "{Weekday}, {Month} {Day}" (e.g., "Tuesday, March 18"). 4. **Yesterday's note moved:** Folder changes to "Daily Notes" folder (auto-created if it doesn't exist). 5. **Yesterday's note unpinned:** `pinned: false`. ### 6.2B.4 Configuration The Today template is configurable via Settings → Workspace → Today Template. Users can: - Add, remove, or reorder default blocks - Configure which ActivityFeed sections appear - Set the rollover time (default: midnight local) - Disable Today note generation (starts with blank workspace instead) Alternatively, users can edit a template note in the Templates folder named "Today Template" — the note's block structure becomes the template. ### 6.2B.4A Today note resolution (R2) Q resolves the current Today note on launch via a read call: ```ts interface TodayNoteResolveReadCall { op: "resolve_today_note" } interface TodayNoteResolveResponse { note_id: string | null // null if not yet generated today needs_rollover: boolean // true if yesterday's note is still current } ``` If `needs_rollover` is true, Q triggers `TodayNoteRolloverCommand` first, then opens the new note. If `note_id` is null and `needs_rollover` is false (first-ever launch), EC auto-generates from template. Deep-link route: `/notes/today` internally resolves via this read call, then redirects to `/notes/{resolved_note_id}`. ### 6.2B.5 Navigation The Today note is always pinned to the top of the browser results when Notes scope is active. It shows a sun icon (☀️) badge. Clicking any other note in the browser navigates to that note. Clicking "Today" in the browser (or pressing a configurable shortcut) returns to the Today note. ### 6.2B.5A Today rollover while editor is open (R2) When `TodayNoteRolloverCommand` completes while the old Today note is open in the editor, Q shows a non-blocking toast: "Today note has rolled over. [Open new Today note]". The old note remains editable (it's now a regular note in the Daily Notes folder). The toast link navigates to the new Today note via the `/notes/today` deep-link. ## 6.2C ModulePreset schema and library (R1.8) ### 6.2C.1 Purpose A ModulePreset is a saved configuration for an ActivityFeed block. It packages a source type, filters or agent instruction, and display preferences into a reusable unit. Pre-built presets ship with ELNOR. Users can create custom presets and reuse them across notes. ### 6.2C.2 Schema ```ts interface ModulePreset { preset_id: string name: string // "System Activity", "Henderson Deadlines" icon: string // icon key from Q design system description: string // shown in picker block_type: "activity_feed" // currently only feed blocks use presets category: "system" | "agent" | "custom" source: SystemEventSource | AgentFeedSource // from §6.2A.3 default_max_items: number // default 25 default_refresh_interval_minutes: number // default 60 for agent, 0 for system (real-time) default_dormancy_days: number // default 15 created_by: "system" | "user" created_at: string } ``` Storage: `ELNOR_MEMORY/module_presets/presets_current.json` (array of `ModulePreset`). ### 6.2C.3 Pre-built system presets These ship with ELNOR and cannot be deleted (but can be hidden in Settings): | Preset | Source type | Event filters / instruction | Cost | |---|---|---|---| | System Activity | `system_events` | `["agent.complete", "chain.complete", "chain.aborted", "memory.extracted"]` | Zero | | System Notices | `system_events` | `["agent.error", "chain.step_error", "watcher.event"]` filtered to errors/warnings | Zero | | Gate Approvals | `system_events` | `["chain.gate_waiting", "chain.approval_needed"]` | Zero | | Active Operations | `system_events` | `["agent.status", "chain.started", "chain.step_started"]` | Zero | | Morning Summary | `agent_instruction` | "Summarize overnight agent activity, upcoming calendar deadlines, and flagged emails. One line per item." Default agent: Elnor. | 1 agent call/refresh | | Email Watch | `agent_instruction` | "Check Outlook inbox for important emails per standing orders. Summarize each in one line." Default agent: Elnor. | 1 agent call/refresh | | Deadline Tracker | `agent_instruction` | "List upcoming deadlines from Outlook calendar and case files, ordered by date." Default agent: Elnor. | 1 agent call/refresh | ### 6.2C.4 Picker UX The +Module toolbar button opens a dropdown. When "Activity Feed" is selected, a second-level panel shows the preset picker: **Tab row:** System | Agent | My Presets **Preset cards:** Each card shows icon, name, description. Click to insert a feed block configured with that preset. **My Presets tab:** Shows only user-created presets (`category: "custom"`). Empty state: "No custom presets yet. Configure a feed and click 'Save as Preset'." **"Create Custom" link:** Opens a configuration form with: - Feed name (text input) - Source type (System Events | Agent — two selectable cards) - Agent picker (dropdown of available agents — default Elnor) - Instruction (textarea — only for agent source) - Update every: number input + unit (min/hr/day) - Keep last: number input + "items" - Expires after: number input + "days" with "never" option - Create button → inserts block and closes picker **Preset → BlockInsert data flow (R2):** When a preset is selected in the picker, Q builds a `BlockInsertCommand` (§8.5) with: - `block_type: "activity_feed"` - `block_data.source` = preset's `source` - `block_data.max_items` = preset's `default_max_items` - `block_data.dormancy_days` = preset's `default_dormancy_days` - `block_data.preset_id` = selected preset's `preset_id` - `block_data.last_updated_at` = now - `block_data.dormant` = false - `block_data.max_visible_height` = 280 (default) - `block_data.sections` = [] (populated on first refresh) For TaskList blocks, no preset is used — Q builds `BlockInsertCommand` with `block_type: "task_list"` and empty `tasks[]`. **Save as Preset:** Any configured feed block's gear menu includes "Save as Preset" which saves the current configuration to `presets_current.json` with `category: "custom"`. ### 6.2C.5 Feed lifecycle rules (R1.8) | Event | Effect on feed blocks | |---|---| | **Delete block** | Feed cache deleted at `feed_cache/{block_id}.json`. No other cleanup needed — no subscriptions to cancel. | | **Delete note** | EC deletes entire `notes/{note_id}/` directory. Feed caches go with it. | | **Note not opened for `dormancy_days`** | All feed blocks in that note marked `dormant: true`. Next open shows "Feed dormant" message. | | **User clicks Refresh on dormant feed** | One refresh fires. `dormant` reset to `false`. `last_updated_at` updated. Dormancy counter resets. | | **Today note rollover** | Yesterday's note becomes historical (no active feeds — dormancy applies normally). Today's new note gets fresh feed blocks from template with `last_updated_at` = now, `dormant: false`. | | **Open the note** | Stale feeds auto-refresh (one call per stale feed). Non-stale feeds show cached data. Dormant feeds show "Refresh now" prompt. | ## 6.2D @mention agent invocation (R1.7) ### 6.2D.1 Invocation pattern Typing `@` followed by an agent name (e.g., `@Elnor`) anywhere in a note triggers agent invocation. Tiptap recognizes the @mention via a custom mention extension and shows an autocomplete dropdown of available agents. ### 6.2D.2 Invocation contexts and behavior | Context | Behavior | |---|---| | **On a new line:** `@Elnor check the Vivendi cite` | Converts the line into an InlineThread block (§6.2A.4). Sends the text after the @mention as the instruction. Agent response appears as a reply in the thread. | | **Inside a TaskList item's @Elnor button** | Creates an InlineThread block indented below the task. Context includes the task text and any subtasks. | | **In a comment reply** | Sends the comment thread to the agent via the existing per-comment ✨ Send flow (§6.15.17). Agent responds in the comment thread. | | **In an InlineThread reply** | Sends the thread history + new message to the agent. Agent responds in the same thread. | | **In the bubble menu** | "Ask inline" creates an InlineThread; "Ask in chat" routes to chat. Both use the selected text as context. | ### 6.2D.3 Agent dispatch (R2 — rewritten) When @mention triggers an InlineThread, Q sends a single `InlineThreadCreateCommand` (§8.5). This is a compound command: EC creates the block AND dispatches to the agent. No separate `NoteAIRequest` is needed for the initial @mention. **Dispatch flow:** 1. Q sends `InlineThreadCreateCommand` with `agent_id`, `initial_message`, `context_quote`, `position`. 2. EC creates the InlineThread block in the Tiptap JSON with the user's message as the first `ThreadMessage`. 3. EC extracts context: the surrounding 2-3 paragraphs (or full task/thread if invoked from those contexts) + the `initial_message` as the instruction. 4. EC dispatches to the agent via the standard Gateway path (DOC11). The `source_context` includes `{ source_type: "inline_thread", note_id, block_id }`. 5. Agent processes and returns a text response. 6. EC appends the agent's response as a new `ThreadMessage` (with `author: agent_id`) to the block's `messages[]` array via an internal `BlockUpdateCommand`. 7. Q receives the updated note revision and re-renders the thread with the new message. **For subsequent replies:** `InlineThreadReplyCommand` with `mention_agent_id` follows the same dispatch pattern — EC saves the reply, dispatches to the agent, and appends the agent's response as another `ThreadMessage`. **Error handling:** If the agent errors, EC appends a system message to the thread: `{ author: "system", body: "Agent error: {message}. [Retry]" }`. The user can click Retry to re-dispatch. ### 6.2D.4 Agent selector The @mention autocomplete dropdown shows: configured agents (from SOUL.md agent list), with the default Note Advisor (§6.15.8) at the top. The bubble menu agent selector dot shows the currently selected agent and is clickable to change. ## 6.3 Note storage and revisions ### 6.3.1 Durable file layout Use the following storage layout under `ELNOR_MEMORY/notes/`: ```text ELNOR_MEMORY/notes/ notes_current.json notes_events.jsonl by_id/ / body_current.json revisions.jsonl comments_current.json comments_events.jsonl tracked_changes_current.json tracked_changes_events.jsonl links_current.json exports.jsonl feed_cache/ # R1.8: cached feed items per block .json # FeedItem[] cached from last refresh ``` `notes_current.json` is the materialized current-note registry (array of `NoteMetadataSchema`). `notes_events.jsonl` is the append-only registry-level event log for create / metadata update / archive / delete. Per-note current files are atomic JSON snapshots. Per-note event files are append-only JSONL. ### 6.3.2 Current body snapshot schema ```ts interface NoteBodyCurrentSchema { note_id: string current_revision_id: string body_tiptap_json: TiptapJSONContent plain_text: string excerpt: string word_count: number updated_at: string updated_by: string base_import: { format: "docx" | "markdown"; filename: string } | null } ``` ### 6.3.3 Autosave policy Autosave rules: - debounce after content edit: **1200ms** - max unsaved interval while typing continuously: **15s** - flush triggers (bypass debounce, save immediately): - editor blur (clicking another note, clicking outside the editor) - route change away from note (navigating to any other page) - explicit close (closing the note or the app) - before applying agent edit result - switching to a different note in the note list - browser/app losing focus (window blur) - title edits and body edits share the same autosave pipeline - one autosave flush creates one `NoteRevisionEvent` - a keystroke stream MUST NOT create per-keystroke revisions **Rapid switching behavior:** If a user types in note A, clicks note B, types in note B, clicks back to note A — each departure triggers an immediate flush. Note A's state is saved on departure to B; note B's state is saved on departure back to A. No data loss occurs regardless of switching speed. The debounce timer is cancelled and replaced by the immediate flush on each departure. ### 6.3.4 Revision source and coalescing ```ts type NoteRevisionKind = | "autosave" | "manual_save" | "ai_apply" | "tracked_change_accept" | "tracked_change_reject" | "restore" | "metadata_update" | "import" ``` Autosave coalescing rule: - edits by the same actor within one pending debounce window collapse into one autosave revision; - once flushed, subsequent edits create a new revision on the next flush; - agent-applied changes are never merged into a user autosave revision. ### 6.3.5 Conflict guard All note write commands MUST include `base_revision_id`. If the submitted `base_revision_id` does not match the current note body snapshot revision id: 1. Q flushes local unsaved editor state if any; 2. EC attempts position remapping for comment anchors and tracked changes where applicable; 3. if remap is safe, apply on latest body and append revision; 4. if remap is unsafe, reject with `NOTE_REVISION_CONFLICT`. ### 6.3.5A Agent edit while user is typing Agents do NOT use the autosave pipeline. Agent edits go through EC commands (`NoteAIRequestCommand`) and create revisions with kind `ai_apply`. The autosave pipeline is exclusively for the user's local Tiptap editor state. When an agent edit arrives while the user has unsaved local changes: 1. EC receives the agent's `NoteAIRequestCommand` with a `base_revision_id` 2. EC signals Q: "agent edit incoming for note {id}" 3. Q immediately flushes the user's unsaved editor state (cancels debounce, saves as `autosave` revision) 4. EC applies the agent's changes on top of the latest revision (which now includes the user's just-flushed state) 5. EC creates a new revision with kind `ai_apply`, containing the agent's tracked changes 6. Q receives the update event and refreshes the Tiptap editor state — the user sees the agent's tracked changes appear inline (red/green marks) without losing any of their own work 7. The user's cursor position is preserved if possible; if the agent's changes overlap the cursor position, cursor moves to the nearest safe position This means: - The user never loses work — their edits are flushed before the agent's changes apply - The agent's changes always appear as tracked changes, never as silent overwrites - The user sees a toast: "{Agent} made changes to this note" with the pending change count updating in the toolbar - If the user is in the middle of typing a word, the flush captures the partial word — it's not lost If the user and agent are both editing rapidly (unlikely but possible), EC serializes all writes through the `base_revision_id` chain. Each write builds on the previous. No parallel overwrites. ### 6.3.6 Version history Version History shows: revision timestamp, source, kind, optional summary, preview, restore action. Restoring a version creates a **new** revision with kind `restore`. It does not delete later revisions. ### 6.3.7 Note autosave state machine ```ts type NoteAutosaveState = "clean" | "dirty" | "saving" | "error" ``` Transitions: - `clean -> dirty` on local edit - `dirty -> saving` when debounce timer fires or flush trigger occurs - `saving -> clean` on successful save - `saving -> error` on failed save - `error -> saving` on user retry or next successful autosave trigger Toolbar indicator text: `dirty` → "Unsaved"; `saving` → "Saving…"; `clean` → "Saved {relative time}"; `error` → "Save failed" ## 6.4 Notes page layout **R2 clarification:** The Notes page renders the note editor in the main content area. Note hierarchy, folder navigation, search, and sort are handled by the Browser's Notes scope (§3.5.8). There is no separate NoteListPane — the browser column provides all note organization when open. When the browser is closed, the Notes page shows the currently selected note (or the Today note) without a left sidebar. The note list is the browser. Three-pane layout: ```text ┌──────────────┬───────────────────────────────────────┬─────────────────┐ │ Note list │ Note editor │ Comment rail │ │ 240px │ flex:1 │ 260px │ │ [New][▤] │ Title │ Open │ │ Search │ Toolbar: format | AI | Review | ... │ Resolved │ │ rows… │ body… │ Orphaned │ └──────────────┴───────────────────────────────────────┴─────────────────┘ ``` Note list: 240px, collapsible to 28px sidebar. Collapse button (SplitV icon) in header next to "New." Comment rail: 260px, collapsible via toolbar Comments toggle button. ## 6.5 Note editor ### 6.5.1 Base editor Tiptap (open-source) with ProseMirror. No paid Tiptap Pro features. Supported formatting: paragraphs, headings (h1-h3), bold, italic, underline, bullet lists, numbered lists, task lists (checkboxes), links, code blocks, blockquotes, horizontal rules, keyboard shortcuts for common formatting. ### 6.5.2 Note title The note title is editable inline at the top of the editor. fontSize 21, fontWeight 700. Title edits are part of the autosave pipeline. ### 6.5.3 Editor toolbar Layout: flex row, wrap, gap 1, padding "4px 10px", bg bgPanelAlt, borderBottom border. Groups separated by Sep (1px vertical divider, 18px tall, borderLight): - **GROUP 1** — Block type: `