Project 012026Web platform and point of sale
Accounting and point-of-sale for Australian small businesses, with about 2,700 automated tests.
- Context
- Australian small businesses juggle a till, a bookkeeper and the tax office. Ahexon puts double-entry bookkeeping, BAS and GST reporting, payroll tax, bank reconciliation and a point-of-sale system with a kitchen display in one web platform, linked to Xero, QuickBooks, MYOB and Square, with an Expo app that keeps selling offline.
- Role
- I designed the product and the data model and built it with AI coding agents: 73 pages, 50 API routes and 37 database migrations across 257 commits from May to October 2026, with about 2,700 automated tests over the tax and ledger logic.
- Stack
- Next.js 16, React 19 and TypeScript; Supabase (Postgres), Clerk, Stripe, Resend and Twilio; Vitest. The mobile point of sale is Expo with SQLite.
The hard problem
Square money, ledger money
Square takes GST-inclusive card sales, refunds and fees, then pays a lump sum into the bank days later. Each of those has to land in a double-entry ledger balanced to the cent, with GST pulled out correctly for the BAS, and a sale must never post twice when a sync is re-run or dies halfway. I routed everything through a Square clearing account: a sale debits clearing and credits revenue and GST payable, a fee debits fees, a payout moves clearing to the bank, so clearing nets to zero when the money arrives. The journal lines come from pure functions with no database access. GST uses Square's own figure when it exists and the one-eleventh rule when it doesn't, and the net is the total minus the GST, so every entry balances by construction, and a balance check runs before anything is written. The sync only picks up rows without a ledger stamp, stamps them after posting, and deletes an orphaned journal if its lines fail. Fifty-two tests cover it. The next step is moving that guarantee into the database itself, with a unique constraint and a single transaction.
Project 022026Desktop voice assistant
A voice-controlled AI assistant for macOS: about 90,000 lines and 840 tests in six weeks.
- Context
- Dante is a voice-first assistant for my Mac. Say the wake word and it answers out loud, reads my calendar and email, plays Spotify, places calls and controls the computer behind a guard that stops destructive actions, with a phone app that syncs through Supabase. It started as my HSC Enterprise Computing major project and grew far past the brief.
- Role
- I designed the system and the voice pipeline and built it with AI coding agents as pair programmers: about 90,000 lines and 840 tests, in 660 commits between 18 May and 28 June 2026.
- Stack
- Electron, React and TypeScript; openWakeWord and faster-whisper running locally in Python; Groq with an Ollama fallback; Edge TTS with ElevenLabs and Fish Audio fallbacks; Supabase; Vitest.
The hard problem
Talking back in about a second
At first every stage waited for the one before it: recognition waited for silence, the model wrote its whole reply, the speech engine rendered all of it, and only then did any audio play. Each wait was small; together they turned a conversation into a batch job. I overlapped the stages. Speech is streamed: each audio chunk crosses from the Electron main process to the window on its own IPC channel and is appended to a MediaSource buffer, so playback starts on the first chunk. The model's reply is split into sentences as it streams, and each finished sentence is spoken while the next is still being written, over one Edge TTS connection kept open instead of reopened per sentence. A model call that only classified the request gave way to keyword routing, Groq runs first with a local fallback that fails fast, and the speech recogniser stays loaded between utterances. In my own testing a typical reply now starts in about a second, down from two to twelve. Talking over Dante cuts it off.
Project 0320263D game
A playable 3D game that teaches Model UN procedure, with delegates who vote by clause.
- Context
- Model UN is mostly learnt by sitting in committee and getting procedure wrong in front of people. MUNGAME is a playable 3D prototype that teaches it first: four resolution committees and a crisis committee, from roll call through debate, amendments and voting, against delegates the computer controls.
- Role
- It is my personal prototype. I designed the rules and the game and built it in Unity with AI coding agents across 19 commits in September and October 2026: about 9,900 lines of C# and 62 edit-mode test cases, with builds for macOS, Windows and the browser.
- Stack
- Unity 6 with the Universal Render Pipeline, C#, the Input System and the Unity Test Framework (NUnit).
The hard problem
Delegates who vote on the worst clause
If a delegate scores a resolution by averaging how it feels about every clause, one bad clause disappears among the good ones: the draft passes too easily, or no single amendment can rescue it, and either way the game teaches the wrong lesson. So every country has stance axes, every clause carries stance tags, and a vote happens in two passes: first on the average of the operative clauses, then on the worst one. Below one threshold a yes becomes an abstention; below a stricter one it becomes a no whatever the rest says, which is exactly how a permanent member vetoes in the Security Council over a single clause. Amendments are judged only by what they change against the current text, so the same amendment twice does nothing. One evaluator runs every voting rule: simple majority, consensus where any objection blocks the final text but amendments pass by majority, and the Security Council's nine votes with no veto. Because the rules are plain C# with no scene attached, the balance is tested directly, including a test that no single amendment wins everyone over.
Project 042026Client website
The live website for a real 2026 pilgrimage to Mount Kailash and Lake Manasarovar.
- Context
- A fifteen-day pilgrimage in July 2026 to Mount Kailash and Lake Manasarovar in Tibet. The organisers needed one place for pilgrims to read the itinerary, the price and how to get in touch, and it had to stay right as the plan changed.
- Role
- I designed and built the site and deployed it at mightyhimalaya.com. I kept the itinerary in step with the organisers as it changed, and moved the site to its new domain with permanent redirects from the old one.
- Stack
- Vite, React, Tailwind CSS, GSAP ScrollTrigger and Lenis, hosted on Vercel.
The hard problem
One card, three acts, one scroll position
The centre of the site is a single card that changes shape as you scroll: a tall portrait for the peak, a landscape for the lake, a wide strip for the kora walk around it, with its tilt, colours, parallax and text changing too. Everything runs off one ScrollTrigger over a 300vh section with a CSS-sticky stage. Its progress is split into three bands, and the work is split by speed: the tilt, parallax and progress bar are written every frame, while the act change (layout classes, content, colours, the text entrance) runs once, behind a guard, so it never re-fires mid-scroll. Lenis drives the loop from the GSAP ticker. When I split the original 2,692-line page into 13 React components, all of it moved into one effect whose cleanup destroys Lenis and kills every trigger, so nothing leaks between renders.
Project 052026Learning web app
A learning platform that turns books and courses into a curriculum and plans every day of study.
- Context
- A personal learning platform. It turns books and courses into a curriculum of concepts and exercises, plans every day of study, tracks mastery and sends a daily push nudge. It runs as a web app you can install, and sign-in is invite-only.
- Role
- I designed and built it with AI coding agents across 69 commits from July to September 2026: 52 database models, 88 API routes, AI-generated lessons and exercises, and a curriculum of about 5,700 concepts across 28 subjects.
- Stack
- Next.js 16, React 19 and TypeScript; Prisma on PostgreSQL; Better Auth with Google sign-in; the Vercel AI SDK with Gemini, Anthropic, OpenAI and Groq; web-push and Vercel Cron.
The hard problem
A study plan with no randomness in it
The scheduler has to fit every unstarted concept into the timeline the learner chose, split each day's slots by subject priority, fit reviews into every day, let a hyperfocus mode hand all the new-concept slots to one subject for a while, and never schedule a concept twice. I kept it deterministic, with no AI in the loop. The daily pace is the unstarted concepts over the timeline, with 15 per cent of days held back for review alone. Subjects get slots in proportion to their weight and the leftovers go round-robin from the heaviest down. Plans are built thirty days at a time with a position marker per subject, every concept already planned is excluded, and each day is written as an upsert on schedule and date, so the whole thing can safely run again. Reviews are never stored: they are worked out live from each concept's mastery, which sets an interval from one day to thirty, and ranked by an urgency score built from mastery, time overdue and error rate.
2026Security
Two podiums, one invite-only call-up, my own capture-the-flag challenges, and a real leak I reported instead of used.
Capture the flag
PECAN+ CTF
- 1stof all the ADF teams
- 4thin the Advanced division
- 8thnationally
- 2ndin NSW
- 1stat the Wollongong venue
- CSECcon CTF, UTS
- 3rd place, CSECcon CTF, UTS (2026).
- ADF Cyber Skills Challenge
- Invited to the ADF Cyber Skills Challenge (invite only, November 2026). adfcsc.au
- My own challenges
- I write my own CTF challenges. One boots an old Linux kernel in QEMU, so the privilege escalation you work through is real, not simulated.
Responsible disclosure, September to October 2026
- What broke
- A live startup's site exposed its paying customers' email addresses and its paid content. Row-level security on its Supabase database was not restricting reads, so the public key that every visitor's browser is given could read those tables.
- How I found it
- I noticed it while studying how the site's front end was built. To confirm it, I recorded only row counts and field names. I did not read or keep any values.
- How I reported it
- Privately, to the founder, with the fix written out. I did not publish it, and I did not use it. The founder thanked me.
- The fix
- Row-level security on every table, policies that grant each role only what it needs, and the paid content served only through an authenticated route.
The company is not named here, on purpose. Finding it was the easy part; the skill is in what you do next.