Part 2 of 2 · Design engineering
A tap is only safe if the state survives
I turned the mobile design into a working React prototype where every attempted action can explain what was allowed, what was blocked, and what information remained intact.
Typed transition functions hold the product rules. React renders their results. The interface makes recovery, recipient order, focus, status, and destructive confirmation visible through the same controls a person would use.
The prototype uses fictional local data and no external service. It demonstrates client behavior, not production authorization, persistence, or signature delivery.
45 second summary
What I implemented and what this story proves
- Problem
- A connected prototype needed executable rules, not convenient screen links.
- Ownership
- I designed the transition model, interface states, recovery behavior, accessibility mechanics, and tests.
- Stack
- Next.js, React, TypeScript, semantic HTML, CSS, Vitest, and Playwright.
- Evidence
- Reachable browser states, exact transition logic, preserved input, and automated checks.
- Status
- A local browser prototype with fictional fixtures and deterministic scenarios.
- Limit
- No backend authorization, account sync, signature delivery, or production write is claimed.

Episode 1 · Transition contract
I turned the prototype path into a contract
Each journey accepts a typed action and returns either a next state or a guard error. Unsupported actions preserve the current data. A screen cannot skip responsibility, required information, recipient validation, or a valid prior state.
reviewing- approved
- failed
- changes requested
- cancelled
selecting template- created
- failed
- draft restored
configuring- waiting
- completed
- declined
- expired
- voided
- Previous state
approval:ready- Attempted action
SUBMIT- Guard result
accepted failure branch- Changed fields
stage- Next state
approval:failed- Announcement
Approval was not sent. Reconnect and retry.
if (action.type === "SUBMIT" || action.type === "RETRY") {
if ((action.type === "SUBMIT" && state.stage !== "ready") || (action.type === "RETRY" && state.stage !== "failed")) {
return rejected(state, action.type, approvalStageKey, "Submission is not available from the current state.")
}
if (!state.responsible) {
return rejected(state, action.type, approvalStageKey, "Assume responsibility before approving.")
}
if (state.network === "offline") {
const next = { ...state, stage: "failed" as const, message: "Approval was not sent. Reconnect and retry." }
return accepted(state, action.type, next, approvalStageKey, ["stage"], next.message)
}
const next = { ...state, stage: "submitting" as const, message: "Approval is ready to confirm." }
return accepted(state, action.type, next, approvalStageKey, ["stage"], next.message)
}Episode 2 · Recovery
Failure does not get to erase intent
Approval answers survive an offline attempt. Creation keeps its title, purpose, and stakeholders through validation and a retryable failure. A saved creation draft can restore after reload on the same device, while the story remains explicit that this is not account or server persistence.
Approval
- Preserve
- Risk answer and budget confirmation
- Interrupt
- Offline submission
- Recover
- Reconnect and retry
Creation
- Preserve
- Title, purpose, and stakeholders
- Interrupt
- Retryable submission failure
- Recover
- Restore and submit
Local draft
- Preserve
- Editing state and entered values
- Interrupt
- Reload on the same device
- Recover
- Resume from saved data
Approval after an offline attempt
Creation after a retryable failure
Episode 3 · Explicit order
I made hidden order operable
Signature recipients are validated before review, stored as an ordered array, and moved through named controls. The same array drives review and progress. After send, visible controls can reach waiting, reminder, complete, declined, expired, and voided states.
Before action
- 1Mara Reed
- 2Casey Morgan
- 3Jon Bell
MOVE Casey Morgan upacceptedAfter action
- 1Casey Morgan
- 2Mara Reed
- 3Jon Bell
Episode 4 · Perception
I made state changes perceivable
Journey changes move focus to the new heading. Validation focuses a summary and connects errors to fields. Accepted and rejected actions update one polite live region. Recipient movement uses named buttons. Destructive actions require confirmation. Reduced motion removes movement without removing feedback.
These are implemented behaviors. I do not call them accessibility compliance because no recorded audit establishes that claim.
- 01Journey change
Move focus to the new screen heading
- 02Validation failure
Focus the summary and associate each invalid field
- 03Accepted action
Announce the concise result through one polite region
- 04Recipient movement
Use a named button and preserve a logical order
- 05Destructive request
Require a second explicit confirmation
- 06Reduced motion
Remove movement without removing state feedback
Production boundary
The prototype stops at the browser
The client can demonstrate rules, persistence on one device, focus, and recovery. Production still needs trusted authorization, account synchronization, server confirmed writes, signature provider callbacks, idempotency, and conflict resolution.
Implemented here
- Fictional fixtures
- Typed transitions
- Local draft restoration
- Validation and recovery
- Focus and announcements
Required in production
- Trusted authorization
- Account synchronization
- Server confirmed writes
- Signature provider callbacks
- Conflict resolution