1:"$Sreact.fragment" 2:I[15385,["/_next/static/chunks/0m6xqag99r4k7.js","/_next/static/chunks/0d3shmwh5_nmn.js","/_next/static/chunks/048q8gvmyw66e.js"],"TableOfContents"] 11:I[22016,["/_next/static/chunks/0m6xqag99r4k7.js","/_next/static/chunks/0d3shmwh5_nmn.js","/_next/static/chunks/048q8gvmyw66e.js"],""] 12:I[97367,["/_next/static/chunks/0m6xqag99r4k7.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"OutletBoundary"] 13:"$Sreact.suspense" :HL["/_next/static/chunks/10k5l4iw31hoh.css","style"] 0:{"rsc":["$","$1","c",{"children":[["$","main",null,{"className":"case-study-module__eilb5G__main","children":["$","article",null,{"className":"container case-study-module__eilb5G__article","children":[["$","header",null,{"children":[["$","h1",null,{"children":"Cloud Authoring: Replacing PR Handoffs with a Real Publishing Workflow"}],["$","p",null,{"className":"case-study-module__eilb5G__summary","style":{"marginBottom":32},"children":"How I designed a branch-based authoring workflow that gave non-engineers the ability to publish directly without asking engineers to give up control."}],["$","div",null,{"className":"case-study-module__eilb5G__metaRow","children":[["$","div",null,{"className":"case-study-module__eilb5G__metaItem","children":[["$","div",null,{"className":"case-study-module__eilb5G__metaLabel","children":"Role"}],["$","div",null,{"className":"case-study-module__eilb5G__metaValue","children":"Senior Product Designer"}]]}],["$","div",null,{"className":"case-study-module__eilb5G__metaItem","children":[["$","div",null,{"className":"case-study-module__eilb5G__metaLabel","children":"Project Date"}],["$","div",null,{"className":"case-study-module__eilb5G__metaValue","children":"September 2022"}]]}],["$","div",null,{"className":"case-study-module__eilb5G__metaItem","children":[["$","div",null,{"className":"case-study-module__eilb5G__metaLabel","children":"Platform"}],["$","div",null,{"className":"case-study-module__eilb5G__metaValue","children":"SaaS Web App"}]]}]]}],"$undefined"]}],["$","div",null,{"className":"case-study-module__eilb5G__body","children":[["$","$L2",null,{"anchors":[{"id":"overview","label":"Overview"},{"id":"role","label":"My Role"},{"id":"process","label":"Process"},{"id":"decisions","label":"Key Decisions"},{"id":"outcomes","label":"Outcomes"},{"id":"lessons","label":"Lessons"}]}],["$","div",null,{"className":"case-study-module__eilb5G__content","children":[[["$","section",null,{"id":"overview","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"01 — Overview & Problem"}],["$","h2",null,{"children":["What we were trying to ",["$","em",null,{"children":"solve"}]]}],["$","p",null,{"children":"When I joined Knapsack, the editing experience was simple: users entered an edit mode, made changes to pages, navigation, and content inside the app, and when they were ready, hit a single \"Propose Changes\" button. That opened a pull request in their Git provider. Then they waited."}],["$","p",null,{"children":"The problem was what happened next. An engineer had to review and merge that PR before anything went live. For documentation updates, content tweaks, or design system page edits (work that didn't touch code) non-engineers were still blocked on engineering availability. In practice, that meant days of waiting for changes that should have been publishable in minutes."}],["$","p",null,{"children":"[ Image: before state / problem framing — 1200×675px ]"}],["$","p",null,{"children":"[Caption: The before state. A single edit mode with a \"Propose Changes\" button that opened a PR, then handed off to engineering to merge.]"}],["$","p",null,{"children":"Designers and content authors could write the change. They just couldn't ship it."}],["$","p",null,{"children":"The ask was to give authorized users the ability to publish directly through the app, merging the PR without engineering involvement, while keeping the underlying Git workflow intact. That meant solving two connected problems at once: introducing a branch-based editing model to replace the old flat edit mode, and building a permission model that gave non-engineers publishing power without making engineers and admins nervous about who could merge to main."}]]}],"\n",["$","section",null,{"id":"role","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"02 — My Role & Team"}],["$","h2",null,{"children":["Where I fit ",["$","em",null,{"children":"in"}]]}],"$L3","$L4","$L5"]}],"\n","$L6","\n","$L7","\n","$L8","\n","$L9"],["$La","$Lb"]]}]]}]]}]}],["$Lc","$Ld"],"$Le"]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"z1Ge3Eagsf2H8nk6KpEOe"} 3:["$","p",null,{"children":"As Senior Product Designer, I owned end-to-end product definition, UX, and cross-functional alignment for cloud authoring. That meant defining the new branch-based mental model, designing the full authoring and publishing workflow, and navigating the internal tension between teams who wanted frictionless publishing and stakeholders who needed confidence that the wrong people couldn't ship to production unreviewed."}] 4:["$","p",null,{"children":"I worked closely with engineering throughout, particularly on the technical constraints that shaped the permission model and the Git provider integrations. A key early alignment was establishing that the app would always create a PR in the Git provider, even for direct publishes, and the app could then merge it programmatically. That kept Git history intact and gave engineering teams an audit trail they could trust, which was essential to getting buy-in."}] 5:["$","p",null,{"children":"I also owned the edge cases: merge conflicts, failed merges, uneditable branch states, and the migration path for customers whose content still lived in the database. Each needed explicit UX and copy to stay supportable at scale."}] 6:["$","section",null,{"id":"process","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"03 — Process"}],["$","h2",null,{"children":["How I ",["$","em",null,{"children":"approached it"}]]}],["$","p",null,{"children":"I started by pressure-testing the mental model shift. The old experience had no concept of a branch; users just entered edit mode and proposed when ready. Introducing branching meant asking every user to adopt a new way of thinking about their work: a branch is a draft, and publishing or requesting review is how it ships. That framing had to be simple enough for content authors while mapping to concepts engineers already trusted."}],["$","p",null,{"children":"[ Image: early explorations / whiteboard / research — 1200×675px ]"}],["$","p",null,{"children":"[Caption: Mental model mapping. Aligning \"branch = draft, merge = publish\" across design, content, and engineering stakeholders.]"}],["$","p",null,{"children":"I ran that model past both sides early and often. The goal wasn't to teach non-engineers Git, it was to give them a workflow that made intuitive sense on its own, while being honest about what was happening underneath."}],["$","p",null,{"children":"The permission model required a separate track of work. The core tension wasn't technical, it was organizational. Engineers and admins were genuinely nervous about giving non-engineers the ability to merge to main. The solution was to scope publish permissions to workspace admins only, while reframing \"Propose Changes\" as \"Request Review\" for everyone else. Teams with formal review cultures kept their PR-and-wait process; teams with trusted authors could publish directly without a bottleneck."}],["$","p",null,{"children":"A key part of both flows was an interstitial review step. Before publishing or requesting review, users saw a summary of everything they'd changed on the branch: pages edited, tokens updated, and other modified content. For admins, this was the last checkpoint before merging. For everyone else, it was what they were handing off for review. That shared step meant both roles were working from the same mental model of \"here's what's in this branch\" regardless of what they could do with it."}],["$","p",null,{"children":"[ Image: mid-fidelity explorations — 900×675px ]"}],["$","p",null,{"children":"[Caption: The interstitial review step. A summary of branch changes shown to all users before publishing or requesting review.]"}],["$","p",null,{"children":"Edge cases were treated as first-class problems throughout: what does an author see on a branch that's been submitted for review and is no longer editable? What happens during a merge conflict or failed merge? Each scenario got explicit UX and copy before we finalized the design."}]]}] 7:["$","section",null,{"id":"decisions","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"04 — Key Decisions & Tradeoffs"}],["$","h2",null,{"children":["The calls that ",["$","em",null,{"children":"mattered"}]]}],["$","h3",null,{"children":"The calls that mattered"}],["$","p",null,{"children":["$","strong",null,{"children":"Branch model vs. per-page draft/publish"}]}],["$","p",null,{"children":"The most fundamental decision was choosing a branch-based model over a traditional CMS-style per-page draft/publish system. A per-page model would have been familiar, as most content tools work that way, but it was the wrong fit for how Knapsack users actually worked. In a typical session, authors weren't editing a single page. They were updating multiple pages, design tokens, navigation, and other interconnected content together. A per-page draft model would have fragmented that work, created inconsistent published states across related content, and made it genuinely difficult to review or roll back a coherent set of changes. Branching solved this naturally: everything edited in a session lives on one branch, gets reviewed together, and ships as a unit."}],["$","p",null,{"children":"A per-page draft model works fine if users edit one thing at a time. They didn't, and designing for how they actually worked pointed directly to branching."}],["$","p",null,{"children":["$","strong",null,{"children":"Request Review vs. Publish as separate actions"}]}],["$","p",null,{"children":"Keeping \"Request Review\" and \"Publish\" as two distinct actions, rather than collapsing them or hiding one behind a permission check, was the right call even though it added surface area to the UI. The distinction is meaningful: teams with formal review cultures need a PR and an engineer in the loop; teams with publish-authorized admins want to merge immediately. One button couldn't serve both without hiding important behavior or misleading one group about what was actually happening. Reframing \"Propose Changes\" as \"Request Review\" also made the action clearer. You're not just proposing, you're explicitly handing something off for someone to act on."}],["$","p",null,{"children":["$","strong",null,{"children":"A shared interstitial for both roles"}]}],["$","p",null,{"children":"Giving both admins and non-admins the same change summary screen before their respective actions was a deliberate decision to keep the mental model consistent across roles. It would have been easy to skip the interstitial for admins on the assumption they already know what they changed. But the summary served a real purpose: it was a forcing function to review the scope of changes before they shipped, and it made \"Publish\" feel considered rather than accidental. The only thing that differed between the two flows was the available action at the end."}],["$","p",null,{"children":["$","strong",null,{"children":"Scoping publish to admins"}]}],["$","p",null,{"children":"Opening publish access to all users would have been simpler from a UX standpoint (one permission level, no role logic) but it would have undermined adoption with customers whose engineers were already skeptical about non-engineers touching Git at all. Giving admins control over who could publish was what made the feature trustworthy enough to roll out broadly. The \"Review Requested\" queue in the app, where submitted branches surfaced for admins to review and publish, also gave those teams a clear, contained place to manage the handoff without leaving the app."}],"$Lf","$L10"]}] 8:["$","section",null,{"id":"outcomes","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"05 — Outcomes & Results"}],["$","h2",null,{"children":["What actually ",["$","em",null,{"children":"shipped"}]]}],["$","p",null,{"children":"Cloud authoring shipped as a complete branch-based workflow inside Knapsack, replacing the old flat edit mode with a full draft-to-published lifecycle:"}],["$","p",null,{"children":[["$","strong",null,{"children":"Branch creation"}]," — Authors create a named branch from latest directly in the app. Knapsack creates it in the customer's Git provider via provider APIs and stays in sync via webhooks."]}],["$","p",null,{"children":[["$","strong",null,{"children":"In-app editing"}]," — All editing (pages, nav, blocks, tokens, content) stays in Knapsack. No terminal, no cloning, no Git commands required."]}],["$","p",null,{"children":[["$","strong",null,{"children":"Interstitial review"}]," — Before publishing or requesting review, all users see a summary of what's changed on the branch so nothing ships without a deliberate review of scope."]}],["$","p",null,{"children":[["$","strong",null,{"children":"Publish and Request Review"}]," — Admins can publish directly from the interstitial, merging the branch to main and updating the live site without engineering involvement. Non-admins request review, which routes the branch into a \"Review Requested\" queue for admins to act on inside the app."]}],["$","p",null,{"children":[["$","strong",null,{"children":"Branch status lifecycle"}]," — An explicit status that reflects where a branch stands at any moment (in progress, review requested, or published) with clear guidance when a branch is no longer editable."]}],["$","p",null,{"children":"[ Image: final shipped UI — 1200×675px ]"}],["$","p",null,{"children":"[Caption: The shipped branch authoring UI. Branch context, change summary interstitial, and role-appropriate publish or request review actions.]"}],["$","p",null,{"children":"Anecdotally, the impact was immediate for customers who had been waiting days for engineers to merge documentation updates. Design system content that used to sit in PR limbo could be published by the people who owned it, without a separate handoff or queue."}]]}] 9:["$","section",null,{"id":"lessons","className":"remark-container","children":[["$","p",null,{"className":"section-label","children":"06 — Lessons Learned"}],["$","h2",null,{"children":["What I'd do ",["$","em",null,{"children":"differently"}]]}],["$","p",null,{"children":"The migration path for customers with content still in the database rather than in Git was handled, but later than it should have been. It was a known constraint from the start, and treating it as a first-class design surface earlier would have saved late-stage rework. Any project with an existing-state problem benefits from pulling that forward rather than deferring it."}],["$","p",null,{"children":"The permission model work reinforced something I now carry into every project involving access or roles: the hard part is almost never the UI. It's the organizational trust problem underneath. Engineers weren't wrong to be cautious about who could merge to main. The design work that actually mattered was finding a permission model that addressed that concern directly, rather than smoothing over it with a simplified interface that would have eroded trust the first time something unexpected shipped."}],["$","p",null,{"children":"This project also deepened my conviction that introducing a new mental model is itself a design problem, not just a prerequisite for one. \"Branch = draft, merge = publish\" wasn't obvious to non-engineers. It needed to be introduced carefully, tested early, and reflected consistently in every piece of copy and UI. Getting that model right before touching the interface made every downstream decision faster and easier to evaluate."}]]}] a:["$","span",null,{"className":"text-eyebrow mb-md","children":"Next Selected Work"}] b:["$","$L11",null,{"href":"/work/vivid-seats-app","children":["Vivid Seats App 3.0: Replacing a Purchase Flow with a Reason to Return","   ↗"]}] c:["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/10k5l4iw31hoh.css","precedence":"next"}] d:["$","script","script-0",{"src":"/_next/static/chunks/048q8gvmyw66e.js","async":true}] e:["$","$L12",null,{"children":["$","$13",null,{"name":"Next.MetadataOutlet","children":"$@14"}]}] f:["$","p",null,{"children":"[ Image: two options considered side by side — 1200×675px ]"}] 10:["$","p",null,{"children":"[Caption: Per-page draft/publish (left) vs. branch-based workflow with shared interstitial review (right). The branch model better matched how users actually worked across multiple content types in a single session.]"}] 14:null