APPLE_TEAM_ID env var)api.steelnotes.app)openssl rand -hex 32JWT_SECRET env varcd server && cargo lambda build --release --features lambdacargo lambda deploy steel-notes-api-productioncd server/deploy/terraform && terraform output rds_endpointpsql <rds_connection_string> < src/db/migrations/001_users.sql (repeat for each migration file)https://bjhfydpwgb.execute-api.us-east-1.amazonaws.comapi.steelnotes.app → the API Gateway URLhttp://localhost:3000 to https://api.steelnotes.app in ServiceBundle.swift and AuthService.swifthttps://bjhfydpwgb.execute-api.us-east-1.amazonaws.comcurl https://bjhfydpwgb.execute-api.us-east-1.amazonaws.com/healthaws s3 ls s3://steel-notes-vaults-production/ --recursiveshared/build.gradle.kts, SKIE enabled for Swift interopSteelNotes.sq — full schema with triggers and named queriesFrontmatterParser.kt — hand-rolled, no external YAML depAnnotationParser.kt[[links]] → markdown links on save)▶Note.kt, NoteType.kt, Statuses.kt, Annotation.kt, Connection.kt. See Phase 6 for Note model refactor.NoteFactory.ktNoteSerializer.ktsrc-2026-03-20-001 pattern)▶IdGenerator.ktFileSystem.kt (expect) + PlatformFileSystem.kt (appleMain/NSFileManager)VaultManager.kt — init, create, update, delete, list, wikilink indexingVaultIndexer.kt — fullReindex, indexFile, deindexFileSearchEngine.kt — search, findRelated, filtersDatabaseDriverFactory.kt (expect + appleMain NativeSqliteDriver)cli/ — all 7 commands, --json flag, Clikt frameworkiosApp/project.yml (XcodeGen), generates SteelNotes.xcodeprojSteelNotesApp.swift entry point▶iosApp/SteelNotes/App/SteelNotesApp.swiftembedAndSignAppleFrameworkForXcode Gradle task in pre-build scriptSharedCoreService.swift (Swift bridge to Kotlin)▶iosApp/SteelNotes/Services/SharedCoreService.swift — facade over VaultManager, SearchEngine, VaultIndexeriosApp/SteelNotes/DI/AppContainer.swift — ObservableObject with vault path resolutioniosApp/SteelNotes/Navigation/AppNavigation.swift — TabView (Vault + Search placeholder)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.
server/ Rust project: Cargo.toml, Axum router, config, error handling▶server/src/main.rs — Axum 0.8, public/protected route groups, Lambda + standalone entryusers, auth_providers, refresh_tokens, devices, file_state▶004_auth_providers_refresh_tokens.sql — migrates provider data, adds auth_providers + refresh_tokensPOST /auth/register — email/password sign-up (Argon2id)▶server/src/routes/auth.rs — Argon2id hashing, creates user + auth_provider + devicePOST /auth/login — unified endpoint with grant_type discriminator▶#[serde(tag = "grant_type")] enumPOST /auth/refresh — refresh token rotation▶POST /auth/link — link additional provider to existing account▶POST /devices/register — device + push token registration▶server/src/routes/devices.rs — upsert by user_id + device_nameserver/src/middleware/auth.rs — extracts user_id + device_id into extensionsserver/src/services/s3.rs — presigned PUT/GET/DELETEserver/deploy/terraform/main.tf — full infra definitionAuthManager.kt, TokenStore.kt (expect/actual for Keychain)▶com.steelnotes.auth — AuthManager + TokenStore (Keychain stub, in-memory for now)SyncClient.kt skeleton (Ktor HTTP wrapper)▶com.steelnotes.sync.SyncClient — all endpoints + S3 direct upload/downloadsteel auth register/login/status/logout commands▶cli/Main.kt — AuthCommand with register/login/status/logout subcommands, tokens in ~/.steelnotes/auth.json (mode 600)SignInView.swift with Sign in with Apple▶iosApp/Views/Auth/SignInView.swift — Sign in with Apple + skip optionGET /vault/state — full and incremental (?since=)▶server/src/routes/vault.rs — filters by updated_at, separates active/deletedPOST /vault/push — version check + presigned upload URLs▶conflicts[] with download URLPOST /vault/push/confirm — update file_state + trigger push notifications▶POST /vault/pull — presigned download URLs▶POST /vault/delete — version-checked deletion with tombstones▶is_deleted=true, deletes from S3, notifies devicesSyncStateDatabase.sq (.steel/sync.db schema)▶SyncState.sq — sync_state, sync_meta, sync_log tables + full query setChangeDetector.kt — SHA-256 hash-based local file diffing▶Sha256.kt (expect/actual, CommonCrypto on Apple) + ChangeDetector.ktSyncManager.kt — full sync cycle (5 phases)▶server/src/services/push.rs — SNS fanout to APNs with silent push payloadcontent-available: 1syncNow()▶AppDelegate.swift handles silent push → posts .vaultChangedRemotely → SyncService.syncNow()SyncStatusView — toolbar sync indicator▶SyncStatusView.swift — toolbar icon (syncing/synced/offline/error/conflict) + SyncDetailViewSyncManager.onLocalFileChanged() — 2s debounce via coroutine JobDesign 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.
ConflictResolver.kt — frontmatter + body split merge▶ConflictResolver.kt — splits YAML frontmatter from body, merges independentlythreeWayMergeLines()ConflictResolver returns AutoMerged with remote (newer) version when 3-way merge fails; SyncManager.processConflicts() always auto-resolvesSyncStateStore.log("auto_merge", ...) and log("last_write_wins", ...) entries for all conflict resolutionsDeferred — not priority until auth, sync, and capture are solid.
GET /vault/export — zip download of entire vault▶SyncSettingsView.swift — account info, storage, device list▶SyncSettingsView.swift — account status, sync detail, sign outSyncDetailView.swift — status dot, sync now button, filterable log list (push/pull/merge/delete/conflict events), relative timestamps, path + detail displayAppError enum covers auth/quota/rate-limit/network; SyncManager catches exceptions with offline fallbackSyncManager marks files PENDING_PUSH immediately, syncs on next syncNow()POST /maintenance/cleanup — deletes tombstones >90 days + expired refresh tokensGoal: 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.
steel add command (URLs, text, files)▶inbox/capture-{timestamp}.md with minimal frontmatter)▶POST /vault/push/confirm for inbox/ pathsaws-sdk-bedrockruntime + aws-sdk-transcribe to Cargo.toml▶server/src/services/bedrock.rs — Bedrock client wrapper▶InvokeModel for text, vision via Claude on Bedrock. Reuse AWS config.server/src/services/transcribe.rs — Amazon Transcribe wrapper▶StartTranscriptionJob reads audio from S3, polls for completionbedrock:InvokeModel + transcribe:Start/GetTranscriptionJobinbox/ to sources/ or thoughts/)▶processing_status indicator in vault list▶getQuestionsToResurface()▶GraphEngine.ktGoal: Full-featured iOS reading and editing experience. Delayed intentionally — sync and capture are higher value than on-phone editing right now.
Theme/Theme.swift — programmatic adaptive colors via UIColor trait collection, follows system preferenceStatusBadge and TagsView components▶Views/Components/ — shared across vault, detail, searchNoteType theme extensions (icon, color, display names)▶Theme.swift — iconName, color, displayName, singularNameSteelNotesApp.swift + AppNavigation.swift — .tint(._steelAccent)CaptureView.swift — camera-first capture▶SteelNotesShare/ target)▶SteelNotesShare/ — accepts text, URLs, images via App Groupgroup.com.steelnotes.shared — entitlements + UserDefaults + auto-import on launchSteelNotesWidget/ target)▶SteelNotesWidget/ — QuickCaptureWidget + RecentNotesWidgetsteelnotes://capture, shows last note titlesteelnotes:// URL scheme — capture, note/{id}, search, vault routesVaultView.swift — async loading, pull-to-refresh▶.refreshable, type chip picker, + button, themed backgroundsNoteRow shows title, status badge, tags, author (for sources), relative dateNoteRow with type-aware status colorsNavigationLink(value:) + navigationDestination(for:)NoteDetailView.swift — note reader with metadata▶MarkdownBodyView.swift — markdown renderer▶AnnotationCard — type icon, highlighted text, note, color-coded by typefindRelated() FTS5 query, shows type icon + title linksEditorView.swift — create/edit all note types▶[[ trigger)SearchView.swift — search with debounced FTS5▶.searchable modifier, 300ms debounce, result rows with snippet + scoreSearchResultRow — type icon, title, snippet, match percentageGraphView.swift)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.
androidApp/ — Gradle module, Material 3, min SDK 26shared as Gradle dependency, no framework bridging needed (Kotlin native)AuthManager.kt from shared moduleVaultScreen — note list with type filters▶NoteDetailScreen — reader with transclusion rendering▶![[id]] inline as embedded cardsSearchScreen — FTS5 search with debounce▶SearchEngine.kt from shared moduleEditorScreen — create/edit all vault item types▶CaptureScreen — camera-first capture▶SyncManager.kt▶syncNow(), replaces APNs pathTokenStore actual for Android (EncryptedSharedPreferences)▶PlatformFileSystem actual for Android▶Context.filesDir based file I/ODatabaseDriverFactory actual for Android▶Sha256 actual for Android▶java.security.MessageDigestRichEditor composable — TextField with AnnotatedString▶AnnotatedString ↔ markdown converter▶![[ and [[ autocomplete for Compose▶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.
Note → VaultItem across codebase▶Note for the concrete engagement classNoteType.NOTE to enum ("note", "notes" directory)▶NoteType.ktNoteType.SYNTHESIS with @Deprecated annotation▶NoteStatus enum (ACTIVE, PARKED, ARCHIVED)▶Statuses.ktNote data class implementing VaultItem▶NoteType.NOTE branch to NoteFactory▶sources from frontmatter, status as NoteStatusNote serialization branch to NoteSerializer▶sources, status, created, updatedTransclusionExtractor.kt — extract ![[id]] from body▶!\[\[([^\]]+)]], same code-block exclusion as WikilinkExtractorWikilinkExtractor regex to skip ![[ via negative lookbehind▶(?<!!)\[\[([^\]]+)]]source_refs TEXT column to notes table▶SteelNotes.sq — nullable, comma-separated Source IDsinsertNote query with source_refs parameterVaultManager.insertNoteIntoDb() for Note type▶Note.sources → comma-separated source_refsVaultManager.dbNoteToModel() for Note type▶source_refs back to List<String>VaultManager.indexLinks() — extract transclusions▶TransclusionExtractor + link_type = "transclusion"VaultManager.indexLinks() — index Note.sources▶createSourceWithNote() to VaultManager▶VaultIndexer — transclusion link indexingTransclusionResolver.kt▶![[id]] with rendered content at display timeNoteType.NOTE to CLI CreateCommand + --sources optionCreateCommand — steel create source auto-creates companion Note▶createSourceWithNote()ListCommand icon for Note typeReadCommand — resolve transclusions for display▶![[id]] for --json outputnote, remove synthesissteel migrate syntheses command▶syntheses/*.md → notes/*.md, remaps IDs and fieldssteel migrate backfill-sources — creates Notes for orphan SourcesNoteDetailView.swift — render transclusions inline▶VaultView.swift — add Note type to filter, icons, colors▶StatusBadge.swift — add NoteStatus color/label mapping▶EditorView.swift — create/edit Notes▶NoteType.directory for file pathsCaptureView.swift — route captures through Source + Note pair▶createSourceWithNote() to create Source + companion NoteSharedCoreService.swift — add resolveTransclusions() method▶resolveTransclusions() via TransclusionResolver, extractTransclusionIds(), createSourceWithNote()Theme.swift — add Note type icon, color, display name▶doc.text.fill icon, blue noteColor, NoteStatus colors (active/parked)sources/ + notes/steel add to create inbox capture that produces Source + Note pairGoal: 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.
MarkdownSpan sealed class (bold, italic, code, heading, link, highlight)▶MarkdownDocument.kt — PlainSpan, BoldSpan, ItalicSpan, BoldItalicSpan, CodeSpan, LinkSpan, WikilinkSpan, HighlightSpanMarkdownDocument model (list of MarkdownBlock with spans)▶MarkdownParser — markdown string → MarkdownDocument▶MarkdownDocumentParser.kt — block + inline span parsingMarkdownSerializer — MarkdownDocument → markdown string▶MarkdownDocumentSerializer.kt — uses block rawText for round-trip fidelityFormattingEngine — toggle operations on spans▶FormattingEngine.kt — toggleBold/Italic/Code/Highlight, toggleHeading, toggleListItem, toggleBlockquoteinsertTransclusion(id)▶![[id]] on own line with surrounding newlinesinsertNoteLink(id, title)▶[[id]] at cursorRichEditorView.swift — TextEditor with formatting toolbar▶FormattingToolbar shown when focusedFormattingToolbar.swift — floating toolbar above keyboard▶FormattingEngine▶FormattingEngine methodsAttributedTextSelection — current editor uses plain text + toolbar![[ autocomplete — search vault items, insert transclusion▶VaultItemPickerSheet with search, selects item and inserts ![[id]][[ autocomplete — search vault items, insert note link▶[[id]] navigation linklink via FormattingEngine.insertLink()AttributedString → markdown serializer for save▶AttributeScope for highlight color▶NavigationStack push▶.navigationDestination instead of .sheetEditorView.swift — replace plain TextEditor▶RichEditorView replaces Form body section, metadata in compact header> prefix toggle!alt reference, copies file to vaultUndoManager integration.keyboardShortcut modifiers on toolbar actions