The Squads app is the operational heart of Squads. Clients use it to hire teams, create and accept sprints, make payments, and manage their work with Squads. Teams use it to organise projects, deliveries, and administration in one place.
A long time ago we wrote our web app in AngularJS. Back then it was a reasonable choice, and we had a team with really good expertise in it. We kept growing and supporting it over the next few years. A lot happened in the meantime: Angular was released, React won the front-end framework war (I know it's a controversial point – let's judge by usage alone).
For our Firstbird client we ran a complex zero-downtime migration from AngularJS to Angular while shipping features and a full platform redesign at the same time. So we had all the expertise needed to do the same for our own beloved platform – we just didn't have the time. That's why the topic kept coming back for the last two years and the answer was always "we don't want to do it now."
LLMs changed that. Getting up to speed with AI-assisted development on real projects made one thing clear: with the right tooling, harness and process, migrating a codebase got dramatically cheaper. A skilled developer with an LLM-enhanced workflow can pull off a stack change in weeks to low months, versus the six-month estimate we'd been carrying around.
So we did it. Here's what the process actually looked like.
The API was the one thing we refused to touch. API layer stayed exactly where it was, and both clients – old AngularJS and new React – talked to the same endpoints, the same session cookie, the same shapes. That decision made the whole thing tractable: every migrated screen had a known-correct reference implementation running next to it in production. When something looked wrong, the question was never "is the backend broken?" It was always "does the new client match the old one?"
The old app had Typescript types, which helped LLM to understand code structure and data flows really well. It received an accurate, battle-tested domain model and didn't need to guess the shape of data.
Before any feature work, we wrote a single MIGRATION.md that translates AngularJS idioms into React ones:
Plus a file-path mapping (modules/projects/list/ → pages/projects/list.tsx). This document have driven the whole migration process. Every session started with preloading it and sharing the same global context.
It makes a difference between a migration and a pile of generated code. ARCHITECTURE.md defines non-negotiable decisions and opinions:
Data fetching: useQuery in every page. Never fetch/axios in a component.
Forms: react-hook-form + zod. Never useState for form state.
States: shared <LoadingState> / <ErrorState> / <EmptyState> components. Never inline markup.
Dialogs: DialogService.open() – never manual open/onOpenChange state.
State split: useState local, TanStack Query server, Zustand global. Server data never goes in Zustand.
Styling: Tailwind utilities only. Spacing scale 4/6/8/12.
Imports: direct only, no barrel index.ts.
LLMs are excellent at following an explicit convention and terrible at inventing a consistent one across 200 files. Every rule you write down is a class of inconsistency that never enters the codebase. Both docs live in the repo and are pulled into context via CLAUDE.md, so every session starts already knowing the house style.
We cut the app into eight vertical slices, each with its own tracking doc under .claude/features/: authentication, account-management, projects, teams, deliveries-orders, payments-invoicing, admin-panel, notifications.
Each doc has the same four sections: Todos (checkbox list of every route and sub-feature), Progress (dated log), Decisions, and Technical Notes (the reverse-engineered old-app routing, resolves, and role logic).
The Technical Notes section is where the real work happens. For example, the projects slice starts by documenting the old UI Router tree – the abstract projects parent that resolves capacity, the smart role-based redirect in projects.single, the nested manage.planner / manage.accounting / manage.feed states. You have to understand the legacy behaviour before you can port it, and having an agent read the old module and write that understanding down is one of the highest-value things it can do. The document survives the context window making you explain once.
We didn't do a like-for-like port. Going to React meant going to Tailwind + shadcn/ui + Radix, so we took the opportunity to refresh the design at the same time. In the past we wouldn't combine a stack migration with a redesign, because humans work better on a single goal in a clear context, but LLMs can hold focus on several goals, until they fit into context. . We were touching every screen anyway, and doing it twice costs more than doing it once.
Cypress e2e suite let us move faster and with more confidence: the tests describe user-visible behaviour, not implementation, so they port across a framework change and tell you whether the new screen actually does what the old one did. E2E specs are the only tests that survive a rewrite intact, which makes them worth far more than usual in this specific situation. Also we made sure to expand out test scenarios to cover ~90% of the app flows.
We generate Codesight maps for each of client, client-next, and server – a compact index of routes, models, components, the dependency graph, and which files are most imported. The client-next map is ~5.7k tokens and replaces roughly 59k tokens of exploration. Across hundreds of sessions it made a real difference and made migration more affordable.
The new app got its own CI pipeline and server. It was deployed alongside and switched via dns, which allows to quickly switch back in case of an issue noticed. Later we just retire the old app and switch off the old server.
Now we have a mobile first experience the old app never had. Roughly 37% fewer code with up-to-date dev-tooling. A month of work of one developer, not even full-time (adjusted by token reset windows) against the six-month 2 developers full-time estimate we'd been carrying since 2023.
Documents beat prompts. Every hour spent on MIGRATION.md, ARCHITECTURE.md and the per-feature docs paid back many times over. A prompt is used once; a document is read by every session forever. If you find yourself explaining the same convention twice, that's a bug in your repo, not in the model.
The old codebase is a spec. This is the thing that makes migrations so much better suited to LLMs than greenfield work. There's no ambiguity about what to build – the answer is running in production. The old code answers every product question ("what happens if a client has insufficient credits on a trusted project?") that would otherwise cost a meeting.
Frozen API = cheap verification. Because the backend never moved, "is this correct?" reduced to "does it match the old screen?" That's a question a human can answer in seconds and an agent can answer with a Cypress run.
Vertical slices keep the thing shippable. Cutting by feature rather than by layer meant there was always a complete, working set of screens, and always an obvious next task. It also matches how agents work best: one bounded area, all its context, a checklist to grind through.
The bottleneck moved. It's no longer typing. It's deciding – what the conventions are, what the design should be, which behaviours to preserve and which were bugs all along. That's still your job, and now it's most of the job.
In 1 month we shipped a rewrite delayed for 2 years. The platform is on a stack we can hire for, the design is current, the mobile experience is real rather than nominal, and the build takes seconds instead of minutes. But the more useful takeaway is the general one: a migration that didn't clear the ROI bar in 2023 clears it comfortably now. If you have a "someday" rewrite sitting in a backlog, the estimate you're carrying around is probably stale. Technical debt gets so much cheaper to manage.
With AI coding assistants reality is nuanced. Specs drift, context windows fail, and micromanagement becomes constant. Without strong architecture, testing, and senior oversight, AI creates chaos. It excels at boilerplate and review—but it doesn’t re...
by Emmanuelle Delescolle, Tiziano Perrucci
In December I let three AI coding assistants, Claude Sonnet 4.5, GPT-OSS and Kimi-K2, solve Advent of Code independently. All 12 days passed. GPT-OSS showed fully local AI works; Kimi-K2 hardcoded outputs; AIs know algorithms but reinvent wheels. Wha...
by Emmanuelle Delescolle
AI product development requires adapting Scrum: set learning-focused Sprint goals, redefine ‘Done’ for spikes, embrace hypothesis-driven metrics, and communicate clearly with stakeholders. Adopting Dual-Track Agile enhances clarity, managing AI’s inh...
by Shrikant Vashishtha