feat: v0.11.0 — collaborative state server + CRDT sync stack#30
Open
cuttlefisch wants to merge 73 commits into
Open
feat: v0.11.0 — collaborative state server + CRDT sync stack#30cuttlefisch wants to merge 73 commits into
cuttlefisch wants to merge 73 commits into
Conversation
- Node struct gains `properties: HashMap<String, String>` for arbitrary org property drawer key-value pairs (foundation for activity tracking) - org parser captures ALL properties, not just :ID: - scan_heading_id → scan_heading_properties for heading-level drawers - update_property() utility for rewriting single properties in-place - SQLite schema v4→v5 migration: properties_json column - 6 new tests (parse, round-trip, update_property, migration) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add kb_write_guard: HashSet<PathBuf> to suppress watcher events for paths MAE is currently writing (activity tracking, chain-fill) - drain_kb_watchers() skips Upserted events for guarded paths - file_ops save path guards the path before sync reimport, preventing the duplicate sync+async reimport on every save - KbWatcherStats gains events_suppressed + reimports_total counters - Add kb_dailies_dir + kb_daily_chain_gap_max fields (for Part 4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New `crates/kb/src/activity.rs`: activity scoring with configurable decay weights, date arithmetic (no chrono), body hash for change detection - `KnowledgeBase::search_sorted_by_activity()` re-sorts results by score - `KnowledgeBase::get_mut()` for in-place property updates - Activity recording: kb_record_access, kb_record_modification, kb_record_link with write-guard protection - Property rewriting via `update_property()` + guarded disk writes - 4 new options: kb_activity_tracking, kb_activity_decay, kb_dailies_dir, kb_daily_chain_gap_max - 10 new tests (date parsing, scoring, hashing, day arithmetic) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Daily note creation with chain-back linking (org-roam-dailies parity): - kb_create_daily_stub, kb_daily_chain_fill (backward chain-linking) - kb_goto_daily_today/yesterday/date, kb_daily_prev/next navigation - show_kb_audit_report with health, watcher stats, instance summary - Commands: daily-goto-today, daily-goto-yesterday, daily-goto-date, daily-prev, daily-next, kb-audit - MiniDialogContext::DailyGotoDate for date input - lookup_key_binding() and query_keybindings() on Editor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e (Part 5) - modules/dailies/ with module.toml + autoloads.scm - SPC n d t/y/d/p/n keybindings for daily navigation - kb-delete moved from SPC n d to SPC n D - concept:dailies seed node with chain-fill docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n cleanup, metrics (Part 6) - Add `detect_stale_nodes()` and `remove_stale_nodes()` to KnowledgeBase (nodes whose source_file no longer exists on disk) - Add `validate_links()` for on-save broken link detection (advisory warning) - Add `kb-cleanup-orphans` command (SPC n C) — removes orphan user notes while preserving seed nodes (cmd:, concept:, lesson:, scheme:, option: prefixes) - Split KbWatcherStats `events_skipped` into `suppressed_debounce` and `suppressed_timebox` for granular watcher metrics - Add cumulative `drain_us_sum`, `drain_count`, `reimports_total` to watcher stats - Show stale nodes section + watcher metrics in `:kb-health` report - Update introspect AI tool with new watcher stat fields - 4 new tests: stale detection, link validation, orphan cleanup, seed preservation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create keymap-doom module (modules/keymap-doom/) with all SPC leader bindings mirrored from kernel — prepares for flavor extraction in follow-up PRs - Register `keymap_flavor` option (default: "doom") in OptionRegistry - Add docs/KEYBINDINGS.md — full keybinding reference organized by kernel, doom flavor, and module overlays - Add server-client architecture line item to ROADMAP.md near-term section Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… version - Add 5 dailies bindings (SPC n d t/y/d/p/n) to kernel keymaps.rs so they work without the dailies module being declared in (mae!) - Mirror dailies bindings in keymap-doom module (intentional redundancy matching existing pattern for all other SPC bindings) - Implement (set-group-name MAP PREFIX LABEL) Scheme API: - New pending_group_names field in SharedState - Registration in SchemeRuntime::new() - Drain in apply_to_editor alongside keymap bindings - Used by dailies module to set "+dailies" group label - Add version + modules sections to introspect MCP tool output (agents can now verify build version and loaded module state) - Tests: dailies_bindings_registered, spc_sub_prefixes_have_group_names, set_group_name_works, runtime_define_key_updates_keymap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Which-key popup UX overhaul: - Scrolling: C-j/C-k, C-n/C-p, Up/Down scroll by 1; C-d/C-u by 5. Directional indicators show count above/below visible entries. - Height config: `which-key-max-height-pct` option (10-90%, default 40) replaces hardcoded 2/3 fraction. All 5 which-key options now have config_key for :set-save persistence. - Sort order: `which-key-sort-order` option (key/desc/none). Groups always sort first. - Shared layout: format_keypress() and which_key_column_layout() moved to text_utils.rs — both TUI and GUI renderers use the same function, eliminating height-vs-render mismatch bug. - Group labels: 14 set-group-name definitions in keymap-doom (+ai, +buffer, +code, +eval, +file, +help, +peek, +notes, +open, +project, +quit, +select, +toggle, +window). - Dailies cleanup: removed 5 duplicate define-key calls (bindings already in keymap-doom); module now only provides set-group-name. - Editor helpers: set_which_key_prefix() / clear_which_key_prefix() ensure scroll resets on prefix change. - Fix: UTF-8 safe truncation in AI grading (floor_char_boundary). - 7 new unit tests for format_keypress and which_key_column_layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi-client MCP server:
- Per-client tokio tasks with ClientSession (session.rs)
- Content-Length framing with line-based fallback for backward compat
- EditorEvent broadcast with per-client bounded channels (broadcast.rs)
- $/ping heartbeat, notifications/subscribe, 5s write timeout
- Initialize handshake extracts clientInfo, reports multiClient capability
File safety (layered defense):
- SHA-256 content-hash verification on buffer load/save/reload
- Advisory file locks (.{name}.mae.lock) with stale PID cleanup
- check_disk_changed_by_hash() catches mtime failures (NFS, sub-second)
KB hardening:
- WAL mode (concurrent readers), busy_timeout 5s, synchronous NORMAL
- New test: wal_mode_enabled
Shared code extraction:
- centered_popup_dims() + truncate_to_width() in text_utils (principle 8)
- TUI and GUI popup renderers deduplicated
File tree focus fix:
- Tree window receives focus on open (was staying on editing window)
- New option: file_tree_focus_on_open (default true, :set-save persistable)
Documentation:
- 4 ADRs in docs/adr/ (protocol, text sync, file safety, KB scaling)
- CLAUDE.md principle 10 (multi-client safety) + server-client section
- ROADMAP KB visibility/scoping/tangle items
- Makefile docs-tangle + docs-tangle-check targets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two integration tests exercising the full server over Unix sockets: - multi_client_concurrent_connections: two clients connect, initialize, tools/list, ping, tool call, then one disconnects while other continues - multi_client_subscribe_events: subscribe + shutdown lifecycle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Editor save_buffer_with_hash_check: blocks on mismatch, passes clean - Editor save_buffer_force: overwrites despite mismatch - Editor file lock lifecycle: acquire/release/release_all/contention - MCP $/resync handler: validates response fields - EventBroadcaster sequence monotonicity: validates AtomicU64 increment - CI: widen KB test filter to include changelog+sync tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the collaborative sync layer using yrs (Yjs Rust port, YATA algorithm) as specified in ADR-002, ADR-005, and ADR-006. Provides: - TextSync: YText <-> Rope bridge for collaborative text editing - KbNodeDoc: CRDT schema for knowledge base nodes (YMap) - Encoding utilities for state vectors and updates - Stress convergence test (5 clients, 200 random ops each) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrates mae-sync into mae-core so that buffers with sync enabled generate yrs updates on every text mutation for broadcast to peers: - Add `sync_doc: Option<TextSync>` + `pending_sync_updates` to Buffer - Hook all 10 mutation methods (insert_char, delete_char_backward/forward, delete_line, delete_word_backward, delete_to_line_start/end, insert_text_at, delete_range, open_line_below/above) - Undo/redo rebuild sync state from rope (correctness over efficiency) - replace_contents/replace_rope recreate sync_doc preserving client_id - Buffer::enable_sync/disable_sync/apply_sync_update public API - MCP: sync/enable, sync/state_vector, sync/update protocol methods - EventBroadcaster: SyncUpdate event variant for peer notification - 6 new unit tests (all passing, 0 regressions in 2000+ test suite) Zero-cost when sync disabled: all hooks are Option::map on None. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up executor handlers for sync/enable, sync/state_vector, sync/update, and sync/full_state so MCP clients can enable CRDT sync on buffers, exchange state vectors, and apply remote updates. Pull-based model (clients poll). - New sync_exec.rs dispatcher (4 handlers) in mae-ai executor chain - Buffer lookup by name or index, idempotent enable, base64 transport - sync/full_state added to MCP protocol method match - mae-sync dependency added to mae-ai - 7 unit tests including two-client roundtrip convergence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire the EventBroadcaster into the MCP server so subscribed clients receive push notifications when buffers change. This closes the gap where local edits queued yrs updates but nothing delivered them. Key changes: - McpServer gains SharedBroadcaster (Arc<Mutex<EventBroadcaster>>) - handle_client restructured with tokio::select! for simultaneous request handling and event pushing - notifications/subscribe now updates broadcaster filter - write_notification helper sends JSON-RPC notifications (no id field) - New sync_broadcast::drain_and_broadcast() drains pending yrs updates from buffers and broadcasts to subscribed clients - Drain points: McpToolRequest (immediate), IdleTick/frame_timer (~100ms) New tests: - 2 broadcast unit: sync_update delivery + subscription filtering - 4 drain unit: noop, clears pending, multiple buffers, skips non-sync - 5 E2E integration: push after subscribe, no push before subscribe, two clients (one subscribed), survives disconnect, backpressure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address 6 findings from the Phase A-D architecture review:
Finding 2 (HIGH — Bug): reload_from_disk() now calls sync_rebuild_from_rope()
to keep yrs doc in sync with rope after file reload.
Finding 3 (MEDIUM): replace_rope() and replace_contents() now use
sync_rebuild_from_rope() instead of inline doc recreation, which
also queues the update for broadcast.
Finding 4 (MEDIUM): remove_buffer_raw() logs a warning when dropping
pending sync updates on buffer close.
Finding 10 (MEDIUM): Push notifications now include per-client `seq`
number in params for event ordering. Notification format changed
from `params: event` to `params: { seq, event }`.
Finding 11 (HIGH — Design): sync_rebuild_from_rope() documented as
creating a fresh Doc (discarding CRDT history). TODO added for
incremental diff approach in Phase E.
Finding 1 (MEDIUM): session.subscriptions documented as informational
copy; broadcaster is the authoritative source for delivery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make read_message, write_framed, handle_request pub and generic over AsyncBufRead/AsyncWrite (no longer Unix-socket-only) - Add TCP loopback tests (initialize, write_framed, coexist) - Fix sync/MCP broadcast architecture (audit fixes from Phase D) - Wire TextSync collaborative edits into Buffer properly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New crate: mae-state-server (TCP binary for multi-client CRDT sync) - CLI: start/doctor/--check-config/--version with manual argv parsing - Config: TOML-based with XDG defaults, validation, CLI overrides - Storage: SQLite WAL-first persistence (StorageBackend trait + SqliteBackend) - DocStore: per-document locking (RwLock<HashMap> + per-doc Mutex) - Handler: JSON-RPC dispatch for sync/update, sync/diff, sync/full_state, sync/state_vector, docs/list, docs/content - Compaction: configurable threshold, snapshot + WAL trim, compact-all on shutdown - Recovery: replay snapshot + WAL tail on startup Integration: - Workspace member + Cargo.lock - Dockerfile: dep cache + runtime binary - Makefile: build/install/docker-network-test targets - CI: state-server tests + check-config in server-client job - Release: builds + ships mae-state-server-linux-x86_64 - Systemd unit, shell completions (bash/zsh/fish) - Docker compose for network E2E tests - ADR-005 (KB CRDT) + ADR-006 (collaborative state engine) - CLAUDE.md + ROADMAP.md updated 5 E2E tests (gated on MAE_STATE_SERVER env), 10 unit tests, all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Merge origin/main (v0.10.1, CI fixes, badges workflow) - Resolve CODE_MAP.md conflicts (keep dailies commands) - Update dashboard screenshot with current TUI splash Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove --lib flag from state-server test step (binary-only crate) - Regenerate CODE_MAP with state-server + new pub MCP APIs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ervability Phase 1 — Scalability: - SQLite sharded connection pool (FNV-1a, 4 shards default) in state-server - CRDT-safe undo via reconcile_to() (LCS diff, preserves vector clocks) - DocAddress type (file:/kb:/shared: namespaces) for document addressing - Event sequence tracking (wal_seq on SyncUpdate for gap detection) - Save protocol (SHA-256 content-hash verification) - Background compaction + idle eviction in state-server Phase 2 — UX: - CollabStatus enum + 3 editor fields (status, synced_docs, intent) - 5 collab options (server_address, auto_connect, auto_share, etc.) - 7 commands (collab-start/connect/disconnect/status/share/sync/doctor) - SPC C keybinding group in doom keymap - Status bar segment (priority 4, shows connection state + peer count) - Splash screen quick action (SPC C c) - init.scm section 6 "Collaborative Editing" Phase 2 — AI Tools: - 4 AI tools: collab_status, collab_connect, collab_share, collab_doctor - Tool definitions with parameter schemas and permission tiers - Intent-based dispatch (core sets intent, binary drains each tick) Phase 3 — Observability: - mae doctor: collab section (binary, env, config checks) - audit_configuration: collaboration JSON section - State-server: sync/resync, docs/stats, docs/save_intent, docs/save_committed, $/debug handler methods 30 files changed, 1,130 insertions, 3 new files. 3,336 tests passing, 0 clippy errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3 (observability): - introspect tool: new "collaboration" section with status/server/synced docs - Scheme API: (collab-status) alist, (collab-synced-buffers) list - $/debug method: uptime_secs, connection_count, version fields - handler.rs: server_start_time threading for uptime tracking Phase 5 (documentation): - KB concept nodes: collab-architecture, collab-workflows - KB lesson node: collab-setup (install → configure → connect → share) - docs/COLLABORATION.md: standalone guide (architecture, quickstart, config, debugging) Phase 6 (tests): - E2E network tests: tcp_docs_stats, tcp_save_intent_ok, tcp_resync_protocol, tcp_debug_endpoint - Sync tests: reconcile_to_empty, reconcile_from_empty - Core dispatch tests: 4 collab dispatch unit tests - Status bar format_collab_status tests All 3,324+ tests pass, 0 clippy warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CLAUDE.md: state-server section expanded with new methods, commands, AI tools, Scheme API, options; API stability counts updated - ADR-006: implementation notes for SQLite pool, reconcile_to, event sequence tracking, save protocol, background compaction, editor UX - CODE_MAP: regenerated (20 crates, 169 public items, 496 commands) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4 of the collaborative editing plan: - Schema migration v6→v7: adds `crdt_doc BLOB` column to nodes table - Node.crdt_doc field: optional encoded yrs document bytes - Node.to_crdt_doc(): creates KbNodeDoc from text fields or restores from bytes - Node.apply_crdt_doc(): updates title/body/tags from CRDT content - save_to_sqlite/load_from_sqlite/sync_to_sqlite: persist crdt_doc column - Backward-compatible: older DBs auto-migrate, crdt_doc defaults to NULL - mae-kb now depends on mae-sync for KbNodeDoc access Tests: migration v6→v7, CRDT roundtrip via save/load, Node↔KbNodeDoc conversion, apply_crdt_doc field updates (132 KB tests pass). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ration - scheme/lib/mae-test.scm: describe/it/should/wait-until/run-tests (TAP v14) - crates/mae/src/test_runner.rs: headless event loop test runner with Rust-side test iteration (inject/apply between each test step) - crates/mae/src/main.rs: --test, --test-filter, --test-output CLI flags - crates/scheme/src/runtime.rs: 9 new primitives (exit, write-file, sleep-ms, file-exists?, current-milliseconds, goto-char, buffer-string, buffer-text, execute-ex) Gap fixes: 1. (execute-ex CMD) routes through ex-command parser (supports arguments) — enables (execute-ex "collab-join test.txt"), (execute-ex "w /path") 2. save-as via execute-ex: (execute-ex "saveas /path") or (execute-ex "w /path") 3. collab-join with args via execute-ex (was blocked by run-command) 4. Buffer state refresh between test steps — Rust-side iteration with sync_scheme_state (set! for mutable binding cells) solves Steel's register_value shadowing behavior 5. Error messages now use error-object-message (was showing "<?>") Steel upstream bug: (void) in tail position crashes — SteelVal::Void missing from handle_tail_call() match arm. Workaround: use #t instead. - tests/collab-e2e/: 6 test files + helpers + verify.sh + docker-compose - Makefile: test-scheme + docker-collab-test targets - Collab E2E tests use fixed sleep-ms delays (no wait-until polling) and Rust-side iteration (no run-tests call) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Scheme primitives for CRDT/sync testing (buffer-enable-sync, buffer-drain-updates, buffer-apply-update, buffer-encode-state, etc.) using the SharedState pattern to bypass Steel binding scope issues. New test suites: - tests/crdt/ (35 tests): sync enable/disable, two-buffer convergence, undo with sync active - tests/editor/ (22 tests): insert/delete/replace, mode transitions, undo/redo Infrastructure: recursive test file collection, SharedState-backed get-buffer-by-name and current-mode, should-mode assertion helper. Docs: CLAUDE.md testing framework section, KB concept nodes (scheme-testing, test-runner), 9 scheme API nodes for test functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cle, user story E2E Testing framework enhancements: - should-error + should-match assertions in mae-test.scm + KB nodes - Region state forwarding (visual mode) via SharedState - buffer-search-forward primitive for in-buffer search - Option values forwarding (get-option reads fresh state each step) - Initial sync_scheme_state before first test (fixes mode check on step 0) CRDT lifecycle tests (5 new files, 116 steps): - Concurrent edits: two buffers insert at same position, exchange, converge - Three-client: independent edits → full exchange → byte-identical convergence - Collaborative undo: A inserts, B extends, A undoes (documents known bug) - State vector: incremental sync via encode-state-vector + compute-diff - Reconcile: buffer-reconcile-to generates valid CRDT update for peers New CRDT primitives (Rust): - buffer-encode-state-vector / buffer-get-state-vector - buffer-compute-diff / buffer-get-diff - buffer-reconcile-to / buffer-get-reconcile-result - Buffer::rebuild_rope_from_sync() public method Editor E2E tests (9 new files, 124 steps): - File roundtrip, multi-buffer navigation, visual mode + region - Options get/set, buffer search, keybinding dispatch, KB help - Complex undo chains, test library self-tests (meta-testing) CI integration: - scheme-tests job (every PR): builds TUI, runs editor + CRDT tests - collab-e2e job (push only): docker collab E2E with timeout - test-scheme-ci Makefile target ROADMAP: Phase 12 (RAG Pipeline) + AI Harness & Per-Model Tuning sections 57 → 310 test steps (5.4x), 6 → 20 test files (3.3x), 0% → 100% CI coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…3,629 tests Phase 1: ADR-008 CRDT target metrics (50 clients/doc, 10MB max, <50ms p99) Phase 2: Critical fixes — atomic compaction, share_doc error handling, collab_bridge unwrap elimination, handler param validation, named constants Phase 3: Runtime enforcement — max_documents, max_update_size (1MB reject), max_wal_entries (forced compaction), max_document_size (warning) Phase 4: 4 new Scheme-accessible collab options (max_pending_updates, reconnect_backoff_factor, max_reconnect_attempts, batch_update_ms) Phase 5: 26 new tests (DAP failure, sync encoding edge cases, doc_store limits, handler validation, test library assertions) Phase 6: TESTING.md + CLAUDE.md updated CI fixes: - when-flag arity mismatch (format module) — 3 args not 2 - SPC t s keybinding conflict (spell → SPC t S) - Collab docker e2e now runs on PRs (was push-only) - Steel home directory created in CI - Removed phantom mae-test-fixtures exclude Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The replace_all for mae-test-fixtures collapsed adjacent YAML lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI consolidation: - Merged check-config + package-install + scheme-tests into single e2e job - Merged container/smoke + container/new-user into single containers job - Eliminates 3 redundant cargo build --release cycles Docker e2e fix: - Pre-create /sync with mae:mae ownership in Dockerfile runtime stage - Fixes "Permission denied (os error 13)" on write-file "/sync/a-shared" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Join-save model (industry standard): joined collab buffers have no auto file_path. Users must :saveas to persist locally. Opt-in project path mapping via collab_auto_resolve_paths + MiniDialog prompt. Server-side suffix matching: sync/resync resolves bare filenames (e.g. "test.txt" finds "file:no-project/test.txt"), fixing the Docker collab E2E doc_id mismatch bug. CI warning fixes: SPC t S→SPC t Z (spell conflict), removed duplicate SPC c f (format owns it), fixed when-flag TailCall error in format module (wrap body in lambda). New: 3 collab options, CollabResolvePath MiniDialog, descriptive save error messages, 16-step Scheme join-save test, ROADMAP E1-E8 + KB fuzzy body search item. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Body content now included in search scoring (command palette + AI kb_search) - all_id_title_body_triples() provides body text for fuzzy matching - kb_search_sort option: "relevance" (default) or "recent" ordering - kb_federated_search() used by both palette and AI tool (single code path) - Scheme integration test for KB search behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… + tiered CI Bug fixes: - get-option now reads from SharedState (fresh after sync_scheme_state) instead of a closure-captured snapshot from inject_editor_state - Added missing get/set_option arms for collab_auto_resolve_paths, collab_default_save_dir, collab_save_on_remote_update - Added corresponding fields to Editor struct with defaults - Fixed saveas test path (parent dir must exist for atomic save) Tiered CI (Makefile): - make ci: fmt + clippy + check + test + Scheme editor tests + check-config + code-map - make ci-extended: ci + CRDT Scheme tests + docker-smoke + docker-new-user - make ci-docker-e2e: Docker collab E2E (on-demand) - make ci-complete: everything (mirrors GitHub CI) Updated TESTING.md with tiered CI docs and new test files. 16/16 collab join-save tests pass, 260/260 editor tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…shness for collab E2E - Remove SPC c f → lsp-format from kernel (owned by format module) - Remove Tab → lsp-accept-completion from kernel insert (dead code — binary handles directly) - Downgrade "module overrides builtin" warning to debug (expected per principle 6) - Make buffer-text read from SharedState for fresh data after sync_scheme_state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
open-file, buffer-insert, and switch-to-buffer are all pending operations processed by apply_to_editor in fixed order (insert before open-file before switch-buffer). Combining them in a single test step caused buffer-insert to target the wrong buffer (scratch instead of test.txt), explaining the Docker E2E failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ab data model v2 Wire the save protocol end-to-end so `:w` on synced buffers triggers `docs/save_intent` (SHA-256 content hash) → `docs/save_committed` (broadcast to peers). Add `docs/metadata` endpoint exposing save_epoch, last_saved_by, and connected_clients. Enhance disconnect UX (peer_count=0 status message). Document disconnect lifecycle in COLLABORATION.md. Clean up 5 stale ROADMAP stub audit items and document undo+CRDT limitation. 8 new tests for save protocol flow (3,567 total, 0 failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y, git identity WU1: Client-side WAL sequence gap detection. Tracks wal_seq per doc in the collab background task. Sequence gaps trigger ForceSync (resync). WU2: Heartbeat/keepalive. 30s interval sends $/ping, logs latency on response. Missed pong triggers disconnect. Configurable via collab_heartbeat_interval option. WU3: Offline edit recovery. Disconnect preserves sync_doc and collab_doc_id instead of clearing them. Sets collab_offline flag on buffers. Reconnect re-shares offline buffers via ForceSync. Status bar shows [C:OFFLINE] during disconnection. WU4: Git-based project identity. compute_project_identity() uses 4-tier fallback: git remote URL (normalized) → .project TOML name → directory basename → FNV-1a of absolute path. Cross-machine collab now works when both sides share the same git remote. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… architecture WU5: Performance benchmark suite using criterion. Buffer ops (create, insert, text extraction) in mae-core, CRDT ops (create, encode, apply update, reconcile) in mae-sync. Makefile targets: bench, bench-save, bench-compare. WU6: Split dispatch/ui.rs (1,250 lines, 113 match arms) into 5 domain files: help.rs, terminal.rs, project.rs, kb.rs, config.rs. Each gets a dispatch_* method chained in dispatch_builtin_inner(). Remaining ui.rs handles dashboard, AI, palette, describe, link editing, and demos (~400 lines). Matches existing pattern (dispatch/collab.rs, dap.rs, git.rs). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…(30 fields) Extract 30 fields from the Editor struct into two cohesive sub-structs: - CollabState (18 fields): collab_status, collab_server_address, collab_synced_buffers, etc. → editor.collab.* - ShellIntents (12 fields): pending_shell_spawns, pending_shell_inputs, shell_viewports, etc. → editor.shell.* Reduces Editor from ~100+ to ~80+ fields. Uses direct field access pattern (self.collab.status) matching existing self.window_mgr, self.diagnostics, self.search_state patterns. No Deref, no accessors. Mechanical rename across 25 files, zero behavior changes. 3,674 tests pass, 0 clippy warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 5 protocol-level E2E tests to collab_bridge_integration.rs: - save_intent_to_committed_roundtrip: SHA-256 hash check, save_epoch increment, save_committed broadcast to second client - save_intent_conflict_on_hash_mismatch: wrong hash returns "conflict" - heartbeat_ping_pong_and_server_drop: $/ping → "pong", server abort → EOF detection (no hang) - reconnect_reshare_preserves_crdt_state: share → edit → server crash → fresh server → re-share with preserved CRDT → content matches Also adds ping(), save_intent(), save_committed() helper methods to the Client test infrastructure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 2 TCP E2E tests (gated behind MAE_TCP_E2E=1): - tcp_offline_edit_reconnect_resync: share → edit → server kill → restart → re-share with preserved CRDT → new peer verifies content - tcp_peer_join_leave_notifications: client B joins via resync → client A receives peer_joined → client B drops → client A receives peer_left Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oadmap Mark complete: - Offline edit recovery (b8d4b6a) - Client-side gap detection (b8d4b6a) - Heartbeat/keepalive (b8d4b6a) - dispatch/ui.rs split (0829dd5) - Performance regression testing (0829dd5) - E1 git-based project identity (b8d4b6a) - State server v2 line updated (E1 + heartbeat complete) Update editor struct extraction item to reflect reduced field count (~80+ after CollabState + ShellIntents) and next candidates (ViModalState, AiSessionState). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract two major field groups from the Editor struct into dedicated sub-structs, continuing the pattern from CollabState + ShellIntents: - ViState (41 fields): vi-modal editing state — operators, counts, registers, marks, macros, visual selection, command-line, jump/change lists, and dot-repeat. Access via `editor.vi.*`. - AiState (34 fields): AI session state — provider config, token counters, streaming flags, conversation pair, permission tier, and target context. Access via `editor.ai.*`. Field names strip the `ai_` prefix (e.g. `ai_streaming` → `ai.streaming`). Editor struct drops from ~100+ fields to ~40 after all 4 extractions (CollabState, ShellIntents, ViState, AiState). All 3,694 tests pass, clean clippy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mechanical rename across 31 test files (366 occurrences): - `ed` → `editor`, `ed2` → `editor2` in all test bindings - `ed_with_text()` → `editor_with_bulk_text()` (signals bulk insertion) - `ed_with_rust()` → `editor_with_rust()` (consistent prefix) - Add doc-comments to all three test helpers explaining semantics No logic changes — purely naming consistency matching production code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document variable naming rules (editor, buf, win, idx), function naming patterns (dispatch_*, handle_*, execute_*), and test helper semantics (editor_with_text vs editor_with_bulk_text vs editor_with_rust). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The collab E2E Docker test is temporarily disabled while the editor struct extraction refactoring is in progress. Will re-enable once the refactor stabilizes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test `compute_cursor_command_mode` does `Editor::default()` then reassigns fields — Clippy flags this but struct-init syntax is impractical with 90+ fields that keep changing during struct extraction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all knowledge-base fields into `editor::kb_state::KbContext`:
- Primary KnowledgeBase → `editor.kb.primary`
- Federation (registry, instances, watchers) → `editor.kb.{registry,instances,...}`
- Config (watcher_enabled, search_max_results, etc.) → `editor.kb.*`
- Capture state → `editor.kb.capture_state`
- AI context (visited IDs, write guard) → `editor.kb.{ai_visited_ids,write_guard}`
Editor field count: ~92 → ~71 (21 fields extracted).
Follows same pattern as ViState, AiState, CollabState, ShellIntents.
20 files updated across mae-core, mae-ai, mae-scheme, mae (binary).
All 3,673 tests pass. Clippy clean (workspace + GUI).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # Cargo.lock
Move debug_state and pending_dap_intents into `editor::dap_state::DapContext`: - `editor.debug_state` → `editor.dap.state` - `editor.pending_dap_intents` → `editor.dap.pending_intents` Editor field count: ~71 → ~69 (2 fields extracted). Follows same pattern as ViState, AiState, CollabState, ShellIntents, KbContext. 18 files updated across mae-core, mae-ai, mae-gui, mae-renderer, mae (binary). All 3,673 tests pass. Clippy clean (workspace + GUI). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
KbContext (21) and DapContext (2) join the existing 4 sub-structs. Remaining candidate: LspContext (7 fields). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ns, reconnect backoff WU1 — Per-buffer collab status indicators (E8): - Add `collab_is_sharer` field to Buffer (default false) - `format_collab_status` shows role + pending updates: [C:3|sharer], [C:3|synced], [C:3|pending:2], [C:OFFLINE|pending:5] - Set `collab_is_sharer = true` on BufferShared event WU2 — Fix `:w` for non-sharer clients (Bug 2): - Guard `SaveCollab` intent behind `collab_is_sharer` - Joiners save locally only, no `save_intent` broadcast WU3 — Sharer quit notification (Bug 3): - Add `SharerLeft` variant to EditorEvent + CollabEvent - Track `sharer_session_id` on DocEntry in state server - On sharer disconnect, broadcast SharerLeft to peers - Client-side: parse notification, display status message WU4 — Reconnect lifecycle hardening (Bug 4): - Exponential backoff: base * factor^min(attempt, 5), capped at 300s - Max reconnect attempts (0 = infinite, configurable) - ForceSync debounce: skip duplicate requests within 2s per doc 15 new tests (3,688 total, 0 failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…DMAP - Bugs 2-4 (save guard, sharer notifications, disconnect lifecycle) fixed in 8de53b8 - E8 (buffer status indicators) complete in 8de53b8 - Bug 1 clarified: reconcile_to() already uses single-txn LCS diff (not full-buffer replacement). Large undos produce proportionally large but correct diffs. Full fix deferred to Phase F (yrs UndoManager). - State server v2 entry updated with completed items - Added Known Limitations section to COLLABORATION.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v0.11.0 delivers collaborative editing (CRDT sync via yrs), org-dailies, and Editor struct decomposition — the three major themes of this release cycle.
Collaborative Editing (Phases A–F)
mae-synccrate: yrs CRDT text bridge + KB node schemamae-state-server: standalone collab server with WAL-based SQLite persistenceOrg-Dailies
Editor Struct Decomposition (197 → ~69 fields)
Infrastructure
Stats
Test Plan
make cipasses (fmt + clippy + check + test)cargo clippy --package mae-gui --all-targets -- -D warningscargo test --workspace— 3,673 tests, 0 failuresmake test-scheme-allMAE_TCP_E2E=1 cargo test -p mae --test collab_tcp_e2e -- --ignored🤖 Generated with Claude Code