All work

CS/03 · Case study

Designing in the codebase

I’m not a core platform developer, but I did my design work inside the product’s codebase: against real data, in small reviewable pull requests, and in prototypes engineers could open without me.

Details generalized under NDA. Every visual is recreated with fictional data.

Role
Product designer and design engineer
Scope
Prototypes, production UX changes, beta analytics, table analysis
Context
AI reporting assistant for a government data platform, in beta
Year
2026

Why code

I set up the full local environment and learned to run the front end against a shared staging environment, so I could design against real data and the live agent instead of static mocks. I had no backend ownership, and kept it that way on purpose.

How I worked

  1. Start small

    I shipped copy first (the entry screen and a contextual input placeholder) to learn the ticket → branch → PR workflow before anything bigger.

  2. Prototype where nothing depends on it

    Bigger ideas went into disposable prototypes: isolated routes nothing else depends on, deletable in one move. A small fake store let me design with services down, without locking engineers into a storage design.

  3. Make it demoable

    Agent-free deep-link routes let engineers open any flow without a live AI session. Static interactive mocks, including an offline bundle, went on tickets so reviewers didn’t need the environment.

  4. Loop fast

    I demoed prototypes to engineers and folded their feedback into the prototype, the diagram and the docs, often the same day.

  5. Hand off in pieces

    Handoffs went from one long doc to a short entry point plus a one-page ticket per component, after developers told me the first version was too long.

Decision 1

Scope the work to where the product is going

Considered
  • Build the canvas empty state the ticket described, pointing at the manual builder
  • Tag every surface for analytics
Chose
The builder had been discontinued, so I built the empty state for the chat panel instead. For analytics I deliberately left deprecated or about-to-be-reworked surfaces untagged.
Why
Designing or instrumenting surfaces that are going away wastes effort and pollutes the data.

Beta analytics

I defined and tagged the beta’s user actions (prompt submitted, report created, widgets added, updated or removed, and sharing outcomes) with a typed event helper and test IDs on menus and controls. I cut the tests down to the ones that protect app behavior, because tracking must never break the app. It merged after code review and gave the beta its first custom events.

Decision 2

Frame the table work by use case, not feature list

Considered
  • A feature-parity checklist against the legacy table
  • Real public-sector reports
Chose
I organized the parity analysis around ten kinds of public-sector report and mapped 28 table capabilities against them.
Why
Each report type maps to the capabilities it actually needs, so the conversation is about real reports instead of a checklist.
Fig. 1The ten report types the analysis was organized around, and what it surfaced.

It surfaced something the team didn’t expect: the assistant was already ahead of the legacy tool on computed columns, date bucketing and top-N. The gap was presentation.

Outcome

  • The analytics work merged and gave the beta its first custom events.
  • The entry-point prototype (a create-menu card, a nav item and a profile callout) went to a developer to productionize.
  • I followed up with why the current table component won’t reach parity.

What I learned

  1. Make prototypes demoable without dependencies.

    The agent-free deep links should have come first. For a while the publish wizard couldn’t be seen at all without a live agent.

  2. Frame by use case, not feature list.

    It made the table conversation legible to non-engineers.