Resolve
Making a support operations platform fast enough to live in for a whole shift.
- Role
- Frontend architecture · UI
- Year
- 2026
- Stack
- React · TS · TanStack
- Status
- Self-directed showcase
The problem with support tooling
Support agents don’t browse an app; they inhabit one screen for an entire shift. Every extra click, every list that re-renders and loses scroll, every reply that waits on a round trip compounds hundreds of times a day. Most helpdesk front ends are built as if each interaction happens once.
Resolve is my attempt at the opposite: an interface tuned for the thousandth interaction rather than the first.
Three rules I held to
Each one is a decision I could be wrong about, so each one is written down where a teammate can argue with it.
Never move the ground
Scroll position, selection, and focus survive every refetch. New tickets arrive as a count, not as a jump.
Optimism with a receipt
Actions land immediately and carry their own undo. Failure restores the exact prior state and says so in words.
Density over disclosure
If an agent needs it hourly it is on screen. Accordions are for things read once.
// reply lands in the thread before the network confirms it const mutation = useMutation({ mutationFn: sendReply, onMutate: async (draft) => { await qc.cancelQueries({ queryKey: thread(id) }); const prev = qc.getQueryData(thread(id)); qc.setQueryData(thread(id), append(draft, 'pending')); return { prev }; }, onError: (_e, _v, ctx) => qc.setQueryData(thread(id), ctx.prev), }); Pick a note to mark the lines it refers to.
The mouse is the fallback
Every triage action has a single key. The palette exists for discovery, not for speed — an agent who has learned the queue never opens it.
- J / K
- Move down / up the queue Selection never leaves the viewport
- E
- Archive and advance Undo toast holds for 8 seconds
- A
- Assign Opens the palette pre-filtered to people
- R
- Reply inline Focus lands in the composer, draft autosaved
- ⇧S
- Snooze until tomorrow 9:00 Custom times via the palette
- ⌘K
- Command palette Only surface that is mouse-first
Results are ranked by recency of use, not alphabetically — the second week feels different from the first.
Status has to be readable at a glance
Colour alone never carries meaning: each state pairs a hue with a shape and a word, so the list stays legible for colour-blind agents and in a screenshot pasted into Slack.
Copper dot, round. SLA is inside one hour — the only state that also sorts to the top.
Neutral dot. Waiting on us, but with time on the clock.
Square, not round. Waiting on the customer; ages differently.
Green dot plus a struck subject line — two signals, never one.
A rule you can read out loud
Routing is where support tools go opaque: a ticket lands somewhere and nobody can say why. Every rule here reads as one sentence — when a trigger fires, if these conditions match, then run these actions in order — and each clause is an editable chip rather than a nested form.
The list beside the editor shows runs in the last thirty days and the last time each rule fired, so a rule that has quietly stopped matching is visible without opening it.
The knowledge base is an output
Searches that returned nothing are ranked by the tickets created straight after them, and solved threads are offered back as article drafts. Coverage gaps are content, not a chart.
Density is a feature. The job was to make more information legible, not to hide it behind progressive disclosure.
A proof link renders only once its destination exists.