Current architecture and vocabulary #
Maintained map of the implemented application, reviewed against source on 2026-10-06. Update this document when ownership or a domain contract changes.
Application #
Reams uses CodeMirror 6 for source and live-preview Markdown editing, with
Obsidian-flavored syntax: GFM, wikilinks, embeds, callouts, comments, and frontmatter.
Cmd/Ctrl+E toggles the editing mode. The layout is sidebar, main area, optional
right panel, and tabs. Sidebar/panel geometry and tabs persist device-locally;
editor mode is a synced vault preference. Mobile delivery is a PWA.
Fresh desktop layouts expand Quick Access; fresh mobile layouts show only the
ribbon. Valid saved sidebar modes take precedence over these responsive defaults.
The formatting toolbar is enabled on mobile and defaults to enabled on desktop.
Settings → Formatting retains a synced desktop visibility switch; explicit saved
desktop off choices survive reload, and resetting restores the enabled default.
Plugin support exposes an Obsidian-shaped compatibility surface through
src/plugin/obsidian-compat/; Reams-specific capabilities live in
src/plugin/reams/. Export/type parity and runtime behavior are separate checks.
Settings → Obsidian Plugins reads a host-owned static catalog of pinned releases.
Only catalog-admitted bundles are restored or executed; old manual installs remain
preserved and inactive. Downloaded code and styles are hash-checked and stored
device-locally in IndexedDB. The catalog and bundles can be published independently
of Reams; plugin-catalog/README.md owns the publishing contract. Indexer plugins
can require initial host metadata readiness. The first-party workspace opens before
their activation so progressive background indexing is not blocked by plugin boot.
Domain vocabulary #
| Concept | Meaning and owning boundary |
|---|---|
| Edition | Boot/storage/transport selection: src/entries/, src/editions/, src/repo.ts, src/atproto/native-repo.ts. Shared editing behavior sits above the repository contract. |
| Document | Persisted CRDT file or folder. DocumentManager owns canonical reads, writes, and frontier validation; src/types/pushwork.ts defines the compatible wire shape. |
| Document identity | Application DocumentId currently aliases an Automerge URL in document-manager.ts. The runtime contract's DocumentId means the backend ID without the URL wrapper. A path/name is presentation/location, not durable identity. |
| Heads/frontier | The exact CRDT document version a read or edit describes. Typed projections also depend on the effective catalog revision. Application heads are hexadecimal; the repository boundary encodes/decodes its heads representation in document-runtime-contract.ts. |
| Vault | User-visible file/folder hierarchy, exposed through FileIndex and the plugin Vault facade. VaultUserEntries filters host-owned state out of user enumeration. |
| Vault state | Host-owned, synced state in the reserved subtree: vault-state-root.ts, vault-state-map.ts, vault-state-data.ts. This includes vault preferences such as editor mode; sidebar/panel geometry and tabs remain device-local. |
| Markdown block | Parser-recognized structure in visible text. Ordinary Markdown blocks do not acquire persistent Record identity merely by being parsed. |
| Subject | A document root or a recognized typed block. Root identity uses document identity; a typed block has a native marker with reams.nodeId. |
| Type | Scoped schema resolved by TypeCatalog, including fields and their bindings/defaults. A #tag is Type evidence only in an active recognized context. |
| Record | One Type application on a subject, not a separate note or database row. Multiple Type applications can share a block subject. |
| Fact | An explicit block field contribution keyed by (nodeId, typeName, fieldId); root fields use the shared frontmatter surface. Defaults and derived values are not explicit facts. |
| Projection/catalog | Disposable read-side interpretations of canonical data. TypeCatalog resolves schemas; TypedNodeCatalog aggregates projected typed subjects. They have different responsibilities. |
| Base | YAML query in a .base document or Markdown fence. Rows select Notes, Records, or Both; a Base does not own another database. |
| Plugin API | Supported Obsidian-shaped surface, explicit Reams namespace, and justified host-internal mechanisms. Export/type parity and runtime functionality are separate obligations. |
Canonical editing path #
CM6 transaction / public text write
→ TextFirstSyncController / DocumentManager
→ attachment planning + Record lifetime policy
→ TextFirstWriter: atomic text / marker / fact contribution
→ canonical CRDT change
├─ change observation → disposable read-side projections
└─ separate flush → edition-specific durability / publication barrier
src/sync/use-document-sync.tsbinds the CM6 view toTextFirstSyncController; captured edit geometry matters for repeated text, selection, attachment continuity, and undo.src/services/document-manager.tsis the application write/read facade. Whole-source APIs remain supported and use bounded text diffing when exact changes are unavailable. Binary writes have an explicit atomic path.src/editor/line-attachment-extension.tsinterprets structure/continuity;src/types-records/record-structure-policy.tsproduces concrete marker/fact effects. For block Records, removing recognized Type evidence retires that Type's explicit block facts; removing the final Type retires the block identity. There is no dormant block Record restoration store. Retyping is distinct from local undo. Root Type removal changes the frontmatter application list without deleting its field values.src/spikes/text-first-writer.tsis production code despite its directory name. It applies exact-frontier edits and tracks materialization versus durability; it does not interpret Markdown.- CM6 local history records concrete inverse contributions. No parallel durable undo ledger is introduced. A committed edit followed by an observer failure needs recovery from the committed state, not replay.
- Disabling Types is distinct from deleting Type evidence. On documents carrying native Record markers or block facts, the controller suspends bound-editor writes to protect existing identity and facts. An unavailable Type catalog defers startup or suspends writes; an authoritative empty catalog can retire formerly recognized block Records. Import/bootstrap/opaque-marker recovery also has explicit compatibility rules; sparse identity is not permission to discard unknown native metadata.
Sparse Record navigation reads supply effective Type recognition explicitly to
DocumentManager.readMarkdown; recognition no longer requires a retained editor
writer session. Background identity bootstrap scopes temporary writer ownership,
while startup leases protect asynchronous editor preparation. Failed durability
retains the writer for retry. Base cell editors use their row's schema descriptors
and bindings; vault-wide Type summaries remain chooser/query presentation data.
Read side and storage #
ATProto account behavior follows the user-approved local-first lifecycle.
BackgroundIndexer coordinates discovery, scheduling, derivation, and checkpoints
for MetadataCache, SearchService, TypeCatalog, and TypedNodeCatalog. Bulk metadata
and Record projection derivation uses workers. Background indexing may request
canonical Record-identity bootstrap through DocumentManager before deriving
projections. Editor preview and Bases consume projections; they do not become
independent canonical writers.
SearchService's device-local IndexedDB checkpoint and keyed journal also retain versioned parser output for metadata at each indexed document frontier. Startup validates cached metadata against content and canonical heads/name/path, then publishes it in one batch so titles and tags are available together. Progressive opening restores this local checkpoint before workspace readiness; replicated contribution loading and discovery remain behind the background activation gate. Search worker activation and typed catalog reconciliation retain their separate completion lifecycle. See local recovery evidence.
ATProto boot opens remembered identity and the existing local account partition
before authentication, discovery or remote validation. account-vault.ts opens
the same local runtime whether connected or disconnected. A new browser discovers
the authoritative root once and opens progressively; uncached bodies load through
the sync worker. Existing staging/adoption does not block opening the account vault.
Initial adoption installs its graph locally before publication. The frozen journal
retains recovery until background independent readback and retirement complete.
account-sync-worker.ts owns library-backed catch-up, publication, body recovery
and ordinary background adoption. IndexedDB/BroadcastChannel connect that worker
to the mounted local Automerge runtime. account-sync.ts supplies the worker's
OAuth storage snapshot and commits credential updates with a cross-tab lock/CAS;
local editing and durability never take that lock. The current library requires
Web Storage for OAuth, so this bridge does not reimplement permissioned protocols.
Read paths infer missing external-file content types without mutating canonical
documents. Current layout, hidden-state maps and replicated index readers no
longer migrate historical pre-release payloads. Historical block reconciliation
and emission remain in tests/format-preservation/harness/legacy/, behind a
production-to-test boundary. Explicit Classic archive import/adoption remains
supported; it is separate from automatic read-time migration.
Classic import is accessed directly through the lightweight /import workspace,
with other Reams tabs closed. The profile import action was removed after its
workspace teardown failed in production. The dedicated import worker owns
the local-only SDK runtime exclusively and merges compatible histories using
deterministic original-document mappings. Source roots/hidden state retain separate
archive identities; current account settings remain active. Compact prospective
folder graphs and complete founding-history identities are checked before writes.
Hidden links and folder repairs are durable before an atomic root-link/receipt
change. Retries inspect receipts rather than replaying removed links. The UI has no
whole-job 15-second deadline or per-document history budget; ownership lasts through settled writes and worker
termination. Normal editor opening and asynchronous publication resume after local
completion. Legacy signed-out staging remains a separate compatibility path.
Import flushes each document before advancing the merge counter. Retry compares stored history and skips complete documents; visible root attachment still happens last, so interrupted new files remain stored but may not appear in the editor yet. Temporary source/verification native documents are explicitly freed. Worker heartbeats and operation/document progress distinguish response silence from an unfinished asynchronous operation. Cooperative stop drains accepted work; Stop now terminates the worker and releases the client operation lock, with retry rechecking actual durable state. SDK local hash tracking uses native metadata rather than decoding operation payloads into JavaScript. Incoming non-folder histories transfer their native backend to the SDK without a snapshot/load copy. Installed histories checkpoint on the first local flush while retaining publication intent; account opening performs one initial hydration.
FileIndex owns the active file hierarchy and file events; the plugin Vault provides Obsidian-style operations. Workspace owns leaves/tabs/navigation; React presents them and persists device-local layout, with editor mode stored as a vault preference. Paths and ranked search hits do not define the entire query candidate universe.
DocumentManager serializes accepted folder publications and deletion by repository instance and document identity. Deletion fences a detached identity through failures; folder attachment/factory publication checks loaded canonical parent evidence in its synchronous mutation segment. Failed factory cleanup preserves owned or concurrently written bodies. These are local mutation guarantees, not distributed ownership locks.
Account lifecycle ownership is explicit: account.ts establishes identity and
validates vault selection; a DID-bound opening intent carries an explicit choice
across reload. workspace-lifecycle.ts owns edit acceptance, accepted-work drains,
local durability and irreversible close/navigation. Initial login and selection
use that handoff. Same-account reauthentication and sync retry keep the mounted
workspace and handles. Logout pauses UI acceptance, requires publication, closes
writers, then resets all Reams-owned local state and recreates anonymous onboarding.
Its durable closing checkpoint resumes irreversible cleanup; an orphaned
draining attempt reopens locally without authentication. Normal edition boot is
the sole mounted account-vault opener.
No temporary login repository or separate login file hierarchy is constructed.
Classic uses Automerge-repo, IndexedDB, BroadcastChannel, and optional WebSocket.
ATProto uses its native repository/compatibility adapter. The shared seam is
src/storage/document-runtime-contract.ts. Canonical change observation can precede
flush completion. Local durability and remote publication are distinct:
ATProto adapter flush/flushLocal acknowledge device-local durability. publish
is a separate barrier used by adoption and logout. Successful publication of a
captured snapshot does not imply no later writes are queued; destructive account
retirement separately requires an empty durable outbox and no live account runtime.
Access tokens refresh automatically. Temporary refresh/provider failures retain
credentials and retry without interactive login. A known local account can recover
an orphaned active marker through library validation of its matching stored session;
explicit logout removes those credentials. A definitively rejected refresh grant
offers reauthorization while the account profile and local editing remain available.
There is no separate offline account workspace.
OnboardingState stores installation completion in .reams/onboarding.json.
Host-created onboarding files, assets and folders record explicit starter
provenance (reamsOnboarding, version 1); origin anonymous identifies signed-out
starters, while vault identifies defaults installed directly into a vault.
Text, binary and folder creation accept explicit onboarding: true; user files
reused by path are not relabeled as starters. First setup adopts
anonymous starters; returning accounts discard them without publishing their
histories and transfer ordinary user files. Clean destination folder snapshots
exclude discarded starter references while preserving user-file identities/history.
Completed account vaults do not reseed defaults on ordinary boot.
install-onboarding.ts installs the bundled v7 welcome note, its nine tagged
wikilink placeholders, and inline photo/icon assets under onboarding/images/
before recording completion. Templates install first; initial timestamps keep
them below companions and welcome in Quick Access's default recent-edit order.
Companion files/folders have root-scoped reserved identities and starter provenance;
existing user paths retain their contents and ownership. Each welcome revision
uses a new reserved scope so earlier immutable founding histories stay compatible.
The 2026-10-10 welcome refresh runs after workspace readiness for existing vaults.
Its descriptor in welcome-refresh-upgrade.ts pins eligibility to template
revisions 1–5, independently of future fresh-user template changes. Revision 6
and newer are ignored, even without a receipt. It also recognizes the original Classic founding
source (including later edits), installs companions, and swaps the old visible
link for one root-scoped replacement. The synced welcomeRefresh20261010 receipt
in .reams/onboarding.json is written after local durability. Fresh installations
record it immediately; subsequent editing, moving or deletion never triggers
another refresh. Old histories are detached, not rewritten or globally tombstoned.
Refresh completion requires a full canonical traversal of uncontested folder
lists, followed by frontier validation after local durability. Missing bodies
defer it; moves trigger rescanning. Occupied companion paths relocate the upgrade
bundle to a free Reams onboarding folder and rewrite its links; replacement
genesis is bound to that source payload. Workspace effects respect newer leaf
open revisions and navigation rather than relying on stale active-file snapshots.
Upgrade companions retain identity/placement bindings, so interrupted-install
renames and moves rewrite guide links instead of restoring old paths. Bindings
are checked at publication and inside the synchronous receipt mutation; untouched
updater artifacts can be rebound before completion, while user-modified guides
and their native state are never reconstructed. Imported guides from another
root are not mistaken for this vault's own replacement.
Scoped leaf handoff leases keep ordinary delete-driven tab closing from racing
the reference swap; leases are released on completion, error and cancellation.
Pending user opens and newer navigation still take precedence.
This is a temporary release migration, not a continuous onboarding updater.
Once the small legacy cohort has updated, retire its background runner, historical
fixture and refresh-only machinery in a follow-up release; keep the synced receipt
and normal first-install/completion policy. App release SemVer is not used to
infer a note's revision or authorize overwriting user documents.
Useful checks #
npm run typecheckchecks the TypeScript project;npm testcombines selected lint, unit/type tests, and one seeded property campaign.npm run test:coveragerequires 100% coverage of the explicit configured scope, not the entire application. It is the configured pre-commit hook.npm run depcruise,npm run knip:report, edition-source and boundary ratchets inspect architecture. Known drift is recorded in the maintainability audit; inspect results before treating them as reliable gates or deleting apparent dead code.- Browser, property, performance, and deployment tests have separate commands
and prerequisites. See
package.json, the corresponding configs, and the Nix browser instructions inAGENTS.md.