PM in the code: How I built and shipped a pricing update

A product manager's honest account of designing, coding, and QA-ing a billing page with AI. See what worked, the tradeoffs, and where the loop got shorter.
10 min. read
Summarize this content with AI

When the last PR merged on our per-user pricing release, I sat staring at my screen for a while. I had just redesigned and shipped our billing page, in our actual codebase, mostly on my own. That had never happened before. It was scary, empowering, mind-blowing, all at the same time.

This is how I got there. It worked, but I want to be honest about why. This isn't a "product managers should ship code now!" manifesto. It's one product manager’s experience, and the questions it left me with.

How I ended up in the code

Per-user pricing was a project months in the making. Arielle, our Interim CEO and head of marketing, had driven the initial spec on pricing and packaging. Peldi, our product designer and owner, wireframed the initial design in Balsamiq Cloud: three tiers, a new seat permission system, AI credits per tier. The plan was to offload most of the new billing flows to Stripe and keep our UI thin. Even at that stage, I'd flagged that I wasn't sure Stripe would cover everything, but we left it as something to figure out during build.

Billing page wireframes

During build, the question came back: how would we handle gated features (SAML, data residency, billing admin) inside Stripe? Stefano, our Balsamiq Cloud architect, said we could route them through Stripe with some workarounds. The technical path existed. But going that route meant we couldn't properly communicate to users why some options weren't available to them. That was the part I couldn't get past. Owning the UI ourselves meant I could choose how to communicate the gating directly. It also meant more surface for us to own and more product decisions to work through.

Engineering split the work. Stefano took the backend: Stripe integration, plan migration, billing rules. The product engineering team took the in-product changes. The billing page existed as a first pass that Claude had generated from the pitch. Functional, but not designed.

When Stefano finished the backend, he flagged the billing page early. It made sense for someone else to take the refinement work. He said in Slack:

"What you see is what Claude came up with after reading the pitch. I think it supports all required use cases but it definitely needs some UX/UI work."

That message was the door opening. I'd been wanting to take the page on myself for a while. There were two things stopping me: a technical blocker (I couldn't run Cloud locally) and a cultural one (PMs writing serious implementation code wasn't something we'd done at Balsamiq before). When I raised both with Peldi in our 1-1, I also flagged that his design time was better spent on bigger problems than a billing page polish pass. He agreed: this would be a good thing for me to work on, and he'd review.

I was already slated to own QA. I'd managed Olio, our internal licensing system, for years, and I'd become the go-to person on subscription state and billing edge cases. So the QA part wasn't new. The new piece was that I would also design and implement.

There was one piece of infrastructure that made any of this possible. Engineering had given me what I needed to run Balsamiq Cloud locally. Stefano set up a single CLI command and made the first few steps super easy. Without that, none of what follows would have been an option.

What hands-on actually looked like

The AI assistance ran through every phase, not just the code.

Prototyping the design. Before any production code, I first built a prototype using Peldi's Balsamiq Cloud wireframes as my reference. It wasn't in our actual codebase yet, just a standalone prototype I could iterate on quickly. I used it to walk Stefano and our CX team through the design decisions and to collect feedback on the trickier ones: refund policy, billing admin gating, what to show in the trial pricing flow. The recap I wrote afterward became the implementation brief.

Liz Slack Recap message
The recap I shared after our design review with CX and Stefano.

Writing the implementation plan. Once the prototype was approved, I wrote an implementation plan with Claude. Task by task, file by file. The plan was mostly for me, a way to make sure I'd covered my bases before diving in.

This was before our MCP server went live. If I’d had access to it, I would have asked Claude to read my project and create the build plan from my prototype.

Claude implementation code
A portion of the implementation plan I wrote with Claude before any code.

Implementing in code. With local Balsamiq Cloud running, I opened the codebase with Claude Code as my build partner. The job was to take the implementation plan and put it into practice. The first round was the billing page UI, adapting Claude's initial pass to fit Stefano's backend hooks. The rest was polish: small visual tweaks, credit limit adjustments, copy changes. For each task, I wrote the spec, Claude Code proposed the change, I reviewed, sometimes pushed back, and committed.

