⌂ Home
Gemini Code Assist: AI Pair Programming in Your IDE
Domain Capstone · Frontend
⏰ 3-5 hours 🏆 Capstone Project
📊
Capstone — TrackFlow Ops Dashboard Build the screen a warehouse operations team stares at all day: a zero-build vanilla JS dashboard over the TrackFlow API — live order board, SLA breach banner, tracking timeline drawer, full keyboard accessibility. Prerequisite: modules G00–G06 (the API capstone pairs perfectly but isn't required — a mock mode is built in).

Capstone: TrackFlow Ops Dashboard

APIs don't answer phones — people do, and they answer faster when the screen in front of them is honest, fast, and readable from six feet away. This capstone is about building that screen well, with Gemini doing the labor of layout, fetch plumbing, and rendering while you direct the UX decisions.

Domain Briefing: The Ops Floor

The Domain, Plainly

The customer-service team at a wholesale distributor lives in a tool like this. A restaurant calls: "where's PO-88412?" The rep needs the answer in under five seconds: current state, carrier events, and — critically — whether the order is about to breach its SLA before the customer mentions it. The pain with bad ops dashboards is always the same three failures: stale data presented as fresh (the rep promises "it shipped" when it didn't), failure states that look like empty states (the API is down but the screen shows "no orders" — so the rep tells the customer there's no order), and mouse-only UIs that slow down power users who live on the keyboard. Your dashboard must defeat all three — they're rubric items.

Why Vanilla JS, No Framework?

Two reasons. Pedagogically: with no framework between you and the DOM, every concept (state, rendering, events, fetch lifecycles) is visible — and those concepts transfer to React/Vue/Svelte, while the reverse is not true. Practically: this capstone tests whether you can direct Gemini through UI work — layout, accessibility, async states — where the review skills differ from backend diffs: you verify with your eyes, the keyboard, and the network tab, not just pytest.

What You Build

The Spec

trackflow-dashboard — three files (index.html, style.css, app.js) plus a pure-logic module, no build step, opened via a static server:

  • Order board: table of POs with color-coded state chips, filterable by state and customer, sortable by promised ship date
  • SLA banner: a persistent strip showing breached / at-risk counts; clicking filters the board
  • Detail drawer: clicking a row (or pressing Enter on it) slides open the order: line items, full audit timeline, carrier tracking events
  • Live-ness: auto-refresh every 30s with a visible "last updated" stamp; manual refresh button; distinct loading / error / empty states
  • Data layer: api.js with two implementations behind one interface — live (fetches the TrackFlow API from the API capstone) and mock (deterministic in-memory data) selected by ?mode=mock
  • Tested logic: the pure functions (SLA classification, filtering, sorting, event formatting) live in logic.js and run under node --test — no browser needed
┌───────────────────────────────────────────────────┐ │ TrackFlow Ops [Search PO/customer...] ↻ Updated 14:02:11 │ ├──────────────────────────────────────────────────┴ │ ⚠ 2 BREACHED · 5 AT RISK (click to filter) │ ├───────────────────────────────────────────────────┤ │ State: [All ▾] Customer: [All ▾] Sort: [Promised date ▾] │ │ PO-88412 Riverside Restaurants [SHIPPED] due 06-12 ● on time│ │ PO-88393 Hilltop Cafes [PICKING] due 06-11 ⚠ at risk│ │ PO-88371 Marina Foods [CONFIRMED] due 06-10 ❌ BREACH │ │ ▸ drawer: lines · audit timeline · carrier events │ └───────────────────────────────────────────────────┐

Executing This Capstone in VS Code / PyCharm (or WebStorm)

Everything in this project is built through Gemini Code Assist inside your IDE — never by copying finished code from elsewhere. Each phase uses some of these five moves; every paste-block below names its move in the header. (Gemini Code Assist works identically in WebStorm if that's your JS home — it's the same JetBrains plugin.)

Before you start (6-point checklist)
  1. Create the project folder and open it: VS Code File → Open Folder (PyCharm/WebStorm: File → Open). New files via the Explorer panel / Project tool window.
  2. Confirm Gemini Code Assist is signed in: the ✦ icon in VS Code's Activity Bar opens the panel with no error badge (JetBrains: the Gemini tool window on the right edge greets you by account). Setup is module G01.
  3. Open the integrated terminal (Ctrl+` in VS Code, Alt+F12 in JetBrains) for git and node --test.
  4. Serve the page from a static server — fetch() won't work from file://. Easiest: python -m http.server 8000 in the integrated terminal, then open http://localhost:8000 (or VS Code's Live Server extension / JetBrains' built-in preview).
  5. After creating GEMINI.md in Phase 1, start a new chat so the layering rules load into context (the G05 lesson).
  6. Keep the browser DevTools open (F12) — console errors and the Network tab are the "evidence" you feed to /fix in this capstone (the G03 habit, frontend edition).
MoveVS CodePyCharm / WebStorm
1 · Spec-commentType the comment in the editor and pause — ghost text appears. Tab accepts, Ctrl+→ accepts word-by-word, Esc rejects (G02)Same: type, pause, Tab; hover the suggestion to cycle alternatives
2 · Chat / smart cmdClick the ✦ icon in the Activity Bar → chat panel opens. Select code first to scope it; /explain /fix /doc /simplify work here (G03)Gemini tool window (right edge) → Chat tab; same slash commands
3 · Generate testsSelect the function → right-click → Gemini Code Assist → Generate unit tests — ask for node:test style in chat if it assumes Jest; save into tests/ (G04)Select → right-click → Gemini → Generate unit tests
4 · Agent ticketIn the chat input, switch the Agent toggle ON → paste the ticket → review the plan in the panel → Accept/Reject each diff → click Allow when it asks to run a command (G06)Gemini tool window → Agent tab → paste the ticket; leave Auto-approve changes OFF; review diffs the same way
5 · Headless CLIIntegrated terminal: gemini -p "..." — same login, same GEMINI.md rules as the IDE (G08)

Example: a block labeled "Move 4 · paste in Agent mode" means: open the Gemini panel, flip the Agent toggle (VS Code) or open the Agent tab (JetBrains), paste, and work the plan→diff→approve loop.

Architecture: Three Layers, Even in Vanilla

project layout — this discipline IS the lesson
trackflow-dashboard/
├── index.html        semantic skeleton only — no inline JS
├── style.css         design tokens as CSS custom properties
├── js/
│   ├── api.js        data layer: liveApi + mockApi behind one interface
│   ├── logic.js      PURE functions: classifySla(order, now),
│   │                 filterOrders(orders, criteria), sortOrders(...),
│   │                 formatEvent(...) — no DOM, no fetch, no Date.now()
│   │                 (time is always a parameter — that's what makes it testable)
│   └── app.js        state + render + events; the ONLY file that touches the DOM
├── tests/
│   └── logic.test.mjs   node --test, no browser required
└── GEMINI.md / .aiexclude / .gemini/styleguide.md
The non-negotiable rule (put it in GEMINI.md): logic.js imports nothing and touches nothing global — current time, orders, and filters always arrive as arguments. This single rule is what makes "frontend logic" testable with node --test and is the vanilla ancestor of every state-management pattern you'll later meet in frameworks.

Build Phases

PhaseWhat happensSkills from
1. FoundationGEMINI.md + skeleton + tokensG05, G02
2. Logic + dataPure functions, tests, dual API layerG02–G04
3. RenderBoard, banner, drawer, async statesG02–G03
4. A11y + agentAccessibility audit; dark mode via agentG03, G06
5. ShipPR review + eyes-on verificationG09
About the "expected output" blocks

Frontend success is partly terminal output (node --test) and partly observable behavior — so the expected-output blocks below describe both. Your visual design will differ; the behaviors (which state shows when, where focus lands, what persists) must match exactly.

Foundation: rules, skeleton, tokens

New repo. GEMINI.md rules: the three-layer separation above; semantic HTML (table for the board, button for actions — never clickable divs); all colors/spacing via CSS custom properties in :root; every interactive element keyboard-reachable; loading/error/empty are three distinct UI states, never conflated. Then generate the skeleton from a spec-comment in index.html (Move 1: type an HTML comment describing the regions — header with search + refresh, SLA banner, filter bar, order table, drawer <aside> — pause for ghost text, accept section by section) and the token sheet in style.css the same way.

Checkpoint Page renders the empty skeleton; HTML passes a quick chat audit (Move 2 — with index.html as the active file): "/explain is this markup semantic? flag any div that should be a button, table, or nav".

Logic and data: testable before visible

Build logic.js function-by-function with spec-comments. The heart is SLA classification — spec it precisely:

Move 1 · type in the editor (js/logic.js), pause for ghost text, Tab to accept
// classifySla(order, nowIso) -> "ON_TIME" | "AT_RISK" | "BREACHED" | "MET"
// MET: order reached SHIPPED/DELIVERED on or before promised_ship_date.
// BREACHED: not shipped and now is past end of promised_ship_date.
// AT_RISK: not shipped and promised_ship_date is today or tomorrow.
// ON_TIME: everything else. Treat dates as YYYY-MM-DD strings in local time.
// Pure function: never call Date.now() — now arrives as a parameter.

Generate tests (Move 3: select classifySla → right-click → Generate unit tests, then in chat demand "node:test suite for classifySla covering all four outcomes plus the boundary: promised date == today"), run node --test tests/ in the integrated terminal, and iterate. Then api.js: mockApi first (deterministic orders covering every state and SLA class — generate it headlessly with Gemini CLI like the other capstones), then liveApi with timeouts and typed errors ({kind: "network" | "http", status?}) so the UI can distinguish failure from emptiness.

expected output — pure logic, tested without a browser
PS> node --test tests/
✔ classifySla: MET when shipped on or before promised date
✔ classifySla: BREACHED when unshipped past promised date
✔ classifySla: AT_RISK when promised date is today (the boundary)
✔ classifySla: AT_RISK when promised date is tomorrow
✔ classifySla: ON_TIME otherwise
✔ filterOrders: state and customer filters combine (AND)
✔ filterOrders: empty criteria returns all orders
✔ sortOrders: promised date ascending, stable for ties
✔ formatEvent: ISO timestamp renders as local short form
ℹ tests 14 • suites 4 • pass 14 • fail 0 • duration_ms 92
Checkpoint node --test all green with zero browser involvement — if any logic test needs a DOM, the function is in the wrong file (the frontend version of G04's "failing tests indict the design").

Render: where you review with your eyes

Build app.js in chat-sized increments: render the board from mock data → wire filters/sort (calling logic.js) → SLA banner → drawer → auto-refresh with the "last updated" stamp. Then force the three async states and look at each: mock mode with empty data (empty state: "No orders match" + a clear-filters action), live mode with the API stopped (error state: "Can't reach TrackFlow — data may be stale" + retry), and a throttled network (loading state visible, UI not frozen). Use chat to fix what you see, feeding it evidence G03-style: paste the console error or describe the misbehavior precisely.

expected behavior — the three async states, forced and observed
http://localhost:8000/?mode=mock
  -> board renders 12 orders · banner reads "⚠ 2 BREACHED · 3 AT RISK"
  -> "Last updated 14:02:11" stamp ticks on every refresh

EMPTY  (mock mode, filter to a customer with no orders)
  -> "No orders match your filters"  +  [Clear filters] button
  -> banner counts unchanged (they reflect ALL orders, not the filter)

ERROR  (live mode, TrackFlow server stopped)
  -> red strip: "Can't reach TrackFlow — data may be stale"  +  [Retry]
  -> previous orders stay visible, marked stale — NOT a blank table
  -> DevTools console: one handled error log, no uncaught exceptions

LOADING  (DevTools → Network → throttle to Slow 3G, press Refresh)
  -> skeleton rows or spinner visible; filters remain clickable;
     the "Last updated" stamp does NOT advance until data lands
The conflation trap

Generated frontend code loves catch (e) { orders = [] } — which renders an API outage as "no orders," the exact lie that gets a rep to tell a customer their order doesn't exist. Hunt for this pattern in every diff you accept; rejecting it is rubric item 3.

Accessibility audit, then an agent-built feature

First the audit — Move 2, attaching both files as context (the +/paperclip control in the chat box, or keep both open in editor tabs): "Audit index.html + app.js for keyboard and screen-reader accessibility: tab order, focus management when the drawer opens/closes (focus moves in, Escape closes, focus returns to the row), aria-live for the SLA banner and 'last updated', missing labels. List concrete fixes." Apply and verify by unplugging your mouse — complete a full task (find a breached order, open its drawer, read tracking, close) with keys alone. Then the G06 ticket:

Move 4 · paste in Agent mode (Agent toggle ON / Agent tab)
Add dark/light theme support:
1. All colors already flow through CSS custom properties — add a light
   theme as a [data-theme="light"] override block. Do NOT touch component CSS.
2. A theme toggle button in the header (aria-pressed, persists in localStorage,
   defaults to the user's prefers-color-scheme).
3. Both themes must keep WCAG AA contrast for state chips and the SLA
   banner — list the contrast ratios you chose in your plan.
Plan first.
expected behavior — keyboard task and theme persistence
KEYBOARD-ONLY TASK (mouse unplugged)
  Tab order: search → refresh → theme toggle → SLA banner → filters → first row
  Arrow/Tab to the breached row, press Enter
    -> drawer opens, focus lands on the drawer heading
  Tab through: line items → audit timeline → tracking events
  Press Esc
    -> drawer closes, focus RETURNS to the row you opened it from

THEME
  Click toggle -> <html data-theme="light">; aria-pressed flips to "true"
  DevTools console:  localStorage.getItem("theme")  ->  "light"
  Reload -> still light.  Clear localStorage + reload
    -> follows OS prefers-color-scheme
  State chips and SLA banner readable in BOTH themes (AA: ≥4.5:1)
Checkpoint The agent's plan listed real contrast ratios (challenge it if the warning-on-light pairing is below 4.5:1 — it often is); the diff touched only the token block, the toggle, and localStorage logic. Toggle works, persists, and respects the OS default on first visit.

Ship: review gate plus the eyes-on checklist

Push to GitHub; .gemini/styleguide.md must-flags: DOM access outside app.js, Date.now() inside logic.js, clickable non-button elements, catch-blocks that render errors as empty states, hardcoded colors bypassing tokens. Open the theme feature as a PR, process the review (G09 discipline). Final verification is manual and honest: the three async states, the keyboard-only task, both themes, and ?mode=mock vs live against your running TrackFlow API.

Grading Rubric

Stretch Goals

  • Optimistic transitions: add a "Mark Delivered" action in the drawer that calls the API's transition endpoint, updates the UI optimistically, and rolls back with a toast on 409.
  • URL state: filters and the open drawer encoded in the query string, so a rep can paste a link to a colleague that opens the exact same view.
  • Big-board mode: a read-only rotation view for the warehouse wall TV (larger type, auto-cycling pages, breaches always pinned).
  • Framework port: rebuild app.js in React keeping logic.js and api.js byte-identical — the layering pays off when only one of three layers changes.
Where this goes on a resume

"Built an operations dashboard with layered vanilla-JS architecture (headlessly tested pure logic, swappable data layer), honest async states, full keyboard accessibility with managed focus, and WCAG AA dual themes; AI-assisted development with human-reviewed agent workflows."