Steel Notes

Implementation Plan · Updated 2026-03-30 11:59 UTC
Agent Guide
207
Tasks
138
Complete
0
In Progress
69
Not Started
67%
Done
Human Tasks 1/9 done

Blocking Phase 2 — Cloud Vault Sync

IN PROGRESS
1 1
50%
Enroll in Apple Developer Program ($99/year)
  • Sign up at developer.apple.com
  • Create an App ID with "Sign in with Apple" capability
  • Create a Services ID for server-side Sign in with Apple validation
  • Note your Team ID (goes in APPLE_TEAM_ID env var)
  • Create an APNs key (.p8 file) for push notifications
  • Enable push notifications entitlement in Xcode Signing & Capabilities
  • Blocks: Sign in with Apple auth flow, push notifications, TestFlight, App Store
Choose and register an API domain
  • Pick a domain (e.g., api.steelnotes.app)
  • Register it and be ready to point DNS to API Gateway after Terraform runs
  • Blocks: deploying the API to a reachable endpoint, client-side API base URL config
  • Run: openssl rand -hex 32
  • Save the output — it goes in the server's JWT_SECRET env var
  • Blocks: auth token signing and validation

Blocking — Deploy Server to AWS Lambda

PENDING
0 5
0%
Build and deploy Rust binary to Lambda
  • Run: cd server && cargo lambda build --release --features lambda
  • Deploy: cargo lambda deploy steel-notes-api-production
  • Blocks: production API, iOS app connecting to real server
Run database migrations on production RDS
  • Get the RDS endpoint: cd server/deploy/terraform && terraform output rds_endpoint
  • Run migrations: psql <rds_connection_string> < src/db/migrations/001_users.sql (repeat for each migration file)
  • Alternatively, the server runs migrations on startup — invoke the Lambda once and check logs
  • Blocks: user registration, auth, sync state storage
Point API domain DNS to API Gateway
  • API Gateway URL: https://bjhfydpwgb.execute-api.us-east-1.amazonaws.com
  • Create a CNAME record for api.steelnotes.app → the API Gateway URL
  • Or set up a custom domain in API Gateway with an ACM certificate
  • Blocks: iOS app using production URL instead of localhost
Update iOS app baseUrl to production
  • Change http://localhost:3000 to https://api.steelnotes.app in ServiceBundle.swift and AuthService.swift
  • Or use the API Gateway URL directly: https://bjhfydpwgb.execute-api.us-east-1.amazonaws.com
  • Blocks: iOS app talking to production server
Verify production health
  • curl https://bjhfydpwgb.execute-api.us-east-1.amazonaws.com/health
  • Register a test user and verify auth flow works
  • Create a note and verify it appears in S3: aws s3 ls s3://steel-notes-vaults-production/ --recursive
  • Blocks: confidence that the full stack works end-to-end

Blocking Phase 3 — Capture Layer & AI

PENDING
0 2
0%
Enable AWS Bedrock model access in your AWS account
  • Go to AWS Console → Bedrock → Model access
  • Request access to Anthropic Claude models (Claude 3.5 Sonnet, Claude 3.5 Haiku)
  • Access is usually granted instantly, but some models require a brief approval
  • Verify your region supports Bedrock (us-east-1, us-west-2, eu-west-1 all do)
  • No API keys needed — Lambda uses IAM role auth
  • Blocks: AI-powered capture parsing, vision OCR, transposition engine, auto-classification
Verify Amazon Transcribe is available in your region
  • Go to AWS Console → Amazon Transcribe → verify it loads
  • No special enablement needed — it's available by default in most regions
  • Blocks: voice note transcription from Share Sheet captures
Implementation Phases

Phase 1 — Foundation

