Skip to content

Case study / Web application

UnTangler

A voice-first app that helps students with ADHD turn an overwhelming task into small steps, then puts those steps on their Google Calendar.

The problem

UnTangler is for students with ADHD who are facing a task that feels too big to start.

What it had to do

  • Turn that task into small steps, scheduled where the student already looks
  • Make adding a task as easy as saying it out loud
  • Land a spoken time such as “tomorrow at 5” on the right day, in the student’s own timezone
  • Reach the phone with the reminder, including an iPhone with the app installed

What was built

An installable web app (a PWA): a React and TypeScript front end, an Express API and a Supabase Postgres database.

How it works, step by step

  1. The student says a task out loud and refines it by talking, with no restart.
  2. Spoken dates and times are resolved in the student’s real timezone, and can be corrected by hand.
  3. The steps sync to Google Calendar, Tasks and Classroom. Finishing a step is written back to the Calendar event.
  4. An end-of-day check-in asks what got done. Reminders arrive as push notifications and in an in-app bell.

My part

  • The Gemini Live voice-to-schedule pipeline and the timezone-aware date handling.
  • Google Calendar, Tasks and Classroom sync, including the stuck and broken-connection cases.
  • The whole end-of-day check-in flow and its notifications.
  • Installable app behaviour on iOS and Android, an app-wide accessibility audit fixed to WCAG AA, and the CI workflow.

The rest of the team

One other developer and a product owner, who writes the specs in plain language. Every change of mine is reviewed in a pull request before it merges.

What I built, in detail

  • Voice

    Speak a to-do, refine it by talking

    • Gemini Live: say a task and adjust it in conversation, with no restart
    • Short-lived, single-use, model-locked tokens issued by the backend
    • No WebSocket proxy: the backend is one Vercel serverless function
  • Dates

    Spoken times that land on the right day

    • A timezone-aware converter from wall-clock time to UTC
    • The student’s real timezone threaded through every date-resolution path
    • A resolved due date can be corrected by hand
  • Google

    Calendar, Tasks and Classroom sync

    • Task completion written back to the real Calendar event
    • Sync states that got stuck silently, fixed
    • An unreadable Google connection is recovered from, not a crash
  • Check-ins

    The end-of-day check-in

    • Finish-time entry, snooze, daily confirmation and catch-up
    • Web Push and an in-app notification bell
    • A scheduled job (GitHub Actions and a Supabase cron migration) that dispatches due check-ins
  • PWA

    Installable on iOS and Android

    • iOS and standalone-mode detection, install metadata and an install option in the app
    • Fixes for service worker and manifest caching problems
    • Microphone permission fixed in the installed iPhone app, and a mic that connects but never captures audio is detected
    • A real-browser end-to-end suite for iOS and Android push
  • Accessibility

    An app-wide audit, fixed to WCAG AA

    • Very Major, Major and Minor findings resolved
    • WCAG AA colour contrast, ARIA landmarks and touch-target sizes
    • aria-live and role=alert on async status text
    • Consistent focus and disabled states
  • Reliability

    Security and stability

    • The Live token endpoint requires authentication
    • Voice sessions are guarded against re-entry
    • Supabase calls are wrapped in error handling
    • Flaky tests fixed: a time-dependent scheduling test and an extraction test that made real network calls
  • Delivery

    CI, deploys and migrations

    • Set up the CI workflow
    • Sped up deploys by installing frontend and backend dependencies in parallel
    • Fixed production build failures
    • Schema changes go through versioned Supabase migrations

Decisions that shaped it

  1. The browser talks to Gemini directly

    The backend hands the browser a token that is short-lived, single-use and locked to one model. There is no WebSocket proxy to run or pay for, and the real API key never leaves the server.

  2. Reminder times live in the database

    Saved per user, not held in the server’s memory. A value in memory is shared by everyone and disappears whenever a serverless function restarts.

  3. A broken Google connection is something to recover from

    The app treats it as a state to repair and tells the student, instead of crashing or leaving a sync silently stuck.

The outcome

UnTangler is in production and I am still building it. Every feature above shipped through a reviewed pull request.

non-merge commits are mine
160 of 286
pull requests merged
~190
database migrations
19
test files
77
CI workflows
2
Jira tickets assigned to me are done
88 of 89

From the project’s git history and Jira board on 17 September 2026, covering 17 July to 17 September. Both are private, so there is no public link to check them against.

How I work with the team

  1. 01

    Specs become software

    I work from the product owner’s plain-language specs and break each epic into stories and subtasks. I am the reporter on 128 of the project’s 181 tickets.

  2. 02

    Ask before building

    On the weekly summary feature I posted my starting plan and asked for a decision first. When a ticket leaves out a priority or an estimate, I ask instead of guessing.

  3. 03

    Two audiences, two registers

    Technical status goes to the engineers. A plain-language note closes the ticket for the product owner: what changed for the student, not which files.

  4. 04

    Own the gaps

    On a push-reliability ticket I wrote that my first comment only covered half the spec, then reported the rest. I also flagged and fixed a gap my own cleanup could cause.

  5. 05

    Small, reviewable changes

    One pull request holds one coherent change, on a branch off main, with a named reviewer.

What you can check

Employer and product

Compass Decisions Science LLC and UnTangler are named with the company’s permission.

The numbers

The repository and the Jira board are private, so the figures cannot be linked. They were read on 17 September 2026.

What is left out

Colleague names, ticket numbers and internal quotes, on purpose.

Have something like this in mind?

Tell me what you are building. You get a written scope and a fixed price before any work starts.