<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jesus Chetia</title>
    <description>The latest articles on DEV Community by Jesus Chetia (@0xna).</description>
    <link>https://dev.to/0xna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153858%2F6233b251-df5d-4acd-ad40-c602cbd73f2b.png</url>
      <title>DEV Community: Jesus Chetia</title>
      <link>https://dev.to/0xna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xna"/>
    <language>en</language>
    <item>
      <title>StorySteps: an offline atlas for the stories attached to places</title>
      <dc:creator>Jesus Chetia</dc:creator>
      <pubDate>Sat, 10 Oct 2026 17:46:09 +0000</pubDate>
      <link>https://dev.to/0xna/storysteps-an-offline-atlas-for-the-stories-attached-to-places-4jai</link>
      <guid>https://dev.to/0xna/storysteps-an-offline-atlas-for-the-stories-attached-to-places-4jai</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is the scene this is built for.&lt;/p&gt;

&lt;p&gt;You are standing at a set of worn stone steps behind the old post office. Someone who grew up here tells you her grandmother cut through them on the way to the market, and that the treads are smooth on one side only. Two years from now that detail exists in your phone and nowhere else, and in four years it may exist nowhere at all.&lt;/p&gt;

&lt;p&gt;StorySteps is a PWA for collecting exactly that, and then getting out of the way. Open the app, press record (after asking the person in front of you, which the app reminds you about), speak for a minute, stop. Transcribe it, fix the two words the model got wrong, save. The memory now has a place, a date you recorded it, a date it reportedly happened, a transcript, and the original audio. Then you put the phone back in your pocket and keep walking.&lt;/p&gt;

&lt;p&gt;No account. No server. No sync. The models run on the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A field session is a walk. You name it, and every memory recorded during it belongs to that walk, so the archive reads as a sequence of outings rather than a pile of loose notes.&lt;/p&gt;

&lt;p&gt;Each memory carries more than a note. It has a &lt;strong&gt;category&lt;/strong&gt; (oral history, natural feature, local tradition, environmental change, observation, built heritage), a &lt;strong&gt;source&lt;/strong&gt; (firsthand, recollection, observation, supplied reference) and a &lt;strong&gt;verification status&lt;/strong&gt; that is yours to set, never the model's.&lt;/p&gt;

&lt;p&gt;The two dates are deliberately separate, and this is the part I care about most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;recorded&lt;/strong&gt;: when you captured it. Always known.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;event date&lt;/strong&gt;: when it supposedly happened. Often vague, so it accepts &lt;code&gt;1998&lt;/code&gt;, &lt;code&gt;1998-04&lt;/code&gt; or &lt;code&gt;1998-04-12&lt;/code&gt; and stores the precision alongside it, so a year never silently pretends to be a day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the speaker said "sometime in the seventies", the event date is &lt;code&gt;1970s&lt;/code&gt;-ish vague and stays vague. The app will not sharpen it for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The map&lt;/strong&gt; shows every placed memory with a marker coloured by category, a legend, filters, and a results list that stays in sync with it. Location is only requested when you press the button, and anything you type or pick by hand is stored as an estimate, labelled as an estimate, never as a device reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The timeline&lt;/strong&gt; is per place. It puts recorded dates next to remembered event dates so the gap between when you heard it and when it happened is visible instead of hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export and import&lt;/strong&gt; exist because the archive should be yours. Export writes a versioned JSON file, with audio as an explicit opt-in because base64 makes it enormous. Import validates every field and the schema version before a single row is written, shows you how many records are new and how many already exist, and applies the whole thing in one transaction, so a malformed file leaves your archive exactly as it was.&lt;/p&gt;

