Our website uses cookies.
Reject AllAllow all

This website stores cookies on your computer. The data is used to collect information about how you interact with our website and allow us to remember you. We use this information to improve and customize your browsing experience and for analytics and metrics about our visitors both on this website and other media.

dodaj tez tutaj ze przycisk ma byc schowany jezeli scrollY jest mniejsze niz 100vh

When Architecture Meets Reality: Two Mid-Build Architecture Pivots and What They Cost

The wall task-by-task building hits

Part 9 covered the workflow that produced the velocity: one operator, Linear ticket → Claude Code plan phase → implement → verify → next ticket. Jakub Wąsowski was the operator. Zero lines of code written manually across the entire project. The Elixir backend and the React frontend were both new territory for him — the agent did the typing.

That velocity has a known cost, which is exactly why Part 9 named frequent demos with users as a control mechanism, not a nice-to-have.When you can close tickets in hours instead of days, the question stops being “are we building it” and starts being “are we building the right thing.” Speed without direction checks builds the wrong thing quickly. We knew this going in.

What you cannot know going in is what the demos will surface. At some point during the standalone-webapp build, Jakub looked at the half-finished interface the way a recruiter would. Two tabs. Recruitee in one, the new tool in the other. Same candidate, two windows, constant switching. The interface that was supposed to remove friction was about to add it. A good technical idea, looked at from the user’s seat, was a bad UX solution.

That moment did not come out of nowhere. It came out of a demo conversation with Magda and Szymon — the kind Part 9 documented as the workflow’s load-bearing control. The pivot below is the demo control mechanism doing its job.

Mid-build architecture pivot #1: standalone webapp → Chrome extension

The original plan was reasonable on paper. Recruitee handles candidate management, pipeline state, and communication logging adequately. The two gaps the 50K POC was scoped to address — interview scheduling and competency-driven evaluation — could be built as a standalone web app that talked to Recruitee through its API. Clean separation. Easy to demo. Easy to throw away if it did not work.

The operational reality the demo surfaced: a recruiter’s day does not have room for a second tool that requires the same context as the first.The recruiter would open Recruitee for the candidate record, switch to the new app to schedule the interview, switch back to Recruitee to add notes, switch again to enter the competency evaluation. Every switch is a new mental model load. The tool that was supposed to compress the workflow was about to fragment it further.

A layer on top of an ATS that requires a separate window is not a solution. It is new friction wearing the costume of one.

The pivot: build the missing UI as a Chrome extension that renders directly inside Recruitee’s interface. The plugin talks to the Elixir backend in the background. The recruiter sees one screen — Recruitee, plus the scheduling and evaluation surfaces we built, sitting inside it. No new tool to learn. No tab to switch to. The candidate record and the new capability share a window because they share a workflow.

The headline learning is short enough to put on a sticker: a working web app that no one wants to use because it forces context switching is a failure even if the code is correct.

Mid-build architecture pivot #2: Phoenix LiveView → React

The Chrome extension pivot did not look expensive on a whiteboard. It looked expensive on the keyboard.

The backend was Elixir. The original UI was Phoenix LiveView. Phoenix LiveView is a strong choice when you control the page the user is looking at — the server holds state, pushes diffs over a WebSocket, and the developer writes one language end to end. It is much less useful when the page the user is looking at is somebody else’s DOM, and the code that has to run there is JavaScript executing in a browser extension sandbox.

So the LiveView UI was abandoned. The interface was rewritten in React, embedded in the Chrome extension, talking to the Elixir backend through the same API the standalone app would have used. The components we had already built in LiveView were rebuilt in React. Same screens, twice.

The honest math on this pivot is simple. If the stack had been TypeScript end to end — Node or a serverless backend, React in the extension — the pivot would have been close to free. The components would already have been React. The backend would already have been speaking the same language as the frontend. The cost of the pivot would have been the cost of repackaging, not rewriting.

With Elixir backend plus LiveView frontend, the pivot cost a rewrite.We do not know exactly how many engineer-days went into the duplicated work, because the project was running in the AI-assisted mode where tickets close fast and the agent absorbs most of the typing. What we know is the work happened twice, and the cost of that duplication is areal number that should be assigned to the original stack choice, not to the pivot.

The Elixir verdict — and why it isn’t “Elixir is bad”

This is the section that requires care. Appunite has been building Elixir systems since 2015. We have shipped Elixir at scale for clients whose throughput, fault-tolerance, and real-time requirements made Elixir the obvious answer. None of that is in dispute here.

What is in dispute is whether Elixir was the right answer for this specific small internal-tool project.

It was not. At the scale of this POC — a handful of recruiters, a few dozen interview meetings a month, no real-time concurrency requirements, no high-throughput pub/sub — Elixir gave us nothing the project needed. The infra footprint cost roughly $50/month on GCP for compute that would have been a flat zero on a serverless platform at this user count. The pivot from LiveView to React forced us into two-language territory: Elixir on the server, TypeScript in the extension, two ecosystems, two sets of types to keep in sync, two deployment paths. Writing in two languages had no value here — neither technical nor business. It slowed development and raised maintenance cost.

That is the project-level verdict. The general claim is different and we should be specific about it.

