<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Kristopher Baker · Updates</title>
  <subtitle>Senior product engineer with deep iOS roots, building consumer product systems, developer tooling, and practical AI-assisted workflows from Tokyo.</subtitle>
  <link href="https://krisbaker.com/" rel="alternate"/>
  <link href="https://krisbaker.com/building/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://krisbaker.com/</id>
  <generator version="0.15.0">Inkwell</generator>
  <updated>2026-06-05T14:00:00Z</updated>
  <author>
    <name>Kristopher Baker</name>
    <email>hello@krisbaker.com</email>
  </author>
  <entry>
    <title>A feed for every section</title>
    <link href="https://krisbaker.com/building/inkwell/inkwell-per-collection-feeds/" rel="alternate"/>
    <id>https://krisbaker.com/building/inkwell/inkwell-per-collection-feeds/</id>
    <updated>2026-06-05T14:00:00Z</updated>
    <published>2026-06-05T14:00:00Z</published>
    <content type="html"><![CDATA[<p>Until now the site had one feed, and it only carried blog posts. The work and the building updates, the most feed-shaped things here, had no way to subscribe.</p>
<p>Inkwell 0.15.0 fixes that. A small <code>feeds</code> config block now gives each section its own feed, so you can follow just the writing, just the work, or just this building log at <code>/building/rss.xml</code>. The site root becomes a combined feed of everything, newest first. Child updates link back to their project, and work case studies that only carry a year still get a sensible date.</p>
<p>The update you are reading is already in all three: this section's feed, the combined feed, and the home timeline.</p>]]></content>
  </entry>
  <entry>
    <title>Click to enlarge</title>
    <link href="https://krisbaker.com/building/inkwell/inkwell-image-lightbox/" rel="alternate"/>
    <id>https://krisbaker.com/building/inkwell/inkwell-image-lightbox/</id>
    <updated>2026-06-05T10:00:00Z</updated>
    <published>2026-06-05T10:00:00Z</published>
    <content type="html"><![CDATA[<p>Content images used to be stuck at the width of the column. Now any of them opens full size in an overlay: click or tap to enlarge, Escape or a click outside to close.</p>
<p>It shipped in Inkwell 0.14.0 as a small piece of quiet-theme JavaScript, plus one change in the image pipeline so each image remembers its original URL. The lightbox shows the true file, not a resized copy. The Domestique screenshots were the reason I finally wanted it.</p>]]></content>
  </entry>
  <entry>
    <title>A Building section, posted from itself</title>
    <link href="https://krisbaker.com/building/inkwell/inkwell-building-section/" rel="alternate"/>
    <id>https://krisbaker.com/building/inkwell/inkwell-building-section/</id>
    <updated>2026-06-04T09:00:00Z</updated>
    <published>2026-06-04T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Added the Building section you are reading right now (v0.11.0), which meant teaching Inkwell something new: child collections.</p>
<p>A project and its updates are a one-to-many relationship, and Inkwell's collections were flat. So a collection can now declare a parent. Update files live in their own folder, each naming its project, and Inkwell routes them under that project, builds the timeline you see here, and surfaces the most recent ones on the home page. A follow-up release (v0.12.0) updated the agent skills to cover it.</p>
<p>The satisfying part is that this very update is the first thing posted through the feature. If the timeline below renders, it worked.</p>]]></content>
  </entry>
  <entry>
    <title>Example apps and tutorials</title>
    <link href="https://krisbaker.com/building/shikisha/shikisha-examples/" rel="alternate"/>
    <id>https://krisbaker.com/building/shikisha/shikisha-examples/</id>
    <updated>2026-06-02T10:00:00Z</updated>
    <published>2026-06-02T10:00:00Z</published>
    <content type="html"><![CDATA[<p>Added the things that make a library actually approachable: a universal SwiftUI chat app, a &quot;Build a Coding Agent&quot; tutorial backed by a real file-editing agent example, and a RAG tutorial.</p>
<p>Writing the examples is also the most honest design review I get. Every place the chat app or the coding agent felt awkward to wire up was a place the API needed another look, not the example.</p>
<p>This also lines up with the post I wrote about why Shikisha exists, which goes deeper on the glue-code problem it is meant to remove.</p>]]></content>
  </entry>
  <entry>
    <title>Tagged 0.1.0</title>
    <link href="https://krisbaker.com/building/shikisha/shikisha-0-1-0/" rel="alternate"/>
    <id>https://krisbaker.com/building/shikisha/shikisha-0-1-0/</id>
    <updated>2026-05-29T11:00:00Z</updated>
    <published>2026-05-29T11:00:00Z</published>
    <content type="html"><![CDATA[<p>First tagged release. <code>0.1.0</code> targets macOS 14+ and iOS 17+ on Swift 6.3, with structured concurrency, <code>Sendable</code> types, <code>AsyncSequence</code> streaming, and <code>Codable</code> wire shapes.</p>
<p>The same day, JPResume switched its LLM transport over to Shikisha, which was the test I cared about: not &quot;does the demo run&quot; but &quot;does another real tool want to depend on this.&quot;</p>
<p>I am calling it active development on purpose. The APIs may still move before 1.0, and I would rather say that than pretend the surface is settled.</p>]]></content>
  </entry>
  <entry>
    <title>v0.6.0, quality loops and Shikisha transport</title>
    <link href="https://krisbaker.com/building/jpresume/jpresume-shikisha-transport/" rel="alternate"/>
    <id>https://krisbaker.com/building/jpresume/jpresume-shikisha-transport/</id>
    <updated>2026-05-29T08:00:00Z</updated>
    <published>2026-05-29T08:00:00Z</published>
    <content type="html"><![CDATA[<p>v0.6.0 was about output quality and consolidation. The generate stages got a self-critique loop, normalize got a validation feedback loop, and a dedicated Japanese constraint checker now catches the conventions that are easy to get subtly wrong. Where the provider supports it, JSON responses are enforced as <code>json_object</code> rather than hoped for.</p>
<p>Under the hood, two cleanups: I pulled the orchestration into a domain-agnostic DocPipeline module, and switched the LLM transport over to Shikisha.</p>
<p>That second one is the satisfying part. JPResume is now the first real consumer of my own workflow library, which is exactly the dependency I built Shikisha to support.</p>]]></content>
  </entry>
  <entry>
    <title>Two languages, two unit systems</title>
    <link href="https://krisbaker.com/building/domestique/domestique-bilingual-units/" rel="alternate"/>
    <id>https://krisbaker.com/building/domestique/domestique-bilingual-units/</id>
    <updated>2026-05-25T09:00:00Z</updated>
    <published>2026-05-25T09:00:00Z</published>
    <content type="html"><![CDATA[<p>A tool that lives on my own machine should speak my languages and my units, both of them.</p>
<p>So over a few days I made Domestique bilingual and unit-aware. All the app's strings moved into an xcstrings catalog: the views, the status and error messages, the enum labels, the workout and plan and adherence prose, even the labels inside the SpriteKit game. Then a unit-system preference, so distance, elevation, speed, and weight render in metric or imperial, with a separate toggle for energy in kJ or kcal. The kJ to kcal conversion even does the real division by 4.184 now, instead of the 1:1 stub I had been getting away with.</p>
<p>The same stretch loosened some hard requirements. Garmin sign-in is now optional rather than the front door: you can run the app Strava-only, and the toolbar sync and status pill respect that. The ramp test got smarter too, auto-ending when I fail to hold the step and showing the estimated FTP right there in the cooldown, with per-step coaching cues that no longer overlap.</p>
<p>None of this is glamorous. It is the layer that decides whether the app feels built for me or merely adapted to me, and living between two countries, I notice the difference.</p>]]></content>
  </entry>
  <entry>
    <title>Mont Ventoux on the trainer</title>
    <link href="https://krisbaker.com/building/domestique/domestique-mont-ventoux/" rel="alternate"/>
    <id>https://krisbaker.com/building/domestique/domestique-mont-ventoux/</id>
    <updated>2026-05-22T09:00:00Z</updated>
    <published>2026-05-22T09:00:00Z</published>
    <content type="html"><![CDATA[<p>The trainer can hold a power target. The next thing I wanted was terrain: ride an actual road, indoors, with the gradient driving the resistance.</p>
<p>So Domestique grew a Routes library. Import a GPX, save a route from any activity, or pull one of your saved Strava routes, then play it back during a free ride or a structured workout while the elevation profile drives the trainer's simulated gradient. There is a trim editor with draggable chart handles and undo/redo for cutting a clean segment out of a messy GPS track, and you can reverse a route end to end.</p>
<p>The first route I bundled was Mont Ventoux, in three presets for its three classic ascents. Climbing the Géant de Provence from my spare room in Fujisawa, with the resistance ramping up exactly where the real road kicks, is the closest the indoor setup has come to feeling like the outdoor one.</p>]]></content>
  </entry>
  <entry>
    <title>A backend of its own</title>
    <link href="https://krisbaker.com/building/domestique/domestique-backend/" rel="alternate"/>
    <id>https://krisbaker.com/building/domestique/domestique-backend/</id>
    <updated>2026-05-21T09:00:00Z</updated>
    <published>2026-05-21T09:00:00Z</published>
    <content type="html"><![CDATA[<p>For a while Domestique was happily single-device: everything in files and SQLite under Application Support. That is fine until you want the same history on a second machine, or simply want a backup that is not a folder you might delete by accident.</p>
<p>So I wrote it a backend. One Go binary, <code>domestiqued</code>, with a <code>serve | migrate | athlete | token</code> CLI, a SQLite store with embedded migrations, bearer-token auth, and an admin console served right out of the binary. No CGO, no container, no managed cloud. It runs on a small box I control.</p>
<p>On the Swift side, a SyncClient does push and pull deltas in one click: activities and streams, goals and plans and adherence, even the workout game's lifetime stats and per-workout personal bests. Autosave is wired into every place the app changes something, coalesced so a flurry of edits collapses into at most a couple of background pushes. The data model is multi-athlete-ready, so the day I want to share this with a riding partner is a small step, not a rewrite.</p>]]></content>
  </entry>
  <entry>
    <title>A workout game I did not plan to write</title>
    <link href="https://krisbaker.com/building/domestique/domestique-workout-game/" rel="alternate"/>
    <id>https://krisbaker.com/building/domestique/domestique-workout-game/</id>
    <updated>2026-05-20T09:00:00Z</updated>
    <published>2026-05-20T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Indoor intervals are boring. That is the core problem every trainer has. So somewhere in the first week I started a small experiment: render the workout as a 2D side-scrolling cycling game in SpriteKit, with the rider's speed driven by my actual power.</p>
<p>It got away from me, in the good way. The rider hops on a trackpad swipe, leans into corners, stands out of the saddle on climbs and tucks on descents. There are day-night cycles, weather, themed environments, parallax birds, and a ghost of your personal-best ride to chase. The finish screen is a real telemetry readout: NP, IF, TSS, time in zone, a power curve, and a stack of career stats that persist across every session.</p>
<p>None of this makes me fitter than a plain target number would. But I finish more of my hard intervals when there is a hill on the screen and a personal best to beat, and that is the only metric that counts here. The pixel art and sound came later, through an Aseprite asset pipeline.</p>]]></content>
  </entry>
  <entry>
    <title>The whole skeleton in one pass</title>
    <link href="https://krisbaker.com/building/domestique/domestique-foundation/" rel="alternate"/>
    <id>https://krisbaker.com/building/domestique/domestique-foundation/</id>
    <updated>2026-05-18T09:00:00Z</updated>
    <published>2026-05-18T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Domestique started as one long day of wiring the full path end to end: sign in to Garmin Connect, pull activities into a local cache, compute the telemetry that matters, and turn it into something that can drive a trainer.</p>