&lt;p&gt;Screenshots:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9t78ye4dfk5cewq1em0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9t78ye4dfk5cewq1em0.png" alt="A memory record, dark theme: the note as written, its transcript, and a metadata column with both dates and coordinates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjb9m4awntw73lxho27t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjb9m4awntw73lxho27t.png" alt="The atlas, light theme: memory markers on an OpenStreetMap map, a category legend, filters and a results list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What it refuses to do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It never invents a transcript. Short or silent recordings are rejected with a reason instead.&lt;/li&gt;
&lt;li&gt;It never marks model output as verified history. Extraction produces suggestions you must accept.&lt;/li&gt;
&lt;li&gt;It never sends a transcript, a recording or a coordinate anywhere.&lt;/li&gt;
&lt;li&gt;It never claims a model is ready when it is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live demo: &lt;a href="https://storysteps.onrender.com" rel="noopener noreferrer"&gt;https://storysteps.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/greenbugx" rel="noopener noreferrer"&gt;
        greenbugx
      &lt;/a&gt; / &lt;a href="https://github.com/greenbugx/StorySteps" rel="noopener noreferrer"&gt;
        StorySteps
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Every place has a story. Go outside and find it.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/StorySteps/public/favicon.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FStorySteps%2FHEAD%2Fpublic%2Ffavicon.png" width="112" height="112" alt="StorySteps logo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;StorySteps&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Every place has a story. Go outside and find it.&lt;/p&gt;
&lt;p&gt;StorySteps turns walks, conversations and observations into an atlas of local memories: oral histories, disappearing
landmarks, local traditions and environmental change. You record a note or a conversation, and it becomes an archive
record attached to a place and a date.&lt;/p&gt;
&lt;p&gt;Everything stays on your device. There is no account, no server, no analytics and no remote AI. The models that
transcribe and structure your notes run in your own browser.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Local knowledge is fragile. A village elder describes where the old crossing used to be, a mason explains which stones
were reused from the railway station, someone notices the last banyan at a crossing is gone. Most of it is written
down in a notebook, or not at all, and within a few years the details are gone with the people who held them.&lt;/p&gt;
&lt;p&gt;StorySteps is…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/greenbugx/StorySteps" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;React 19, Vite, TypeScript, Tailwind v4, Dexie over IndexedDB, Leaflet, LiteRT-LM, Transformers.js, Zod, &lt;code&gt;vite-plugin-pwa&lt;/code&gt;, and 29 tests. &lt;code&gt;pnpm install &amp;amp;&amp;amp; pnpm run dev&lt;/code&gt; and it runs, with no API key and no environment file.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Two open models, both inside the browser:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;How it runs&lt;/th&gt;
&lt;th&gt;Fetched when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whisper base, quantised ONNX&lt;/td&gt;
&lt;td&gt;about 76 MB, plus about 26 MB for the ONNX runtime&lt;/td&gt;
&lt;td&gt;Transformers.js in a Web Worker, WebGPU when a real adapter answers, WASM otherwise&lt;/td&gt;
&lt;td&gt;first time you press Transcribe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E2B, web build&lt;/td&gt;
&lt;td&gt;about 1.9 GB&lt;/td&gt;
&lt;td&gt;LiteRT-LM on the main thread&lt;/td&gt;
&lt;td&gt;when you press Download model in Settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Walk and conversation] --&amp;gt; B{Input}
    B --&amp;gt;|Typed note| C[Original text]
    B --&amp;gt;|Audio| D[Whisper in a worker]
    D --&amp;gt; E[Transcript, audio kept]
    C --&amp;gt; F[Gemma extracts fields]
    E --&amp;gt; F
    F --&amp;gt; G[Zod validation]
    G --&amp;gt; H{A human accepts it?}
    H --&amp;gt;|Yes| I[Dexie and IndexedDB]
    I --&amp;gt; J[Archive, timeline, map marker]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Whisper runs in a worker, and the audio never enters React state
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;MediaRecorder&lt;/code&gt; writes chunks straight into IndexedDB as blobs. The blob is decoded and resampled to 16 kHz mono, checked for length and for actual silence, and only then handed to the worker. Silence and near-silence are rejected before the model is invoked, because there is no point spending 76 MB and 13 seconds to learn that nothing was said. My own fake-microphone test recording tripped that guard, which is the nicest possible confirmation that it works.&lt;/p&gt;

&lt;p&gt;The worker reports download progress, so the first run tells you it is fetching about 76 MB rather than appearing to hang.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemma streams, because 1.9 GB will not fit twice
&lt;/h3&gt;

&lt;p&gt;The obvious way to use Gemma is to hand LiteRT-LM a URL, but that means a 1.9 GB download every session and no offline use. Instead the download is streamed through a &lt;code&gt;TransformStream&lt;/code&gt; into the Cache API, with progress and a cancel, and the engine is handed &lt;code&gt;cache.match(...)&lt;/code&gt; as a stream. The browser never holds two copies in memory, a reload costs nothing, and the app has no way to reach for the network on a later run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The runtimes are served from this origin, not a CDN
&lt;/h3&gt;

&lt;p&gt;LiteRT-LM and onnxruntime-web both default to fetching their WASM from jsDelivr. That would quietly break local AI the moment the signal drops, which is the one thing this app cannot afford. A small Vite plugin copies both runtimes out of &lt;code&gt;node_modules&lt;/code&gt; at build time and serves them from &lt;code&gt;/runtime/litert&lt;/code&gt; and &lt;code&gt;/runtime/ort&lt;/code&gt;, where the service worker caches them on first use.&lt;/p&gt;

&lt;h3&gt;
  
  
  The schema refuses sloppy data
&lt;/h3&gt;

&lt;p&gt;Partial dates are validated as real dates, &lt;code&gt;1998-13&lt;/code&gt; is rejected, coordinates must appear as a pair or not at all, and an event date's declared precision has to match its own length. That last one sounds fussy until you remember that a year and a day look identical in a string, and a database full of fake precision is worse than one full of honest gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bugs I hit along the way
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Whisper died on WebGPU with &lt;code&gt;webgpuInit is not a function&lt;/code&gt;.&lt;/strong&gt; I had pointed onnxruntime at the &lt;code&gt;ort-wasm-simd-threaded.jsep&lt;/code&gt; factory. In the version installed here, WebGPU has moved out of &lt;code&gt;jsep&lt;/code&gt;, so the runtime asked the module for an entry point it does not define, then reported that no backend was available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;asyncify.mjs -&amp;gt; 1   (webgpuInit)
jspi.mjs     -&amp;gt; 1
jsep.mjs     -&amp;gt; 0   &amp;lt;-- what I was loading
plain .mjs   -&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Repointing at &lt;code&gt;asyncify&lt;/code&gt; fixed it, and WebGPU transcription now runs with a real adapter. There is now a test that fails if that factory ever stops exporting &lt;code&gt;webgpuInit&lt;/code&gt;, because I verified it catches the original bug by reintroducing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline, the app quietly lost its typography.&lt;/strong&gt; The service worker precached js, css, html and svg but not &lt;code&gt;woff2&lt;/code&gt;, so with no connection the headings fell back to system serif. Nothing errored, it just looked wrong. Fonts are precached now, 18 entries and 1.74 MB, and I check it by reading the computed font family on an offline reload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The map rendered inside a fraction of its own box.&lt;/strong&gt; &lt;code&gt;leaflet.css&lt;/code&gt; was never imported, so panes and tiles had no size rules and the map drew into about a quarter of its container with the rest blank. It looked like a tile outage, which is exactly what it was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The onboarding gate fought the router.&lt;/strong&gt; A flag read on first render redirected to the welcome page after you had already left it, because the state was initialised as "not done" instead of "not known yet". Small bug, immediate bounce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selects displayed raw values&lt;/strong&gt; like &lt;code&gt;observation&lt;/code&gt; instead of &lt;code&gt;Observation&lt;/code&gt;, because the component needs its item labels declared. Every control now has an accessible name, which I checked by walking all five routes and asserting no button, link or input is nameless.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because of what the data is.&lt;/p&gt;

