ted@career ~ /developer-portfolio

Developer Portfolio

This page will hold engineering work: architecture, code quality, and implementation details from production builds.

Reposait

Apr 2026 - present | Private alpha

A CBT-grounded journaling app with an AI coach that asks instead of tells.

  • Next.js 16
  • React 19
  • TypeScript
  • Supabase + RLS
  • Vercel AI SDK
  • Claude Sonnet
  • Vitest

Reposait comes from the French reposer: to rest, to set something down. You write two or three times a day, and an AI coach answers every entry with a short reflection and exactly one Socratic question. It never names a technique and never diagnoses. I am the builder and the first user.

why_i_built_it

Most people who could benefit from structured reflection never start, or stop within a week. I was one of them. I knew my own patterns but had no practice for working through them, and therapy carries real cost and real activation energy.

  • The blank page asks too much. A warm, specific prompt at the right time of day is what gets you writing.
  • Writing into a void stops working by day three. A response that takes the entry seriously is the feedback loop that makes the habit stick.
  • Existing tools are too clinical or too shallow. Workbooks read like textbooks, and mood trackers flatten a day into a number. I wanted the technique to be invisible: CBT, ACT, and positive psychology in practice, never in vocabulary.
  • I wanted to learn agentic engineering on something real. A product I use every day, holding private writing, forces the decisions a demo never does: data boundaries, latency, cost, and prompt quality over time.
how_i_built_it

Next.js 16 App Router on Vercel, Supabase for auth and Postgres, and Claude through the Vercel AI SDK. Sixteen migrations, 88 test files, and a decision log that records why, not just what.

  • Row-level security is the security boundary. Application code does not filter user data; Postgres does. The service-role key appears only where the server writes on a user's behalf, and every use carries a comment saying why.
  • One provider seam. Every model call goes through lib/ai/provider.ts, the only file that imports the Anthropic SDK. Swapping a model or a provider is a one-file change.
  • Structured output over parsing. Coaching comes back through generateObject against a Zod schema, which replaced a brittle 40-line JSON parser.
  • The user's local date, not UTC. Entries group by a date computed in the user's time zone, because an entry written at 10pm in Chicago belongs to today. Getting this wrong was the first real bug.
  • Rhythm is a display filter, never a data filter. Users pick two or three sessions a day, and switching never drops an entry.
building_with_claude_code

Claude Code was the primary implementer. My job moved to defining the problem, setting constraints, and verifying the result, and what made that work was treating the agent's context as an engineering artifact.

  • CLAUDE.md is the contract. Architecture rules, naming conventions, scope guards, and known mistakes live in one file every session reads first. When the agent drifts, the fix is usually a sentence there, not a correction in chat.
  • Memory carries feedback across sessions. Two rules I set early still hold: tests are written alongside code, not after, and schema designs are reviewed before any SQL is written. Both exist because I caught the agent skipping them once.
  • Specs before code, UAT after every feature. Each feature starts as a GitHub issue with acceptance criteria and ends with me clicking through it in a browser. Green tests prove correctness in isolation; the browser proves it works.
  • A decision log the next session can trust. Every reversed decision is recorded with its reason, so the agent does not re-propose an idea that was already rejected.
  • Multi-agent review, then delegation. Before beta I ran a 62-agent adversarial review across security, one-way doors, duplication, and dead code. 51 findings survived verification, and a skeptic pass talked an initial six "high" findings down to zero. The fixes became a wave-based plan of self-contained briefs, each sized for one context window, sequenced by file contention rather than by topic.
agents_in_production

Reposait runs coaching, summarization, pattern detection, and a tool-using goal coach. Each one taught something different about putting a model in front of real users.

  • Context engineering beats a bigger context window. Coaching first sent 14 days of raw entries with every call. Now a background agent rewrites a therapist-style summary every seven entries, and coaching assembles three layers: that summary, a recurring-themes index, and the two latest entries truncated to 500 characters. Input tokens fell about 40% and the questions got sharper.
  • Agents stay off the critical path. Summarization and pattern detection run through Next.js after() once the entry is saved. A failure is logged and never blocks the write.
  • Not everything needs a model. The morning prompt used to be generated and added two seconds to the first screen of the day. A static "Good morning, Ted. What's on your mind?" did better.
  • Give the agent an action, not a field. The goal coach first returned a settledField key in structured output, and the model left it empty while asking one more question. Rebuilt as a tool-using agent with suggest_field, offer_options, and complete_goal, it had a forcing function, and the form started filling.
  • Propose, don't commit. The coach suggests a starting value for each goal field and the user accepts or edits it. Nothing the model writes becomes the user's without their hand on it.
  • Probe live before wiring UI. A live run of the tool-using coach caught it calling tools and returning no text, which would have rendered an empty bubble every turn. One prompt line and a fallback reply fixed it before any UI depended on it.
  • Guardrails are versioned like code. One question, never name a technique, warmth without praise. Each prompt exports a version string, so a change in coaching quality can be traced to the prompt change behind it.

n3xtgig

Prototype | Built for the Codility GTM team

A prototype applicant tracking system that shows how Codility integrations work, running against the real Codility API.

  • Next.js
  • React
  • TypeScript
  • Postgres (Neon)
  • Drizzle
  • Tailwind
  • dnd-kit
  • Zod
  • Vitest
  • Playwright
  • Codility API

n3xtgig is a working applicant tracking system I built for Codility's go-to-market team to demo how an assessment provider plugs into a recruiting workflow. A recruiter drags a candidate into the assessment stage, an invite goes out through the real Codility API, results come back by webhook, and they render as configurable widgets on the candidate profile.

why_i_built_it

A slide cannot show what an integration feels like. The goal was a Solutions Engineer running a ten-minute demo with no prep, never leaving the app, and never explaining away a rough edge.

  • It has to feel like a real product. Everything beyond the assessment flow exists to make the story credible: a kanban pipeline, a careers page, a candidate drawer, a command palette.
  • Integrations are glass boxes. The presenter needs the machinery, so every automated action shows what was sent, what came back, and when.
  • Safe to run live. Stage triggers default to confirm mode, and a Demo menu resets the seeded data.
how_i_built_it

Next.js with Postgres on Neon and Drizzle, deployed on Vercel. The code is layered so the integration logic stays separate from the screens.

  • Enforced layers. Pure domain types and the assessment state machine sit apart from the server services, and only repositories import Drizzle. ESLint enforces the boundaries.
  • One connector interface, two implementations. The Codility connector is plain fetch; the Mock connector makes the whole demo work offline.
  • Missed callbacks are covered. A reconcile endpoint fetches stale sent and in_progress sessions, and the same code backs a Refresh from provider button.
  • Tested where it can fail. Vitest covers connector fixtures, the state machine, and the webhook; a Playwright smoke test covers the happy path.
demo_design

The demo controls are part of the product, not an afterthought.

  • Simulated results use the real path. Simulate result POSTs the payload to the app's own webhook, so the inbound handler, status change, and widgets all run for real.
  • Widgets are configurable. Presenters toggle and reorder result widgets, and the layout persists workspace-wide.
  • Disposition in one motion. Moving a candidate is a drag or a keystroke, with an undo toast instead of a confirm.