EN
Blog
Notes from building JustSend.
Other notesHow One image_path Becomes Both a List Thumbnail and Detail HeroThe data flow from caller-owned artwork through staging and attachment storage to two user-facing work-record surfaces.
Other notesWhy 22 Unused APIs Became Keep, Hold, or Recheck—not DeleteA consumer audit that turns route inventory, source callers, traffic, and prior decisions into three explicit disposition outcomes.
iOS app qualityReading a Permission Review Rejection as a State MachineWhy we removed the first-run pre-prompt and reassigned explanation, consent, state, and recovery to the components that actually own them.
Other notesTurn App Store Resubmission into One Consistency ProcessA release audit that treats the submitted build, metadata, App Privacy, subscriptions, and public sharing as one ordered gate.
Backend and operationsTwo Trust Boundaries between a Public DMG and MCP WritesHow intent authorization and content-host policy protect user records and private networks even when the implementation is fully inspectable.
Other notesThe Data Flow from a JustSend Seed to Research, Evidence, and ReviewWhy a work record is only a starting signal, and how source expansion, claim mapping, type-correct diagrams, and Fidelity Audit produce a publish candidate.
Other notesWhere Direct Distribution Actually Runs: App DMG and Helper ZIPThe deployment structure behind moving the Mac app to Developer ID while keeping the MCP helper as a separate notarized artifact.
Data and syncA Provisional–Live–Dead Contract for Asynchronous MCP Work RecordsHow we stopped losing notes sent immediately after work_start by moving identifier lifetime and account recovery into an explicit state machine.
Starting out and structural callsSolo Dev Stack: Running an iOS App, Go Backend, and Kubernetes AloneThe stack boundaries a solo developer settled on while running an iOS app, a Go backend, and self-hosted Kubernetes. It starts with three characters in a note that two code paths read differently.
Starting out and structural callsSwift Package Split: The Core Moved but the Build Read the Local PathThe boundary drawn when the AI core moved out of the app as a Swift Package, and the gap between the declaration and the actual build. Four functions and nine failure kinds are the boundary.
- On-device AIApple Foundation Models: Measuring @Generable Structured OutputFirst measurements from LanguageModelSession and @Generable on device: 1.89s structured versus 2.16-2.36s plain text, and why an array minimum makes the model invent facts.
On-device AIOn-Device LLM Long Docs: Chunking Past the Context LimitA 4,096-token context cannot summarize a long document in one pass. How chunking and map/reduce were built, and the two compounding errors that split a 10-page document into 19 pieces.
- On-device AIFoundation Models Errors: Designing a guardrailViolation FallbackOrdinary Korean prose was rejected as a guardrail violation, leaving summaries empty. Folding eight failure kinds into four retry branches and a four-step fallback ladder.
On-device AIOn-Device Inference Heat: thermalState Gating and a Serial QueueConcurrent inference calls and impossible retries produced heat reports on iPhone Air. Admission control by thermalState, and why the retry was impossible in the first place.
- On-device AILLM Summary Quality: Scoring Copy Rate and Repetition in CodeA summary that copies the source verbatim passes a length check. Measuring copy rate and repetition as numbers, and why the threshold sits at 0.6 rather than 0.9.
- iOS app qualityiOS On-Device OCR: Removing the Server with Vision and PDFKitThe privacy policy said text recognition happens on device while the code was uploading originals to a server gateway. Moving OCR to Vision and PDFKit, and the five paths it was scattered across.
- iOS app qualityiOS Cold Start: From 873ms to 482ms, MeasuredWhere 873ms of app-side launch time went, why RootView.init alone took 310ms, and what the signed-in path still costs. Measurements first, then the fixes.
- iOS app qualitySwiftUI Memory Hunt: Narrowing 725MB with jetsam LogsOnly screen transitions froze while scrolling still worked. jetsam logs showed a 725MB peak. Narrowing the cause with CoreAnimation and _logChanges() instead of guessing.
- Data and syncSQLite FTS5 CJK Search: Trigram Tokenizer and a 2-Char LIKE FallbackSearching a saved note by a substring returned zero rows because unicode61 only indexes token prefixes. Moving six tables to trigram, and the residual LIKE path for queries under three characters.
iOS app qualitySwiftUI Design Tokens at Runtime: A Facade for 367 Call SitesForty-four static color constants were read from 91 files at 367 call sites, so themes could not be swapped at runtime. Moving values behind computed properties without touching a single caller.
- iOS app qualityApp Theme Palette Audit: Cutting 20 Sets to 12Palettes lifted from a web reference had correct hex values, yet cards vanished into the background in 20 of them. The criteria that survived the audit, and how a parameter silently dropped 17 times.
- Backend and operationsGo Content Extraction: A Density Parser That Distrusts article TagsPages ship several article elements, so trusting the tag picks the wrong block. Building a text-density scorer in Go, and why per-site exception tables were deleted rather than extended.
- iOS app qualityStoreKit Subscription Sync: Six Paths Where Server and App DisagreedOne symptom — pro in the console, free in the app — led to nine defects. Six server-side read paths collapse into one, and expiry is decided at read time instead of by webhook.
- Backend and operationsNews Near-Duplicate Detection: Where 3-gram Jaccard and IDF FailedSyndicated articles are not identical, so exact hashing misses them. Why 3-gram Jaccard with IDF weighting still produced false positives, and the leading-word gate that fixed it.
Data and synciOS App Group SQLite: One Database for App and WidgetThe app, the widget, and the share extension each opened their own store. Moving them onto one App Group database, and what WAL mode and schema drift require once they share a file.
Data and syncCryptoKit End-to-End Encryption: A Key Hierarchy Without libsodiumBuilding a key hierarchy on CryptoKit alone: ECDH agreement, HKDF derivation, and per-record keys wrapped for each device. What the scheme covers and what it deliberately does not.
- Data and syncCloudKit Attachment Migration: The Server Holds a Manifest, Not BytesAttachment bytes moved from our server into each account’s iCloud, leaving the server with a manifest. Two decisions were withdrawn and two defects were fixed during verification.
Backend and operationsArgoCD App-of-Apps: What sync-wave and selfHeal Cannot SeeNodes were Ready and all 13 exporters were up, yet metrics never arrived. A root Application manages the cluster, but selfHeal only reverts what the manifests already declare.
- Backend and operationsSelf-Hosted Mail Server: Changing the Send Path Twice Without a PTRSending and receiving on our own domain, with no PTR: the send path changed twice. SPF, DKIM, and DMARC go in, Stalwart takes delivery, and the recovery listener answers 502.
- Backend and operationsoauth2-proxy Shared Gate: Adding Auth via Traefik forwardAuthOne oauth2-proxy in front of several internal sites, wired through Traefik forwardAuth. Why an allowlist stays in a ConfigMap and what the two isolation layers actually separate.
Backend and operationsKubernetes Sidecar Readiness: When One Container Takes Down the PodThe SPA returned 200 while the API intermittently returned 503 with zero Service endpoints. A worker container without a readiness probe crashed 56 times in eight hours, unnoticed.
- Backend and operationsNostr NIP-29 Relay: AI Agent Channels and a NIP-98 Replay ProblemPutting an AI agent team on a NIP-29 relay and verifying what is canonical and who may write. Two NIP-98 auth events signed in the same second share an id and get rejected as replays.
Backend and operationsMCP Server Role Scoping: Block at Tool Registration, Not in the PromptSix support agents shared one MCP server, and two read-only roles held write tools. Enforcing scope where tools are registered, so the model never learns the tool exists.
Backend and operationsApp Store Connect Multilingual Replies: Catching Locale Mismatch EarlyReplying to App Store reviews in 16 locales. Territory codes are ISO alpha-3 while locales are not, and a reply whose language differs from the review is rejected before the API call.
Starting out and structural callsSolo Architecture Retrospective: What 26 Posts RevealedOne universal iPhone and iPad app and 14 Kubernetes namespaces, drawn on a single page. Most of the 26 incidents shared one shape, and only the device notification path was missing.
Backend and operationsBuilding an AI Support Center on Buzz: Six Agents in Human RoomsSelf-hosting the Buzz Nostr relay and attaching six AI agents with different roles over the same protocol people use. Why Buzz, and what changes once it is deployed.
Starting out and structural callsI Delegate Execution to AI, Not JudgmentWhere the boundary sits when a solo developer hands work to AI, and where that boundary leaks. It starts with a repository handed over as evidence that was already out of date.