Elixir is a serious answer to a specific class of question: concurrency at scale, fault tolerance, real-time channels at high throughput, long-running stateful processes you want to supervise. When the question is “we need to handle a hundred thousand WebSocket connections with predictable latency and graceful degradation under load,” Elixir is one of a small number of correct answers. That class of question is not what this project asked. This project asked a small CRUD-and-integration question with a handful of users. Elixir was the wrong shape for that question. The right shape would have been TypeScript end to end — one language, one ecosystem, no rewrite when the UI moved into a browser extension.

The rule we are leaving with is the most boring possible rule: match the tool to the problem. Elixir was not picked because we thought small internal tools should be written in Elixir. It was picked partly to build vertical Elixir capability for commercial engagements, which is a defensible business reason — and the cost of that learning was budgeted at roughly 35–40K PLN going in. What we did not budget for was the architectural cost when the UI had to move into a third-party DOM. That is the cost the Chrome extension pivot exposed.

The audio recording gap — and the dry run that found it

The architecture pivots were not the only mid-build surprises. The first dry run on staging found the others.

The first dry run had Kinga from the people team playing the recruiter, with Jakub as the candidate. It surfaced the integration bugs we expected to find. Roam On-Air, the format we had originally picked because of its scheduling API, was the wrong shape for a one-on-one interview — conference-style, candidates confused about what to expect. We switched to Roam Live Meeting, which does not have API scheduling,and bridged the gap with a dedicated ats@appunite.com Google Calendar that sends invites with the Live Meeting link. That fix was straightforward and the kind of thing a first dry run is supposed to find.

What the first dry run did not find: that recruiters had been receiving audio recordings of meetings from Recruitee, and the new tool removed that capability. Kinga was not the target user. She was a thoughtful operator running through the workflow as designed. She caught what was in front of her. She did not catch what was missing, because missing things only stand out to the people who depend on them.

The second dry run had Milena, a recruiter who would actually use the tool daily. She caught the audio recording gap inside the first few minutes. Recruiters listen back to interviews. The recordings live in their workflow. The new tool was technically complete and operationally regressive — it gave them new capability while quietly removing one they relied on.

The framing we want to be clear about: this was not a discovery failure to apologize for. The discovery work in Part 8 was rigorous, and it covered the gains the new tool was meant to provide. It did not cover the losses — the things recruiters had today that they would no longer have tomorrow. That gap is the lesson, and it is forward-looking. Next time, discovery has to map both: what the tool will add, and what it will silently take away.Gains are easier to surface. Losses require someone who lives in the daily workflow to notice.

The fix turned out to be no code at all. Enabling Recording in Roam auto-triggers Magic Minutes — Roam’s transcript pipeline. The recordings live in Roam, the transcripts feed our evaluation flow, and the recruiter keeps the recording habit they already had. Sometimes the solution is a config flag, not a feature.

The unexpected blocker no one budgets for: access collection

The most boring lesson of the build is the one that costs the most time and almost never makes it into a slide deck. Collecting keys and accesses for Roam, GCP, Recruitee, Resend, Notion, Anthropic, the service account, and DNS required going to contributors across multiple rounds. No one had everything. Jakub got bounced multiple times.

Some of this is unavoidable — DNS, service accounts, and mail configuration only emerge once you start deploying, and you cannot ask for credentials you have not yet realized you need. What you can do is assign one owner for infra access end to end at project start. Not glamorous. Reliably costs days you did not plan for. Worth a single line in the project setup.

What this means for AI-assisted development

AI-assisted development reduces the cost of writing software. That is real, and the Paradox of Cheaper Code argument holds — falling implementation cost makes more custom builds viable, not fewer.

It does not reduce the cost of deciding what to write.

Wrong architecture still costs a rewrite. Wrong technology choice still costs a stack penalty. Wrong UX assumption still costs adoption. The cost AI eliminates is the cost that was previously borne by typing. The costs it does not touch are the costs of judgment, and those compound when you are moving fast.

The implication for how to plan an AI-assisted build is concrete: the time the agent gives back to you is not free engineering capacity. It is decision-quality capacity. Spend it on demos, on dry runs with target users, on architecture reviews you would have skipped under the old timeline because there was no time for them. The wall task-by-task building hits is not “the agent stopped working.” It is “the agent built exactly what we told it to, and we told it wrong.”

The rule

Match the tool to the problem.

AI compresses the implementation budget. It does not compress the decision budget. The two pivots in this article are what happens when you spend the freed implementation budget without expanding the decision budget to match. Both pivots were honest course corrections, neither preventable by upfront planning, both recoverable because the demos and dry runs were already wired into the workflow.

Part 11 is the verdict — the test design, the numbers, and what they say about whether the build was worth doing in the first place. The honest math is in there.

Sources

  • Part 7 — Why We Killed Our Full ATS Build: https://www.appunite.com/blog/saas-replacement-experiment-pivot
  • Part 8 — Not Every Problem Needs Code: Discovery for a 50K POC: https://www.appunite.com/blog/not-every-problem-needs-code-discovery-scoping
  • Part 9 — One Operator, Zero Manual Lines: How the POC Got Built: https://www.appunite.com/blog/one-operator-zero-manual-lines-ai-driven-dev
  • The Paradox of Cheaper Code — Why AI is Making Custom Software Development More Valuable: https://www.appunite.com/blog/why-ai-is-making-custom-software-development-more-valuable
  • Process Native Software: https://www.appunite.com/blog/process-native-software
  • POC repo (private; to be released publicly when ready)

Further reading

AI-assisted engineering

The build series