DONE
21 0 0
100%
Phase 1A — Shared Kotlin Core 15/15
Set up KMP project (iOS, macOS targets)
shared/build.gradle.kts, SKIE enabled for Swift interop
Define SQLDelight schema (notes, links, annotations, FTS5)
SteelNotes.sq — full schema with triggers and named queries
Build frontmatter parser (YAML parse + serialize)
FrontmatterParser.kt — hand-rolled, no external YAML dep
Build annotation parser (blockquote + list-item format)
AnnotationParser.kt
Build wikilink extractor ([[links]] → markdown links on save)
WikilinkExtractor.kt — user types [[]], stored as title
Define note models (Source, Thought, Question, Synthesis)
Note.kt, NoteType.kt, Statuses.kt, Annotation.kt, Connection.kt. See Phase 6 for Note model refactor.
Implement NoteFactory (frontmatter → typed model)
NoteFactory.kt
Implement NoteSerializer (typed model → markdown)
NoteSerializer.kt
Implement IdGenerator (src-2026-03-20-001 pattern)
IdGenerator.kt
Create platform file I/O abstraction
FileSystem.kt (expect) + PlatformFileSystem.kt (appleMain/NSFileManager)
Implement VaultManager (CRUD on .md + DB sync)
VaultManager.kt — init, create, update, delete, list, wikilink indexing
Implement VaultIndexer (full + incremental reindex)
VaultIndexer.kt — fullReindex, indexFile, deindexFile
Implement FTS5 search engine (BM25 ranked)
SearchEngine.kt — search, findRelated, filters
Create DatabaseDriverFactory (platform SQLite driver)
DatabaseDriverFactory.kt (expect + appleMain NativeSqliteDriver)
Build macOS CLI (init, create, list, search, read, delete, index)
cli/ — all 7 commands, --json flag, Clikt framework
Phase 1B — iOS App Shell 6/6
Create Xcode project + App target
iosApp/project.yml (XcodeGen), generates SteelNotes.xcodeproj
Create SteelNotesApp.swift entry point
iosApp/SteelNotes/App/SteelNotesApp.swift
Link KMP framework into Xcode project
embedAndSignAppleFrameworkForXcode Gradle task in pre-build script
Build SharedCoreService.swift (Swift bridge to Kotlin)
iosApp/SteelNotes/Services/SharedCoreService.swift — facade over VaultManager, SearchEngine, VaultIndexer
Set up dependency injection
iosApp/SteelNotes/DI/AppContainer.swift — ObservableObject with vault path resolution
Build navigation coordinator
iosApp/SteelNotes/Navigation/AppNavigation.swift — TabView (Vault + Search placeholder)

Phase 2 — Cloud Vault Sync 🚧 PRIORITY: ACTIVE

DONE
39 0 0
100%

Goal: Get .md files syncing across devices via S3 + custom REST API. This is the highest-priority feature — ship sync before rich iOS editing.

See SYNC_ARCHITECTURE.md for the full sync design spec.

See AUTH_ARCHITECTURE.md for the multi-provider auth design.

See SYNC_BUILD_GUIDE.md for the actionable build plan.