Github commits page
The set of my first commits into our working branch.

Testing the same way. QA wasn't a separate phase done with a different toolkit. I wrote an AI-assisted QA plan, kept a running bug list across multiple passes, used an AI-generated probe template to test billing flows at scale, and wrote unit tests for the changes. The same partnership that wrote the implementation wrote the verification.

Probe template built with Claude
The probe template I built with Claude to test billing flows across fixture states.

The work also surfaced gaps the pitch hadn't named explicitly. That's pretty much always the case: a pitch can take you far, but some claims only get proven or disproven once you're in the build. Legacy plans needed to retain the ability to switch between legacy tiers, and they also needed a path to migrate to per-user pricing. Canceled legacy plans could only resubscribe to per-user. These showed up during implementation, not during QA or post-launch. I surfaced each one in Slack or 1-1s while we were still in the build phase.

Liz's QA pass
Surfacing P0 items I found during my own QA pass.

Working with engineers, not around them

The starting point wasn't blank, as Stefano had already implemented the backend and a basic version of the updated billing page with Claude. But it still needed a pass to answer questions such as:

  • How should the plan-switching table behave when someone has a gated feature enabled?
  • What's the right hover state for a Cancel link that shouldn't compete with the primary CTA?
  • Should the trial pricing flow show the editor cap or not?

These were product calls, and I was the closest person to them.

After the main PR merged but before release, the work didn't stop. I opened additional PRs for things I found during QA prep, including one I caught that I considered a showstopper for the release: a path where a re-subscribe at lower capacity could allow free seats. I fixed it in code, opened the PR, and it was approved the next morning. Without me in the code, that fix would have moved at a different speed: a ticket filed, picked up, context shared, reviewed. We'd have caught it later than we did.

PRs opened by Liz
The PRs I opened across this project, organized in small commits Stefano could actually review.

At one point I asked Stefano directly whether the division of labor was working for him. Whether it would have been easier to just do it himself. His answer reframed how I had been thinking about the whole thing:

"Coding comes easiest to the person who's closest to the domain or topic. In this case it's you, so this is the most efficient solution overall. I'm just serving as the safety net, to ensure Claude behaves... sometimes it veers off track [with respect to] our conventions."

His answer gave me confidence that things were working well. The way I see it now: when AI is in the loop, the work goes to whoever's closest to the product judgment. Engineering becomes the safety net, the convention-keeper, the one who makes sure Claude doesn't ship something weird. That's a different role from writing the implementation, but it's just as important.

Stefano and Liz Slack discussion about a bug
Stefano catching a bug in my rate limit implementation. He fixed it, plus a race condition he spotted, and walked me through the trade-offs.

Some specific moves that kept this from feeling like an imposition:

Engineering set the boundary at the start. Stefano naturally worked in his area of expertise, which is the backend, and I stepped in to refine the UI. As the work moved into the fixing phase, those lines blurred. I'd find something during testing, fix it in the codebase, send it for review. The clean handoff stopped being clean, and that was fine because trust had been built.

I asked before touching anything I wasn't sure about, especially early on, and I never deployed to production myself. PRs went through normal review, I specifically requested Stefano’s eyes, and I learned the team's conventions (squash-merge to keep history clean; reading existing patterns before proposing new ones).

The same tightening showed up on the design side. After the working call with Stefano and CX, our brand design lead sent me a Slack thread with issues she'd spotted on the billing page. In the old workflow, that feedback would have become tickets in engineering's backlog and shipped over the following days or weeks. Instead, I opened a PR with the fixes that afternoon. (Even shorter if our designer had written the fixes herself; same principle, different role.)

A lot has changed for PMs in the AI era. The past year proved that to me. The core craft of the job, though, has stayed the same. The cross-team glue is still the work, and so is the institutional knowledge that comes with it: knowing our users, knowing the edge cases, knowing the history, and advocating for the user when the code gets written. What changed for me was the cadence: with AI helping me write the implementation, I could close the gap between cross-functional feedback and a shipped change without losing the coordination that makes everything work.