&lt;p&gt;An oral history with coordinates attached is not something you want sitting on a server you do not control, especially when the people in it never agreed to that. With a closed API, every transcript, every recording and every location would leave the device, there would be a per-token bill attached to a hobby that happens on a walk, and the entire thing would die the moment the signal does.&lt;/p&gt;

&lt;p&gt;Open weights meant the models are a detail rather than a dependency. I could build the part that actually matters, the quiet collecting and the honest archive, and let the AI sit behind a toggle that is off by default.&lt;/p&gt;

&lt;p&gt;It also meant I could go and read what onnxruntime actually ships. The bug above was not a support ticket or a vague "model sometimes does not work", it was a &lt;code&gt;grep&lt;/code&gt; away, and the fix was mine to make. Being able to read the runtime is half the value of it being open.&lt;/p&gt;

&lt;p&gt;Both models ran on my machine with a real WebGPU adapter, and Whisper also on the WASM fallback, producing the same transcript either way. It also transcribed with every non-localhost request blocked, which is the proof that it needs no network.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;StorySteps: Building a Local-First Memory Atlas in the Browser&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build StorySteps, a privacy-first offline-first PWA that turns walks, conversations and observations into an atlas of local memories. (Full 500 line specification abridged here.) Hard rules: no backend, no account, no API key, no analytics, no em dashes in any file, do not scaffold a new project or replace the package manager, do not commit or stage anything, and never fake a capability. Model inference and transcription must run locally; map tiles may need the network. Ship a README with honest claims about what was actually tested.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Repository audit first, before any edits. Node 26.7.0, pnpm 11.3.0, Vite 8, React 19, Tailwind v4, shadcn/ui on Base UI. The template shipped Dexie, Leaflet, LiteRT-LM and Transformers.js but no tests, no router and a bare counter page.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;pnpm run build &amp;amp;&amp;amp; pnpm run lint&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ pnpm run build &amp;amp;&amp;amp; pnpm run lint&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;build clean; lint failed with 3 pre-existing react-refresh errors in ui/badge.tsx, ui/button.tsx and ui/tabs.tsx. Recorded as baseline so they are not misattributed later.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Data model first, since everything else depends on it. Partial event dates need explicit precision, otherwise a year and a day look identical to the schema. Coordinates are nullable but must appear as a pair.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/lib/validation/schemas.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;memoryRecordSchema&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;partialDateSchema &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;YYYY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;YYYY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;MM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;YYYY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;MM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;DD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="nx"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;refinement&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;rejects&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;half&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;filled&lt;/span&gt; &lt;span class="nx"&gt;coordinate&lt;/span&gt; &lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;refinement&lt;/span&gt; &lt;span class="nx"&gt;tying&lt;/span&gt; &lt;span class="nx"&gt;eventDatePrecision&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Dexie with two versions: v1 plus a v2 upgrade that backfills the fields added during the build, so an early archive survives. Audio is a separate table so metadata queries never drag blobs through.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/lib/db/database.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Database&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Dexie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;backfills&lt;/span&gt; &lt;span class="nx"&gt;eventDatePrecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventDateUncertain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locationPrecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;needsCorroboration&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;fieldSessionId&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Local AI is the part most likely to quietly lie, so it got the most scrutiny. Gemma 4 E2B is 1.9 GB, which is far too big to hold twice in memory, so the model streams into the Cache API and is handed to LiteRT-LM as a stream.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;curl -sIL on the gemma-4-E2B-it-web.litertlm resolve URL&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ curl -sIL on the gemma-4-E2B-it-web.litertlm resolve URL&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;content-length: 2008432640 (1.87 GiB), ungated, public.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The LiteRT-LM and ONNX runtimes load their WASM from a CDN by default, which would break local AI offline and send a request to a third party. A small Vite plugin copies both runtimes out of node_modules and serves them from this origin under /runtime/litert and /runtime/ort.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Verification was browser-driven wherever possible, because the acceptance criteria are behavioural. Typed memories, search, editing, deletion, export and import were exercised through the real UI.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-browser"&gt;browser&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;fill Title and Original note, press Save memory&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;fill Title and Original note, press Save memory&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;redirected to the new memory route with the record rendered; persisted across reload; empty title and an impossible date (1998-13) both blocked save with a specific message.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;no available backend found. ERR: [webgpu] TypeError: j(...).webgpuInit is not a function when I press Transcribe on the Device&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;That is a real bug and it is mine. The WebGPU path pointed at the ort-wasm-simd-threaded.jsep factory. In the onnxruntime-web version installed here, WebGPU has moved out of jsep, so the runtime called a function the factory never defines, then reported no backend at all.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;grep -c webgpuInit onnxruntime-web/dist/ort-wasm-simd-threaded.*.mjs&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ grep -c webgpuInit onnxruntime-web/dist/ort-wasm-simd-threaded.*.mjs&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;asyncify.mjs -&amp;gt; 1, jspi.mjs -&amp;gt; 1, jsep.mjs -&amp;gt; 0, plain .mjs -&amp;gt; 0. The asyncify factory carries webgpuInit and is also what Transformers.js defaults to.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fixed the paths, dropped the unusable 28 MB jsep pair from the build, and added a one-shot fallback so a WebGPU failure lands on WASM with an honest message instead of a dead end. Added a regression test that asserts the configured factory really exports webgpuInit.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-vitest"&gt;vitest&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;revert the fix, run src/lib/ai/ortAssets.test.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;revert the fix, run src/lib/ai/ortAssets.test.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;x points at a factory that exposes the webgpu entry point -- expected 'async function ortWasmThreaded' to contain 'webgpuInit'. The test reproduces the original bug.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Whisper was proven on the real model rather than mocked: an 11 second speech sample transcribed correctly in about 13 seconds including the first download, and about 3 seconds once cached. Re-running with every non-localhost request blocked produced the same transcript, which is the evidence that it works offline.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-browser"&gt;browser&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;transcribe a fetched speech sample through the transcriber module&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;transcribe a fetched speech sample through the transcriber module&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{ status: 'transcribed', device: 'wasm', text: 'And so my fellow Americans ask not what your country can do for you, ask what you can do for your country.' } A synthetic silent WAV returned { status: 'rejected', reason: 'That recording is silent.' } rather than a fabricated transcript.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;change the project name to StorySteps please&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Renamed the product name everywhere, and the identifiers too, not just the strings: the IndexedDB name, the Dexie class, the archive format id and export filename, both Cache API names, the manifest name and the icon files. Flagged the two real consequences: an archive exported under the old format string no longer imports, and a browser profile with old data starts empty.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;please add MIT license to the project please&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Adding MIT surfaced a problem worth deciding out loud: react-leaflet 5.0.0 is not MIT, it is Hippocratic License 2.1, which adds a termination clause, arbitration and an indemnity and binds users to human rights compliance. Leaflet itself is BSD-2-Clause. Asked whether to keep it and document the exception, or drop it for a hand written Leaflet wrapper. The author chose to keep it, so the README states the exception plainly and names the single file that would have to change for a purely MIT dependency set.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Final pass found a real offline defect: the service worker precached js, css, html and svg but not woff2, so offline the display face silently fell back to system serif. Fonts are precached now (18 entries, 1.74 MB), verified by resolving getComputedStyle on an offline reload.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;What I would tell someone considering this build: everything except Gemma inference is verified in a real browser, the map tile layer genuinely needs the internet, and browser storage is neither encrypted nor durable. Gemma is implemented and its runtime loads, but the test browser returns a null WebGPU adapter, so the 1.9 GB download, generation and the extraction review screen are unproven on real hardware and the README says so.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        17 of 17 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;li&gt;Best Use of Render&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>What if your journal could understand you? So I built DayBook</title>
      <dc:creator>Jesus Chetia</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:33:51 +0000</pubDate>
      <link>https://dev.to/0xna/daybook-a-private-ai-journal-that-learns-my-friend-habits-patterns-and-help-him-improve-powered-37kn</link>
      <guid>https://dev.to/0xna/daybook-a-private-ai-journal-that-learns-my-friend-habits-patterns-and-help-him-improve-powered-37kn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a team submission&lt;br&gt;
