| title | HTTP Inspector — Implementation Plan | ||
|---|---|---|---|
| description | User stories, architecture, and build phases for the Playground network panel | ||
| audience |
|
||
| status | ready-to-build |
Wiki Home › Future Features › Plans
Plan for the HTTP Inspector proposal: wrap fetch inside the sandbox iframe and render each request in a mini Network panel beside the Playground console. Open choices live in the decision log — resolve those before building.
- Empty-result debugging. As a learner whose fetch returned an empty array, I can see the request's final URL, status code, and duration without writing any logging code — so I notice the typo'd query param instead of blaming the API.
- Status-code literacy. As a learner, every request my code makes shows its method and status (200 / 201 / 404 / 429), color-coded the way browser DevTools does, so the skill transfers to real tools.
- Header discovery. As a learner, I can expand a request row and see teaching-relevant response headers —
X-Total-Count, theLinkpagination header, rate-limit headers — that I'd otherwise never know exist. - Failure made visible. As a learner whose request failed at the network layer (bad host, CORS), I see a failed-request row with a plain-language explanation instead of a bare
TypeError: Failed to fetch. - Unchanged code behavior. As a user, my code sees the exact same
Responseobject it would without the inspector — nothing consumed, nothing mutated.
Three pieces: a fetch wrapper in the sandbox bootstrap, two new message kinds on the existing tokened postMessage channel, and a panel component in the Playground's output pane.
sequenceDiagram
participant U as User code
participant W as fetch wrapper (bootstrap)
participant P as Playground page
participant S as API server
U->>W: fetch(url, opts)
W->>P: __net {phase: request, id, method, url}
W->>S: original fetch
S-->>W: Response
W->>P: __net {phase: response, id, status, ms, size, headers}
W-->>U: Response (untouched)
Extends the bootstrap script in Playground.tsx, alongside the existing console wrapper:
- Replace
window.fetchbefore user code runs. Each call gets a sequential id so the response row can update its request row. - On call: post a
requestevent — id, method (defaultGET), resolved URL,performance.now()start. - On resolve: post a
responseevent — id, status, statusText, elapsed ms, and headers. Duration is time-to-headers (whenfetchresolves), which is the honest numberfetchitself gives you. - Size: read
Content-Lengthif present; otherwise readresponse.clone()'s body and report byte length. The clone is what gets consumed — the original returns to user code untouched. - On reject: post an
errorevent (id, error message), then rethrow so user code'scatchstill fires. Never swallow. - Header capture is limited to CORS-exposed headers by the browser; see the server change below.
- Best-effort by design: if user code reassigns
window.fetchor usesXMLHttpRequest, those requests simply don't appear. Not worth defending against.
Existing messages are { token, level, values } with levels log|info|warn|error|debug|__ready|__done. Add one level, __net, whose single value is the event object ({ id, phase, method, url, status, statusText, ms, size, headers }). Events are built by the wrapper from primitives, so they need no sanitize() pass. The page-side handler routes __net into separate state (netEvents), everything else into output as today. Both reset per run.
- Lives in the Playground's existing output pane; exact layout (tabs vs. interleaved) is decision D1.
- Row: method · path (origin elided when it matches the active endpoint's) · status chip (2xx green, 3xx blue, 4xx orange, 5xx/network red) · duration · size.
- Expanded row: full URL and a headers table. Which headers, and whether an explainer line appears for failures and 429s, are D2/D3.
- Pending requests render immediately from the
requestevent with a spinner status, then fill in — this is itself a lesson in request lifecycle.
Browsers only let JS read CORS-exposed response headers. Add to the cors() options in sampleapis.js:
exposedHeaders: ["X-Total-Count", "Link", "Location", "RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset", "Retry-After"]
This also benefits every external consumer of the API, independent of the inspector.
| Phase | Scope | Done when |
|---|---|---|
| 1. Expose headers | exposedHeaders in the CORS config + server test |
A Playground fetch can read X-Total-Count on a collection GET |
| 2. Wrapper + protocol | Bootstrap fetch wrapper, __net level, page-side state |
Events arrive for success, 404, and network-error runs (verify via temporary console output) |
| 3. Network panel | Panel component, rows, expansion, per-run reset, CSS | The user-story walkthroughs above all pass by hand |
| 4. Teaching polish | Failure explainers (per D2), header curation (per D3), empty-state copy | Copy reviewed; feature doc written in docs/features/ and the proposal page marked accepted |
- Server (Jest + supertest in server/tests): assert the exposed-headers list on a collection response.
- Client has no test runner today — verification is a manual checklist: 200 GET, POST 201, GET a missing id (404), fetch a nonexistent host (error row + rethrow reaches user
catch), a run with zero fetches (empty state), two rapid runs (panel resets), and a response with noContent-Length(clone-based size). - Type-check and lint (
tsc,oxlint) as usual.
- Capturing request/response bodies (privacy-neutral here, but heavy UI; revisit if Guided Challenges needs it — its checks consume the same events, not the panel).
XMLHttpRequest, WebSocket, orEventSourcecapture.- Persisting network logs across runs.
- client/src/components/Playground/Playground.tsx — bootstrap, message handling, output pane
- server/sampleapis.js — CORS config
- client/src/components/Playground/Playground.css — panel styling
- HTTP Inspector — Decisions
- Proposal · Roadmap
- Rate Limiting — the headers worth teaching
- Guided Challenges plan — reuses this fetch wrapper for request-level checks