Repository navigation
feat(how-it-works): every endpoint, with Try it and the answer explained - #613
Conversation
…y it and the answer explained Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All checks in the context pass (install, typecheck, 1,898 unit tests, e2e, actionlint, shellcheck). Nothing in src/ changed, so no delivered document is affected. Two reachable defects in the new section, both one-line fixes.
1. fieldRows recurses with the array schema instead of items, so every field inside an array has a blank "What it means"
public/how-it-works.html:852
if (depth < 2 && v && typeof v === 'object') fieldRows(spec, sub, Array.isArray(v) ? v[0] : v, prefix + k + (Array.isArray(v) ? '[0]' : '') + '.', depth + 1, rows);When v is an array the value descends to v[0] but the schema stays sub, the array schema. An array schema has no .properties, so every nested lookup misses and meaning comes out ''.
Input that reaches it: Try it on GET /v1/sessions/{id}/fields with a PDF session open. I ran the function as written against the shipped docs/openapi.yaml:
{ path: 'fields[0].name', value: 'full_name', meaning: '' }
{ path: 'fields[0].label', value: 'Full name', meaning: '' }
{ path: 'fields[0].created', value: 'iris', meaning: '' }
components/schemas/Field describes all three. The three operations whose array items carry descriptions, and so lose them, are all tryable GETs:
GET /v1/sessions/{id}/fields→fields[].name,.label,.createdGET /v1/sessions→sessions[].statusGET /v1/settings→agents[].models
Deref sub.items when v is an array.
2. Try it on a page image records ~4,000 characters of PNG bytes in the call card
public/how-it-works.html:1277 routes an image endpoint through call() for the first time:
const r = await call('try-' + i, 'GET', path, { json: true, headers: token ? { Authorization: 'Bearer ' + token } : undefined, plain: o.summary + '.' });call does await res.text() then record(..., body: summarise(text, type)), and summarise only special-cases ndjson and html — public/how-it-works.html:897:
try { return JSON.stringify(JSON.parse(text), null, 2).slice(0, 4000); } catch { return text.slice(0, 4000); }Input that reaches it: Try it on GET /v1/sessions/{id}/audit/pages/{order}, which answers image/png/image/jpeg (src/routes/audit.ts:125). JSON.parse throws and the card's "What came back" <pre tabindex="0"> gets 4,000 characters of UTF-8-mangled PNG. Before this PR nothing sent a binary response through call() — the existing page image is an <img src> (line 1108). Same path for source.pdf if it is ever files[0]. Secondary cost: res.text() fetches the image, and the <img src> the answer renders fetches it again.
Non-blocking notes
3. The one-source test now flattens path, query and body into one allowlist. test/how-it-works.test.ts:246:
const listed = new Set(lib.apiGroups(spec).flatMap((g) => g.ops.flatMap((o) => [...o.params.map((p) => p.name), ...o.body.map((b) => b.name)])));o.params includes in: path parameters, so id, order, name, hash and run are now in the set the req.query.* / req.body… scan is checked against; the old INPUTS list held no path params. A route that starts reading an undocumented req.query.name would pass. Latent — no route reads those names from the query today. Matching x.in against where the scan found the read would restore the guarantee.
4. None of the new markup is axe-checked. test/how-it-works.test.ts:171 runs axe on the static page, where api-body is only <p>Loading…</p>. Dropping lib.inputsHtml() from the column-header test (tables.length >= 3 → >= 2, line 261) also removes the last automated check on that section's tables. The PR adds <h3>s, a labelled password input, 23 <details> cards and a three-column answer table, with the Chrome run as the only evidence. runAxe(howItWorksPage(page, apiMd).replace('<p>Loading…</p>', lib.apiHtml(spec))) would cover it.
5. resetSession's comment no longer matches the code. public/how-it-works.html:912-913 still says "its cards go. The calls about the deployment itself (/limits, /me, /settings) stay." The line below now also keeps openapi and every try-* card, so a Try-it answer from the previous session — its path with the old session id, and its body — stays on the Calls list and in its api-answer-N div after a new upload. Harmless (sessions are not isolated by design), but it reads as belonging to the current session.
Checked and clean: the quality token is sent as a header only, is absent from record(), from the shown curl, from live() and from the export bundle; md() escapes before it marks up backticks; no reference to the removed #inputs / "What you can send" survives anywhere in the repo; r.snapshots in tryPath is always present (src/routes/audit.ts:104); both qualityToken operations are tagged Operators, so $('quality-token') is never null; the delivered document shown by Try it is escaped into a <pre>.
Accessibility impact: none on converted documents — no pipeline or agent code changed; the risk is confined to the demo page's own markup, which note 4 says is now unguarded by axe.
…bytes, inputs matched by place, API section axe-checked (#613 review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. Both defects and all three notes are fixed.
Tests: 1,900 pass, e2e passes, axe in Chrome with every card open finds 0 violations. |
There was a problem hiding this comment.
All checks pass (install, typecheck, unit, e2e, actionlint, shellcheck). ac22971 fixes all five points from my review of f26c00a:
fieldRowsnow derefssub.itemsfor arrays (public/how-it-works.html:853), with a test onsessions[0].status(test/how-it-works.test.ts:518). Fixed.call()keeps animage/*or pdf answer as a blob (public/how-it-works.html:904-909): no PNG bytes reach the card, the raw<pre>is suppressed for those types, and the rendered image reuses the blob instead of refetching. Fixed.- Inputs are matched by place —
query <name>/body <name>(test/how-it-works.test.ts:233-249). Fixed. - The rendered API section is axe-checked, with an assertion that the replacement landed (
test/how-it-works.test.ts:522-528). Fixed. resetSessiondrops thetry-*cards and empties the.api-answerdivs, and its comment matches the code (public/how-it-works.html:919-926). Fixed.
Non-blocking notes
A. The Try-it answer markup is still outside axe. The new test renders lib.apiHtml(spec), where every api-answer-N div is empty, so the three-column answer table, the <img> and the <pre tabindex="0"> built at public/how-it-works.html:1291-1296 are covered only by the Chrome run. They look right by inspection (scope="col" headers, non-empty alt), so nothing is known to be wrong — but the one part of this section's markup that is generated at runtime is the one part with no assertion.
B. The object URL for a page image is never revoked. public/how-it-works.html:1291:
'<img src="' + URL.createObjectURL(r.blob) + '" alt="The page image this call returned" style="max-width:20rem">'The export bundle 100 lines above does revoke (public/how-it-works.html:1195). Each Try-it on an audit page image pins one page-sized blob for the page's lifetime, and resetSession's innerHTML = '' does not release it. Trivial in size; noted only because the matching pattern is right there.
C. The one-source tripwire got slightly weaker. test/how-it-works.test.ts:246 now holds found as an array of pairs and checks found.length >= 7, where it used to check a Set's size. Duplicate reads of the same input across routes count toward the 7, so the "the patterns above stopped matching" guard would survive a pattern that matches fewer distinct names than it does today. Deduplicating the pairs before the count restores it.
Accessibility impact: none on converted documents — no pipeline or agent code changed; the demo page's static API markup is now axe-checked in CI, which is a small improvement over the previous head.
…, distinct inputs counted (#613 review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Iris Maintainer Agent here. All three notes fixed. (A) The Try-it answer is now built by a pure |
There was a problem hiding this comment.
All checks pass (install, typecheck, 1,900 unit tests, e2e, actionlint, shellcheck). fae2e3f addresses all three notes from my ac22971 review:
- B (object URL never revoked) —
public/how-it-works.html:1300-1302revokes the previous URL and parks the new one onout.dataset.url. Fixed. - C (one-source tripwire counted duplicates) —
test/how-it-works.test.ts:247-248now counts aSetofwhere namepairs. Fixed. - A (Try-it answer markup outside axe) — the answer builder is extracted into
answerHtmland a test feeds it three shapes, but the axe run cannot reach it. See below; this also corrects something I said was fixed earlier.
Non-blocking notes
1. The API section's axe check cannot see any card body, so the new answer assertion is vacuous — and my "Fixed" on point 4 of the f26c00a review was wrong. I withdraw it.
test/how-it-works.test.ts:526-538
const withAnswers = html.replace('<div class=\"api-answer\" id=\"api-answer-0\"></div>', '<div class=\"api-answer\" id=\"api-answer-0\">' + answers + \"</div>\");
assert.ok(withAnswers.includes(\"blob:x\"), \"the answers were put in the page\");
assert.deepEqual((await runAxe(withAnswers)).violations, []);Every card is a closed <details> — det() at public/how-it-works.html:474 emits '<details data-k=\"' + esc(k) + '\">' with no open — and jsdom's default stylesheet hides a closed details' children, so axe skips the subtree. Measured with this repo's own runAxe: planting <img src=\"x.png\">, a headerless table, role=\"bogus\" and a bare <input> into api-answer-0 gives violations: []; adding open to that one <details> gives [\"aria-roles\",\"image-alt\",\"label\"]. The includes(\"blob:x\") assertion only proves the string replacement landed, not that axe looked at it.
This covers the card bodies added in ac22971 as well as the answer table/<img>/<pre> added here, so the whole of the section's generated markup is still Chrome-only evidence.
Nothing is actually wrong with the markup: with the real apiHtml(spec) plus all three answerHtml shapes and every <details> forced open, runAxe returns violations: []. It is a coverage gap. .replace(/<details data-k=\"api/g, '<details open data-k=\"api') before runAxe closes it, and would have been the thing to catch a future regression in a card.
2. resetSession still leaks the last page image's blob. public/how-it-works.html:929 does d.innerHTML = '' on each .api-answer but leaves d.dataset.url unrevoked, so an image from the previous session stays pinned until that same card is tried again. One page-sized blob at most; the revoke you added two lines up is already the right call to make there.
Accessibility impact: none on converted documents — no pipeline or agent code changed. The demo page's own API section is axe-clean when I render it with the cards open, but CI is not checking that.
Iris Maintainer Agent here.
Step 2 of the owner's request:
/how-it-worksexplains every endpoint, built from the OpenAPI document merged in #612, with real data and plain answers."The API" section replaces "What you can send":
401(key) and429(rate limit) every gated or limited endpoint shares are said once, in a line, not as rows on each card. It also shows the matchingcurl.curlshowsYOUR_QUALITY_TOKEN. In Chrome, the token appears nowhere in the page after use.closegets an "Accept the open document" button that says it cannot be undone.One source: the test that tied the old input list to the routes now checks the OpenAPI document. Every
req.query.*,req.body…, andupload.array(…)a route reads must be an input the API section shows. The README's doc table now points endpoint changes atdocs/openapi.yaml.Tests: new tests cover every operation getting a card, the who-may-call detection, the token field and the no-real-token rule, field-by-field explanations, and code in descriptions shown as code with nothing else becoming markup. 1,898 unit tests pass and e2e passes. Checked in Chrome against a local Iris: 23 cards, Try it on the tally, sessions, diagnostics, an audit page image, and the quality tally without and with a token. Full axe with every card open finds 0 violations.
Next: trim the run-log docs (contract events kept exact, the rest one line each), then shrink API.md to a short guide.
🤖 Generated with Claude Code