Teammate - &lt;a class="mentioned-user" href="https://dev.to/piyushnextgen"&gt;@piyushnextgen&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Daybook&lt;/strong&gt; began with a simple problem: a friend of ours who was already journaling, but most of the time, the entries just stayed there.&lt;/p&gt;

&lt;p&gt;He could write about a stressful day, a productive week, a bad habit, or something that kept coming up, but noticing those patterns across dozens of entries was almost impossible manually.&lt;/p&gt;

&lt;p&gt;So we built &lt;strong&gt;Daybook&lt;/strong&gt; for him.&lt;/p&gt;

&lt;p&gt;Daybook is a private AI journal that reads your entries locally and helps surface patterns in your habits, mood, routines, and thoughts over time. Instead of just storing what you write, it helps you understand what keeps showing up.&lt;/p&gt;

&lt;p&gt;The important part is where this happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your journal never needs to leave your device.&lt;/strong&gt; Daybook uses a local LLM, so the AI can work with your personal entries without sending them to a cloud AI service.&lt;/p&gt;

&lt;p&gt;We wanted to build something that was genuinely useful to one person, while also answering a question we kept coming back to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can AI be helpful with something as personal as journaling without requiring you to give away your data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Daybook is our attempt at an answer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;



&lt;p&gt;&lt;a href="https://daybook-bioo.onrender.com" rel="noopener noreferrer"&gt;DayBook UI Demo&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Local AI features are not available in the deployed demo because DayBook runs AI locally using Ollama. The backend requires a running Ollama instance on port &lt;code&gt;11434&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://videotourl.com/videos/1791149236593-0e794a29-e2e1-479e-be35-de7426d70e87.mp4" rel="noopener noreferrer"&gt;Video Demo&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/greenbugx" rel="noopener noreferrer"&gt;
        greenbugx
      &lt;/a&gt; / &lt;a href="https://github.com/greenbugx/Daybook" rel="noopener noreferrer"&gt;
        Daybook
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A private AI journal that learns your habits, finds patterns, and helps you improve, powered by a local LLM with your data staying on-device.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;
   &lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./src/assets/images/logo-nobg.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fsrc%2Fassets%2Fimages%2Flogo-nobg.webp" alt="DayBook logo" width="280" height="240"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
   A local-first AI journal that learns your habits, finds recurring patterns, and helps you reflect on what you could improve
&lt;/p&gt;

