Under the hood

Shared primitives catalog

Shared primitives catalog

Before writing any helper, scan these tables first. Most “I need a small string transform / id helper / md coercion / tree walk” needs already have an owner here — the cost of finding the existing one is a grep; the cost of missing it shows up later as drift between two parallel implementations (the user is the one who hits the divergence).

This catalog is mirrored at .github/copilot-instructions.md §5.1. When you edit either copy, sync both — a PostToolUse hook flags drift, but the discipline starts before the hook fires.

The catalog is grouped by area. Skim the headings, then drill in.

For the reuse-first rule (why this matters, past drift incidents, what to do when a primitive doesn’t exist yet), see Contributing → Reuse-first.

1. Workspace lifecycle, op log, and HLC (outl-core)

IntentUse thisFile
Open a workspace (in-memory for tests, on-disk JSONL for prod)outl_core::Workspace::open_in_memory / open_with_storagecrates/outl-core/src/workspace.rs
Route an op through the log → tree (the only mutation path)outl_core::Workspace::apply(LogOp)crates/outl-core/src/workspace.rs
Read the materialized tree / op log from a workspaceoutl_core::Workspace::tree / log / block_textcrates/outl-core/src/workspace.rs
Build a Yrs text-replace update payload for an opoutl_core::Workspace::build_text_replace_updatecrates/outl-core/src/workspace.rs
Generate HLC timestamps with actor tiebreak (required for every op)outl_core::HlcGenerator::new / next / observecrates/outl-core/src/hlc.rs
Wrap an Op into a LogOp (timestamp + actor) for applyoutl_core::Op + outl_core::LogOpcrates/outl-core/src/op.rs
Extract the NodeId an op targetsoutl_core::op::op_node(&Op) -> Option<NodeId>crates/outl-core/src/op.rs
Sentinel node ids (root, trash)outl_core::NodeId::root() / trash()crates/outl-core/src/id.rs
Per-device identity for opsoutl_core::ActorIdcrates/outl-core/src/id.rs
Stable, shared workspace identity (read/generate, persist, pairing-adoption) — the gossip-topic key, NOT the pathoutl_core::WorkspaceId::read_or_create / write / from_raw (errors: outl_core::WorkspaceIdError)crates/outl-core/src/workspace_id.rs
Fractional index for sibling orderingoutl_core::Fractionalcrates/outl-core/src/fractional.rs

2. Tree reads (outl-core + outl-actions::tree)

IntentUse thisFile
Does a node still exist in the tree?Tree::containscrates/outl-core/src/tree/mod.rs
Parent of a nodeTree::parentcrates/outl-core/src/tree/mod.rs
Fractional position of a nodeTree::positioncrates/outl-core/src/tree/mod.rs
Single property lookup on a nodeTree::propertycrates/outl-core/src/tree/mod.rs
Iterate every property currently set on a nodeTree::properties_ofcrates/outl-core/src/tree/mod.rs
Collapsed flag for a nodeTree::is_collapsed / collapsed_idscrates/outl-core/src/tree/mod.rs
Walk every node in the treeTree::iter_nodes / node_countcrates/outl-core/src/tree/mod.rs
Children of a parent (in fractional order)outl_actions::tree::children_ofcrates/outl-actions/src/tree.rs
Walk a subtree applying a closureoutl_actions::tree::walk_subtreecrates/outl-actions/src/tree.rs
Sibling after a node + position helpers (for inserts)outl_actions::tree::next_sibling / position_after / position_for_new_last_childcrates/outl-actions/src/tree.rs
Which page (slug-bearing root child) does this node sit under?outl_actions::tree::enclosing_page_idcrates/outl-actions/src/tree.rs

3. Block mutations (outl-actions::block + collapsed + todo + quote)

Every entry here routes through Workspace::apply — never build a LogOp from a client and apply it directly.

