Markdown-centric team collaboration: chat, CRDT canvases, and voice huddles.
  • Rust 62.4%
  • JavaScript 23.7%
  • HTML 8.1%
  • CSS 5.8%
Find a file
Sameh Abouel-saad f153ed71d4
All checks were successful
lab release / release (push) Successful in 14m52s
feat(canvas): one fixed-height toolbar + font-size; fix editor load (#92)
Consolidate the canvas editor's floating bubble (selection) menu and
in-table toolbar into the single fixed #canvas-toolbar, and add a
font-size control.

- Inline-format controls (bold/italic/.../link) and the 11 table actions
  now live in the top bar; the table group is greyed/disabled unless the
  cursor is in a table, so the button set never changes and the toolbar
  stays a constant height (no editor-content jump entering/leaving tables).
- Remove the two floating-menu IIFEs (~120 lines) and their CSS; net -64
  lines. Toolbar buttons keep selection via a single mousedown handler.
- Add font-size: export FontSize from @tiptap/extension-text-style in the
  vendored bundle; Size/12-32px dropdown -> setFontSize.

Fix editor failing to load: exporting FontSize pulled a newer text-style
that moved @tiptap/extension-collaboration onto the @tiptap/y-tiptap fork
for its ySyncPlugin, while the bundle still used the deprecated
@tiptap/extension-collaboration-cursor@3.0.0 (resolves ySyncPluginKey from
upstream y-prosemirror -- a different key). The cursor plugin init read
getState()===undefined and threw "reading 'doc'", aborting editor
construction. Replace it with the official successor
@tiptap/extension-collaboration-caret (shares the y-tiptap key); update
remote-caret CSS (.collaboration-cursor__* -> .collaboration-carets__*).

Pin the whole @tiptap family to exact 3.22.3 (deps + overrides) so the
vendored bundle is deterministic and won't drift on rebuild, and restore
scripts/vendor-bundle/build.sh (referenced by README but never committed).

Verified in-browser against the running daemon: editor loads, toolbar
fixed at 71px in/out of tables, table group toggles enabled/greyed,
setFontSize('24px') applies, floating menus gone.

Closes #92.
2026-06-23 22:20:35 +03:00
.cargo Fix hero_sockets service naming and add cargo git-over-http config 2026-04-06 12:43:34 +02:00
.forgejo/workflows ci: canonical-only lab-release (+cargo test); remove other workflows 2026-06-15 10:06:00 +00:00
crates feat(canvas): one fixed-height toolbar + font-size; fix editor load (#92) 2026-06-23 22:20:35 +03:00
docs refactor(collab): rename crate hero_collab_admin -> hero_collab_web 2026-06-23 15:11:32 +03:00
scripts feat(canvas): one fixed-height toolbar + font-size; fix editor load (#92) 2026-06-23 22:20:35 +03:00
.gitignore ci: commit Cargo.lock and refresh herolib_core 2026-05-29 17:40:54 -04:00
Cargo.lock chore: bump hero_lib dependency to latest development commit 2026-06-22 11:29:08 +02:00
Cargo.toml refactor(collab): rename crate hero_collab_admin -> hero_collab_web 2026-06-23 15:11:32 +03:00
LICENSE Initial commit 2026-03-18 03:58:27 +00:00
PURPOSE.md refactor(collab): rename crate hero_collab_admin -> hero_collab_web 2026-06-23 15:11:32 +03:00
README.md refactor(collab): rename crate hero_collab_admin -> hero_collab_web 2026-06-23 15:11:32 +03:00
rust-toolchain.toml refactor(deps): migrate from hero_blueprint/hero_rpc to hero_lib crates 2026-06-01 07:44:41 +02:00

hero_collab

A markdown-centric team collaboration platform built as a first-class Hero OS service. Channels and threads, real-time chat with reactions, @mentions, file attachments, full-text search, collaborative canvases (CRDT via Yjs/yrs), and voice huddles via LiveKit.

Service model: hero_collab_server exposes JSON-RPC 2.0 over a Unix domain socket (~/hero/var/sockets/hero_collab/rpc.sock), oschema-first (generated from oschema/ via the openrpc_server! macro), with a Server-Sent Events firehose at /events for live updates. hero_collab_web (built from the hero_collab_web crate) serves the HTML/JS web UI and proxies browser-originated RPC/WS (~/hero/var/sockets/hero_collab/web.sock). Both are run by hero_proc via their declarative service.toml manifests, typically behind hero_proxyhero_router for identity injection and TLS.


Quick start (local development)

Use the nushell service script (preferred):

service collab start --update --reset # build, wipe DB, seed 4 test users (dev mode)
service collab start --update # build + start, keep existing DB
service collab start # start without rebuild
service collab stop # stop all collab processes
service collab status # show running status

The --reset flag wipes ~/hero/var/data/hero_collab/collab.db* and attachment files, then seeds a canonical 4-user fixture (Alice=id 1, Bob=2, Carol=3, Dave=4) + one "General" workspace + one #general channel with all 4 users as members. Alice is the channel admin.

Then open http://localhost:9988/hero_collab/web/ — that's hero_router's port, which forwards to hero_collab_web's socket. The user picker appears; pick any of the 4 seeded users. Open another tab (or incognito) and pick a different user to simulate multi-user chat.

--reset is destructive — intended for iterative dev where starting from a known clean slate is the point. Do not run it against a DB you care about.

Prerequisites

  • Rust (edition 2024) — rustup update stable
  • SQLite 3 (bundled via rusqlite, so system SQLite isn't strictly required)
  • A running hero_proc + hero_router + optional hero_proxy
  • Optional: a LiveKit server for huddles — provided by the hero_livekit service

Auth modes

Mode Behavior
proxy (default) Expect X-Hero-User / X-Hero-Context from hero_proxy. Reject unauthenticated RPCs.
dev User-picker fallback, no headers required. Logs a prominent warning at startup. Never ship to production.

Set the mode via hero_collab_server --auth-mode=<dev\|proxy> (the flag survives the hero_proc spawn boundary), or COLLAB_AUTH_MODE for direct-binary launches / tests (clap's env fallback; ignored under hero_proc's clean-env supervision). The web binary reads COLLAB_AUTH_MODE the same way.


Architecture (4 crates)

crates/
├── hero_collab_server/   JSON-RPC service on rpc.sock (oschema-first, SQLite-backed)
├── hero_collab_web/    axum HTTP + WS relay serving the web UI on web.sock (binary: hero_collab_web)
├── hero_collab_sdk/      Auto-generated typed Rust client (via openrpc_client!)
└── hero_collab_examples/ Runnable examples + the integration test suite

Data layout:

  • ~/hero/var/data/hero_collab/collab.db — SQLite DB (WAL mode)
  • ~/hero/var/data/hero_collab/files/ — attachment blobs, namespaced by workspace then attachment id
  • ~/hero/var/logs/core/... — structured logs via hero_proc's aggregator

Wire protocol: JSON-RPC 2.0 over HTTP/1.1 over Unix Domain Socket, served on the canonical hero_sockets surface (/api/ping, /api/{domain}/rpc, /api/{domain}/openrpc.json, /events SSE). The schema in oschema/ is the source of truth: the openrpc_server! macro generates the typed trait

  • Input/Output types + the serving entry, emits the spec to crates/hero_collab_server/openrpc/, and hero_collab_sdk is generated from that spec via openrpc_client!. ~110 methods, stable error codes per src/rpc_error.rs.

Real-time: handlers fan out typed events to the /events SSE firehose (fanout::dispatch); hero_collab_web consumes the firehose and de-muxes per recipient to each browser's /ws/user/{id} WebSocket.


Key features

  • Workspaces / channels / DMs — Slack topology with public + private channels and direct-message kinds.
  • Messages — send, edit, delete (soft), pin, full-text search (SQLite FTS5), attachments with per-user ownership, and threaded replies.
  • Reactions — atomic message_toggle_react returning {action: "added"|"removed"}; no reactions on tombstoned messages.
  • @mentions — parsed on send, delivered via the SSE firehose (mention.created) and surfaced as OS-level browser notifications when the tab is backgrounded.
  • Canvases — Yjs/yrs CRDT-backed collaborative docs with a Tiptap editor; multi-client real-time sync over binary WS; role-gated (owner, editor, viewer).
  • Voice huddles — LiveKit SFU integration; JWTs signed by livekit.rs. The hero_livekit service provides the SFU.
  • @-mention identity — caller identity comes from HeroRequestContext (X-Hero-User / X-Hero-Context); a user_id param only ever names the object of an action, never the caller.
  • Observabilityrpc.dispatch tracing on every call; counters via system_metrics; /health shows active WS connection count.

Testing

# Unit tests (rate_limit, rpc_error, validation, fanout, activity, …)
cargo test -p hero_collab_server --bin hero_collab_server

# Integration tests (spawn a real server per test, ephemeral sockets,
# exercise the /events SSE firehose)
cargo test -p hero_collab_server --test integration

# Full workspace
cargo test --workspace

Current baseline: 52 unit + 61 integration tests passing.


Operations

  • docs/BACKUP.md — online DB backup + restore; retention hints.
  • Metrics to watch:
    • /health on web.sock: active_ws_connections.
    • system_metrics RPC: rpc_calls_total, rpc_errors_total, avg_latency_ms, workspaces, users, channels, messages.
    • Log stream: grep rpc.dispatch for per-call timing + error codes; task=huddle_reaper / task=attachment_cleanup for background tasks; trace_id= to correlate user-reported issues with sanitized -32603 Internal responses.

Browser support

Minimum: Chrome/Edge 100+, Firefox 100+, Safari 15.4+. See crates/hero_collab_web/BROWSER_SUPPORT.md for the full API coverage rationale.


Contributing

  • Commit messages: one header line describing the change + a body explaining why.
  • New handlers return RpcResult<Value>, not anyhow::Result — see src/rpc_error.rs for the typed-error contract.
  • Input validation uses the typed newtype pattern (Name, Email, ChannelName, MessageContent, …) from src/validation.rs; handlers call parse_input::<T>(params) to deserialize + validate.
  • Run cargo test before committing; run the browser smoke when touching chat-app.js or canvas-app.js.

License

See LICENSE.