&lt;p&gt;Everything happens on your own machine. Your writing never leaves your computer, and the AI that reads it runs locally through Ollama, so there is no account to create, no key to paste, and no company holding a copy of your thoughts.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#screenshots" rel="noopener noreferrer"&gt;Screenshots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#what-daybook-does" rel="noopener noreferrer"&gt;What DayBook Does&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#requirements" rel="noopener noreferrer"&gt;Requirements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#ai-model" rel="noopener noreferrer"&gt;AI Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#running-daybook" rel="noopener noreferrer"&gt;Running DayBook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#how-the-project-works" rel="noopener noreferrer"&gt;How the Project Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#project-structure" rel="noopener noreferrer"&gt;Project Structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#database" rel="noopener noreferrer"&gt;Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#how-ai-answers-questions" rel="noopener noreferrer"&gt;How AI Answers Questions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#journal-images" rel="noopener noreferrer"&gt;Journal Images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#session-and-ownership" rel="noopener noreferrer"&gt;Session and Ownership&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#state-that-lives-only-in-memory" rel="noopener noreferrer"&gt;State That Lives Only in Memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#frontend-details" rel="noopener noreferrer"&gt;Frontend Details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#backend-details" rel="noopener noreferrer"&gt;Backend Details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#api-reference" rel="noopener noreferrer"&gt;API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#data-on-disk" rel="noopener noreferrer"&gt;Data on Disk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#development" rel="noopener noreferrer"&gt;Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#privacy" rel="noopener noreferrer"&gt;Privacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#troubleshooting" rel="noopener noreferrer"&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/greenbugx/Daybook#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Screenshots&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Home&lt;/th&gt;

&lt;th&gt;Journal Spread&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/home.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Fhome.png" alt="Home screen"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/journal.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Fjournal.png" alt="Journal spread"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Analytics&lt;/th&gt;

&lt;th&gt;Goals&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/analytics.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Fanalytics.png" alt="Analytics"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/goals.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Fgoals.png" alt="Goals"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Calendar&lt;/th&gt;

&lt;th&gt;Library&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/calendar.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Fcalendar.png" alt="Calendar"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/greenbugx/Daybook/./docs/screenshots/library.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgreenbugx%2FDaybook%2FHEAD%2F.%2Fdocs%2Fscreenshots%2Flibrary.png" alt="Library"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What DayBook Does&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;DayBook is built around a few simple habits that turn into something useful over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write a page a day.&lt;/strong&gt; Each date has one journal entry with a topic…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/greenbugx/Daybook" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  How We Built It
&lt;/h2&gt;

&lt;p&gt;We wanted Daybook to be useful with genuinely personal data, which made the usual "send it to an AI API" approach feel wrong for this project.&lt;/p&gt;

&lt;p&gt;So we built it &lt;strong&gt;local-first from the beginning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the core is &lt;strong&gt;Gemma 3 4B&lt;/strong&gt;, an open-weight model running locally through &lt;strong&gt;Ollama&lt;/strong&gt;. There is no OpenAI API, Gemini API, or hosted AI service sitting between the journal and the model.&lt;/p&gt;