The unexpected wins

A few effects I hadn't predicted.

The first was how much earlier I started catching bugs. Running the codebase locally meant I could run a scenario, watch what happened, change something, and run it again. The re-subscribe showstopper I mentioned earlier is the clearest case, but it wasn't the only one; the lag between thinking about an edge case and reproducing it shrank from days to probably minutes. As I worked through implementation, gaps in the pitch started surfacing that hadn't been visible before: the legacy migration path that wasn't specified, the legacy-to-per-user switch behavior, the cancel-and-resubscribe edge case. I flagged them in Slack while we were still building, and we agreed on the design before they became QA surfaces.

The business logic got clearer, too. Writing a spec when you'll be the one implementing it forces you to think through the rules differently. And the QA plan I wrote was tighter than my pre-code QA plans had been.

But the part that surprised me most was how the product manager job started to feel different: it felt like less translation, and more direct ownership. I wasn't writing a doc for someone else to interpret, I was making the call. What felt like magic to me was realizing how much I enjoyed it. I'd always considered myself someone who likes to do, not just direct, and being in the code let me act on that. Watching changes happen, catching small bugs and fixing them on the spot, there was a rhythm to it I hadn't experienced in PM work before.

The honest tradeoffs

The release itself went smoothly, although my personal hours stretched. Owning design, implementation, and QA in one person means I was doing work that would normally pass between roles. Some weeks I worked longer days than I would have otherwise.

It only worked because the conditions around me were right: engineering let me in, the team's culture didn't read product manager-in-code as a threat, and I had a manager who actively watched my hours and pushed me to stop when I crossed the line.

None of this generalizes. "Every product manager should ship code now" is the wrong takeaway. Your product process, access to the right tools/budget, and team culture have to enable it.

What I'm taking with me

I'm not going to claim this is the future of product management, but I will say it's a future I'm interested in living in.

I've been sitting with a question since this release shipped. Where else could the cycle be shorter? Where could the handoffs be replaced by closer collaboration with AI in the mix?

I don't have a clean answer yet, and I'm okay with that. It's not "everywhere," and that took me some time to sit with. Bounded work in a codebase I understand makes sense. New systems or critical infrastructure probably don't. The discipline part matters more than the tool, too.

The part I want to hold onto is the cadence. The same release, but a smaller loop around it: bugs caught the same day, design feedback shipped the same afternoon, edges of the pitch worked through during build instead of after.

Looking back

Scary, empowering, mind-blowing, all at the same time. That's how the release actually felt.

What surprised me most was finding out how much I enjoy being a builder. I'd always seen myself as a doer, but this was the first time the PM job let me act on that.

And because I was the one designing the billing page, the result was more customer-focused than it would have been otherwise. I'd been close to our customers through years of support, and I knew which edges they hit, which language confused them, what they looked at first. That knowledge went into the design directly, not translated through a doc for someone else to interpret. Arielle told me afterward that the work felt focused on the customer in a way it wouldn't have been otherwise.

That's the part I keep coming back to.

Author

Liz Green
Liz Green

Product Manager @ Balsamiq

Questions or feedback? Email liz@balsamiq.com.

Related blog posts


Introducing prototyping in Balsamiq

You can now generate interactive prototypes from your wireframes in Balsamiq, and connect your projects to Claude, ChatGPT, and other AI tools with our MCP server.

Arielle Johncox
By Arielle Johncox

Balsamiq's next chapter: A fresh new look for a well-loved tool

Launching a new brand, a new website, and a new UI for our wireframing tool! A design update that doesn’t change our focus: speed and clarity for product teams.

Arielle Johncox
By Arielle Johncox

Looking back at Balsamiq's 2024

In 2024 we pruned to grow, we focused on clarity, and we adapted to embrace data analysis. Read about all the improvements and new features, too!

Arielle Johncox
By Arielle Johncox