IntentUse thisFile
Append a single block under a parentoutl_actions::block::append_blockcrates/outl-actions/src/block/create.rs
Append a tree / forest (with children) under a parentoutl_actions::block::append_tree / append_forest (uses BlockTreeSpec → returns BlockTreeOutcome)crates/outl-actions/src/block/create.rs
Create sibling before a block (vim O; floor-slot swap when the anchor is first child)outl_actions::block::create_beforecrates/outl-actions/src/block/create.rs
Create sibling after / child under a blockoutl_actions::block::create_after / create_undercrates/outl-actions/src/block/create.rs
Create sibling after a block, appending at page end when the anchor is staleoutl_actions::block::create_after_or_append (the desktop/mobile create_block stale-anchor fallback — one owner, no per-client duplication)crates/outl-actions/src/block/create.rs
Edit a block’s textoutl_actions::block::edit_textcrates/outl-actions/src/block/edit.rs
Move a block to sit after an arbitrary target (cut-and-paste-block; crosses pages; one Op::Move, preserving id + refs; rejects self-subtree cycles)outl_actions::block::move_aftercrates/outl-actions/src/block/moves.rs
Indent / outdent / move up / move down a blockoutl_actions::block::indent / outdent / move_up / move_downcrates/outl-actions/src/block/moves.rs
Re-parent a block under an arbitrary page/block (cross-page move)outl_actions::block::move_undercrates/outl-actions/src/block/moves.rs
Delete a block (Move(node, TRASH_ROOT), never physical)outl_actions::block::deletecrates/outl-actions/src/block/moves.rs
Toggle a block’s collapsed flag (converges via Op::SetCollapsed)outl_actions::collapsed::toggle_block_collapsed / set_block_collapsedcrates/outl-actions/src/collapsed.rs
Cycle / split / read TODO/DONE state (encoded as text prefix)outl_actions::todo::cycle_todo / split_todo / TodoState / TODO_PREFIX / DONE_PREFIXcrates/outl-actions/src/todo.rs
Toggle TODO/DONE on a block in one calloutl_actions::block::toggle_todocrates/outl-actions/src/block/edit.rs
Read / toggle blockquote state (encoded as "> " text prefix, CommonMark-compatible)outl_actions::quote::is_quote / split_quote / toggle_quote / QUOTE_PREFIXcrates/outl-actions/src/quote.rs
Toggle blockquote on a block in one calloutl_actions::block::toggle_quotecrates/outl-actions/src/block/edit.rs

4. Pages and journals (outl-actions::page + journal)

