Projects

Four things I built.

Real screenshots, the decisions behind each one, and where to get it.

Hope's Today view: the day drawn as a ring from wake-up to bedtime, with the records underneath

Hope

A calm time tracker for macOS, Windows, iPhone and Apple Watch

  • Tauri 2 + Rust + React on the desktop, native Swift on iPhone and Apple Watch, Supabase in between.
  • About 23K lines and 150 unit tests across Rust, TypeScript and Swift.
  • Desktop installers ship on GitHub; the iPhone and Watch apps run on device. The demo is the real front end with an in-memory backend.
Decisions 4
  1. Pause is not a table. A pause ends the session; resume opens a new one that points back through continues_id.

    Because every statistic stays a plain sum over sessions, and a pause chain is only a UI grouping.

    Cost: the UI walks the chain to draw one bar, and resume is a write, not a flag.

  2. Sync is last-writer-wins on (updated_ms, device_id), a server_seq cursor, and Postgres triggers. No Realtime.

    Because three clients in two languages need one rule they can all implement and test against a fake server, and a five-second pull already shows a watch-started timer on the desktop.

    Cost: a websocket would be faster, and pulls re-read the last 50 sequence numbers to cover concurrent commits.

  3. Class time from an imported timetable stays out of the totals by default.

    Because the ring is about time I chose. A lecture I had to attend is not that.

    Cost: one more setting, and every total on five surfaces has to honor it.

  4. Sub-activities go one level deep through activity.parent_id and take the parent's color.

    Because one level covers Math → homework without turning the picker into a file browser.

    Cost: a third level is a no.

The wallpaper running: the courtyard by day, at dusk, at night and late at night, wind in the hair and leaves, light on the water
The illustrations are AI-generated. The code is mine, written with Codex.

Tingjian

A living desktop wallpaper

  • Five scenes of one courtyard follow the local clock from dawn to deep night.
  • A WebGL shader in wind.js displaces pixels only inside masks: hair, leaves, clouds, lamplight, a slow breath at night. motion.js ripples the water.
  • Triple-click the desk clock for a mood board with a poem. A hidden settings panel. aria-*, inert and prefers-reduced-motion respected.
Decisions 3
  1. Displace pixels inside hand-drawn masks instead of animating whole layers.

    Because one illustration stays one image; the wind touches hair and leaves and leaves the wall alone.

    Cost: every scene needs its masks drawn by hand, and a wrong mask edge shows at once. See the case study.

  2. Scenes switch on local time, with no scheduler to configure.

    Because a wallpaper should be looked at, not set up.

    Cost: a night-shift worker sees the courtyard asleep while they are awake.

  3. Ship a web page for Plash, not a native app.

    Because about 930 lines of HTML, CSS and JS are enough, and the same page runs in any browser.

    Cost: no wallpaper host on Windows out of the box.

Office Flow

An Obsidian plugin for faster task notes, in the community store

  • A CodeMirror 6 editor extension: decorations, inline widgets and keymaps put task buttons right after every unfinished checkbox.
  • An office-progress code-block DSL renders live progress cards for any section of any note.
  • Hold a card's badge to archive a whole section into today's file; hold again to bring it back. Zero runtime dependencies.
Decisions 3
  1. Shift+Enter inserts a task. Plain Enter on a task line only breaks the line.

    Because writing a note under a task is more common than adding a sibling, and Obsidian's default hands you a checkbox every time.

    Cost: a habit to unlearn, and a setting for people who will not.

  2. Archive writes the destination file before it trims the source.

    Because a failed step must never lose a task. A duplicated block is cheap to clean up.

    Cost: a crash between the two writes leaves the block in both places.

  3. A card counts leaf tasks only. A task with children is a group.

    Because 3 of 7 should mean seven things to do, not four things plus three headings.

    Cost: a parent task never counts as done on its own.

ai_status

Claude Code and Codex session status on an Apple Watch

Built, used for a month, parked

  • A zero-dependency Node daemon fed by agent hooks, pushing state over SSE, discovered on the LAN with Bonjour, with a Windows forwarder and a Supabase relay for when I am out; a SwiftUI app on the wrist.
  • Parked because the need went away. It is not maintained and may not run.
  • The model here runs the daemon's real state machine on simulated hook events; the watch is a drawing of the app.

AI workflow

Most of my code is written with AI.

These are the judgments I keep.

Experience

Three places, three years.

  • Backend Lead, part-timeAltheros Capital · Cara AISep 2026 – now

    Promoted to backend lead of an AI companion app after three weeks; designed its RAG ingestion pipeline, review the team's pull requests.

  • AI/ML R&D internXiamen Limayao Network TechnologyJun – Sep 2026

    Rebuilt an untested fine-tuning codebase into a versioned pipeline; QLoRA on Qwen3-4B took validation accuracy from 59% to 88%, and a 2B model shipped at 2.3× the throughput.

  • Software engineering internBeijing Mapvision TechnologyJun – Aug 2025

    An internal full-stack platform on Spring Boot, PostgreSQL and React; normalized 440K brand records into 400K canonical brands.

Now

Starting Relay Studio UCSD, a student club.