<p>By the end the skeleton was standing. On the analytics side: an activity list with intensity filters, a detail view with the route on a MapKit map, a power and heart-rate chart, a mean-max power curve, and a dashboard of 28-day load with a 180-day PMC chart. On the training side: a hybrid plan engine (calendar anchors plus an adaptive interior, Friel-style phases with auto-recovery weeks), a library of built-in workouts, and a runtime that pushes ERG targets to an FTMS trainer over BLE while it records the ride.</p>
<p>The piece I wanted most was the loop closing: ride a structured workout, have the app score how closely I matched the prescription, and feed that back into what it recommends tomorrow. Everything since has been filling in that frame.</p>]]></content>
  </entry>
  <entry>
    <title>The core landed in one push</title>
    <link href="https://krisbaker.com/building/shikisha/shikisha-core/" rel="alternate"/>
    <id>https://krisbaker.com/building/shikisha/shikisha-core/</id>
    <updated>2026-05-17T09:00:00Z</updated>
    <published>2026-05-17T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Scaffolded the package and brought up the whole core in a single day: messages and runnables, provider adapters for OpenAI, Anthropic, Ollama, and Google Gemini, then prompts, output parsers, document loaders, text splitters, vector stores, retrievers, incremental indexing, memory, a tool-calling agent loop, and a state graph for cyclic workflows.</p>