Phase 2A — Auth & API Foundation 14/14
Scaffold server/ Rust project: Cargo.toml, Axum router, config, error handling
server/src/main.rs — Axum 0.8, public/protected route groups, Lambda + standalone entry
Write Postgres migrations: users, auth_providers, refresh_tokens, devices, file_state
004_auth_providers_refresh_tokens.sql — migrates provider data, adds auth_providers + refresh_tokens
Implement POST /auth/register — email/password sign-up (Argon2id)
server/src/routes/auth.rs — Argon2id hashing, creates user + auth_provider + device
Implement POST /auth/login — unified endpoint with grant_type discriminator
Apple + email/password grants via #[serde(tag = "grant_type")] enum
Implement POST /auth/refresh — refresh token rotation
DB-backed rotation — old token deleted, new token issued on each refresh
Implement POST /auth/link — link additional provider to existing account
Protected endpoint; supports Apple + email providers; idempotent
Implement POST /devices/register — device + push token registration
server/src/routes/devices.rs — upsert by user_id + device_name
Add Tower middleware: JWT extraction + validation
server/src/middleware/auth.rs — extracts user_id + device_id into extensions
Configure S3 bucket + presigned URL generation (aws-sdk-s3)
server/src/services/s3.rs — presigned PUT/GET/DELETE
Write Terraform: Lambda + API Gateway + RDS + S3 + IAM roles
server/deploy/terraform/main.tf — full infra definition
Build client AuthManager.kt, TokenStore.kt (expect/actual for Keychain)
com.steelnotes.auth — AuthManager + TokenStore (Keychain stub, in-memory for now)
Build client SyncClient.kt skeleton (Ktor HTTP wrapper)
com.steelnotes.sync.SyncClient — all endpoints + S3 direct upload/download
Add CLI steel auth register/login/status/logout commands
cli/Main.kt — AuthCommand with register/login/status/logout subcommands, tokens in ~/.steelnotes/auth.json (mode 600)
Build iOS SignInView.swift with Sign in with Apple
iosApp/Views/Auth/SignInView.swift — Sign in with Apple + skip option
Phase 2B — Core Sync Protocol 9/9
Implement GET /vault/state — full and incremental (?since=)
server/src/routes/vault.rs — filters by updated_at, separates active/deleted
Implement POST /vault/push — version check + presigned upload URLs
Version mismatch returns in conflicts[] with download URL
Implement POST /vault/push/confirm — update file_state + trigger push notifications
Updates file_state, vault_size, notifies other devices via SNS
Implement POST /vault/pull — presigned download URLs
Returns presigned GET URLs for requested paths
Implement POST /vault/delete — version-checked deletion with tombstones
Marks is_deleted=true, deletes from S3, notifies devices
Define client SyncStateDatabase.sq (.steel/sync.db schema)
SyncState.sq — sync_state, sync_meta, sync_log tables + full query set
Build client ChangeDetector.kt — SHA-256 hash-based local file diffing
Sha256.kt (expect/actual, CommonCrypto on Apple) + ChangeDetector.kt
Build client SyncManager.kt — full sync cycle (5 phases)
5-phase orchestrator: detect → compare → resolve → execute → record
Implement client first-launch flows (new user / existing user / migration)
Handled by SyncManager.syncNow() — detects new/existing/migration via change detection + remote state comparison
Phase 2C — Push Notifications & Real-Time 5/5
Integrate APNs server-side via SNS
server/src/services/push.rs — SNS fanout to APNs with silent push payload
Define silent push payload for vault-changed events
Payload struct defined with content-available: 1
Handle client background push → trigger syncNow()
AppDelegate.swift handles silent push → posts .vaultChangedRemotely → SyncService.syncNow()
Build client SyncStatusView — toolbar sync indicator
SyncStatusView.swift — toolbar icon (syncing/synced/offline/error/conflict) + SyncDetailView
Add debounce & batching for rapid local edits (2s debounce)
SyncManager.onLocalFileChanged() — 2s debounce via coroutine Job
Phase 2D — Conflict Resolution 5/5

Design choice: Conflicts resolve automatically — no manual diff UI. Last-write-wins for overlapping edits, auto-merge for non-overlapping. Users should never see a conflict screen.

Build ConflictResolver.kt — frontmatter + body split merge
ConflictResolver.kt — splits YAML frontmatter from body, merges independently
Implement three-way merge for non-overlapping body edits
Line-level 3-way merge in threeWayMergeLines()
Implement auto-merge rules (tags union, timestamps, non-overlapping)
Tags → union, timestamps → latest wins, non-overlapping → merge both
Add last-write-wins fallback for overlapping edits
ConflictResolver returns AutoMerged with remote (newer) version when 3-way merge fails; SyncManager.processConflicts() always auto-resolves
Log auto-resolved conflicts to sync log for auditability
SyncStateStore.log("auto_merge", ...) and log("last_write_wins", ...) entries for all conflict resolutions
Phase 2E — Polish & Data Portability 6/6

