Course
Welcome back! In the first tutorial of this series, Devin dusted off my old fp-ts-exercises repo and opened a neat dependency-upgrade PR. In this second part, we’re cranking up the difficulty and building full-stack features.
You can access all the tutorials in the Devin series here:
- Setup and First Pull Request (Part 1)
- Shipping a Vertical Slice with Devin (Part 2)
- Integrations, Testing, and CI/CD (Part 3)
- Security, Deployment, Maintenance (Part 4)
By the time you scroll to the bottom of this second part, you’ll have:
- A Next.js 14 playground, where learners edit code in-browser;
- Live Vitest tests that turn green (or red) in real time; and
- A NestJS with SQL database backend that saves progress with nothing more than an anonymous UUID in localStorage.
Along the way, we’ll let Devin generate its own task list, watch it juggle front- and back-end tickets, and step in when it trips over config gremlins. I’ll show you where I spent (and saved) ACUs, what I fixed by hand, and how to keep sessions efficient.
Recap and Prerequisites
Throughout this tutorial series, we're working on my (formerly) abandoned fp-ts exercises repo.
The project is a collection of small challenges meant to teach functional programming concepts (like Option, Either, etc.) in TypeScript. Right now, it’s a clone-and-run repo: learners pull it down, edit files locally, and run the tests in their own terminal.
The goal is to turn those exercises into a browser-based, interactive playground where people can read an explanation, tweak code in an in-page editor, and see the tests pass (or fail) instantly. Learners will also have the opportunity to log in to save their progress.
What we accomplished in the first part
Recall that in the first tutorial, we:
- Modernised the old fp-ts repo: latest dependencies, linting, type-checks.
- Connected Devin to GitHub and proved it can open a clean pull request.
Local tools you need for the second part
For this second tutorial, we’ll need:
- Node 20+ and npm 10 (comes with workspaces support out of the box).
- Docker running a PostgreSQL container (or a local PostgreSQL instance).
Recommended Devin settings
I recommend the following settings for Devin:
- Set a soft cap of 5 ACUs per session in Settings > Usage & limits; we’ll spin up a fresh session for each phase.
- Keep the timelapse tab visible to pause or nudge Devin if it stalls.
Let Devin Write the Game Plan
I thought it would be interesting to start by testing Devin’s planning capacities. I created a prompt and asked Devin, using the “Ask” feature (currently free in Beta), to help me plan the work we’ll be doing today.
Prompt:
I want to turn my existing fp-ts-exercises Node + TypeScript repo (currently clone-and-run CLI exercises) into a browser-based learning platform that tracks progress.
Target experience:
- Learners open a Next.js web app, choose an exercise, edit code in-browser, and see Vitest tests pass or fail instantly.
- Progress (completed slugs) is stored server-side and reloaded on later visits, but we’re not adding user accounts yet (use an anonymous UUID stored in the browser).
Tech choices I’m leaning toward:
- Frontend: Next.js 14 (React 18, App Router)
- Code editor & runner: Sandpack + @vitest/browser
- API: NestJS 13 with @nestjs/graphql (Apollo Driver)
- Database: Prisma 5 with PostgreSQL
- Workspace: plain npm workspaces (apps/web, apps/api, packages/exercises)
Propose a step-by-step implementation plan (task list) that brings this vertical slice to life. Estimate the relative ACU cost for each task and flag any long-running steps (e.g., large migrations or heavy dependency installs).
Devin helpfully returned a six-phase task list with ACU estimates for each, and flagged “heavy dependency install” steps.
Devin’s answer:
Phase 1: Workspace Setup (2–3 ACU)
- Initialize npm workspace structure (1 ACU)
- Create apps/web, apps/api, and packages/exercises directories
- Configure root package.json with workspace definitions
- Move existing exercise files to packages/exercises/src/
- Setup Next.js 14 frontend (1–2 ACU — Long-running: dependency install)Initialize Next.js with App Router in apps/webConfigure TypeScript and Tailwind CSSSetup basic routing structure
Phase 2: Exercise Package Migration (3–4 ACU)
- Extract exercise content to shared package (2 ACU)
- Create packages/exercises with exercise metadata extraction
- Parse existing exercise files to extract:
- Instructions
- Starter code
- Solutions
- Define TypeScript interfaces for exercise structure
- Implement exercise discovery system (1–2 ACU)
- Build file system scanner (like fs.readdirSync logic in exercise.js:17–28)
- Export exercise metadata: title, description, difficulty, tests
Phase 3: Backend API (4–5 ACU)
- Setup NestJS API with GraphQL (2 ACU — Long-running: dependency install)
- Initialize NestJS project in apps/api
- Configure Apollo GraphQL server
- Setup basic health check endpoints
- Implement Prisma + PostgreSQL (2 ACU — Long-running: database setup)
- Design schema for:
- User sessions
- Completed exercises
- Setup Prisma client and migrations
- Create progress tracking resolvers
- Anonymous user session management (1 ACU)
- Generate UUID and store in browser localStorage
- Enable session-based progress persistence without authentication
Phase 4: Frontend Core Features (5–6 ACU)
- Exercise browser interface (2 ACU)
- List exercises by category (option, either, task, etc.)
- Show progress indicators and completion status
- Enable exercise selection and navigation
- Sandpack code editor integration (2–3 ACU — Long-running: Sandpack setup)
- Configure Sandpack with TypeScript support
- Load starter code and fp-ts dependencies
- Setup file system for exercise + test files
- Vitest browser runner integration (1 ACU)
- Replace execSync Vitest calls (exercise.js:45–47) with @vitest/browser
- Enable real-time test execution and result display
Phase 5: Progress Tracking (2–3 ACU)
- Client-side progress management (1 ACU)
- Setup Apollo Client for GraphQL
- Sync progress with backend
- Exercise completion detection (1–2 ACU)
- Adapt existing success detection logic (exercise.js:48–49)
- Trigger backend progress updates on test pass
Phase 6: Polish & Deployment (2–3 ACU)
- UI/UX improvements (1–2 ACU)
- Ensure responsive design
- Add loading states and error handling
- Improve exercise instructions display
- Production deployment setup (1 ACU)
- Configure Docker for both apps
- Manage environment variables
- Set up database deployment
Total Estimated Cost: 18–24 ACU
The plan was pretty good overall. I changed some things, notably in phase six, since we’ll tackle deployment in the fourth tutorial.
Phase 1: Workspace & Front-End Scaffold
Goal: Spin up npm workspaces, move the legacy files, and bootstrap a blank Next.js 14 shell.ACU burn: 3.66 (planned 1–2).Outcome: Repo now has /apps/web
, shared packages/exercises
, and a Tailwind-ready Next.js app running on localhost:3000
.
The prompt
I asked Devin to build the plan it made for the first phase. Agentic Devin has no context of what you previously might have asked in the “Ask Devin” feature, so you have to create a separate prompt and provide context.
I won’t be pasting all the prompts in this article, but here is the first one:
Prompt:
Task: Initialize npm workspace structure and Next.js frontend for fp-ts-exercises browser platformContext:
I'm converting my existing fp-ts-exercises CLI-based learning system into a browser-based platform. The current system has exercises organized in 10-from-either.exercise.ts:1-3 with TypeScript configuration at tsconfig.json:88 and Vitest setup in vitest.config.ts:7 .
Requirements:
- Create npm workspace structure:
- Root package.json with workspace configuration for apps/web, apps/api, packages/exercises
- Move existing exercise files from current src/ to packages/exercises/src/
- Preserve the current exercise file naming convention (.exercise.ts and .solution.ts)
- Initialize Next.js 14 frontend in apps/web:
- Use App Router (not Pages Router)
- Configure TypeScript with similar strict settings as current tsconfig.json:88
- Add Tailwind CSS for styling
- Create basic routing structure for exercise browsing
- Maintain compatibility:
- Ensure existing exercise structure and imports still work
- Keep fp-ts dependencies accessible across workspace
- Preserve current Vitest configuration patterns
Deliverables:
- Workspace-configured package.json at root
- Next.js app in apps/web/ with TypeScript and Tailwind
- Migrated exercises in packages/exercises/
- Basic routing structure (/exercises, /exercises/[category], /exercises/[category]/[slug])
Tech Stack:
- Next.js 14 with App Router
- TypeScript (strict mode)
- Tailwind CSS
- npm workspaces (not yarn/pnpm)
Submission:
- Ensure lint and typecheck script pass
- Create a PR with your changes
All of the prompts I subsequently wrote followed the same pattern: task highlight, context, requirements, deliverables, tech stack, and submission details.
What Devin actually did
It took a fairly long time deciding its course of action (around four minutes), but it:
- Created the workspace skeleton
- Migrated all exercise folders into
packages/exercises/src
. - Bootstrapped Next.js 14 with the App Router, TypeScript, and Tailwind.
- Added lint & type-check scripts to the frontend app (after I nudged it).
Why the ACU cost spiked
The ACU cost spiked because:
- Devin spent a full minute looping on
npm install
retries after a mirror timeout. - It attempted to stop the devs server and processes with
q
in Vim, thenCtrl-C
, and both failed. I gave it a little time to try and fix it itself, but ended up manually intervening and exiting the processes both times. Every extra minute meant extra ACUs, and I wouldn’t have caught this issue quickly if I hadn’t been following Devin’s actions closely.
Human interventions and lessons learned
Here’s a summary of the fixes that I needed to do:
Fix |
Time |
How I did it |
Killed hanging node watch task |
15 s |
Killed process in the VM shell. |
Re-installed Tailwind |
90 s |
npm install tailwindcss postcss autoprefixer --workspace=apps/web and reran tailwind init. |
Added .next/ to .gitignore |
10 s |
Committed manually to prevent bloated PRs. |
Moving large file trees is faster by hand, and a lot less expensive since Devin charges by time/task. I dragged the folders around, committed, and let the agent pick up the new structure. It was able to pull my changes just fine and do a merge by itself!
That said, I had to prompt it to run the lint and typecheck scripts, even if I had specified it in my initial prompt. In the end it ran both of them, but they failed. Devin still considered it a success and went along with the PR.
I fixed both issues locally, ran the scripts myself and ran the new web development server to make sure it started as intended, and pushed the commit.
Phase 2: Packaging the Exercises
Goal: Extract all challenge files into a reusable packages/exercises
package and generate metadata for each exercise.ACU burn: 0.6 (planned 2+).Outcome: Shared package exports typed metadata that the web app and future API can import with import { exercises } from '@exercises/registry'
.
What Devin actually did
Devin created:
- Structured TypeScript interfaces for exercises, metadata, categories, and the full catalog.
- Exercise parser that:
- Extracts metadata from code comments and function signatures
- Parses starter code, solution code, and Vitest test cases
- Auto-generates tags from
fp-ts
usage - Converts camelCase names to readable titles
- Exercise discovery system that:
- Scans the file system for
.exercise.ts
and.solution.ts
pairs - Builds a slug-based catalog (for example,
option-01
) - Organizes exercises by category with caching for performance
- Convenient exports for querying exercises and categories:
getExercises()
,getExerciseBySlug()
,getExercisesByCategory()
,getCategories()
,getCategoryBySlug()
Why Devin finished under budget
Devin managed to keep the ACU cost low—here’s how it did this:
- No heavy installs or network calls
- File moves were straightforward; Devin used
git mv
(cheap) instead of copy-paste plus diff - Metadata generation was pure TypeScript, fast to plan and execute
Human interventions
None this time around!
Learning from last time, I checked out Devin’s branch and ran the scripts locally. Everything compiled and the registry exports clean type definitions, which makes it perfect input material for the UI and API we’ll build next.
Phase 3: NestJS + GraphQL API
Goal: Stand up a NestJS 13 service that exposes exercise progress via GraphQL and persists data in PostgreSQL with Prisma.ACU burn: 4.2 (planned 4).Outcome: apps/api
boots on localhost:4000/graphql
. You can query and mutate progress records keyed by an anonymous session ID.
What Devin actually did
For the third phase, Devin:
- Created a full NestJS API in
apps/api
with TypeScript and Apollo GraphQL - Integrated PostgreSQL with Prisma ORM and set up schema with
Session
andCompletedExercise
models - Implemented anonymous session management using UUIDs (no auth required)
- Built GraphQL resolvers for session creation/validation, exercise queries, and progress tracking
- Connected backend to shared exercise package (
packages/exercises
) for data discovery - Configured CORS to support communication with the Next.js frontend
- Ensured type compatibility between the GraphQL schema and existing TypeScript interfaces
- Set up development scripts, database migration flow, and Prisma Studio integration
Where Devin stumbled
These are the issues I encountered during the third phase and the fixes I had to make:
Issue |
Fix |
Time |
Forgot |
Prompted Devin: “I am running into this error when trying to run The 'import.meta' meta-property is only allowed when the |
1 ACU, 5 minutes |
Lint script missing |
Prompted Devin: “Add lint script to api package and make sure it passes.” |
1 ACU, 4 minutes |
Accidentally committed |
|
20 s |
Lessons learned
These are my takeaways:
- Config vs. code: Devin excels once the framework boilerplate is in place, but diagnosing dependency issues still needs a human eye.
- Always run locally: I had already created a local PostgreSQL database on my machine, and the PR instructions to run the Prisma migration and start the dev environment were clear and easy to follow (see the image below). Devin’s comment said it succeeded in running the API locally, yet
npm run dev
crashed untilreflect-metadata
was added.
- Devin is good at handling PR comments. I commented on the PR, explaining to Devin that I had tried to run the API locally and encountered an error. It immediately gave me a little eye emoji, and the session resumed by itself to fix the error, and a fix was pushed a few minutes later.
- Always check for secret files: Devin had committed a
.env
file with aDATABASE_URL
variable, which contained a dummy username and password. Once pulled locally, I changed the username and password to my local database identifiers and made sure to remove the file from the git tracking system and add it to the.gitignore
. We will see in future tutorials how to handle secrets safely.
Phase 4: Sandpack Playground & Live Tests
Goal: Give learners a browser editor that runs fp-ts solutions and Vitest checks in real time.ACU burn: 7.2 (planned 3-4).Outcome: Visiting /exercises/option-01
opens a split view: Sandpack editor on the left, test tunnel on the right, and a success message pops up the moment all specs pass.
What Devin actually did
Here’s what Devin did for the fourth phase:
- Built exercise listing and navigation pages with dynamic category and slug support
- Integrated Sandpack editor with TypeScript, fp-ts, and real-time file system support
- Replaced CLI test runner with in-browser Vitest and live feedback
- Connected frontend to backend via Apollo Client for session-based progress tracking
- Implemented responsive UI with loading states and error handling
Where Devin burned extra ACUs
Devin burned more ACUs than expected for this phase—here’s why:
Cause |
How I fixed / mitigated |
Left stale CLI runner code in |
Deleted |
Waited for feedback but didn’t ask explicitly → idle loop. |
Typed “Looks good, continue.” in chat after three minutes of silence. |
Lessons learned
My takeaways this time are:
- Watch silently = burn money. Keep the Timelapse tab open, and if Devin loops, nudge it or pause.
- Delete dead code early. Devin doesn’t really delete stale code by itself, unless you tell it to. I made the mistake earlier to ask it to move files without explicitly telling it to delete them, and so we ended up with multiple entry points that greatly confused poor Devin in this phase.
Devin did take some time here. It looped and looped, trying to save the same issues, not connecting the backend and frontend properly, making changes, restarting the servers, checking in the browser if it worked, etc. It was just like regular development work, I suppose…
We now have a functional app. I was able to launch both the API and web servers, navigate to localhost, edit the stub function, press Save, and watch the test pane flick from red to green. I then realised it only did the work halfway, and hardcoded the first exercise’s test and solution on every exercise page. I pointed it out, and after more loops, it was able to correct it and push its fixes.
Phase 5: Progress Sync With UUID
Goal: Persist each learner’s completion status and repaint the sidebar on reload, all without user accounts.ACU burn: 2.7 (planned 2–3).Outcome: A tiny React hook now stores a browser-generated UUID, calls the GraphQL API on every test pass, and pre-loads progress whenever the playground mounts.
What Devin actually did
For the fifth phase, Devin:
- Integrated Apollo Client for GraphQL communication between the frontend and the backend
- Implemented anonymous session management with UUIDs stored in localStorage
- Synced real-time progress data and visual indicators at both category and exercise levels
- Detected exercise completion by hooking into the in-browser Vitest test runner
- Added offline support, optimistic updates, and robust error handling with retry logic (which I never asked for).
One of the wins is that Devin added optimistic-UI update so badges flip to ✅ instantly. This mattered because it keeps the interface snappy even on high-latency connection.
Lessons learned
My takeaways are:
- Small scope means minimal errors: This was quite a small task and tightly scoped, so Devin executed it well and within a good timeframe.
- Say what you want, but also what you don’t want: I noticed that Devin has a tendency to try and extend scope, and once again it gave me more than what I asked for. In this case, I didn’t mind, but if there is something that you really don’t want it to do, then explicitly say it. For instance, in every single prompt, I specified “no login functionality” because I figured it was going to try and go there.
We now have proof that our anonymous session pipeline works end-to-end. Time to make it look good in the final polish phase.
Phase 6: UI Polish & Progress Dashboard
Goal: Transform the utilitarian interface into a pastel, “airy” experience and add a dashboard that visualizes every learner’s streak, difficulty ladder, and weekly activity.ACU burn: 11 (planned ≈ 3).Outcome: Light / dark pastel themes, redesigned exercise cards, and a live dashboard showing rings, bars, and streak counters. Devin achieved the broad strokes, but I stepped in to rescue naming mismatches and rogue dependency swaps.
In this phase, I deliberately extended the scope to see how Devin would handle a larger set of requests. I actually thought it would be a lot easier for Devin to tackle than phase 3, with all the backend implementation. Spoiler alert: I was wrong.
What Devin tackled from the prompt
This is the breakdown of my requirements and Devin’s output:
Requirement |
Devin’s output |
Pastel design system |
Added Tailwind CSS variables for soft-blue primary, mint success, lavender accents. Set |
Typography & spacing |
Introduced a |
Progress dashboard |
New route
|
Enhanced cards |
Replaced |
Catalog expansion |
Generated 10 more stub exercises with narrative names and placeholder templates. Metadata includes difficulty, estimated time, and category. |
Where Devin went wrong
Things didn’t go perfectly—Devin:
- Changed exercises package file names without updating the wrapper or resolver → runtime 500s; patching +1 ACU.
- Quietly replaced Postgres with SQLite “for easier local dev” without asking for my go-ahead. I wouldn’t have known if I hadn’t been watching.
- Left dozens of
console.log
statements in React components, and lint checks kept retrying.
Lessons Learned
My takeaways:
- Dashboard logic still needs work: Devin wired streak and time-spent counters but left them non-functional. Back-end metrics should have at least one integration test so the “happy-path” UI isn’t merged prematurely.
- ACU usage accelerates in long sessions: After ~8 ACUs, the session slowed noticeably. Short, capped sessions (< 5 ACUs) keep context tight and the agent responsive.
- Devin will “helpfully” swap dependencies: It replaced Postgres with SQLite without asking. Pin critical services in your prompt: “Do not change the database engine; stay on Postgres.”
- Design tasks trigger hot-reload churn: Every Tailwind/Vite refresh counts as agent time. Perhaps a better way to go about it is to pause the session before mass-editing styles, then wake Devin when the project compiles cleanly.
That task was long and costly, compared to the rest of the work we did. At least Devin is aware of where things went wrong:
We do have something pretty, but it took a bit of time and isn’t fully functional. Some of the dashboard metrics are hardcoded—two exercises in our option category? We have 10. The streak and time spent metrics are also not hooked up to anything, and the UI on the individual exercises pages is pretty bad.
Cost & Time Retrospective
How the numbers turned out
Here’s a summary of how it all went:
Phase |
Planned ACUs |
Actual ACUs |
Delta |
Notes |
1 – Workspace & scaffold |
1 – 2 |
3.66 |
▲ ≈ 1.7 |
Extra loops on |
2 – Exercise package |
2 |
0.60 |
▼ 1.4 |
Simple file moves; no heavy installs |
3 – API & Prisma |
4 |
4.20 |
▲ 0.2 |
Config wrinkles (missing |
4 – Playground & tests |
3 – 4 |
7.20 |
▲ ≈ 3.5 |
Hot-reload cycles, merge-editor stall |
5 – Progress sync |
2 – 3 |
2.70 |
▼ ≈ 0.3 |
Straightforward hook + queries |
6 – UI polish & dashboard |
2 – 3 |
11.00 |
▲ ≈ 8 |
Re-installs, silent DB swap, large CSS reloads |
Total |
18 – 24 |
29.36 |
▲ ≈ 6 – 11 |
Roughly 35 % over the initial estimate |
Cost: 29.36 ACUs × $2.25 ≈ $66 |
Time saved vs manual work
This my estimation of time saved:
Task |
Human estimate |
Devin (+ fixes) |
Net |
Bootstrapping NestJS + GraphQL + Prisma |
4 hrs |
1 hr (inc. fixes) |
−3 hrs |
Sandpack + Vitest browser wiring |
3 hrs |
1 hr 20 min |
−1 h 40 min |
Full pastel redesign & dashboard |
6 hrs |
2 hrs (but still not functional) |
−4 h |
Total time |
~13 hrs |
~4 hrs 20 min |
-8 hrs 40 min |
Key takeaways
These are things I recommend:
Theme |
What to do |
Why it matters |
Keep sessions short |
• Cap every run at ≤ 5 ACUs. • Split big goals into bite-sized tickets. |
Sessions > 8 ACUs are slow, and error rates jump at ≈35 %. Short bursts stay responsive and cheaper. |
Nudge early, pause often |
• Watch the Timelapse tab; if Devin loops, type “Continue” or “Sleep”. • Pause while you mass-edit CSS or docs, then wake Devin once the code compiles. |
Idle loops still tick the meter; hot-reload churn during design work is pure ACU burn. |
Treat config as manual work |
• Move/rename big file trees yourself (drag-drop beats 0.8 ACU scripts). • Fix first-time package errors manually, retries are ACU sinks. |
File I/O and repeat installs account for ~30 % of wasted credits. |
Lint & type-check locally |
• Always run your test scripts (in our case, lint and typecheck) before merging. • Add these commands to your CI (more on that in the next tutorial!). |
Devin will declare success even when the pipeline fails. |
Pin critical tech |
• State what must not change: “Stay on Postgres, no login, keep Tailwind.” • List required env vars in |
Devin will “helpfully” swap Postgres to SQLite or add new test runners unless told not to. |
Handle secrets yourself |
• Remove • Plan a secret-manager step for later tutorials. |
Devin doesn’t necessarily distinguish dummy creds from real ones. |
Kill merge-editor stalls |
• Jump in to help with merge commits and process exits. |
Vim lock-ups waste minutes and ACUs. |
Delete dead code early |
• After moves, |
Redundant files confuse the agent and inflate build times. |
Add unit tests for new logic |
• Streak counters, timers, or metrics need backend tests, don’t trust UI alone. Again, bide your time, we’ll cover that in the next tutorial. |
Devin wired the dashboard but left counters non-functional until tests exposed the gap. |
Say what you want and what you don’t |
• Explicit negatives (“No auth yet”) prevent scope creep. • Review the plan Devin proposes and trim extras. |
The agent likes to over-deliver, great for demos, bad for budgets. |
What’s Next?
We now have:
- A pastel-polished playground that runs fp-ts tests in-browser
- A NestJS + PostgreSQL API that remembers progress with nothing more than a UUID
- A progress dashboard that actually feels fun
In the next tutorial, we’ll shift focus from features to workflow and collaboration with:
- Continuous testing: Add Jest and Playwright suites, then let Devin keep both green with every PR.
- CI/CD pipeline: Generate GitHub Actions that build the web and API apps and run the tests in headless Chrome.
- Team integrations: Hook Devin into Jira for ticket updates, push build summaries to Slack, and experiment with Devin’s new in-product Wiki feature for lightweight documentation.
If you’re ready to continue, click on the third list item below to go to the next tutorial:
- Setup and First Pull Request (Part 1)
- Shipping a Vertical Slice with Devin (Part 2)
- Integrations, Testing, and CI/CD (Part 3)
- Security, Deployment, Maintenance (Part 4)

I am a product-minded tech lead who specialises in growing early-stage startups from first prototype to product-market fit and beyond. I am endlessly curious about how people use technology, and I love working closely with founders and cross-functional teams to bring bold ideas to life. When I’m not building products, I’m chasing inspiration in new corners of the world or blowing off steam at the yoga studio.