A local-first note taking app
textile src ARCHITECTURE.md
3.4 kB
Markdown
at main

Textile core abstractions #

Textile separates where files live, how document bytes sync, and how editing UI renders. Each layer has a narrow job; concrete backends (Habitat PDS, Yjs, Zen/Tiling) plug in at the edges.

FileSystemProvider #

Role: Talk to a source about the file tree and other top-level actions.

  • Lists entries (sidebar): names and stable ids
  • createFile() for new persisted entries (sidebar “New document”)
  • Readiness and auth hints (isReady, subscribeReady)
  • Does not open document contents or run merge/sync
  • Pairs with a DocumentProvider via documents + defaultEditorKind (injected in App.tsx)

Examples: habitat-docs (PDS record listing), future disk roots.

Key types: FileSystemProvider, FileSystemEntry — src/filesystem/types.ts

DocumentProvider #

Role: Resolve a document from local or remote storage and keep its contents coherent.

  • openDocument(entryId) — load/sync for an entry from listFiles
  • createDocument() — new in-memory or persisted doc
  • Transport from the source (XRPC, filesystem reads, …)
  • Merge / sync when needed (e.g. Yjs CRDT via docSession, libp2p)
  • Exposes DocumentHandle: title, loadPromise, release, getEditorBinding() (data only, no React)

Does not: render UI or own TipTap/Yjs editor widgets.

Examples: HabitatDocumentsProvider — src/documents/providers/

Key types: DocumentProvider, DocumentHandle — src/documents/types.ts

Editor #

Role: The UI that displays and edits file contents. Deeply tied to the sync method (Yjs + TipTap today).

  • Registered in src/editors/registry.ts (Editor: { kind, Component })
  • Shared chrome and typography: src/editors/ui/
  • Per-backend wiring: src/editors/<kind>/ (e.g. yjs/yjsDocumentEditor.tsx)
  • Consumes EditorBinding from getEditorBinding(); only the matching editor reads payload

Does not: fetch from PDS, run libp2p, or list the file tree.

Entry point for workspaces: DocumentPane — src/editors/DocumentPane.tsx

Key types: Editor, EditorKind, EditorBinding, EditorProps — src/editors/types.ts

Workspace #

Role: How open files are arranged on screen (tiles, splits, empty states, workspace chrome).

  • Subscribes to open requests from the shell sidebar
  • Holds DocumentHandle(s) via useDocumentSlot, calls release() on switch/close
  • Renders DocumentSlotView inside layout — does not import Habitat or Yjs

Examples: ZenWorkspace, TilingWorkspace — src/workspaces/

Composition (App.tsx) #

const habitatDocuments = new HabitatDocumentsProvider();
new HabitatFilesystemProvider({
  documents: habitatDocuments,
  defaultEditorKind: 'yjs',
});

Workspaces receive resolveProvider → fs.documents.openDocument(entryId) → DocumentPane handle={…}.

Adding a new backend #

  1. New source only (same Yjs docs): new FileSystemProvider + reuse HabitatDocumentsProvider or a sibling DocumentProvider.
  2. New sync/editor (e.g. plain markdown): new DocumentProvider (or branch in existing) returning { kind: 'markdown', payload }; add editors/markdown/ + register in editors/registry.ts.
  3. New layout: new workspace under src/workspaces/; reuse DocumentPane and DocumentHandle.