Deferred — not priority until auth, sync, and capture are solid.

Implement GET /vault/export — zip download of entire vault
Returns presigned download URLs for all vault files; client assembles locally
Build SyncSettingsView.swift — account info, storage, device list
SyncSettingsView.swift — account status, sync detail, sign out
Build sync log viewer (debug screen)
SyncDetailView.swift — status dot, sync now button, filterable log list (push/pull/merge/delete/conflict events), relative timestamps, path + detail display
Add error handling: auth failures, quota, network timeouts
AppError enum covers auth/quota/rate-limit/network; SyncManager catches exceptions with offline fallback
Build offline queue — batch pending changes, push when online
SyncManager marks files PENDING_PUSH immediately, syncs on next syncNow()
Add tombstone cleanup (90-day retention cron)
POST /maintenance/cleanup — deletes tombstones >90 days + expired refresh tokens

Phase 3 — Capture Layer & AI Processing

UPCOMING
0 0 23
0%

Goal: Fastest possible capture, with background AI that classifies, structures, and links notes automatically. AI is additive — original capture always preserved.

See CAPTURE_ARCHITECTURE.md for the full design spec.

Phase 3A — Capture Surfaces 0/3
Add CLI steel add command (URLs, text, files)
Single command, no flags — AI classifies after
Build inbox system (inbox/capture-{timestamp}.md with minimal frontmatter)
Raw capture written immediately, AI processes async
Build bookmarklet / browser extension (future)
Phase 3B — Background AI Pipeline 0/15
Build server-side processing trigger (new inbox file → Lambda job)
Fires on POST /vault/push/confirm for inbox/ paths
Add aws-sdk-bedrockruntime + aws-sdk-transcribe to Cargo.toml
Same SDK pattern as existing S3/SNS crates
Create server/src/services/bedrock.rs — Bedrock client wrapper
InvokeModel for text, vision via Claude on Bedrock. Reuse AWS config.
Create server/src/services/transcribe.rs — Amazon Transcribe wrapper
StartTranscriptionJob reads audio from S3, polls for completion
Update Terraform — add Bedrock + Transcribe IAM policies to Lambda role
bedrock:InvokeModel + transcribe:Start/GetTranscriptionJob
Update Lambda config — increase memory to 512MB, timeout to 60s
Needed for Bedrock inference latency
Build AI pipeline: transcribe (voice notes via Amazon Transcribe)
Async batch — audio in S3 → transcript text
Build AI pipeline: OCR / extract (images → text via Bedrock Vision, PDFs → text, URLs → HTML)
Claude Vision on Bedrock for photos (book covers, pages), standard extraction for PDFs/URLs
Build AI pipeline: resolve (check if Source already exists in vault)
FTS title/author match — avoids duplicate Sources
Build AI pipeline: classify (Source vs Thought vs Annotation on existing Source)
Claude on Bedrock. Bundle of book cover + pages → existing Source + new Annotation
Build AI pipeline: structure (build note(s) from extracted content)
Claude on Bedrock. One capture bundle may produce multiple notes
Build AI pipeline: enrich (add tags, generate ID, update frontmatter)
Claude on Bedrock. Additive only — original capture block preserved
Build AI pipeline: link (FTS for related notes, match open Questions)
Add markdown links to related notes in a "Related" section
Build AI pipeline: file (move from inbox/ to sources/ or thoughts/)
Sync processed .md back to all devices
Add processing_status indicator in vault list
Shows "processing" badge until AI pipeline completes
Phase 3C — Transposition & Resurfacing 0/5
Build TranspositionEngine — annotations → themed groups + questions
AI reads annotations across sources, extracts patterns
Build Insights feed — auto-generated connections, dismissable
Replaces approval gate; user can dismiss, edit, or promote to notes
Build QuestionEngine — lifecycle, getQuestionsToResurface()
Questions generated by AI, not manually captured
Add question resurfacing on app launch
Show inbox of stale open questions
Build GraphEngine — relationship tracking and traversal
GraphEngine.kt

