← Back to Plan

Note Architecture

Steel Notes — Note Architecture

**The Note is the primary unit of engagement.** You think, react, and explore inside Notes. Sources hold external content. Thoughts and Questions maintain their own identity but are transcluded into Notes so you see everything in one place.

The Problem

Knowledge doesn't come in clean single-type atoms. Reading an article produces the source AND 3 thoughts about it AND 2 questions it raises. With separate files per type, that's 6 files across 4 folders — too much context-switching for what is really one unit of engagement.

The Model

Note (new primary type)

A Note lives in notes/ and is where the user engages with ideas. It can:

  • Reference zero or many Sources via sources: in frontmatter
  • Embed Thoughts and Questions inline via ![[id]] transclusion
  • Contain free-form markdown (user's own unstructured notes)
  • Exist without any Source — pure thinking is fine
  • ```markdown


    id: note-2026-03-27-001

    type: note

    title: "Reading Great Work"

    sources: [src-2026-03-26-003]

    tags: [creativity, philosophy]

    status: active

    created: "2026-03-27T10:00:00Z"

    updated: "2026-03-27T11:30:00Z"


    Reading Great Work

    The Graham essay resonated deeply...

    Thoughts

    ![[th-2026-03-27-001]]

    ![[th-2026-03-27-002]]

    Questions

    ![[q-2026-03-27-001]]

    Free Notes

    This might become a blog post connecting Graham, Nietzsche, and Ericsson.

    ```

    Source (unchanged)

    A Source lives in sources/ and holds external content — the article text, book metadata, PDF content. Sources are reference material. Every Source automatically gets a companion Note created alongside it.

    The user never interacts with a Source alone. They interact with the Note.

    Thought (unchanged as standalone)

    A Thought lives in thoughts/ with its own ID, status lifecycle (SEED → DEVELOPING → SOLID → ARCHIVED), connections, and sparkedBy references. But instead of being a standalone reading destination, it's transcluded into one or more Notes.

    A single Thought can appear in multiple Notes. Its status evolves independently.

    Question (unchanged as standalone)

    A Question lives in questions/ with its own ID, status lifecycle (OPEN → EXPLORING → RESOLVED → EVOLVED), and lifecycle tracking. Same transclusion pattern — embedded in Notes, tracked independently.

    Synthesis (eliminated)

    A Note that references multiple Sources replaces the Synthesis type entirely. No need for a separate type — it's just a Note with sources: [src-001, src-002, src-003].


    Vault Structure

    ```

    vault/

    ├── notes/ ← primary engagement folder

    ├── sources/ ← external content (articles, books, PDFs)

    ├── thoughts/ ← standalone ideas with own lifecycle

    ├── questions/ ← standalone questions with own lifecycle

    ├── inbox/ ← raw captures before AI processing

    └── .steel/

    ├── index.db ← disposable SQLite index

    └── sync.db ← sync state

    ```


    Transclusion

    Syntax

  • ![[th-2026-03-27-001]] — embed a Thought inline
  • ![[q-2026-03-27-001]] — embed a Question inline
  • [[path/to/note]] — navigation link (existing behavior, unchanged)
  • The ! prefix distinguishes transclusion (embed content) from navigation (link to).

    How It Works

    1. Storage: The .md file stores ![[id]] — never the expanded content.

    2. Display: At read/display time, TransclusionResolver replaces ![[id]] with the rendered content of the referenced note.

    3. Indexing: TransclusionExtractor finds all ![[id]] references and stores them in the links table with link_type = "transclusion".

    Rendering Example

    In the .md file:

    ```markdown

    Thoughts

    ![[th-2026-03-27-001]]

    ```

    Rendered in CLI or iOS:

    ```

    Thoughts

    ┌─ Amor Fati Connection ─────────────── seed ─┐

    │ │

    │ The bus ticket collector analogy maps │

    │ perfectly to Nietzsche's amor fati — │

    │ pursuing something not because it's useful │

    │ but because you can't help it. │

    │ │

    └──────────────────────────────────────────────┘

    ```

    Cross-Note References

    A Thought in one Note can be transcluded in another. This is how ideas connect across Notes without duplication:

  • notes/reading-great-work.md contains ![[th-2026-03-27-001]]
  • notes/intrinsic-motivation.md also contains ![[th-2026-03-27-001]]
  • The Thought file exists once in thoughts/. Both Notes display its content. If the user updates the Thought, both Notes reflect the change.

    Backlink Queries

    "Which Notes transclude this Thought?" is a simple query:

    ```sql

    SELECT n.* FROM notes n

    JOIN links l ON l.source_id = n.id

    WHERE l.target_path = :thoughtId AND l.link_type = 'transclusion';

    ```


    Source + Note Auto-Creation

    When a Source is created (via capture or steel create source), a companion Note is automatically created alongside it:

    ```

    steel create source --title "Great Work" --url "https://..."

    → creates sources/great-work.md (the external content)

    → creates notes/great-work.md (empty engagement Note, linked to source)

    ```

    The Note starts with just the Source reference. The user fills it in as they engage with the material.

    Capture Pipeline Change

    Previously, the AI pipeline wrote everything into a single Source file (article text + user's voice note + related links). Now it splits:

    ContentGoes into
    Extracted article/book/PDF textsources/ file
    User's voice note / capture contextnotes/ file (companion Note)
    AI-generated tagsBoth files
    AI-suggested related linksNote file

    This cleanly separates external content (Source) from user engagement (Note).


    NoteStatus Lifecycle

    Notes have a simple status — they're living documents, not pipeline artifacts:

    StatusMeaning
    ACTIVECurrently being worked on
    PARKEDSet aside for now
    ARCHIVEDDone / no longer active

    Code Changes

    Sealed Interface Rename

    The sealed interface is renamed from Note to VaultItem. This frees up Note for the concrete class — the user-facing concept.

    ```kotlin

    sealed interface VaultItem {

    val id: String

    val type: NoteType

    val title: String

    val created: Instant

    val updated: Instant

    val tags: List<String>

    val filePath: String

    val bodyText: String

    }

    // Concrete types implement VaultItem

    data class Note(...) : VaultItem // NEW — the engagement unit

    data class Source(...) : VaultItem // unchanged fields

    data class Thought(...) : VaultItem // unchanged fields

    data class Question(...) : VaultItem // unchanged fields

    @Deprecated("Use Note instead")

    data class Synthesis(...) : VaultItem // kept for migration

    ```

    NoteType Enum

    ```kotlin

    enum class NoteType(val prefix: String, val directory: String) {

    NOTE("note", "notes"),

    SOURCE("src", "sources"),

    THOUGHT("th", "thoughts"),

    QUESTION("q", "questions"),

    @Deprecated("Use NOTE instead")

    SYNTHESIS("syn", "syntheses");

    }

    ```

    New Files

    FilePurpose
    TransclusionExtractor.ktExtract ![[id]] references from markdown body
    TransclusionResolver.ktReplace ![[id]] with rendered content at display time

    Modified Files

    FileChange
    Note.ktRename interface to VaultItem, add Note data class, deprecate Synthesis
    NoteType.ktAdd NOTE, deprecate SYNTHESIS
    Statuses.ktAdd NoteStatus enum
    NoteFactory.ktAdd NoteType.NOTE parsing branch
    NoteSerializer.ktAdd Note serialization branch
    WikilinkExtractor.ktNegative lookbehind regex to skip ![[
    VaultManager.ktnotes/ dir, createSourceWithNote(), transclusion indexing
    VaultIndexer.ktTransclusion link indexing
    SteelNotes.sqAdd source_refs column to notes table
    Main.kt (CLI)steel create note, Source auto-creates Note, read resolves transclusions

    DB Schema Change

    One new nullable column on the existing notes table:

    ```sql

    ALTER TABLE notes ADD COLUMN source_refs TEXT;

    ```

    Stores comma-separated Source IDs for Notes. No new tables needed — the links table already supports transclusion via link_type = "transclusion".


    FAQ

    Why not embed Thoughts/Questions as headings inside the Note file?

    Because Thoughts and Questions need their own identity. A Thought has a status that evolves (SEED → SOLID), connections to other Thoughts, and can appear in multiple Notes. If it's just a heading, it can't be independently tracked, searched, or linked.

    Why transclusion instead of just links?

    Links require the user to click through to see content. Transclusion shows the content inline — you see everything in one place while each piece maintains its own file and lifecycle.

    Why does every Source get an auto-created Note?

    Because you never just "have" a source — you engage with it. The Note is where that engagement lives. Without the auto-creation, users would need to manually create a Note every time they add a source.

    What happens to existing Synthesis files?

    A migration command (steel migrate syntheses) converts them to Notes: remaps IDs, moves sourceRefs to sources, maps status. The Synthesis class stays in the codebase (deprecated) so existing files parse correctly.

    Can a Thought exist without being in any Note?

    Yes. AI-generated Thoughts from the capture pipeline may not be in a Note yet. Search and the vault list still find them. The user can add them to a Note later via ![[id]].

    What about AI processing?

    Only captured content (photos, share sheet, URLs) gets AI processing. When the user types directly (CLI steel create, in-app editor), no AI runs — the content is saved as-is.