<p>That sounds like a lot for one push, and it was, but it is mostly a faithful Swift port of the LangChain shape, so the design work was deciding what to keep rather than inventing from scratch.</p>
<p>The parts I most wanted were there from the first commit: streaming, tool calling, and streaming JSON output parsers with self-fixing wrappers. Those are exactly the pieces every previous project had been re-implementing by hand.</p>]]></content>
  </entry>
  <entry>
    <title>Bilingual routes under /ja/</title>
    <link href="https://krisbaker.com/building/inkwell/inkwell-bilingual-routes/" rel="alternate"/>
    <id>https://krisbaker.com/building/inkwell/inkwell-bilingual-routes/</id>
    <updated>2026-05-01T09:00:00Z</updated>
    <published>2026-05-01T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Added the i18n foundation: config, content, data, and routing, so this site serves English at the root and Japanese under <code>/ja/</code>. The same work brought alias redirects, hreflang tags, and the top-bar language switcher, with localized feeds and a sitemap following shortly after.</p>
<p>The rule I settled on is that every page exists in both languages. A page is translated where a <code>.ja.md</code> file exists and falls back to English where it does not, so adding a translation later never breaks a link.</p>
<p>Doing it in my own generator meant I could keep the URL story simple instead of bending the site around a plugin's assumptions.</p>]]></content>
  </entry>
  <entry>
    <title>Content collections and the quiet theme</title>
    <link href="https://krisbaker.com/building/inkwell/inkwell-collections-quiet-theme/" rel="alternate"/>
    <id>https://krisbaker.com/building/inkwell/inkwell-collections-quiet-theme/</id>
    <updated>2026-04-30T09:00:00Z</updated>
    <published>2026-04-30T09:00:00Z</published>
    <content type="html"><![CDATA[<p>This is the change (v0.3.0) that turned Inkwell from a blog tool into something that could run a portfolio.</p>
<p>Content became collections: each one declares where its markdown lives, its route, how it sorts, and which taxonomies it exposes, with per-collection routing instead of one hardcoded blog shape. The same release added <code>author</code>, <code>nav</code>, <code>home</code>, and the <code>content new</code> command for any collection.</p>
<p>The quiet theme shipped alongside it: Fraunces, Manrope, and JetBrains Mono, with portfolio and blog layouts. It is the look this whole site still runs on.</p>]]></content>
  </entry>
  <entry>
    <title>v0.5.0, source-aware ingestion</title>
    <link href="https://krisbaker.com/building/jpresume/jpresume-source-aware/" rel="alternate"/>
    <id>https://krisbaker.com/building/jpresume/jpresume-source-aware/</id>
    <updated>2026-04-20T09:00:00Z</updated>
    <published>2026-04-20T09:00:00Z</published>
    <content type="html"><![CDATA[<p>Tagged v0.5.0 and reworked input parsing so the tool is genuinely source-aware: it reads my existing western resume, as text or PDF, and treats that as the source of truth instead of asking me to re-enter everything.</p>
<p>That is the version I wrote about on the blog. The point of JPResume was never to author a resume from nothing. It was to take the English one I already maintain and keep the Japanese 履歴書 and 職務経歴書 aligned with it.</p>
<p>Getting ingestion right is what closes the alignment gap, the thing that otherwise bites you at the worst moment, right before you apply.</p>]]></content>
  </entry>
  <entry>
    <title>The pause-point pipeline, in the first releases</title>
    <link href="https://krisbaker.com/building/jpresume/jpresume-pipeline/" rel="alternate"/>
    <id>https://krisbaker.com/building/jpresume/jpresume-pipeline/</id>
    <updated>2026-04-17T10:00:00Z</updated>
    <published>2026-04-17T10:00:00Z</published>
    <content type="html"><![CDATA[<p>The first tagged releases (v0.3.0 through v0.4.3) put the real shape in place: an artifact store with discrete stages and an external bridge, stepwise subcommands, and an agent mode where the language-heavy steps can hand off to an outside agent and re-ingest the result.</p>
<p>PDF support landed here too, on both ends. Input parsing reads an existing resume via PDFKit, with Vision OCR for image-only PDFs, and output rendering produces the 履歴書 and 職務経歴書 with proper Japanese typography.</p>
<p>Separating the deterministic stages from the model stages early is what made everything after this manageable. I can stop at any pause point and look at the artifact before it becomes a document.</p>]]></content>
  </entry>
</feed>