IntentUse thisFile
Page-property keys (constants — don’t hardcode the strings)outl_actions::page::SLUG_KEY / KIND_KEY / TYPE_KEYcrates/outl-actions/src/page.rs
Canonical type:: value marking a page as a person (@ mention autocomplete filter)outl_actions::page::PERSON_TYPEcrates/outl-actions/src/page.rs
Page metadata (slug, kind, title, page_type) for a node idoutl_actions::page::page_meta / PageMeta / PageKindcrates/outl-actions/src/page.rs
Validate a slug for filesystem safety (.., /, \, control chars)outl_actions::page::is_valid_slugcrates/outl-actions/src/page.rs
Derive a deterministic page id from slug (so two peers converge)outl_actions::page::page_id_from_slugcrates/outl-actions/src/page.rs
Find / list / create-if-missing pagesoutl_actions::page::find_by_slug / list_all / open_or_createcrates/outl-actions/src/page.rs
Delete a page (move root to NodeId::trash() via one Op::Move; whole subtree travels with it; returns PageMeta so callers can drop projections + navigate away; ActionError::PageNotFound when the slug doesn’t resolve)outl_actions::page::delete (re-exported as outl_actions::delete_page)crates/outl-actions/src/page.rs
Remove a page’s .md + .outl from disk (the inverse of apply_page_md_with_sidecar; idempotent on missing files; pairs with page::delete)outl_actions::journal::remove_page_projection (re-exported at crate root)crates/outl-actions/src/journal.rs
Open-or-create a page from a human-typed name (slugifies + keeps original as title, used when a [[ref]] / #tag / picker query may not be a valid slug)outl_actions::resolve::open_or_create_by_namecrates/outl-actions/src/resolve.rs
Open-or-create whatever a user-typed ref target points at (date → journal, else literal/slugified/title match → existing page, else create) — handles @-prefixed mentions by stripping the @ and marking new pages as type:: person; the one decision tree so frontend regex and backend parser cannot drift on [[2026-13-01]] or [[@avelino]]outl_actions::resolve::open_or_create_by_refcrates/outl-actions/src/resolve.rs
Search pages typed type:: person, fuzzy-ranked by query (powers the @ mention autocomplete in every client)outl_actions::person::search_personscrates/outl-actions/src/person.rs
Read / write a property on a page (or any node)outl_actions::page::read_text_prop / set_propertycrates/outl-actions/src/page.rs
Migrate pre-page-model blocks under today’s journal (run on boot)outl_actions::page::migrate_legacy_into_todaycrates/outl-actions/src/page.rs
Open / create the journal for a specific date or todayoutl_actions::page::open_journal / open_todaycrates/outl-actions/src/page.rs
Journal date labels & day arithmetic (slug ↔ date, title, [[YYYY-MM-DD]] ref, prev/next day)outl_actions::dates::journal_slug / journal_title / journal_ref / date_from_slug / previous_journal_date / next_journal_datecrates/outl-actions/src/dates.rs
Today’s journal date in the configured timezone (delegates to clock)outl_actions::page::todaycrates/outl-actions/src/page.rs
Week arithmetic — ISO-week tag (#2026-W22, %G-correct at year boundaries) and “days until next <weekday>” (same weekday → 7, never 0)outl_actions::dates::week_tag / days_until_next_weekdaycrates/outl-actions/src/dates.rs
Current date / time in the user’s configured timezone ([calendar] timezone, DST-aware via chrono-tz; OS local when unset). Call init once per client at boot; page::today delegates here, so use now_local / today instead of chrono::Local::now() (issue #107)outl_actions::clock::init / now_local / todaycrates/outl-actions/src/clock.rs
Parse a human-typed date in any supported spelling (2026-04-22, 2026/04/22, 22/04/2026, Roam’s April 22nd, 2026, Sept 3rd, 2025, 22 April 2026) into a NaiveDate, or into the ISO label outl uses for journal slugs / [[date]] refs — the one owner of the ordinal-stripping logic that used to be copied in four places (paste normalization, outl daily, outl import, Obsidian frontmatter). parse_date_arg layers relative offsets (+3d, -2w, +1m, bare 5d) on top for slash-command / CLI argumentsoutl_actions::dates::parse_flexible_date / parse_date_label / parse_date_argcrates/outl-actions/src/dates.rs
Parse an outl:// deep link URL into a navigation target (one parser, every GUI client routes the result to its own open_* command — never reparse per client)outl_actions::parse_deep_link / DeepLinkTarget / DeepLinkError / DEEP_LINK_SCHEMEcrates/outl-actions/src/deeplink.rs
Filesystem paths for journals / pages / a specific pageoutl_actions::journal::journals_dir / pages_dir / page_md_pathcrates/outl-actions/src/journal.rs
Render a page node out to .mdoutl_actions::journal::render_page_mdcrates/outl-actions/src/journal.rs
Apply an edited .md back into the workspace (with / without sidecar)outl_actions::journal::apply_page_md / apply_page_md_with_sidecarcrates/outl-actions/src/journal.rs
Apply every page’s .md to disk in one passoutl_actions::journal::apply_all_pages_mdcrates/outl-actions/src/journal.rs
Run a closure that mutates a page’s .md (read → modify → write atomically)outl_actions::journal::mutate_page_mdcrates/outl-actions/src/journal.rs
Atomic .md write (crash-safe, wraps outl_md::atomic::write_atomic)outl_actions::journal::write_md_atomiccrates/outl-actions/src/journal.rs

5. Parse / render (outl-md::parse + render)

IntentUse thisFile
Parse .md → outline AST (no IDs)outl_md::parse::parseParsedPage (includes warnings: Vec<ParseWarning>)crates/outl-md/src/parse.rs
Render outline AST → .md (clean, no IDs)outl_md::render::rendercrates/outl-md/src/render.rs
Non-fatal parser recovery records (heading instead of bullet, etc.)outl_md::ParseWarning + outl_md::ParseWarningKind (re-exported from parse)crates/outl-md/src/parse.rs
The outline AST node DTO (UI-friendly, no Workspace coupling)outl_md::OutlineNode / outl_actions::outline::OutlineNodecrates/outl-md/src/parse.rs + crates/outl-actions/src/outline.rs
Project the workspace tree under a node into the UI DTOoutl_actions::outline::project_outline / project_outline_nodecrates/outl-actions/src/outline.rs
Flatten an OutlineNode subtree to DFS paths (for selection / navigation)outl_actions::outline::flatten_subtree_pathscrates/outl-actions/src/outline.rs
Read a page from disk + project to outline view in one calloutl_actions::outline::read_page_view / read_page_view_with_workspacecrates/outl-actions/src/outline.rs
Read a page and surface parser warnings (banner, doctor, status line)outl_actions::outline::read_page_outline / read_page_outline_with_workspacePageOutline { nodes, warnings }crates/outl-actions/src/outline.rs
Slugify a user-visible page name into a filesystem-safe slug (lowercase, folds Latin diacritics áa, non-alphanumerics collapse to -; empty input → UNTITLED_SLUG) — never hand-roll an ASCII-only copyoutl_md::slug::slugify / UNTITLED_SLUGcrates/outl-md/src/slug.rs
IntentUse thisFile
Coerce external markdown (line endings, indent unit 4→2, Roam/GitHub/Logseq tokens, long-form dates → ISO, strip id:: with Crockford validation, strip unknown {{…}} / ^^…^^)outl_actions::paste::normalize_external_syntaxcrates/outl-actions/src/paste/normalize.rs
Split a leading --- YAML frontmatter fence off a .md body (CRLF-safe, honours ... end marker, no-fence → verbatim body)outl_md::frontmatter::split_frontmattercrates/outl-md/src/frontmatter.rs
Parse a YAML frontmatter block into flat key:: value properties (title lifted, tags normalized to #name, caller-supplied drop-list; values verbatim — date normalization stays with the caller)outl_md::frontmatter::parse_frontmatterFrontmattercrates/outl-md/src/frontmatter.rs
Lift a leading # H1 line into a page title (first non-blank line only)outl_md::frontmatter::extract_leading_h1crates/outl-md/src/frontmatter.rs
Collapse external wiki-link variants ([[Note|alias]], [[Note#h]], [[Note^blk]], [[folder/Note]]) to canonical [[Note]]outl_md::wikilink::rewrite_wikilinks (whole text) / clean_wikilink_target (one target)crates/outl-md/src/wikilink.rs
Convert image wiki-links / embeds (![[img.png]], [[a/b.jpeg|cap]]) into CommonMark links, folder path preservedoutl_md::wikilink::convert_image_links (+ is_image_target predicate)crates/outl-md/src/wikilink.rs
”Does this clipboard look like an outline?” classifieroutl_actions::paste::looks_like_outlinecrates/outl-actions/src/paste/mod.rs
Convert clipboard markdown into outl ops grafted at a positionoutl_actions::paste::paste_markdownPasteOutcome (anchor described by PasteAnchor)crates/outl-actions/src/paste/mod.rs
Paste raw text as a single block with no normalisation or outline parsing (the “without formatting” path)outl_actions::paste::paste_plain(workspace, hlc, anchor, raw)PasteOutcomecrates/outl-actions/src/paste/mod.rs
Serialize a block selection (+ subtrees) to clean outl markdown for the clipboard (the inverse of paste_markdown / parse)outl_actions::clipboard::copy_markdown (workspace + NodeIds; GUI backends) / copy_markdown_nodes (already-projected OutlineNodes; the TUI’s AST-first yank)crates/outl-actions/src/clipboard.rs
Ingest a .md as a real page (creates page node + reconciles blocks; used by import / serve / mobile + TUI orphan scanners)outl_actions::ingest::ingest_md_file / ingest_dircrates/outl-actions/src/ingest.rs
Create stub pages for every [[ref]] with no file of its own (Logseq “implicit pages”)outl_actions::ingest::create_missing_ref_pagescrates/outl-actions/src/ingest.rs

7. Reconcile & matching (outl-md::reconcile + matching + diff)

IntentUse thisFile
Reconcile an existing .md against its sidecar (3-level matching → diff → min ops)outl_md::reconcile::reconcile_md (no sidecar = fresh random id) / reconcile_md_with_page_id (pin id for first ingest)crates/outl-md/src/reconcile.rs
Reconcile every .md in a directoryoutl_md::reconcile::reconcile_dircrates/outl-md/src/reconcile.rs
Materialise a page root in the tree (Op::Create at root + page-slug / page-kind SetProps, idempotent) without running the full reconcile pipelineoutl_md::reconcile::ensure_page_root_in_treecrates/outl-md/src/reconcile.rs
Reconcile error / report typesoutl_md::ReconcileError / ReconcileReportcrates/outl-md/src/reconcile.rs
3-level matching algorithm (hash → similarity → orphan log)outl_md::matching::match_blocksMatch / MatchLevelcrates/outl-md/src/matching.rs
Diff old AST + new AST + old sidecar → minimum sequence of Opsoutl_md::diff::diff_to_opsDiffPlancrates/outl-md/src/diff.rs
Same diff, plus propagate page-level properties (title::, type::, pinned::, icon::, …) into the op log as Op::SetProp on the page root so the CRDT tree agrees with what’s on disk (legacy .md files populated via fixtures / external editors get materialised here on the next reconcile)outl_md::diff::diff_to_ops_with_page_propscrates/outl-md/src/diff.rs
Reconcile-pipeline version number stamped on every sidecar — orphan scanner re-runs reconcile_md when a sidecar’s version is below this constant, so a binary that gains a new pipeline pass automatically rematerialises every legacy page on the next bootoutl_md::sidecar::CURRENT_PIPELINE_VERSIONcrates/outl-md/src/sidecar.rs

8. Sidecar (outl-md::sidecar + atomic)

IntentUse thisFile
The full sidecar struct + per-block entriesoutl_md::Sidecar / SidecarBlockcrates/outl-md/src/sidecar.rs
Construct a fresh sidecar for a new pageoutl_md::sidecar::Sidecar::new_for_page(page_id, &file_hash)crates/outl-md/src/sidecar.rs
Read / write sidecar (JSON, version 2, backward-reads v1)outl_md::sidecar::read / writecrates/outl-md/src/sidecar.rs
Sidecar path resolution for a .mdoutl_md::sidecar::sidecar_path_for / resolve_sidecar_pathcrates/outl-md/src/sidecar.rs
Derive ((blk-XXXXXX)) ref handle from NodeId (deterministic, collision-aware)outl_md::sidecar::derive_ref_handlecrates/outl-md/src/sidecar.rs
Hash block / file content for sidecar (content_hash = single block; file_hash = whole .md)outl_md::sidecar::content_hash / file_hashcrates/outl-md/src/sidecar.rs
Low-level crash-safe write (use the journal::write_md_atomic wrapper unless you have a reason)outl_md::atomic::write_atomiccrates/outl-md/src/atomic.rs

9. In-flight outline AST helpers (outl-md::outline_ops)

These operate on Vec<OutlineNode> before the tree is rebuilt from the op log — typing into a buffer that hasn’t been parsed back yet. UI-agnostic; both TUI and mobile consume them.

IntentUse thisFile
Flat count / TODO+DONE counts across an outlineoutline_ops::flat_count / count_todoscrates/outl-md/src/outline_ops.rs
Convert flat index ↔ path / look up a node at a pathoutline_ops::path_for_index / index_for_path / node_at_path / node_at_path_mutcrates/outl-md/src/outline_ops.rs
Count descendants under a path / grab a mutable siblings sliceoutline_ops::descendants_count_at_path / siblings_mutcrates/outl-md/src/outline_ops.rs
Insert a sibling before / after a pathoutline_ops::insert_sibling_before / outline_ops::insert_sibling_aftercrates/outl-md/src/outline_ops.rs
Indent / outdent / delete / move up / move down at a pathoutline_ops::indent_at_path / outdent_at_path / delete_at_path / move_up_at_path / move_down_at_pathcrates/outl-md/src/outline_ops.rs

10. Indices and search (outl-md::index + block_index)

IntentUse thisFile
Build / query the workspace-wide index (slug → page, backlinks, block lookups)outl_md::WorkspaceIndex::build / by_slug / by_title / pages / pages_by_title_prefix / pages_by_typecrates/outl-md/src/index.rs
Patch / remove a page in an existing indexWorkspaceIndex::patch_page / remove_pagecrates/outl-md/src/index.rs
Resolve ((blk-XXXXXX)) to a block / look a block up by id or locationWorkspaceIndex::resolve_block_ref / block_by_id / block_at_locationcrates/outl-md/src/index.rs
Reverse refs to a block / iterate / searchWorkspaceIndex::block_refs_to / iter_blocks / search_block_text / block_count / block_index (borrow the inner BlockIndex)crates/outl-md/src/index.rs
Stand-alone block-level index (when you don’t need the page facade)outl_md::BlockIndex + BlockEntry + BlockReferencecrates/outl-md/src/block_index.rs
PageEntry DTO returned by WorkspaceIndex lookupsoutl_md::PageEntrycrates/outl-md/src/index.rs

11. View helpers for editors (outl-md::view + inline)

IntentUse thisFile
Char ↔ (line, col) on a buffer (both TUI and mobile editors share)outl_md::view::char_to_line_col / line_col_to_charcrates/outl-md/src/view.rs
Project a block to renderable rows (with BlockRowKind discrimination)outl_md::view::block_to_rowsBlockRow / BlockRowKindcrates/outl-md/src/view.rs
Tokenize inline markdown (**bold**, [[refs]], #tags, ((blk-…)), !((blk-…)))outl_md::inline::tokenizeInlineTokcrates/outl-md/src/inline.rs
Tokenize inline markdown into an owned, Serde-friendly form for wire / DTO payloads (mobile renders these straight; no parallel TS tokenizer)outl_md::inline::tokenize_ownedInlineTokencrates/outl-md/src/inline.rs
Reconstruct the source markdown from a Vec<InlineTok> (Bold / Italic / Strike now carry recursively-tokenized inners; use this when a surface wants the whole inner span as one styled string instead of dispatching per-variant)outl_md::inline::inline_to_sourcecrates/outl-md/src/inline.rs
Resolve the ref under a caret position (Page / Journal / Tag / Block)outl_md::inline::ref_at_cursorRefTargetcrates/outl-md/src/inline.rs
Does a text mention #tag as a whole tag token? Boundary-correct via the tokenizer (#tag-longer / #tagged never match tag; #tag inside a code span is not a tag) — never use text.contains("#tag")outl_md::tag::text_contains_tagcrates/outl-md/src/tag.rs
Validate a ((blk-XXXXXX)) handle stringoutl_md::inline::is_valid_block_handlecrates/outl-md/src/inline.rs
Byte offset for a char index (UTF-8 safe)outl_md::inline::byte_index_for_charcrates/outl-md/src/inline.rs
Canonicalize a fence info-string (rsrust, js/javascript/nodejs, …) — single source of truth for both outl-exec’s runtime dispatch and the frontend syntax highlighteroutl_md::lang::canonical, outl_md::lang::KNOWN_ALIASEScrates/outl-md/src/lang.rs
Resolve a :shortcode: to its unicode glyph (one-way; never retro-translate glyph → shortcode, multiple shortcodes can alias the same codepoint)outl_md::emoji::shortcode_to_unicodecrates/outl-md/src/emoji.rs
Validate the [a-z0-9_+-]+ shape of an emoji shortcode (does not check the catalog — that’s shortcode_to_unicode)outl_md::emoji::is_valid_shortcodecrates/outl-md/src/emoji.rs
Validate one char of a shortcode ([a-z0-9_+-]) — use this when walking the buffer char-by-char (try_emoji, TUI’s detect_trigger) so you don’t allocate a 1-char String per keystroke just to call is_valid_shortcodeoutl_md::emoji::is_valid_shortcode_charcrates/outl-md/src/emoji.rs
Search the GitHub gemoji catalog for shortcodes matching a query (exact → prefix → substring; shorter shortcodes win ties) — powers the :emoji: autocomplete in every client through one shared outl_emoji_search Tauri commandoutl_md::emoji::searchEmojiHitcrates/outl-md/src/emoji.rs
IntentUse thisFile
Extract [[ref]] tokens out of a block’s text (tolerates unbalanced openers)outl_actions::backlinks::extract_refscrates/outl-actions/src/backlinks.rs
Backlink DTO returned by the queries belowoutl_actions::backlinks::Backlinkcrates/outl-actions/src/backlinks.rs
Walk every backlink for a target / a PageMeta (matches [[ref]] literally and #tag via slugify — same resolution a tag click uses)outl_actions::backlinks::backlinks_for_target / backlinks_for_pagecrates/outl-actions/src/backlinks.rs

13. Code-block execution (outl-actions::exec)

The cross-client glue every UI uses to wire a “run this fence” gesture (TUI g x, desktop Cmd+Shift+X, mobile long-press → “Run code”) through to outl-exec and back. outl_actions::exec::run_code_block is the only entry point a Tauri command / TUI action should call — never re-implement the flat-DFS walk, the .md path lookup, or the DTO shape per client.

IntentUse thisFile
Resolve a NodeId to its flat DFS index inside an outline forest (the order outl_exec::run_block_at_index expects)outl_actions::flat_index_for_blockcrates/outl-actions/src/outline.rs
Orchestrate execution: walk DFS, resolve .md path, call outl_exec::run_block_at_index, build DTOoutl_actions::exec::run_code_blockcrates/outl-actions/src/exec.rs
Serializable mirror of outl_exec::ExecOutput (stdout/stderr/duration_ms/exit)outl_actions::ExecOutputDtocrates/outl-actions/src/exec.rs
Outcome shipped to the client (language + result_ok xor error; client adds the refreshed view)outl_actions::RunCodeBlockOutcomecrates/outl-actions/src/exec.rs

The runtime catalog (which languages are available) is selected by the binary that consumes this crate, via outl-exec features in its own Cargo.toml. outl-actions itself depends on outl-exec with default-features = false so it doesn’t drag wasmtime (Rust runtime) into the mobile IPA via the back door.

14. Sync engine, locks, storage trait

IntentUse thisFile
The shared sync entry point (TUI poller + mobile iCloud watcher both use it)outl_actions::SyncEngine::newcrates/outl-actions/src/sync.rs
Bind a sync engine to an explicit transport (iroh, test doubles)SyncEngine::with_transportcrates/outl-actions/src/sync.rs
Start the transport’s background tasks once the caller’s channel is readySyncEngine::start_transport(tx)crates/outl-actions/src/sync.rs
Announce new local ops to connected peers (no-op for file transport)SyncEngine::announce_local_ops(workspace_id, hlc)crates/outl-actions/src/sync.rs
Reload workspace from disk after a peer changeSyncEngine::reload_workspacecrates/outl-actions/src/sync.rs
Re-project a page’s .md + sidecar to disk / reload + reproject in one callSyncEngine::reproject_page / refresh_pagecrates/outl-actions/src/sync.rs
Snapshot every / peer-only ops-*.jsonl (size + mtime) for change detectionSyncEngine::snapshot / snapshot_peers (OpsFileSnapshot)crates/outl-actions/src/sync.rs
Scan journals/ + pages/ for orphan .md (no sidecar / stale hash)SyncEngine::scan_for_orphanscrates/outl-actions/src/sync.rs
Detect projections that ran ahead of the op log (sidecar hash-in-sync but referencing ids no op log ever created — e.g. app killed after writing .md+sidecar but before the ops append)outl_actions::scan_for_desynced_projections(ws, root) / SyncEngine::scan_for_desynced_projections(ws)crates/outl-actions/src/desync.rs
Recover a desynced projection: re-emit Create/Edit/SetProp ops for the sidecar ids the tree has never seen (ids preserved, strictly additive — never resurrects a trashed block, never touches existing ones), then re-project the merged pageoutl_actions::recover_desynced_projection(ws, hlc, root, md_path)crates/outl-actions/src/desync.rs
Transport abstraction (iroh QUIC default; file/iCloud polling opt-in)outl_actions::SyncTransport (trait)crates/outl-actions/src/sync.rs
Filesystem / iCloud opt-in transport (polls ops/ every 2 s, delivery is no-op)outl_actions::FileSyncTransportcrates/outl-actions/src/sync.rs
Per-peer reachability snapshot from the running transport’s own dials (GUI status; never bind a probe endpoint)SyncTransport::peer_healthoutl_actions::PeerHealthSnapshotcrates/outl-actions/src/sync.rs
Acquire the cross-process workspace lock (one writer at a time)outl_core::WorkspaceLock::acquirecrates/outl-core/src/lock.rs
Acquire the per-actor write lock (one process writing this actor’s jsonl)outl_core::ActorWriteLock::try_acquirecrates/outl-core/src/lock.rs
Resolve which actor this process writes asoutl_core::resolve_write_actorcrates/outl-core/src/lock.rs
The Storage trait every persistent backend implements (invariant #5)outl_core::Storage / StorageErrorcrates/outl-core/src/storage/mod.rs

15. Undo / redo history (outl-actions::history)

Bounded snapshot stacks with vim semantics (a new edit clears redo) shared by GUI clients — the desktop’s Cmd+Z / Cmd+Shift+Z ride these. Restores route through outl_md::reconcile_md, so an undo is new ops in the log, never a rewrite (invariant #1 holds). This is not per-keystroke undo inside an uncommitted draft — that belongs to the client’s editor widget.

IntentUse thisFile
Bounded undo / redo stacks over any snapshot type (record / undo / redo / can_undo / can_redo / clear)outl_actions::history::HistoryStackscrates/outl-actions/src/history.rs
Default per-stack bound (matches the TUI’s session cap)outl_actions::DEFAULT_HISTORY_CAPcrates/outl-actions/src/history.rs
Restore a page to a previously-rendered .md snapshot (write + reconcile → min ops through Workspace::apply)outl_actions::restore_page_mdcrates/outl-actions/src/history.rs

When your need isn’t in this catalog

If you’ve grepped honestly and the primitive doesn’t exist, that’s a fair sign — add it in the upstream crate that owns the concept:

  • outl-md for parse / render / sidecar / inline / tokenizers
  • outl-actions for workspace mutations, ingest, page/journal helpers
  • outl-core for op-log / tree / HLC / storage trait

Then update this catalog in the same commit, and sync the mirror at .github/copilot-instructions.md §5.1. The PostToolUse hook will flag drift, but the discipline starts before the hook fires.

For the broader reuse-first rule and past drift incidents that justify this catalog, see Contributing → Reuse-first.