Phase 4 — iOS UI Polish

IN PROGRESS
30 0 3
91%

Goal: Full-featured iOS reading and editing experience. Delayed intentionally — sync and capture are higher value than on-phone editing right now.

Theme & Adaptive Color System 6/6
Build adaptive color system (light/dark mode)
Theme/Theme.swift — programmatic adaptive colors via UIColor trait collection, follows system preference
Define note type colors (source, thought, question, synthesis)
Distinct brand colors per type, tuned for both light and dark
Define status colors (per-status adaptive palette)
All statuses across all note types have adaptive color pairs
Build reusable StatusBadge and TagsView components
Views/Components/ — shared across vault, detail, search
Add NoteType theme extensions (icon, color, display names)
Centralized in Theme.swifticonName, color, displayName, singularName
Integrate theme tint into app and navigation
SteelNotesApp.swift + AppNavigation.swift.tint(._steelAccent)
Phase 4A — Capture Views 8/8
Build CaptureView.swift — camera-first capture
Camera auto-opens, PhotosPicker for library, saves to inbox with tags
Build photo strip component (horizontal scroll, tap to remove)
Horizontal scroll thumbnails with X-to-remove, add-more menu
Build iOS Share Sheet extension (SteelNotesShare/ target)
SteelNotesShare/ — accepts text, URLs, images via App Group
Configure App Group for share extension ↔ main app data
group.com.steelnotes.shared — entitlements + UserDefaults + auto-import on launch
Build WidgetKit extension (SteelNotesWidget/ target)
SteelNotesWidget/QuickCaptureWidget + RecentNotesWidget
Build small widget: quick capture button + last note preview
Deep links to steelnotes://capture, shows last note title
Build medium widget: recent notes list (3-4 items)
Shows title, type icon, timestamp for up to 4 recent notes
Add widget deep links into main app
steelnotes:// URL scheme — capture, note/{id}, search, vault routes
Phase 4B — Vault View Enhancements 5/5
Implement VaultView.swift — async loading, pull-to-refresh
Full VaultViewModel binding, .refreshable, type chip picker, + button, themed backgrounds
Build source row cell (title, author, status badge)
NoteRow shows title, status badge, tags, author (for sources), relative date
Build thought/question row cells
Covered by generic NoteRow with type-aware status colors
Add type filter chip picker (replaces segmented control)
Horizontal scroll chips with type icon + color, animated selection
Add navigation to note detail
NavigationLink(value:) + navigationDestination(for:)
Phase 4C — Reader View 4/5
Build NoteDetailView.swift — note reader with metadata
Header, metadata (dates, tags, URL), body text, edit button
Build MarkdownBodyView.swift — markdown renderer
Headings, bold/italic, code blocks, blockquotes, lists, links
Build annotation display in reader
AnnotationCard — type icon, highlighted text, note, color-coded by type
Build related notes section in reader
Uses findRelated() FTS5 query, shows type icon + title links
Build annotation overlay (select text → pick type → write note)
Phase 4D — Editor View 2/3
Build EditorView.swift — create/edit all note types
Form-based: title, type-specific fields, tags, body. Create + update flows.
Build frontmatter editor (title, tags, status)
Title, tags, and status picker for all note types
Add wikilink autocomplete ([[ trigger)
Phase 4E — Source Creation 2/3
Build new source sheet (URL input → fetch title)
URL field exists in EditorView; auto-fetch TBD
Build manual source creation form
EditorView handles Source creation with author + URL fields
Add status change control (unread → reading → read)
Status picker in EditorView, persists on save
Phase 4F — Search 3/3
Build SearchView.swift — search with debounced FTS5
.searchable modifier, 300ms debounce, result rows with snippet + score
Add type + status filter chips
Horizontal scroll FilterChips — type toggles show contextual status options
Add result row with snippet preview
SearchResultRow — type icon, title, snippet, match percentage

Phase 5 — Depth

UPCOMING
0 0 4
0%
Phase 5A — Graph & Synthesis 0/1
Build idea graph visualization (GraphView.swift)
Phase 5B — Advanced Reading 0/2
Build PDF reader + annotation
Add weekly digest notification
Phase 5C — macOS App 0/1
Build macOS SwiftUI app

Phase 8 — Android Implementation

UPCOMING
0 0 26
0%

Goal: Bring Steel Notes to Android using Jetpack Compose for UI and the existing KMP shared module for all business logic. Same architecture as iOS — UI layer only, all logic in Kotlin shared.

Phase 8A — Android App Shell 0/5
Create Android project with Jetpack Compose
androidApp/ — Gradle module, Material 3, min SDK 26
Link KMP shared module into Android project
Add shared as Gradle dependency, no framework bridging needed (Kotlin native)
Set up dependency injection (Koin or Hilt)
VaultManager, SearchEngine, SyncManager, AuthManager
Build navigation (Jetpack Navigation Compose)
Bottom nav: Vault, Search, Capture
Build sign-in screen (email/password + Google Sign-In)
Uses existing AuthManager.kt from shared module
Phase 8B — Android Vault & Reader 0/5
Build VaultScreen — note list with type filters
LazyColumn, type chip picker, pull-to-refresh
Build NoteDetailScreen — reader with transclusion rendering
Render ![[id]] inline as embedded cards
Build SearchScreen — FTS5 search with debounce
Uses existing SearchEngine.kt from shared module
Build markdown renderer (Compose)
Headings, bold/italic, code blocks, blockquotes, links
Add Note type theming (icons, colors, status badges)
Material 3 color system
Phase 8C — Android Editor & Capture 0/4
Build EditorScreen — create/edit all vault item types
Form-based: title, type fields, tags, body
Build CaptureScreen — camera-first capture
CameraX, photo strip, save to inbox
Build Android Share Sheet target
Accept text, URLs, images via Android Intent filters
Build Home Screen widget (Glance)
Quick capture button + recent notes
Phase 8D — Android Sync & Notifications 0/4
Integrate sync via existing SyncManager.kt
Uses shared KMP sync module, triggered on file changes
Implement FCM push notification handler
Silent push → trigger syncNow(), replaces APNs path
Build sync status indicator
Toolbar icon: syncing/synced/offline/error
Add TokenStore actual for Android (EncryptedSharedPreferences)
Secure token storage, replaces iOS Keychain actual
Phase 8E — Android Platform Integration 0/4
Add PlatformFileSystem actual for Android
Context.filesDir based file I/O
Add DatabaseDriverFactory actual for Android
Android SQLite driver for SQLDelight
Add Sha256 actual for Android
java.security.MessageDigest
Configure ProGuard / R8 rules for KMP
Keep serialization classes, KMP framework
Phase 8F — Android Rich Editor (Compose) 0/4
Build RichEditor composable — TextField with AnnotatedString
Mirrors iOS editor behavior
Build formatting toolbar for Compose
Same button set as iOS, shared formatting logic from KMP
Build AnnotatedString ↔ markdown converter
Platform-specific attributed text mapping
Add ![[ and [[ autocomplete for Compose
Transclusion embed + navigation link

Phase 6 — Note Model Refactor

IN PROGRESS
32 0 3
91%

Goal: Introduce Note as the primary engagement unit. Sources hold external content. Thoughts and Questions keep their own identity but are transcluded into Notes. Synthesis type eliminated.

See NOTE_ARCHITECTURE.md for the full design spec.

Phase 6A — Model + Parser 9/9
Rename sealed interface NoteVaultItem across codebase
Frees Note for the concrete engagement class
Add NoteType.NOTE to enum ("note", "notes" directory)
NoteType.kt
Deprecate NoteType.SYNTHESIS with @Deprecated annotation
Keep for backward-compat parsing
Add NoteStatus enum (ACTIVE, PARKED, ARCHIVED)
Statuses.kt
Add Note data class implementing VaultItem
Fields: sources, status. No thoughts/questions list — those are body transclusions
Add NoteType.NOTE branch to NoteFactory
Parse sources from frontmatter, status as NoteStatus
Add Note serialization branch to NoteSerializer
Write sources, status, created, updated
Create TransclusionExtractor.kt — extract ![[id]] from body
Regex: !\[\[([^\]]+)]], same code-block exclusion as WikilinkExtractor
Update WikilinkExtractor regex to skip ![[ via negative lookbehind
(?<!!)\[\[([^\]]+)]]
Phase 6B — Storage Layer 8/8
Add source_refs TEXT column to notes table
SteelNotes.sq — nullable, comma-separated Source IDs
Update insertNote query with source_refs parameter
Update VaultManager.insertNoteIntoDb() for Note type
Map Note.sources → comma-separated source_refs
Update VaultManager.dbNoteToModel() for Note type
Parse source_refs back to List<String>
Update VaultManager.indexLinks() — extract transclusions
TransclusionExtractor + link_type = "transclusion"
Update VaultManager.indexLinks() — index Note.sources
Structured links for source references
Add createSourceWithNote() to VaultManager
Creates Source + companion Note in one call
Update VaultIndexer — transclusion link indexing
Phase 6C — CLI + Transclusion Resolution 6/6
Create TransclusionResolver.kt
Replace ![[id]] with rendered content at display time
Add NoteType.NOTE to CLI CreateCommand + --sources option
Update CLI CreateCommandsteel create source auto-creates companion Note
Uses createSourceWithNote()
Update CLI ListCommand icon for Note type
Update CLI ReadCommand — resolve transclusions for display
Raw ![[id]] for --json output
Update CLI help text — add note, remove synthesis
Phase 6D — Migration 2/2
Build steel migrate syntheses command
Converts syntheses/*.mdnotes/*.md, remaps IDs and fields
Create companion Notes for existing Sources (backfill)
steel migrate backfill-sources — creates Notes for orphan Sources
Phase 6E — iOS Updates 7/7
Update NoteDetailView.swift — render transclusions inline
TransclusionCard component, extracts ![[id]] and renders embedded content as cards
Update VaultView.swift — add Note type to filter, icons, colors
Note type first in type picker, VaultItem base type throughout
Update StatusBadge.swift — add NoteStatus color/label mapping
Active/Parked/Archived status colors with VaultItem extension
Update EditorView.swift — create/edit Notes
NoteStatus picker, Note creation/update, uses NoteType.directory for file paths
Update CaptureView.swift — route captures through Source + Note pair
Uses createSourceWithNote() to create Source + companion Note
Update SharedCoreService.swift — add resolveTransclusions() method
resolveTransclusions() via TransclusionResolver, extractTransclusionIds(), createSourceWithNote()
Update Theme.swift — add Note type icon, color, display name
doc.text.fill icon, blue noteColor, NoteStatus colors (active/parked)
Phase 6F — Capture Pipeline Update 0/3
Update AI pipeline step 4 (Structure) — split Source content from user capture context
Source gets extracted content, Note gets user's voice note / reaction
Update AI pipeline step 7 (File) — create Source + Note pair, not just Source
Move to sources/ + notes/
Update steel add to create inbox capture that produces Source + Note pair

Phase 7 — Rich Markdown Editor

IN PROGRESS
16 0 10
62%

Goal: Replace the plain TextEditor with a rich editing experience. Users can type markdown syntax (which renders live) or apply formatting via a floating toolbar. iOS 26's native TextEditor + AttributedString is the foundation.

Architecture decision: Build on iOS 26's native TextEditor with AttributedString and AttributedTextSelection — no WebView layer, no third-party dependencies. Formatting operations (toggle bold, insert link, etc.) live in the shared Kotlin module; platform-native code handles the actual text rendering and input.

Cross-platform strategy (Pattern 1): Shared Kotlin module owns the document model and formatting operations. Swift handles the AttributedString editor on iOS. Android rich editor is in Phase 8F. ~30-40% code sharing on logic, fully native editing feel on each platform.

Phase 7A — Shared Document Model (KMP) 8/8
Define MarkdownSpan sealed class (bold, italic, code, heading, link, highlight)
MarkdownDocument.kt — PlainSpan, BoldSpan, ItalicSpan, BoldItalicSpan, CodeSpan, LinkSpan, WikilinkSpan, HighlightSpan
Build MarkdownDocument model (list of MarkdownBlock with spans)
ParagraphBlock, HeadingBlock, CodeBlock, BlockquoteBlock, ListItemBlock, TransclusionBlock, HorizontalRuleBlock, BlankBlock
Build MarkdownParser — markdown string → MarkdownDocument
MarkdownDocumentParser.kt — block + inline span parsing
Build MarkdownSerializerMarkdownDocument → markdown string
MarkdownDocumentSerializer.kt — uses block rawText for round-trip fidelity
Build FormattingEngine — toggle operations on spans
FormattingEngine.kt — toggleBold/Italic/Code/Highlight, toggleHeading, toggleListItem, toggleBlockquote
Add transclusion insertion helper — insertTransclusion(id)
Ensures ![[id]] on own line with surrounding newlines
Add note link insertion helper — insertNoteLink(id, title)
Inserts [[id]] at cursor
Unit tests for round-trip parsing (markdown → model → markdown)
119 tests: parser, serializer round-trip, formatting engine, model rawText. JVM target added for Linux/CI.
Phase 7B — iOS Rich Editor (SwiftUI, requires iOS 26+) 8/11
Build RichEditorView.swiftTextEditor with formatting toolbar
Monospaced text editor with FormattingToolbar shown when focused
Build FormattingToolbar.swift — floating toolbar above keyboard
Bold, italic, code, highlight, heading menu, list, quote, link, note link, embed buttons
Implement toolbar actions via FormattingEngine
Each button calls shared KMP FormattingEngine methods
Build live markdown → attributed string converter
Requires iOS 26 AttributedTextSelection — current editor uses plain text + toolbar
Add ![[ autocomplete — search vault items, insert transclusion
VaultItemPickerSheet with search, selects item and inserts ![[id]]
Add [[ autocomplete — search vault items, insert note link
Same picker sheet, inserts [[id]] navigation link
Add link insertion sheet (title + URL fields)
URL field sheet, inserts link via FormattingEngine.insertLink()
Build AttributedString → markdown serializer for save
Requires iOS 26 — current editor saves plain markdown directly
Define custom AttributeScope for highlight color
Requires iOS 26
Refactor editor to full-screen NavigationStack push
VaultView + NoteDetailView use .navigationDestination instead of .sheet
Integrate into EditorView.swift — replace plain TextEditor
RichEditorView replaces Form body section, metadata in compact header
Phase 7C — Editor Polish 0/7
Add heading level picker (H1-H4) in toolbar
Long-press or submenu for heading levels
Add list insertion (bulleted, numbered)
Toolbar button toggles list item prefix
Add blockquote toggle
> prefix toggle
Add image insertion from photo library
Inserts !alt reference, copies file to vault
Add undo/redo support
Native UndoManager integration
Add keyboard shortcuts (⌘B bold, ⌘I italic, ⌘K link)
.keyboardShortcut modifiers on toolbar actions
Add focus mode — dim everything except current paragraph
Optional writing mode, toggled from toolbar