This case study is about where copy standards live and how they get enforced—the design system and the PR pipeline. How content gets produced at scale (the AI skills and drafting tools) is its own case study: Judgment, scaled. Same standards, but solving a different problem.

One shipped rewrite, four rules applied—the confirmation modal from APP-149676.
context
Pendo's UI copy standards lived in places engineers never looked: a style guide in a 74-slide deck, guidance in Confluence and Figma, a Google Doc mirroring what our AI tools had loaded, and my head. The copy itself lived in the codebase, written and changed daily by engineers and designers who had no quick way to check a string against our standards before it shipped.
I was the quality gate. That doesn't scale, and it makes content quality a bottleneck instead of a property of the system.
So I moved the standards into the codebase and set them up to enforce themselves.
the problem
Two structural issues, not a copy issue:
- Standards and code lived in different worlds. Every handoff between them was a chance for quality to leak: placeholder text shipping to prod, terminology drifting across components, new flows launching in inline English while the rest of the surface was ready for translation.
- Enforcement was a person, not a process. Review quality depended on whether I saw the PR in time. At my most stretched, I was the only writer supporting nearly 25 development teams; even at normal capacity, I’m one content designer across eight teams. Either way, "Maddy reviews everything" never really worked.
the work
One home for copy standards. I standardized copy guidance across 27 component pages in the design system's Storybook—five of them net-new (error messages, toasts, dropdowns, form fields, headings)—plus a 569-line product-terminology glossary and a 180-line engineer quick-reference. All of it shipped as one merged PR to the frontend repo, ~2,300 lines total. Not a doc—a code change.
Every component's guidance includes real before/after examples pulled from actual Jira tickets, so engineers see what "wrong → right" looks like in their own product instead of abstract principles. We agreed this is where copy guidance lives now—one home, inside the component docs engineers already use every day. If guidance isn't there, it doesn't count.

Every rule cites the real change it came from—this one originated in a review where the team kept mis-flipping the meaning of an inverted checkbox.
A ruleset for a bot that didn’t exist. Alongside the PR, I delivered a ruleset with every copy rule tiered by how confidently a bot could act on it, from "safe to auto-flag" down to "needs human judgment." I flagged the two hardest technical problems before anyone started building: reliable string extraction (telling UI copy apart from code) and per-rule accuracy tracking, so we'd know which rules produce false positives instead of trusting the bot blindly. V1 ships every rule as a comment; a rule earns the right to block only after its acceptance rate proves the flags are real.
The rules themselves anticipate a machine reader—the hyphen rule only flags space-surrounded or digit-adjacent hyphens, never word-internal ones, or every legitimate compound term lights up.

~42 rules, tiered by how much the bot can be trusted with each.
And the PRs themselves. None of this is in my job description. The PRs happen on top of a full docs and UX-writing load across most of the platform—dozens merged since February 2026, from a one-line sentence-case fix to the ~2,300-line guidance PR above. A few worth naming:
- Caught a new flow shipping hard-coded English on a surface that was otherwise translation-ready. Kept my PR to the copy, filed the i18n gap as its own ticket so the debt was tracked instead of silent, and told the team how to loop me in earlier next time.
- Unified two near-duplicate versions of the same "manage columns" instruction into one shared string across the design system—rewritten to lead with the user's goal ("Customize your table by adding, removing, or reordering columns") instead of the list of clicks—and retired the redundant translation keys in every language. While goal-first phrasing is a UX-writing staple, what's ours is that the guidance now cites this commit as its example—so the rule is backed by a real change in our own product, not just industry convention.
- Swapped "Disabled" for "Inactive" as a status label across guides and the Resource Center—"disabled" describes a setting someone turned off; "inactive" describes a lifecycle state, and users kept reading the first as the second. Same pattern as above: that change is where the guidance's "avoid Enabled/Disabled in user copy" rule came from—the fix happened first, then became the rule.
the tradeoffs
- Two homes for the same standards. My guidance ended up in two codebases built for different jobs—a context pack in the AI tooling repo, where prototypes get drafted, and the component guidance in the product repo, where real code ships and the PR bot reads. Different audiences soften the risk but don't remove it: a prototype drafted against stale standards still lands on my desk wrong. So I raised the precedence question, we landed it—the product repo's guidance is canonical for anything that ships—and I wired the prototyping pack to point there.
- Rules vs. skills for AI enforcement. The team weighed broad always-on rules against skills that load only when relevant, and landed on skills-first (more reliable, less noise) with rules layered on top to trigger them on any frontend branch. I own the content half of that architecture; the engineers own the plumbing.
- Helping without becoming the cleanup crew. Shipping copy fixes myself created a risk: the more I fixed, the easier it got for teams to skip looping content in and assume we'd catch it after. So I drew the line in a decision framework—if a team ships copy without content review, that's a defect they own. We jump in when there's bandwidth, when the miss was ours, or when we want to. By choice, not by default: "content can clean it up later" never gets to become the reason we weren't looped in.