Frontend Labs
⋯

63. Mini project: Support Desk — a working agentic web app

Everything in this Part comes together in Support Desk, a customer support app where a signed-in user chats with an agent that looks up orders and proposes refunds, which the user approves in the UI. It has no dependencies at runtime: a node:http server and plain ES modules. The full source is on the companion page Support Desk — full source, and it lives in the wiki at JS/projects/support-desk/.

┌─────────────────────────── browser ───────────────────────────┐
│ index.html  CSP: script-src 'self'; require-trusted-types…     │
│ app.js      login · orders · chat · idle-session dialog        │
│ api.js      fetch wrapper (+CSRF header) · SSE reader (§59)    │
│ agent-ui.js component registry + prop validation (§61)        │
└───────────────┬───────────────────────────────────────────────┘
                │ POST /api/chat  (cookie + X-CSRF-Token)
                ▼ text/event-stream: run-started, tool-call, text-delta, ui, run-finished
┌─────────────────────────── server.mjs ─────────────────────────┐
│ sessions (HttpOnly cookie, scrypt passwords, CSRF)    §67      │
│ access control: you only see/refund your own orders   §65 A01  │
│ rate limits: failed logins, chat per session          §66      │
│ agent: picks a tool, streams words, emits UI events   §60–§61  │
│ one error handler: generic message + request id       §65 A10  │
└────────────────────────────────────────────────────────────────┘

Run it:

cd JS/projects/support-desk
npm start                       # http://127.0.0.1:3100  ·  alice@example.com / correct-horse-1
npm install && npx playwright install chromium
npm test                        # 26 E2E tests (§74–§78)

Try these in the chat: "where is A-1002?" (an order card), "list my orders" (several cards), "refund A-1001" (an approval card: approve it and watch the sidebar update), and "where is B-2001?" (Bob's order: "I couldn't find it", the same answer as for an order that doesn't exist).

The model is a stand-in on purpose. runAgent() in server.mjs is a deterministic rule-based "model", so the app and its tests run offline and the same way every time. Swapping in a real model changes one function, not the protocol:

  • On the server: replace runAgent with §60's loop, using a model.complete() that calls your OpenAI-compatible server. Map its tool calls to tool-call events, its text to text-delta events, and a lookupOrder result to a ui event with component: "order-card".
  • In the browser: run §58's WebLLM engine in a worker, let it pick the tool with constrained JSON, and call the same /api/orders endpoints with the user's session. The server stays the authority: it checks ownership and CSRF on every refund, whoever asked.
  • At the edge: put §62's gateway in front of the model so the key never reaches the browser.

🎯 Challenges. (1) Add a track-shipment component with a progress bar, from schema to Playwright test. (2) Persist the conversation in IndexedDB (§45) so a reload restores it. (3) Add a "regenerate" button that aborts the current stream and replays the last user message. (4) Put §57's embedding search behind a searchHelpCenter tool.


Generated from real-life-JS-guide.md · 8 Oct 2026 16:36