&lt;p&gt;The whole stack lives on the user's machine:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    U["You, in a browser"] --&amp;gt;|"localhost:5173"| V["Vite dev server"]
    V --&amp;gt;|"React app"| U
    V --&amp;gt;|"proxies /api to 3001"| B["Fastify backend"]
    B --&amp;gt;|"SQL, WAL mode"| D[("SQLite&amp;lt;br/&amp;gt;data/daybook.db")]
    B --&amp;gt;|"local HTTP"| O["Ollama"]
    O --&amp;gt; G["Gemma 3 4B"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The frontend is built with &lt;strong&gt;React 19 and TypeScript&lt;/strong&gt;, while the local backend handles application logic and communication with Ollama. Journal data and personal memories are stored locally using &lt;strong&gt;SQLite&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That separation was intentional. The application can use an LLM for things like identifying recurring habits, finding patterns across entries, and pointing out areas for reflection, while the actual data stays on the same machine.&lt;/p&gt;

&lt;p&gt;When a user asks Daybook to analyze their journal, the request stays within that local stack:&lt;/p&gt;

&lt;p&gt;Journal → Backend → Ollama → Gemma → Analysis → Daybook&lt;/p&gt;

&lt;p&gt;That's what allows Daybook to look for recurring habits, patterns, wins, struggles, and areas for improvement without requiring a cloud AI API.&lt;/p&gt;

&lt;p&gt;To run Daybook, we only need three local processes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React + TypeScript&lt;/strong&gt; for the interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local backend&lt;/strong&gt; for application logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama + Gemma 3 4B&lt;/strong&gt; for AI analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No API key required&lt;/p&gt;

&lt;h2&gt;
  
  
  What Daybook Can Actually Do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily entries&lt;/strong&gt; with topic, mood, weather, location, and a rich text editor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goals tied to a date&lt;/strong&gt;, so you can see what you meant to do next to what you wrote about&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A reflection chat&lt;/strong&gt; where you ask in plain language and get an answer grounded in your actual entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per entry observations&lt;/strong&gt; the AI generates after you save&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory suggestions&lt;/strong&gt; you approve or dismiss, so nothing is remembered without your say so&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A streak&lt;/strong&gt; that counts unbroken days honestly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One image per entry&lt;/strong&gt;, stored as a real file on your disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saved quotes&lt;/strong&gt; collected in a Library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A profile&lt;/strong&gt; built through onboarding that shapes how reflections are written&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The AI is grounded, not creative
&lt;/h3&gt;

&lt;p&gt;This was the part we spent the most time on. A journal AI that invents things is worse than useless, because you cannot trust what it tells you about your own life.&lt;/p&gt;

&lt;p&gt;The system prompt carries &lt;strong&gt;18 explicit rules&lt;/strong&gt;. The model is told that its only source of truth is the context handed to it, that it must never invent entries, dates, goals, or statistics, that it must separate a one time observation from a recurring pattern, and that it must say plainly when there is not enough evidence. It is also told to avoid diagnosing anything about mental or physical health, and to respect a &lt;code&gt;thingsToAvoidAssuming&lt;/code&gt; list the user fills in during onboarding.&lt;/p&gt;

&lt;p&gt;Eight reflection intents decide how the model reads the context:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;th&gt;Looks for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RECURRING_PATTERNS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The same thing showing up again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MOOD_EMOTIONAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mood and emotional tone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STRUGGLES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What keeps getting in the way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WINS_PROGRESS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wins, progress, momentum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GOALS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Goal follow through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HABITS_ROUTINES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routines and timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CHANGE_OVER_TIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How things shifted over time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SELF_UNDERSTANDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What Daybook knows about you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Questions that never reach the model
&lt;/h3&gt;

&lt;p&gt;We route every question before it costs anything.&lt;/p&gt;

&lt;p&gt;If a question can be answered from the database, Daybook answers it with SQL and skips the model entirely. Ask how many entries you have written, or what your goals are, and you get an exact answer instantly. Only questions that genuinely need interpretation go to Gemma.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Q["Your question"] --&amp;gt; ROUTE["classifyAiIntent"]
    ROUTE --&amp;gt;|"quote"| DQ["Return today's quote"]
    ROUTE --&amp;gt;|"streak"| DS["Return the streak"]
    ROUTE --&amp;gt;|"goals"| DG["Read goals from SQL"]
    ROUTE --&amp;gt;|"journal"| DJ["Read entries from SQL"]
    ROUTE --&amp;gt;|"reflection, or anything else"| LLM["Ask Gemma"]
    LLM --&amp;gt; INTENT["classifyReflectionIntent"]
    INTENT --&amp;gt; CTX["buildAiContext&amp;lt;br/&amp;gt;entries, goals, profile,&amp;lt;br/&amp;gt;stats, memories, observations"]
    CTX --&amp;gt; PROMPT["buildAiSystemPrompt&amp;lt;br/&amp;gt;18 grounding rules plus context"]
    PROMPT --&amp;gt; RUN["Ollama generates the answer"]
    RUN --&amp;gt; OUT["Answer with its intent label"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This keeps answers exact where exactness matters, and spends model time only where interpretation is actually needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The streak is not AI
&lt;/h3&gt;

&lt;p&gt;The streak is plain arithmetic over your entry dates. It converts dates to day numbers, ignores the future, returns zero unless you wrote today, then counts backwards while days are unbroken. If you skip a day, it ends there. No model, no guessing, no being generous with dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-Level System Architecture
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB
subgraph USER["User Machine - 100% Local"]
  BROWSER["Browser&amp;lt;br/&amp;gt;localhost:5173"]
  subgraph FRONTEND["Frontend - React 19 + TS + Vite + Tailwind 4"]
    APP["App.tsx&amp;lt;br/&amp;gt;Routing and shared state"]
    VIEWS["Views&amp;lt;br/&amp;gt;InteractiveBook / JournalBook&amp;lt;br/&amp;gt;Calendar / Goals / Library&amp;lt;br/&amp;gt;AnalyticsAI / Settings / Onboarding"]
    APICLIENT["lib/api.ts&amp;lt;br/&amp;gt;Typed fetch wrapper for /api/*"]
  end
  subgraph BACKEND["Backend - Fastify :3001"]
    ROUTES["REST API&amp;lt;br/&amp;gt;users / journals / goals&amp;lt;br/&amp;gt;quotes / memories / ai/*"]
    ZOD["Zod Validation"]
    CONTEXT["buildAiContext()&amp;lt;br/&amp;gt;entries + goals + memories + profile"]
    AILOGIC["Intent routing and&amp;lt;br/&amp;gt;grounded prompt building"]
  end
  subgraph DATA["Data Layer"]
    DRIZZLE["Drizzle ORM"]
    SQLITE["SQLite&amp;lt;br/&amp;gt;better-sqlite3&amp;lt;br/&amp;gt;data/daybook.db&amp;lt;br/&amp;gt;WAL + foreign keys ON"]
    FILES["data/media/&amp;lt;userId&amp;gt;/&amp;lt;date&amp;gt;/&amp;lt;id&amp;gt;.png"]
  end
  subgraph AI["AI Layer - Ollama :11434"]
    OLLAMA_SDK["ollama npm SDK&amp;lt;br/&amp;gt;format json, temp 0.4"]
    GEMMA["Gemma 3:4b"]
  end
end
BROWSER --&amp;gt; APP --&amp;gt; VIEWS --&amp;gt; APICLIENT
APICLIENT -- "/api/* via Vite proxy" --&amp;gt; ROUTES
ROUTES --&amp;gt; ZOD --&amp;gt; DRIZZLE --&amp;gt; SQLITE
ROUTES --&amp;gt; CONTEXT --&amp;gt; AILOGIC --&amp;gt; OLLAMA_SDK --&amp;gt; GEMMA
ROUTES --&amp;gt; FILES&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Using an open-weight model also meant we weren't locked into one AI provider. The model can be changed, experimented with, or eventually customized without redesigning the entire product around a proprietary API.&lt;/p&gt;

&lt;p&gt;For a journal, where the data can be deeply personal, &lt;em&gt;local inference isn't just a technical choice. It is part of the product itself.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Data model
&lt;/h3&gt;

&lt;p&gt;Twelve tables, with foreign keys turned on so deleting an entry cleans up its goals, media rows, and observations in one step.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;erDiagram
    users ||--o| user_profiles : has
    users ||--o{ local_sessions : opens
    users ||--o{ journal_entries : writes
    journal_entries ||--o{ journal_goals : contains
    journal_entries ||--o| journal_media : illustrated_by
    journal_entries ||--o{ journal_observations : analysed_by
    journal_entries ||--o{ memories : may_seed
    quotes ||--o{ saved_quotes : saved_as&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Details we are proud of
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ownership is decided on the server, every time.&lt;/strong&gt; No endpoint accepts a user id, an entry id, or a file path from the client. Each request resolves the active local session, finds that user's entry for the requested date, then works only with the media and goals hanging off it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images are safe by construction.&lt;/strong&gt; Files are named with a server generated UUID, so your original filename never reaches the filesystem. Every path is built from your user id and the date you asked for, and any path resolving outside the media folder is refused. We tested a &lt;code&gt;../../../../etc/passwd&lt;/code&gt; payload and it landed on a UUID filename inside the media root like any other upload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A photo failure never costs you your writing.&lt;/strong&gt; The journal text is saved first. If the image fails afterwards, your text stays saved, your preview stays visible, and you get a clear error instead of a false success.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Date changes cannot leak content.&lt;/strong&gt; Opening a date fires four requests in parallel, each carrying an &lt;code&gt;AbortController&lt;/code&gt;. Switch to October 5 while October 4 is still loading and the stale reply is discarded, so October 4 can never appear on October 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Port / Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;React 19, TypeScript, Vite 8, Tailwind CSS 4, shadcn/ui, Lucide&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:5173&lt;/code&gt; · &lt;code&gt;src/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Fastify 5, Zod 4, Drizzle ORM, tsx&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:3001&lt;/code&gt; · &lt;code&gt;server/src/index.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;better-sqlite3, Drizzle Kit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data/daybook.db&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Ollama, Gemma 3 4B, Ollama npm package&lt;/td&gt;
&lt;td&gt;&lt;code&gt;:11434&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;pnpm, Ollama&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Running Daybook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama → Backend (:3001) → Frontend (:5173)
  ↓
Gemma 3 4B

ollama serve
ollama pull gemma3:4b

cd server
pnpm dev

# In another terminal
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Requires Node.js 26 or newer, pnpm, and Ollama.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For Daybook, open innovation wasn't just about using a different AI model. It changed what we were comfortable building in the first place.&lt;/p&gt;

&lt;p&gt;A journal is full of things you probably wouldn't want sitting on someone else's servers. Thoughts, bad days, habits, goals, personal memories. We wanted Daybook to actually analyze that information without making the user choose between &lt;strong&gt;useful AI and privacy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using an open-weight model like &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; made that possible.&lt;/p&gt;

&lt;p&gt;Using Gemma 3 4B through Ollama gave us a way to do that. The model runs locally, so in our setup, the journal and its AI analysis can stay on the user's machine without requiring a third-party AI API or API key.&lt;/p&gt;

&lt;p&gt;And that's where open models became more interesting to us than just being a cheaper alternative.&lt;/p&gt;

&lt;p&gt;We can run the model locally through Ollama, keep the journal and its analysis on the user's machine, and build around the model without depending on a third-party AI API or API key.&lt;/p&gt;

&lt;p&gt;It also gives us something a closed API doesn't: &lt;strong&gt;control&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We have control over what runs, where it runs, and how we build around it. We can experiment with different models, swap them out, run the system offline, and eventually customize the model for the kind of reflection Daybook needs.&lt;/p&gt;

&lt;p&gt;For us, that control matters because of the kind of data we're working with.&lt;/p&gt;

&lt;p&gt;It's that we could build an AI product around &lt;strong&gt;data we don't want to send anywhere in the first place&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For us, that's the real promise of open innovation: it lets developers decide where AI runs, what it sees, and how the system is built around it.&lt;/p&gt;

&lt;p&gt;That's what it meant for Daybook.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;DayBook: Making the Beautiful UI for people's journal&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement the FIRST-TIME PERSONAL ONBOARDING UI for DayBook. The goal is to create a beautiful, calm, journal-like onboarding experience when an existing local user has onboardingCompleted === false. The onboarding should collect personal information DayBook will later use to personalize journal analysis and AI responses. Follow the existing DayBook theme (calm, personal, warm, minimal, editorial, journal-inspired).&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Created OnboardingView.tsx with a 7-step calm, editorial questionnaire reflecting DayBook's visual identity:&lt;br&gt;
- Steps for current focus, personal bio, habits/motivators, known struggles, ideal day, reflection style (Gentle, Balanced, Direct), and things to avoid assuming.&lt;br&gt;
- Integrated seamlessly with FirstRunRegistration and InteractiveBook flow so first-time users see it prior to opening the journal spread.&lt;br&gt;
- Data structured for profile update with zero local storage hacks or database schema alterations.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writetofile"&gt;write_to_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;DayBook/src/components/OnboardingView.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;DayBook/src/components/OnboardingView.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created with editorial onboarding component and responsive steps.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Some fix in Journal: the Bold, Italic, and Underline tool is not working properly. Check it out and fix it.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Investigated the contenteditable formatting mechanism in OpenJournalSpread.tsx:&lt;br&gt;
- Added proper selection restoration and inline style dispatching (journal-style-sync).&lt;br&gt;
- Ensured bold, italic, and underline commands toggle reliably across selected ranges and update toolbar highlight states.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-replacefilecontent"&gt;replace_file_content&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Updated selection formatting listeners and toolbar synchronization.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The GOALS and Observations 'Daybook noticed' overlaps when Daybook noticed has many things to show. Can you fix that, make both of them cleanly scrollable and not overlapping each other?&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Updated the left page layout in OpenJournalSpread.tsx:&lt;br&gt;
- Configured Today's Goals and DayBook Noticed containers with flex-1 min-h-0 overflow-hidden and inner overflow-y-auto scrollbar-none.&lt;br&gt;
- Added subtle section dividing borders and bounded max heights so neither section pushes or overlaps the other.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-replacefilecontent"&gt;replace_file_content&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Layout converted to independent scrollable containers.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Collapse this mood selections to a drop down selection, so goals and Daybook Noticed gets good space.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Collapsed the 14-button mood selection grid into an inline dropdown menu in OpenJournalSpread.tsx:&lt;br&gt;
- Added dropdown trigger displaying the active mood emoji, title, and chevron.&lt;br&gt;
- Implemented floating popover menu with 2-column layout and click-outside dismissal.&lt;br&gt;
- Freed up vertical space, providing ample room for Goals and DayBook Noticed.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-replacefilecontent"&gt;replace_file_content&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;DayBook/src/components/OpenJournalSpread.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Mood selector transformed to compact dropdown picker.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        8 of 8 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;DayBook: Core Backend Architecture, Local AI Reflections, and Persistence&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Review the DayBook codebase and implement the next persistence phase: Journal Goals. The goals need a dedicated SQLite table with foreign keys to journal entries, REST endpoints for CRUD operations, and full UI integration.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented Journal Goals persistence end-to-end:&lt;br&gt;
- Created journal_goals schema in Drizzle ORM with foreign key cascades to journal_entries.&lt;br&gt;
- Added REST API routes for listing, creating, toggling completion, and deleting goals.&lt;br&gt;
- Integrated goals state and optimistic UI updates into the journal view.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/db/schema.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;journal_goals&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;foreign&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement real journal streak calculation in DayBook. Streaks should be computed from actual journal entry dates in SQLite without relying on client-side mocks.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented SQLite-backed consecutive streak counting:&lt;br&gt;
- Added date normalization and consecutive day algorithm spanning back from today or yesterday.&lt;br&gt;
- Created GET /api/journal/streak endpoint returning current and longest streak.&lt;br&gt;
- Updated UI header badge to display live database streak count.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/index.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt; &lt;span class="nx"&gt;computation&lt;/span&gt; &lt;span class="nx"&gt;logic&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement DayBook core local AI reflection system. It must run locally using Ollama, ground reflections in SQLite journal context, and provide thoughtful journal insights without leaking user data.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Integrated local AI reflection engine:&lt;br&gt;
- Connected to Ollama local instance running Gemma.&lt;br&gt;
- Implemented deterministic retrieval of recent entries, mood trends, and active goals to construct the reflection prompt context.&lt;br&gt;
- Added streaming reflection generation with fallback handling.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/ai/engine.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Created&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="nx"&gt;LLM&lt;/span&gt; &lt;span class="nx"&gt;reflection&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement smart AI queries and deterministic data retrieval. Factual queries (like goal counts, mood history, dates) should be answered directly from SQLite, while reflective prompts use the local LLM.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Built hybrid query router:&lt;br&gt;
- Intent classification routes factual questions directly to SQL aggregations.&lt;br&gt;
- Reflective and emotional questions are enriched with retrieved SQLite context and passed to the local LLM.&lt;br&gt;
- Drastically reduced LLM latency and eliminated hallucinations for historical journal facts.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/ai/router.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Implemented&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="nx"&gt;routing&lt;/span&gt; &lt;span class="nx"&gt;between&lt;/span&gt; &lt;span class="nx"&gt;SQL&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;LLM&lt;/span&gt; &lt;span class="nx"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement DayBook Quote System and Persistent Library: daily quotes, quote catalog, and SQLite-backed saving/unsaving of favorite quotes.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Created persistent Quote &amp;amp; Library system:&lt;br&gt;
- Added quotes and saved_quotes tables in SQLite.&lt;br&gt;
- Built deterministic daily quote selection based on date hashing.&lt;br&gt;
- Added endpoints for favoriting quotes and browsing saved quotes in the Library view.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/db/schema.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;quotes&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;saved_quotes&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement per-entry AI observations (DayBook Noticed). When an entry is written, the system should generate concise categorized observations (patterns, achievements, emotions).&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented Per-Entry AI Observations (DayBook Noticed):&lt;br&gt;
- Added journal_observations schema with observation types (habit, emotion, milestone).&lt;br&gt;
- Extracted structured observations upon saving journal entries.&lt;br&gt;
- Rendered categorized observation tags in the left page margin of the journal spread.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/index.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt; &lt;span class="nx"&gt;generation&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="nx"&gt;linkage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement AI Memory Suggestion with explicit user confirmation. DayBook should detect long-term facts or habits from reflections, propose them to the user, and only persist them to user memory upon approval.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented AI Memory Suggestions with human-in-the-loop confirmation:&lt;br&gt;
- Added suggested_memories table with status tracking (pending, confirmed, dismissed).&lt;br&gt;
- Built memory suggestion extraction prompted from recent journal reflections.&lt;br&gt;
- Provided confirmation controls in the UI so users retain complete authority over stored memories.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/db/schema.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;suggested_memories&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;confirmed_memories&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implement deep personalization, context-aware AI reflections, and the knowledge endpoint showing what DayBook knows about the user.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented Deep Personalization &amp;amp; Knowledge Surface:&lt;br&gt;
- Added GET /api/ai/knowledge endpoint aggregating profile facts, confirmed memories, recurring themes, and reflection preferences.&lt;br&gt;
- Dynamically tuned AI reflection tone according to user style (Gentle, Balanced, Direct).&lt;br&gt;
- Ensured all personalization remains strictly local on device.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;server/src/ai/knowledge.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;knowledge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Aggregated&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;knowledge&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;personalized&lt;/span&gt; &lt;span class="nx"&gt;prompting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        16 of 16 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemma&lt;/strong&gt; - Daybook uses &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; as its core AI model, running locally through Ollama to analyze journal entries while keeping personal data on-device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render&lt;/strong&gt; - Used to deploy the parts of Daybook that need to be accessible beyond the local development environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt; - Used for open-source development, CI/CD, collaboration, version control, and sharing Daybook's code with the community.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
