Skip to content

// 022026

Hatch

A mood tracker whose creature leaves the window and roams your desktop

Role
Sole developer
Stack
Electron · TS · SQLite
Year
2026
Status
Works
Licence
MIT

// Why it exists

I wanted to track my own mood and knew I wouldn’t stick with a form. The mechanic came from a websocket demo that syncs a sprite across browser windows so it can walk between them; I pulled it apart to understand it, then wondered what it would be as a real desktop application rather than a browser toy — a creature that treats your whole screen as its world.

Once the creature can leave the window, the check-in stops being a notification you dismiss and becomes something that walks over to you. That reframing is the entire product idea.

Underneath it is a rule the simulation obeys in code rather than just in documentation: Hatch responds to care and consistency, never to whether you reported a good day. A hard day is logged data. It cannot make your creature unwell, and the creature can never die of neglect — it naps until you come back.

// The hard part

Electron gives you windows. It does not give you a sprite that can be at desktop coordinates (1840, 300) regardless of what’s on screen. So the creature has one position in global desktop coordinates, owned by the main process, ticked at ~30Hz and broadcast over IPC to every renderer. Inside a Hatch window, that window draws it; outside, the main process spawns transparent click-through overlay windows at its location and those draw it instead.

The interesting work was all at the seams, not in the idea: spawn margins so overlays don’t appear directly under the cursor, debouncing so a creature loitering at a window edge doesn’t thrash overlays in and out, and dedupe so two surfaces never draw the same creature at once.

The creature also has eight demeanours, composed at runtime from anchor points rather than drawn as separate sprites — so any palette, pattern or accessory wears every expression without new art.

Eight demeanours composited on one body, labelled
Eight demeanours, one body — faces are composited at runtime

// Decisions

The simulation engine is pure.

It takes events and produces state, knowing nothing about Electron or SQLite. That is what makes a 30-day life cycle testable: you feed it 30 days of events in a loop instead of waiting a month.

Security locked down properly.

contextIsolation on, nodeIntegration off, renderer sandboxed, strict CSP, and every asset served through a custom hatch-asset: protocol handler rather than opening up file:// — necessary because generated art means loading images written at runtime.

Migrations versioned, database copied before each one runs.

A backup is taken before any migration executes, so a bad migration costs a restore rather than the user’s history.

Faces composited at runtime, not baked.

Art generation emits faceless bodies plus a face-anchor table, and all eight demeanours are drawn from that single source, so a mood change never repaints existing art. A check script asserts every face pixel lands on the body.

Session detection with a docked fallback.

Positioning windows at arbitrary screen coordinates works on X11 and XWayland and is restricted under native Wayland, so the app detects the session type and keeps the creature in its own window there. Everything else is identical.

// What didn't work

The plan was to generate all the pixel art with a local diffusion model. It largely failed, and the reason is specific. General-purpose image models produce images that look like pixel art: approximately-aligned blocks, anti-aliased edges, and a palette that drifts between generations. Pixel art needs exact block boundaries and a fixed palette. Sprite sheets need frame-to-frame consistency that independently sampled output cannot give you — every frame is a fresh sample, so the creature subtly changes shape as it walks. It works acceptably for one-off static assets and poorly for anything on a sprite sheet.

The mistake was choosing a general-purpose tool for a problem with hard structural constraints. That’s the transferable lesson, and it’s why the runtime face-composition system exists: it gets guaranteed consistency from one anchor table instead of hoping a sampler stays on-model.

The four life stages: egg, infant, mature, old
Egg → infant → mature → old, then the Meadow and a new egg

// Current state

Works: the main interface, check-ins, the 30-day simulation, progression, Meadow and analytics; desktop roaming on X11 and XWayland; and mock-mode AI, which returns instant placeholder responses with nothing downloaded.

Deliberately deferred: there’s no packaged installer yet — no AppImage or .deb, so you run it from source. Analytics are CSS bar charts, which are adequate for a 30-day window where a charting library would be weight for little gain.

Known rough edges: native Wayland gets docked mode, so the creature won’t leave its window. The AI sidecar’s real-model path is written and wired but not validated end to end — mock mode is solid and is the better experience. Palette customisation is a hue-rotate tint rather than true indexed-colour palette swapping, and some compositors have transparency quirks.

Read the code and the full README on GitHub