[ Contact ]
Product UX / Frontend engineering / Design systems / Accessibility · 2026 · Completed for v1

QRCodeFlip — a multi-context ordering system

[ A QR ordering platform for many hotels, each with its own menus and locations: one product with a guest side and an admin side on one shared domain model — designed and built in React on one design system, on top of the client's existing backend and its data. ]

A laptop open on a mustard armchair, showing the QRCodeFlip admin dashboard: today's net sales, orders, menu views, the busiest QR point, recent orders and the top-selling drinks
[ THE ADMIN DASHBOARD — TODAY'S SALES, ORDERS AND TOP SELLERS AT A GLANCE ]
[ Project ]QRCodeFlip — a multi-context QR ordering platform serving many hotels, each with its own menus and its own set of locations
[ Client ]A returning client — we have worked together on several projects (e.g. Foodlace)
[ Live demo ]See demo
[ My role ]UX architecture, product design, design system and frontend engineering — React, domain and state modelling, responsive behaviour, accessibility and QA
[ Scope ]Version one of the product — guest ordering and the admin panel — built around the client's existing backend and data model
[ Client-owned ]The existing backend and connecting it to the new frontend, production sign-in, payment integration, and testing with real guests and staff — whose findings become the input for a version two
[ Stack ]React 19 · Vite · React Router · Zustand · CSS Modules on custom-property tokens · Framer Motion · Swiper · Figma
[ Timeline ]2026
[ Status ]Completed for v1 — a frontend prototype. The client's team will connect it to their existing backend and its data
[ Context ][ 01 / 14 ]

Many hotels. Any number of locations. One product.

QRCodeFlip is a QR ordering system used by many hotels, each with its own number of service points, and each service point can offer a different menu at different prices. A guest who scans a code lands in that hotel's menu, at the service point where the code sits, and staff run the menus, orders and feedback from an admin application. Both sides are one product, built on the same domain model and design system. The hotel this case follows has five service points: a restaurant, a bar, a lounge, a pool bar and room service.

The client already had a backend holding the menus, outlets, prices and images. It worked, but its interface was data-heavy and hard to navigate, and the hotels found their menu data difficult to manage. That set two constraints from the start: make ordering simple for guests without simplifying the business rules underneath, and make the operation manageable for staff without replacing the client's existing data system. What made it interesting was the relationship between context, catalogue state, basket identity and operational change.

I designed and built the UX architecture, the domain and state model, the design system and the React frontend for version one, around the JSON the client's system already produces. Version one is complete as a frontend prototype, with live basket reconciliation, role-based staff workflows, layouts from phone to desktop in light and dark, accessibility validation and cross-browser QA.

Payment, staff sign-in and the dashboard's activity stay simulated until the client's backend is connected. The client's team owns the existing backend and connecting it to this frontend, production sign-in, payment integration and testing with real guests and staff, which becomes the input for version two. I don't present adoption or revenue figures, because there aren't any yet; the numbers in the demo are generated data.

The live demo opens the menu the way a guest would, from the QR code on a lounge table. The admin panel is one tap away through the dashboard icon in its corner, and the sign-in page offers demo accounts.

The guest menu on two desktops and a phone, in light and dark: category tabs, collapsible sections of dishes with Add buttons, and on the desktops a details step of checkout open beside the menu
[ THE GUEST SIDE AT 1920, 1440 AND 390, LIGHT AND DARK — ONE COLUMN, WITH CHECKOUT BESIDE IT ON DESKTOP ]
[ Problem ][ 02 / 14 ]

The menu changes while someone is ordering.

A QR ordering flow looks linear — scan, menu, basket, checkout, order. The system underneath is not. During service a dish can sell out, a price can change, an option can disappear, a required choice can become invalid, and a dish can exist at one location but not another.

So the menu a guest saw when they scanned may not be the menu that exists when they reach checkout. A stale basket is not just a UX problem: it can mean a wrong price, an impossible order, or an operational problem for staff. The real question was what should happen when the truth in an active basket is no longer the truth in the live catalogue.

The basket on two phones, light and dark: a warning that one item is no longer available, that line marked with a Remove button, and the proceed-to-checkout button disabled
[ THE BASKET RE-CHECKS ITSELF — A DISH THAT SOLD OUT IS NAMED, AND CHECKOUT WAITS ]
The basket on two phones, light and dark: a drink whose price changed after it was added, the old price struck through beside the new one with a Price updated note, and the new total
[ A PRICE CHANGED WHILE THE GUEST WAS ORDERING — THE OLD ONE STAYS, STRUCK THROUGH ]
[ System model ][ 03 / 14 ]

A scan is context. A basket line is an identity.

The central modelling decision was to separate context from selection. A QR code establishes the service context: which hotel and which of its locations the guest is ordering from, which menu they should see, which price applies, and which code started the session. That context stays with the browser tab.

A basket line establishes a different kind of identity: the dish plus its selected options. The same configuration twice raises the quantity; a different configuration stays a separate line. Every line keeps a stable identity even while the catalogue changes.

Whenever the catalogue changes, every line is reconciled against the current menu and resolves to one of six defined states: still valid, repriced, unavailable, removed, an option no longer available, or a choice to make again. These aren't backend errors exposed to the guest — they are product states, each with its own sentence. A line that can't safely be ordered drops out of the total and holds checkout until the guest resolves it.

That rule became the bridge between product design and engineering — the foundation for the UX, the state management and the frontend architecture.

Diagram: the guest side and the staff side of one product, with one shared domain between them — catalogue, locations, a price per location, availability, orders and QR context — with what each side reads and what each side changes
[ THE SYSTEM MODEL — ONE PRODUCT, TWO SIDES, ONE SHARED DOMAIN, AND WHO READS AND CHANGES WHAT ]
Diagram: a basket line, identified by the dish and its sorted choices, is reconciled against the live menu whenever staff change it, with six outcomes — still good, repriced, unavailable, removed, option gone, choose again — and any flagged line stopping the order
[ WHAT CAN HAPPEN TO A BASKET LINE — SIX OUTCOMES, AND THE SENTENCE THE GUEST READS FOR EACH ]
Three design-system pages, each in light and dark. Menu item row and category section: a dish with an Add button, a dish with a Choose options hint, a sold-out dish, and a dish with its dressing and protein choices open in place above an Add · ₦11,000 button; the category collapsed and expanded. Cart line and editable option chip: default, with options, no longer available, price changed, a chosen option no longer available, and a dashed Choose dressing chip asking for the options again. Order timeline, order line and details panel: placed, confirmed, in transit and delivered with their times, a 3× Tender Grilled Chicken line and the customer's details
[ THE COMPONENTS IN FIGMA — A DISH AND ITS OPTIONS, EVERY STATE A BASKET LINE CAN LAND IN, AND AN ORDER'S FOUR STEPS, LIGHT AND DARK ]
[ Key decisions ][ 04 / 14 ]

Four decisions carry the product.

First, keep the client's backend model outside the UI. Their JSON passes through one adapter into the application's own model, so no component depends on the shape of an external system.

Second, give every piece of state one owner. The catalogue, the guest's basket and the staff's operational data live in separate stores with explicit responsibilities, so the guest side and the admin side never hold competing versions of the menu.

Third, let catalogue changes reach active baskets. A menu change is a domain event the basket has to answer, not an error the guest discovers at checkout.

Fourth, treat phone and desktop as different interaction modes, not different widths. The admin panel changes its navigation, filters and tables; the guest app stays a single column at every size.

Together they make the product predictable: a guest always knows what changed, why, and what to do next, and staff know that what they edit is exactly what guests read.

Design-system page: a breakpoint table saying what changes at each width, and layout rules for the guest column, the guest compact and desktop modes, the admin shell and admin modals
[ GRID AND LAYOUT — ONE PRODUCT, TWO LAYOUTS, AND THE FILES THEY'RE READ FROM ]
An order dialog over the orders page on two desktops and a phone, in light and dark, each showing the status steps, items and customer details
[ ONE ORDER AT 1920, 1440 AND 390, LIGHT AND DARK — THE SAME DIALOG, THE SAME FOUR STEPS ]
[ What I didn't do ][ 05 / 14 ]

Some of the most important decisions were refusals.

I didn't give guests and staff separate copies of the menu. Both sides read one catalogue, so a change made in the admin panel — a new price, a sold-out dish, a renamed option — is exactly what the next guest sees after scanning the QR code.

And I didn't let the screens read the client's data as it arrives. Their backend describes a menu in its own format: an option's price sits in a field called topping_option_price and is left empty when the option is free; a dish's category and subcategory are packed into one string, such as “SUYA:GRILLS”; names arrive in lowercase. One adapter translates all of that into the app's own model — a price, an in-stock flag, a category and a subcategory, a readable name — before any screen sees it. The client's format is understood in that one file, so when their team connects the real backend, the adapter changes, not every screen.

I didn't let a stale basket update its prices quietly: a repriced line keeps the old price, struck through, so the guest sees what changed. Nor did I let an unavailable item stay orderable.

I didn't treat mobile as a scaled-down desktop.

And I didn't let the QA agent change the product on its own authority. It finds; I decide.

The admin menus page on two desktops and a phone, in light and dark: the sidebar and inline filters on the desktops, a bottom navigation bar and docked search on the phone
[ MENUS AT 1920, 1440 AND 390, LIGHT AND DARK — THE SAME PAGE, REARRANGED RATHER THAN SHRUNK ]
The menus page on two phones with a filter sheet open: sort, availability, location and subcategory pickers, a Reset button and a Show 36 items button
[ FILTERS BECOME A SHEET — AND ITS BUTTON SAYS WHAT YOU'LL GET ]
The orders list on two phones with an order actions sheet open offering refresh orders and download report
[ PAGE ACTIONS COLLAPSE INTO ONE SHEET ON A PHONE ]
[ Guest experience ][ 06 / 14 ]

Every step between the scan and the order is designed, not defaulted.

The menu has category tabs, collapsible sections and a search that reads names and descriptions and ignores accents. A configurable dish opens its options in place — required choices marked — instead of sending the guest to another screen, and the Add button names the dish and states the total.

Checkout is four steps — basket, details, payment, summary. Choices stay editable from the basket, so changing a side doesn't mean deleting the dish and starting again, and the guest chooses how to pay: online, cash, card terminal or charge to room.

After ordering, the guest gets a confirmation, tracking through the status staff set, a delivery time labelled as the estimate it is, WhatsApp support, and feedback connected to a recent order. Loading, empty, error and unavailable states are designed as part of the product, not treated as exceptions to the happy path.

The guest app on a desktop in light and dark: the menu column on the left and the details step of checkout open as a side panel, with name, phone and room fields and the order total
[ ON DESKTOP, CHECKOUT OPENS BESIDE THE MENU INSTEAD OF OVER IT ]
The guest menu on two phones, light and dark: category tabs, an open Mains section with three dishes, one marked sold out with its Add button dimmed, a docked search and the cart bar
[ THE MENU — OPTIONS OPEN IN PLACE, AND A SOLD-OUT DISH STAYS VISIBLE BUT CAN'T BE ADDED ]
Order tracking on two phones: an estimated delivery card, five stages from received to delivered with preparing in progress, and a WhatsApp help card
[ TRACKING FOLLOWS THE STATUS STAFF SET, UNDER AN ESTIMATE THAT SAYS IT'S ONE ]
[ Admin panel ][ 07 / 14 ]

The admin panel is organised around what staff need to do next.

Five operational areas with role-based access: an administrator manages everything, a manager everything but the menus. The dashboard surfaces today's picture — net sales against the same point yesterday, orders in progress, menu views and how many became orders, and the busiest QR point.

Orders move through explicit states — placed, confirmed, in transit, delivered — rather than disappearing into a generic “processed”. Filters live in the URL, so opening an order and coming back keeps your place. Feedback is organised around follow-up: a low rating brings its order, the guest's contact options and an internal note with it.

On a phone the information architecture adapts instead of shrinking: the sidebar becomes an icon rail and then bottom navigation, filters become sheets, dense tables become readable rows, and page actions collapse into one contextual menu.

The dashboard in light and dark: net sales today with a sparkline, order, order-value, menu-view and busiest-QR-point cards, recent orders and top sellers
[ THE DASHBOARD OPENS ON TODAY — AGAINST THE SAME POINT YESTERDAY ]
An order dialog in light and dark: placed, confirmed, in transit and delivered with times, the items and total, and the customer's name, phone, room and payment method
[ AN ORDER — ITS FOUR STEPS, WHO IT'S FOR AND HOW THEY PAID ]
A feedback dialog in light and dark: a two-star rating marked needs follow-up, ratings per aspect, the guest's details with call and WhatsApp buttons, the linked order and an internal note
[ A TWO-STAR REVIEW, ITS ORDER, AND WHAT WAS DONE ABOUT IT ]
The dashboard on two phones, light and dark: stacked sales, orders, order-value and menu-view cards above a bottom navigation bar
[ THE DASHBOARD ON A PHONE — CARDS STACK, A BOTTOM BAR REPLACES THE SIDEBAR ]
The orders list on two phones as two-line rows with order number, time, guest, room, total and status, above a docked search and bottom navigation
[ ORDERS ON A PHONE — THE TABLE BECOMES TWO-LINE ROWS ]
[ Menu management ][ 08 / 14 ]

Adding a dish is a sequence, not a form.

Menus are what guests see when they scan the QR code, so a change in the admin panel reaches the guest side straight away: it decides what the next guest at that location sees, what they can order, and whether a basket already in progress is still valid. Menu data was also where the client's old system was hardest to use — data-heavy and difficult to navigate.

The Menus page is where it starts. Each dish is one row showing its category, its price at each location and its actions: edit, options, delete, and an availability switch. Turning the switch off marks the dish sold out at once — guests still see it on the menu, but can't order it.

Adding a dish is split into six short steps, each with one job: name and description; category and subcategory, picked from the list or created on the spot; price and availability; option groups; the locations that sell it, each with its own price if needed; and a photo, which can also be added later. A review of everything closes the flow. Each step is checked before the next one opens, and if something is missing, focus moves to the field that needs fixing.

Editing starts with a different question: what do you want to change? Every part of the dish appears as a card showing its current value, and the admin ticks only the parts they need — only those steps follow. The review then lists just what changed, with the old value struck through above the new one, and Save stays disabled until something has actually changed. The Options action on a row skips the question and opens the edit straight on the option groups.

Option groups, such as a spice level or a choice of protein, carry their own rules. A group is either required or optional, and every option has its own extra price and its own stock switch. The form won't accept two options with the same name in one group, or a required group with every option out of stock — guests couldn't order that dish at all.

Closing the dialog with unsaved changes asks first, so one press of Escape can't lose a long entry. Together, these turn menu administration from data entry into a short, guided task — and every change is one the guest side can safely show.

The menus page in light and dark: search, sort and filter controls, category chips with counts, and rows of dishes showing location prices, edit, options and delete actions and an availability switch
[ MENUS — LOCATION PRICES ON EVERY ROW, AND A SOLD-OUT SWITCH THE GUEST MENU OBEYS AT ONCE ]
The add menu item dialog in light and dark: a stepper of six steps and a review, and the first step with name and description fields
[ ADDING A DISH — SIX SHORT STEPS AND A REVIEW, EACH WITH ONE JOB ]
The review step of the edit dialog in light and dark: only the base price is listed, the old price struck through above the new one, with a save changes button
[ THE REVIEW LISTS ONLY WHAT CHANGED — THE OLD PRICE STRUCK THROUGH ]
The edit dialog on two phones, light and dark: cards for each part of the dish with their current values, price and toppings selected, and Cancel and Next buttons
[ EDITING ON A PHONE — TICK ONLY THE PARTS TO CHANGE; EACH CARD SHOWS ITS CURRENT VALUE ]
The toppings and options step on two phones: a spice level group with a guests must choose switch and options with extra price and stock switches
[ OPTION GROUPS ON A PHONE — A REQUIRED SWITCH, EXTRA PRICES, STOCK PER OPTION ]
[ Engineering ][ 09 / 14 ]

How the rules reach the code.

The adapter maps the client's JSON into dishes, option groups, options, locations, location prices and availability. When the prices proved wrong, the adapter didn't convert them: the data was corrected at the source, and a versioned migration rescaled baskets and orders already saved on devices, leaving anything priced in the admin panel untouched.

The three stores — catalogue, guest and admin — also form the integration contract. The client's developers connect their existing backend themselves, so replacing the simulated sources with API calls changes where the data comes from, not the model the interface understands.

Around them: routes code-split and guarded in their loaders, sessions that expire, redirects that can only point inside the admin panel, simulated functionality labelled as such in the code, and shared behaviour — dialog focus, scroll restoration, page titles — in reusable hooks. The frontend architecture follows the domain model, not the other way round.

The admin sign-in card in light and dark: email and password fields, a sign in button, a link back to the menu and two demo accounts
[ STAFF SIGN-IN — EVERY ROUTE BEHIND IT IS GUARDED BY ROLE ]
An error card in light and dark: something went wrong, a note that the basket and orders are safe, and Try again and Go to menu buttons
[ WHEN A ROUTE FAILS — THE ERROR STATE AS DESIGNED, LEADING WITH WHAT MATTERS: THE BASKET AND ORDERS ARE SAFE ]
Diagram: a QR link feeds the guest app; a role-guarded admin panel sits beside it; below them a guest store, a catalog read by both sides and an admin store that guest actions write into; JSON from the client's existing data system enters only through an adapter; payment, sign-in and dashboard activity are marked as simulated
[ ARCHITECTURE — THREE STORES WHERE THE CLIENT'S BACKEND CONNECTS, AND ONE ADAPTER BETWEEN THEIR DATA AND THE APP ]
[ Design system and accessibility ][ 10 / 14 ]

Figma, tokens, components and accessibility share the same rules.

The guest app and the admin panel share one design language: semantic colour tokens, light and dark themes, typography, spacing and sizing, component states, responsive layout rules, icons and focus states. Primitive values never reach a screen; each semantic token carries a light value, a dark value, the CSS custom property it ships as and a Figma scope — a traceable line from design to code.

Follow-up on the design system is checked, not assumed. A design-system agent compares Figma with the code and the running app, and records where they don't match instead of drawing over it: spacing tokens exist, but some components still use raw values, so spacing is marked as proposed in Figma rather than shown as finished.

The QA agent checks accessibility in the same components: that dialogs hold focus and return it to the control that opened them, that status changes and basket totals are announced, that accessible names start with the visible label, and that charts don't rely on colour alone. Its findings go through the same gate as every other finding — I decide what is fixed.

The feedback form on two phones: an overall rating, four aspect ratings in rows of stars, an optional comment box and a submit button
[ FEEDBACK — EVERY ROW OF STARS IS A RADIO GROUP ]
An order dialog on two phones over the orders list, with its four status steps, the item and total, the customer's details and a Close button
[ AN ORDER OPENS AS A DIALOG — FOCUS GOES IN, AND COMES BACK TO THE ROW ]
Diagram following the focus ring: amber primitives in Figma, the focus/ring semantic variable with a light and a dark value scoped to strokes, the --focus-ring custom property, and the components on both sides that draw it — with three rules that keep Figma and the code in step
[ ONE DECISION, FOUR PLACES — THE FOCUS RING FROM FIGMA PRIMITIVE TO COMPONENT, AND THE RULES THAT KEEP THEM IN STEP ]
Design-system page: a table of semantic colour tokens, each with light and dark swatches, the primitive it points to in each theme, its CSS custom property and its Figma scopes
[ COLOUR TOKENS — A LIGHT VALUE, A DARK VALUE, THE CSS NAME AND THE FIGMA SCOPE ON EVERY ROW ]
Design-system page: spacing tokens including two marked proposed, radius tokens, and font family, size and weight tokens, each with its value, CSS name and notes
[ SPACING, RADIUS AND TYPE TOKENS — WITH THE PROPOSED ONES MARKED AS PROPOSED ]
Four design-system pages, each in light and dark. Data table: header and body cells, rows in default, hover and focus states, and a recent-orders table with status badges. Order list row: the phone row with order number, time, item count and total, the guest and status, and room and location, on its own and in a list. Charts: the net-revenue hero with a change badge or a sparkline, a column chart of menu views, a stacked bar of delivered, open and cancelled orders, legend rows and dot-plot rows. Tracking phase: Preparing as completed, in progress and pending
[ THE ORDERS TABLE AND THE PHONE ROW THAT REPLACES IT, CHARTS IN ONE MARK COLOUR WITH RED AND GREEN ONLY FOR BAD AND GOOD, AND A TRACKING PHASE IN ITS THREE STATES — LIGHT AND DARK ]
[ Validation ][ 11 / 14 ]

The agent finds. I decide. The system validates.

A read-only browser agent navigates the app in Chromium, Firefox and WebKit at widths from 320px. It reproduces findings, inspects keyboard and focus behaviour, runs accessibility checks, computes contrast from the tokens actually shipping and collects evidence. It does not modify the application.

Every finding gets an ID and a severity. I decide whether it is real, whether it is a defect, where the correction belongs — a token, a component, a shared hook or the design itself — and whether the result is acceptable. Only approved findings are fixed, and the next validation pass checks the specific change.

Across five audit and validation passes, the first audit raised 48 findings, including the critical hundred-times prices. A later validation pass ran 74 targeted checks — each a scripted assertion that an approved fix holds — in each of three browser engines: 222 checks, all passing, after it had caught the rebuilt orders search losing keystrokes in Firefox alone.

AI accelerates the audit — browser coverage, accessibility checks, repetitive analysis. Product decisions, acceptance and risk stay with me. The point isn't the agent. It's the gate.

A Playwright test report for admin-to-guest.spec.ts: a lounge price edit reprices the open guest basket, a dish switched to sold out holds the guest's checkout, a deleted dish is flagged in the guest's basket, and each status change reaches the guest's order tracking — each passing in Chromium, Firefox and WebKit
[ ADMIN EDITS CHECKED ON THE GUEST SIDE — 54 OF 54 PASSING IN CHROMIUM, FIREFOX AND WEBKIT ]
The Playwright trace viewer for the sold-out test: the admin menus are searched and Coffee is edited, then the guest's cart flags Coffee as not available right now and the test asserts that Proceed to Checkout is disabled
[ ONE TRACE — A DISH MARKED SOLD OUT IN ADMIN, AND THE GUEST'S CHECKOUT BUTTON DISABLED ]
Diagram of the quality loop: a read-only agent audits in three engines, findings are approved by ID, fixes land in tokens, components or hooks, a validation pass re-runs checks, and the next audit starts from scratch — with three things one loop moved: the focus ring, route changes and the orders search
[ THE QUALITY LOOP — AUDIT, APPROVE, FIX WHERE THE RULE LIVES, VALIDATE, START AGAIN ]
[ Outcome ][ 12 / 14 ]

Version one: one product, two sides, one system.

Version one delivers one product — guest ordering and an admin panel — on one domain model and one design system; hotels with any number of locations, each with its own menus and prices — five in the demo hotel; live basket reconciliation; role-based staff workflows; phone to 1920px; light and dark themes; accessibility and cross-browser validation.

I implemented the UX architecture, product design, design system, React frontend, domain and state model, responsive behaviour, accessibility and the QA workflow.

Payment, staff sign-in and dashboard activity stay simulated until the client's backend is connected. No adoption or revenue figures are claimed because none exist yet; every figure in the demo is generated data.

The client's team owns the existing backend and its connection to the new frontend, production sign-in, payment integration, and validation with real guests and staff — the input for version two.

The staff dashboard on two desktops and a phone, in light and dark: net sales for today with a sparkline, order, order-value, menu-view and busiest-QR-point cards, recent orders and top sellers
[ THE DASHBOARD AT 1920, 1440 AND 390, LIGHT AND DARK — EVERY FIGURE ON IT IS GENERATED DEMO DATA ]
[ Open register ][ 13 / 14 ]

Accessibility is not a one-time certificate.

The build keeps a visible open register. Guest details are still kept on the device after an order. Long admin labels truncate when text spacing is enlarged on a phone. And a styling pass after the last audit softened the outline and focus colour of form fields below the 3:1 target.

The last one matters most: a product can pass an audit and regress a week later, so accessibility has to stay inside the development loop rather than be stamped once at the end.

The menus list on two phones, light and dark: category chips and dish rows with location prices, edit, options and delete actions and an availability switch
[ THE MENUS LIST ON A PHONE — WHERE LONG DISH NAMES STILL TRUNCATE UNDER ENLARGED TEXT SPACING ]
The price step of the edit dialog on two phones: a base price field with a hint that locations can set their own price
[ A FORM FIELD AS IT SHIPS TODAY — ITS OUTLINE AND FOCUS COLOUR ARE THE OPEN CONTRAST ITEM ]
[ What I'd tell the next team ][ 14 / 14 ]

Design the rules before the screens.

The screens were not the hardest part. The difficult decisions sat underneath them: what does a scan mean? What makes a basket line the same line? What happens when the menu changes under an active order? Which system owns the truth, and where should each rule live — an adapter, a store, a shared hook, a token or a component?

Once those rules were explicit, the interface became much easier to design — and QA changed too: instead of asking an agent to “check the application”, I could ask it to test specific behaviour against explicit rules.

AI can find evidence, accelerate the audit and expose inconsistencies. The product decisions still need an accountable human owner. That is the difference between using AI as a shortcut and using it inside a controlled product-engineering workflow.

[ Next case ][ 01 / 02 ]
Uxproof — engineering reliable AI experiences [ BOOK A 30-MIN INTRO CALL — NO COMMITMENT ]