Conversation
Add ApiConfig, OpenAPI normalized types, and pass api config through loadConfig. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Parse OpenAPI 2.0 and 3.0 specs, normalize to V3 Document - Use openapi-types package types directly, no custom normalized types - Resolve $ref references recursively with cycle detection - flattenSchema utility for table rendering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- api-routes.ts: route building, operation lookup, sidebar tree - apis/[[...slug]]/page.tsx: landing + endpoint pages - apis/[[...slug]]/layout.tsx: API layout with sidebar - PageTreeItem: add optional icon field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Map icon strings to Heroicons (code-bracket, rectangle-stack) - Use NextLink for client-side nav instead of full page reloads Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate browser-safe schema utils from fs-dependent openapi parser. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- method-badge: colored HTTP method badges - params-table: field/type/required/description table with nested indentation - endpoint-page: two-column layout with method badge, path, params, responses - index.ts: re-exports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sidebar items show colored method badges (GET/POST/PUT/DELETE/PATCH) - Use NextLink for client-side navigation - API page uses EndpointPage component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ara Flex and Button - Add FieldSection and FieldRow components with Apsara Flex/Button - Two-column endpoint page layout with Fields/JSON toggle for body - Add generateExampleJson to schema utils - Delete ParamsTable (replaced by inline field display) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use Apsara Tabs for Fields/JSON toggle in body and response sections - Use Apsara CodeBlock for syntax-highlighted JSON - Use Apsara Accordion for expandable nested fields - Use Apsara Badge for method badges (GET/POST/PUT/DELETE/PATCH) - Add micro size badges in sidebar - Replace hardcoded color fallbacks with Apsara semantic tokens - Fix URL overflow with text-overflow ellipsis - Add response JSON examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add right-column code snippets panel to endpoint page with cURL, Python, Go, and TypeScript generators using Apsara CodeBlock language select. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ResponsePanel component showing response examples with dropdown to switch between status codes. Remove tag label, align copy buttons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support inline editing for all parameter types with two-way data binding. FieldRow handles string, number, boolean, enum, object and array fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Inline editable fields sync with code snippets panel. API requests proxied through /api/apis-proxy to avoid CORS. Two-way body sync between form fields and JSON editor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ThemeLayoutProps now accepts classNames for layout, body, sidebar, and content. API docs layout uses this for 100vh sticky sidebar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Custom headers via dynamic key-value pairs with add/remove. FieldSection supports children slot. Single scroll for both panels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Index all OpenAPI operations in Cmd+K search. Results show colored method badges (GET/POST/PUT/DELETE/PATCH) and deduplicate by URL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds OpenAPI support and an interactive API reference: loads/normalizes specs (v2→v3), builds routes/indexes, integrates API operations into search and sidebar, provides an Endpoint UI with Try‑It using a proxy, code-snippet generators, editors, and related UI components and styles. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant EndpointPage as Endpoint Page
participant ApisProxy as /api/apis-proxy
participant ExternalAPI as External API
User->>EndpointPage: Populate params/headers/body and click "Send"
EndpointPage->>EndpointPage: Build payload (specName, method, path, headers, body)
EndpointPage->>ApisProxy: POST payload
ApisProxy->>ApisProxy: Resolve spec -> compute server URL + substitute path params
ApisProxy->>ExternalAPI: Forward request (method, headers, body)
ExternalAPI-->>ApisProxy: Return response (status, headers, body)
ApisProxy->>ApisProxy: Normalize/parse body (JSON or text)
ApisProxy-->>EndpointPage: Return { status, statusText, body }
EndpointPage->>User: Render response panel
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (8)
packages/chronicle/src/components/ui/search.tsx (1)
163-173: Consider adding HEAD and OPTIONS to API_METHODS.OpenAPI specs commonly include
HEADandOPTIONSmethods. If any API endpoints use these methods, they won't be recognized, causing the badge not to render and the method prefix to remain in the displayed text.Additionally,
stripMethodreturns an empty string when content is just the method (e.g.,"GET"), which could result in blank text in the search results.♻️ Suggested improvement
-const API_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH"]); +const API_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/ui/search.tsx` around lines 163 - 173, Add missing HTTP verbs HEAD and OPTIONS to the API_METHODS set and update stripMethod to avoid returning a blank string when content is only the method; specifically, in API_METHODS include "HEAD" and "OPTIONS", and in stripMethod (which currently uses content.split and content.slice) detect if the sliced remainder is empty or just whitespace and in that case return the original first token (or the original content) instead of an empty string so search results don't render blank; leave extractMethod behavior as-is.packages/chronicle/src/components/api/field-row.module.css (1)
73-94: Stylelint false positive::global()is valid CSS Modules syntax.The
:global()pseudo-class is used correctly here to escape CSS Modules' local scoping and target third-party accordion component classes. The Stylelint errors are false positives.Consider adding a Stylelint configuration to recognize
:global():{ "rules": { "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] }] } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/field-row.module.css` around lines 73 - 94, Stylelint is flagging the CSS Modules :global() pseudo-class used in selectors like :global([class*="accordion-header"]) and :global([class*="accordion-content-inner"]); update the project's Stylelint configuration to treat "global" as a known pseudo-class by adding the selector-pseudo-class-no-unknown rule with ignorePseudoClasses including "global" (i.e., modify your Stylelint config used by the repo to include "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] }]). Ensure the updated config is in the same config file the linter uses so the :global(...) selectors in field-row.module.css are no longer reported as unknown.examples/basic/petstore.json (1)
1-1: Consider removing HTTP scheme from example spec.This is the standard Petstore demo spec, which is appropriate for examples. However, static analysis flagged that it allows both
httpsandhttpschemes. For better security posture even in examples, consider keeping only HTTPS:-"schemes":["https","http"] +"schemes":["https"]The other security warnings (implicit OAuth flow, missing global security) are inherent to this demo spec and acceptable for example purposes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/basic/petstore.json` at line 1, The example OpenAPI JSON includes both "https" and "http" in the "schemes" array which enables insecure HTTP; update the "schemes" array (the JSON key "schemes") to remove the "http" entry so it contains only "https" (i.e., change ["https","http"] to ["https"]) and run any example validations to ensure no other code depends on plain-http in this spec.packages/chronicle/src/lib/openapi.ts (1)
125-133: Consider preserving array/enum metadata for v2 params.
For non‑body parameters, onlytypeandformatare mapped, so array parameters loseitemsand enums/defaults disappear. This will render asunknown[]and can mislead users in the request editor.♻️ Suggested enrichment
- const v3Params: OpenAPIV3.ParameterObject[] = params - .filter((p) => p.in !== 'body') - .map((p) => ({ - name: p.name, - in: p.in as 'path' | 'query' | 'header' | 'cookie', - required: p.required ?? false, - description: p.description, - schema: { type: (p as JsonObject).type as string, format: (p as JsonObject).format as string | undefined } as OpenAPIV3.SchemaObject, - })) + const v3Params: OpenAPIV3.ParameterObject[] = params + .filter((p) => p.in !== 'body') + .map((p) => { + const pJson = p as JsonObject + return { + name: p.name, + in: p.in as 'path' | 'query' | 'header' | 'cookie', + required: p.required ?? false, + description: p.description, + schema: { + type: pJson.type as string, + format: pJson.format as string | undefined, + items: pJson.items as OpenAPIV3.SchemaObject | undefined, + enum: pJson.enum as unknown[] | undefined, + default: pJson.default, + } as OpenAPIV3.SchemaObject, + } + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/lib/openapi.ts` around lines 125 - 133, The v3Params mapping currently only copies type and format, which loses array/items, enum, and default metadata; update the params -> v3Params conversion (the mapping that builds OpenAPIV3.ParameterObject) to build the schema by copying relevant fields from the original param (handle when (p as JsonObject).type === 'array' to copy items, and always copy enum and default if present), ensuring the resulting schema is a proper OpenAPIV3.SchemaObject for the ParameterObject; modify the mapping in the v3Params creation to merge type/format with items/enum/default from the source param instead of only setting type/format.packages/chronicle/src/components/api/index.ts (1)
8-8: Consider also exporting theKeyValueEntrytype.The
KeyValueEntryinterface is exported fromkey-value-editor.tsxbut not re-exported here. Consumers ofKeyValueEditorwill need to import this type separately to properly type their state.💡 Proposed fix
-export { KeyValueEditor } from './key-value-editor' +export { KeyValueEditor, type KeyValueEntry } from './key-value-editor'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/index.ts` at line 8, The index re-export currently exposes KeyValueEditor but omits the KeyValueEntry type used by consumers; update the export list in this module to re-export the KeyValueEntry type (alongside KeyValueEditor) from './key-value-editor' so callers can import both KeyValueEditor and the KeyValueEntry interface from this entry point.packages/chronicle/src/components/api/endpoint-page.tsx (1)
111-112: Catch block swallows the original error; consider logging for debugging.The catch block at line 111 loses the original error information. While the user sees a generic message, developers would benefit from having the error logged for debugging purposes.
💡 Proposed improvement
- } catch { + } catch (err) { + console.error('API request failed:', err) setResponseBody({ status: 0, statusText: 'Error', body: 'Failed to send request' }) } finally {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/endpoint-page.tsx` around lines 111 - 112, The catch block that currently calls setResponseBody({ status: 0, statusText: 'Error', body: 'Failed to send request' }) swallows the original error; update that catch to capture the error (e.g., catch (err)) and log it (e.g., console.error or the app logger) before calling setResponseBody so developers can see the original exception when debugging; locate the try/catch around the request/send logic where setResponseBody is used to identify the exact spot to change.packages/chronicle/src/components/api/key-value-editor.tsx (2)
67-69: Icon button lacks accessible label for screen readers.The trash button uses only an icon without an accessible label. Screen reader users won't understand the button's purpose.
♿ Proposed fix for accessibility
- <IconButton size="small" variant="ghost" onClick={() => removeEntry(i)}> + <IconButton size="small" variant="ghost" onClick={() => removeEntry(i)} aria-label="Remove header"> <TrashIcon /> </IconButton>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/key-value-editor.tsx` around lines 67 - 69, The IconButton rendering the TrashIcon in key-value-editor.tsx lacks an accessible label, so add a clear accessible name (e.g., aria-label="Remove entry" or aria-label={`Remove entry ${i + 1}`} or aria-labelledby) to the IconButton that calls removeEntry(i) so screen readers can announce its purpose; ensure the label is descriptive and unique enough for list context and use the existing IconButton/TrashIcon elements (no visual text required).
49-50: Using array index as React key can cause issues if entries are reordered.While this simple editor likely won't have reordering, using index as key (
key={i}) can cause unexpected behavior with animations or component state if entries are added/removed from the middle. Consider using a stable identifier.💡 Proposed improvement using a unique ID
Update the interface:
export interface KeyValueEntry { + id: string key: string value: string }Then use
entry.idas the key and generate IDs when adding entries:const addEntry = () => { - onChange([...entries, { key: '', value: '' }]) + onChange([...entries, { id: crypto.randomUUID(), key: '', value: '' }]) }- <Flex key={i} align="center" gap="small" className={styles.row}> + <Flex key={entry.id} align="center" gap="small" className={styles.row}>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/key-value-editor.tsx` around lines 49 - 50, The list is using the array index as a React key in the entries.map (key={i}) which can break reconciliation when items are inserted/removed; update the entry shape to include a stable id (e.g., add an id: string field on the entry interface/type used by KeyValueEditor), change the mapping to use entry.id as the key, and ensure any code paths that create new entries (the add-entry handler or reducer used by the component) generate and assign a unique id (UUID, nanoid, or a monotonic counter/timestamp) so keys remain stable across reorders and edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/app/api/apis-proxy/route.ts`:
- Around line 29-56: The proxy currently always returns HTTP 200 because it uses
NextResponse.json(...) without setting the HTTP status; update both the success
and error returns to set the actual HTTP status via NextResponse.json(..., {
status: <code> }) so clients see the upstream response code: for the normal
response use response.status (and keep response.statusText/body as before), and
in the catch block set the status to 502 when returning the error payload.
Ensure you modify the returns that call NextResponse.json (the success return
after fetching and the catch return) to pass the appropriate { status: ... }
option.
In `@packages/chronicle/src/app/api/search/route.ts`:
- Around line 31-38: operationToIndex is building URLs using raw op.operationId
which can contain characters like '/', spaces or '#' that will break routing;
create a single helper (e.g., slugifyOrEncodeOperationId) and use it to encode
the operationId before constructing url/id in operationToIndex and reuse the
exact same helper in buildApiRoutes, findApiOperation, and buildApiPageTree (and
the helper imported into api-routes.ts) so generated routes and lookup logic
share the same encoded mapping; ensure the helper deterministically
replaces/encodes problematic characters and is applied wherever op.operationId
is used as a URL segment.
In `@packages/chronicle/src/components/api/endpoint-page.tsx`:
- Around line 89-91: The code currently only treats POST and PUT as methods that
send a body and set the Content-Type header; update every conditional that
checks (method === 'POST' || method === 'PUT') so it also accepts 'PATCH' (e.g.,
the block that sets reqHeaders['Content-Type'] using body?.contentType and any
logic that attaches bodyJsonStr/payload to the request, plus the
snippet/header-generation code that only includes Content-Type for POST/PUT).
Locate the checks using the method variable, reqHeaders['Content-Type'],
bodyJsonStr/body payload assignment, and the snippet/header rendering logic and
add 'PATCH' to those conditionals so PATCH requests include the request body and
Content-Type header.
- Around line 172-185: The Body rendering condition and any related snippet-body
checks currently only treat POST and PUT as methods that show the body; update
the conditional that uses the variable method (e.g., the JSX around FieldSection
and any similar checks used for snippet generation) to also include 'PATCH'
(e.g., method === 'POST' || method === 'PUT' -> include method === 'PATCH'),
ensuring handlers like handleBodyJsonChange, handleBodyValuesChange, and props
passed to FieldSection (jsonExample, editableJson, values) continue to be used
unchanged so PATCH requests render and handle body input the same as POST/PUT.
In `@packages/chronicle/src/components/api/field-row.tsx`:
- Around line 72-118: Array items without children are being coerced to strings
(InputField/string) which loses original types for numbers/booleans and enum
values; update the array-item rendering in FieldRow so primitive items use the
same type-aware editor as single-value fields (reuse EditableInput instead of
InputField) and when handling enums map the selected string back to the original
typed value (use field.values or the enum definition to convert to
number/boolean as needed) before calling onChange; ensure the onChange handlers
in the array branch (the add-item PlusIcon handler and the per-item onChange for
primitive items) create/propagate values with correct types and apply the same
fix to the other array rendering block referenced (lines ~166-179) so enums and
numeric/boolean array elements are preserved.
In `@packages/chronicle/src/components/api/field-section.tsx`:
- Around line 63-65: The Fields tab currently renders only fieldsContent inside
Tabs.Content (value="fields") so when jsonExample or alwaysShow triggers the
tabbed layout the children prop is omitted; update the tabbed path to include
children alongside fieldsContent (e.g., render fieldsContent followed by
children inside Tabs.Content for value="fields") so custom components like
KeyValueEditor appear in the tabbed Fields view—refer to Tabs.Content
(value="fields"), fieldsContent, children, and the jsonExample/alwaysShow
conditional that determines the tabbed layout.
In `@packages/chronicle/src/components/api/json-editor.tsx`:
- Around line 17-49: The JsonEditor effect currently only runs on isDark so
updates to value, readOnly, and onChange are ignored; update the useEffect in
JsonEditor to depend on isDark, value, readOnly, and onChange and then inside
the effect: if viewRef.current exists, sync the buffer by dispatching a change
to replace the whole document with the new value (use viewRef.current.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: value } })) and update
readOnly by reinitializing extensions or recreating the view when
readOnly/onChange/isDark change; otherwise create the EditorState/EditorView as
before and assign viewRef.current; ensure the effect cleans up by destroying the
previous view before creating a new one to avoid leaks (references: JsonEditor,
containerRef, viewRef, EditorState, EditorView, useEffect).
In `@packages/chronicle/src/components/api/response-panel.module.css`:
- Around line 5-6: Stylelint flags the :global pseudo-class in the CSS rule
".panel :global([class*=\"code-block-module_header\"])" with
selector-pseudo-class-no-unknown; to fix, wrap that specific selector block with
local Stylelint directives: add a comment /* stylelint-disable
selector-pseudo-class-no-unknown */ immediately before the ".panel
:global([class*=\"code-block-module_header\"])" rule and /* stylelint-enable
selector-pseudo-class-no-unknown */ immediately after the rule so only this
selector is exempted and CI no longer fails.
In `@packages/chronicle/src/components/api/response-panel.tsx`:
- Around line 1-39: Replace the server-only import of STATUS_CODES (which is
causing client-side bundling failures) by using the existing description on each
response; update the ResponsePanel component so CodeBlock.LanguageSelectItem
renders "{resp.status} {resp.description ?? resp.status}" (and ensure the
fallback is resp.status) and remove any import or usage of STATUS_CODES; also
verify the defaultValue and CodeBlock.Code value keys still use resp.status and
that jsonExample is used as before in the CodeBlock.Code children.
In `@packages/chronicle/src/lib/openapi.ts`:
- Around line 65-85: deepResolveRefs incorrectly treats any repeated $ref as
circular because the single shared seen Set marks refs globally; change to track
refs that are currently being expanded with an inStack Set and use a
resolvedCache Map to store fully resolved schemas so repeated non-circular
references return the cached resolved value. Concretely, in deepResolveRefs use
an inStack (or visiting) Set to detect true recursion (return the circular
placeholder when a $ref is found in inStack), add the $ref to inStack before
resolving and remove it after recursion, store the result in resolvedCache keyed
by the $ref returned from resolveRef, and on encountering a $ref first check
resolvedCache to return the cached resolution immediately instead of treating it
as circular; keep function names resolveRef and deepResolveRefs unchanged.
In `@packages/chronicle/src/lib/schema.ts`:
- Around line 13-23: flattenSchema currently expands array items into separate
fields losing the array container; update the array branch in flattenSchema so
that when schema.type === 'array' and schema.items is present you return a
single SchemaField that represents the array container (e.g., type 'array' or
keep `${items.type}[]`) and set its children to the flattened result of the item
schema (call flattenSchema(items) for children), preserving requiredFields and
any metadata; reference flattenSchema, SchemaField, and schema.items to locate
and replace the current map-based expansion with this container-with-children
approach.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 24-27: The Layout component contains a debug console.log(config)
that leaks internal config and should be removed; open the Layout function
(export function Layout) and delete the console.log(config) statement (or guard
it behind a dev-only check if you must keep debugging output), ensuring no
client-side debug logging remains in
packages/chronicle/src/themes/default/Layout.tsx.
---
Duplicate comments:
In `@packages/chronicle/src/components/api/code-snippets.module.css`:
- Around line 5-6: The Stylelint complaint is caused by using :global(...)
inside the .snippets selector; apply the same fix used in
response-panel.module.css by either moving the
:global([class*="code-block-module_header"]) selector out to a top-level rule or
add a local stylelint suppression comment above the rule to allow the :global
usage; update the selector reference .snippets
:global([class*="code-block-module_header"]) accordingly (or add /*
stylelint-disable-next-line selector-class-pattern */ immediately before it) so
the lint error is resolved.
In `@packages/chronicle/src/lib/api-routes.ts`:
- Around line 10-23: buildApiRoutes currently pushes raw operationId into route
slugs; update it to use the same operationId encoding/slugging utility used in
the search route (import and call the canonical encoder, e.g.,
encodeOperationId) so slugs become { slug: [specSlug,
encodeOperationId(op.operationId)] } instead of raw op.operationId; apply the
same fix to the other blocks that push or build URLs from operationId (the other
occurrences around the later route-generation loops and the URL-building code
noted in the review) so all routing and lookup use the identical encode/decode
strategy.
---
Nitpick comments:
In `@examples/basic/petstore.json`:
- Line 1: The example OpenAPI JSON includes both "https" and "http" in the
"schemes" array which enables insecure HTTP; update the "schemes" array (the
JSON key "schemes") to remove the "http" entry so it contains only "https"
(i.e., change ["https","http"] to ["https"]) and run any example validations to
ensure no other code depends on plain-http in this spec.
In `@packages/chronicle/src/components/api/endpoint-page.tsx`:
- Around line 111-112: The catch block that currently calls setResponseBody({
status: 0, statusText: 'Error', body: 'Failed to send request' }) swallows the
original error; update that catch to capture the error (e.g., catch (err)) and
log it (e.g., console.error or the app logger) before calling setResponseBody so
developers can see the original exception when debugging; locate the try/catch
around the request/send logic where setResponseBody is used to identify the
exact spot to change.
In `@packages/chronicle/src/components/api/field-row.module.css`:
- Around line 73-94: Stylelint is flagging the CSS Modules :global()
pseudo-class used in selectors like :global([class*="accordion-header"]) and
:global([class*="accordion-content-inner"]); update the project's Stylelint
configuration to treat "global" as a known pseudo-class by adding the
selector-pseudo-class-no-unknown rule with ignorePseudoClasses including
"global" (i.e., modify your Stylelint config used by the repo to include
"selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"]
}]). Ensure the updated config is in the same config file the linter uses so the
:global(...) selectors in field-row.module.css are no longer reported as
unknown.
In `@packages/chronicle/src/components/api/index.ts`:
- Line 8: The index re-export currently exposes KeyValueEditor but omits the
KeyValueEntry type used by consumers; update the export list in this module to
re-export the KeyValueEntry type (alongside KeyValueEditor) from
'./key-value-editor' so callers can import both KeyValueEditor and the
KeyValueEntry interface from this entry point.
In `@packages/chronicle/src/components/api/key-value-editor.tsx`:
- Around line 67-69: The IconButton rendering the TrashIcon in
key-value-editor.tsx lacks an accessible label, so add a clear accessible name
(e.g., aria-label="Remove entry" or aria-label={`Remove entry ${i + 1}`} or
aria-labelledby) to the IconButton that calls removeEntry(i) so screen readers
can announce its purpose; ensure the label is descriptive and unique enough for
list context and use the existing IconButton/TrashIcon elements (no visual text
required).
- Around line 49-50: The list is using the array index as a React key in the
entries.map (key={i}) which can break reconciliation when items are
inserted/removed; update the entry shape to include a stable id (e.g., add an
id: string field on the entry interface/type used by KeyValueEditor), change the
mapping to use entry.id as the key, and ensure any code paths that create new
entries (the add-entry handler or reducer used by the component) generate and
assign a unique id (UUID, nanoid, or a monotonic counter/timestamp) so keys
remain stable across reorders and edits.
In `@packages/chronicle/src/components/ui/search.tsx`:
- Around line 163-173: Add missing HTTP verbs HEAD and OPTIONS to the
API_METHODS set and update stripMethod to avoid returning a blank string when
content is only the method; specifically, in API_METHODS include "HEAD" and
"OPTIONS", and in stripMethod (which currently uses content.split and
content.slice) detect if the sliced remainder is empty or just whitespace and in
that case return the original first token (or the original content) instead of
an empty string so search results don't render blank; leave extractMethod
behavior as-is.
In `@packages/chronicle/src/lib/openapi.ts`:
- Around line 125-133: The v3Params mapping currently only copies type and
format, which loses array/items, enum, and default metadata; update the params
-> v3Params conversion (the mapping that builds OpenAPIV3.ParameterObject) to
build the schema by copying relevant fields from the original param (handle when
(p as JsonObject).type === 'array' to copy items, and always copy enum and
default if present), ensuring the resulting schema is a proper
OpenAPIV3.SchemaObject for the ParameterObject; modify the mapping in the
v3Params creation to merge type/format with items/enum/default from the source
param instead of only setting type/format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/chronicle/src/components/api/key-value-editor.tsx (1)
34-35: Avoid using array index as React key for dynamic lists.Since entries can be added and removed, using the index as a key can cause React reconciliation issues—leading to unexpected focus loss or input state glitches when removing items from the middle of the list. Consider adding a unique
idto eachKeyValueEntryor generating one when entries are created.♻️ Suggested approach
Update the interface:
export interface KeyValueEntry { + id: string key: string value: string }Then use
entry.idas the key:- <Flex key={i} align="center" gap="small" className={styles.row}> + <Flex key={entry.id} align="center" gap="small" className={styles.row}>And update
addEntryto generate unique IDs (e.g., usingcrypto.randomUUID()or a simple counter).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/key-value-editor.tsx` around lines 34 - 35, The list is using the array index as the React key which breaks reconciliation for dynamic edits; update the KeyValueEntry shape to include a unique id (e.g., add an id field on the entry objects), change the map key from the index to entry.id in the component where entries.map is used, and modify the addEntry (and any entry creation logic) to assign a stable unique id (e.g., crypto.randomUUID() or an incrementing counter) when new entries are created so keys remain stable across reorders/removals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/components/api/field-row.tsx`:
- Line 76: The list rendering uses the array index as a React key (key={i} on
the <Flex ... className={styles.arrayItem}>), which can cause stale component
state when items are inserted/removed; change the data model and rendering to
use a stable unique id instead (e.g., store items as { id: string, ... }[] or
generate a UUID when adding an item) and replace key={i} with key={item.id};
also update any add/remove handlers and where items are created (inside the
component that maps items, and functions that push/splice items) to ensure every
new item receives a persistent id so keys remain stable across renders.
In `@packages/chronicle/src/components/api/key-value-editor.tsx`:
- Around line 52-54: The delete IconButton in the key-value editor is missing an
accessible label; update the IconButton (the element rendering the TrashIcon
that calls removeEntry(i)) to include an aria-label describing its action (e.g.,
"Delete entry" or a contextual label like `Delete entry ${i + 1}` or using the
entry key) so screen readers convey the button's purpose; keep the onClick
(removeEntry) and the TrashIcon unchanged and ensure the label is descriptive
and unique enough for assistive tech.
---
Duplicate comments:
In `@packages/chronicle/src/components/api/field-row.tsx`:
- Around line 151-166: The Select currently passes the raw string from
onValueChange to onChange, losing original enum types; update the handler in the
Select block (where onValueChange is defined) to map the selected string back to
the original typed enum value by finding the matching entry in field.enum (e.g.,
const selected = field.enum.find(opt => String(opt) === v) ?? v) and then call
onChange?.(field.name, selected); keep the Select value prop as String(value ??
'') and keep rendering Select.Item values as String(opt) to preserve display.
- Around line 93-103: Array items are being coerced to strings via String(item
?? '') and e.target.value in the InputField branch, breaking typed arrays
(number[]/boolean[]); replace this ad-hoc string handling by using the existing
EditableInput component (the same one used elsewhere for primitive fields) so it
preserves and parses the original type. Locate the block rendering InputField in
field-row (the InputField JSX with value={String(item ?? '')} and onChange
updating updated[i] = e.target.value) and swap it to render EditableInput
configured with the field schema/props and an onChange that writes back the
parsed/typed value into updated[i] before calling onChange?.(field.name,
updated), ensuring numbers/booleans are not left as strings.
---
Nitpick comments:
In `@packages/chronicle/src/components/api/key-value-editor.tsx`:
- Around line 34-35: The list is using the array index as the React key which
breaks reconciliation for dynamic edits; update the KeyValueEntry shape to
include a unique id (e.g., add an id field on the entry objects), change the map
key from the index to entry.id in the component where entries.map is used, and
modify the addEntry (and any entry creation logic) to assign a stable unique id
(e.g., crypto.randomUUID() or an incrementing counter) when new entries are
created so keys remain stable across reorders/removals.
- Propagate upstream HTTP status in proxy route - Encode operationId in search URLs - Remove Node http import from client component - Fix $ref resolution with stack+cache for circular detection - Sync JsonEditor value/readOnly/onChange props - Use EditableInput for array items, preserve enum types - Preserve array container in schema flattening - Add PATCH method body support - Render children in tabbed Fields view - Add stylelint disable for :global selector - Remove debug console.log from Layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
packages/chronicle/src/themes/default/Layout.tsx (1)
97-105: Minor consideration: JSX element creation in render loop.The
as={<NextLink href={item.url ?? "#"} />}creates a new element on each render. For typical sidebar sizes this is fine, but if performance becomes a concern with large navigation trees, consider extracting this to a memoized component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.tsx` around lines 97 - 105, The Sidebar.Item is recreating a JSX element each render via as={<NextLink href={item.url ?? "#"} />}, which can hurt performance for large nav trees; refactor by extracting a memoized Link component (e.g., create a MemoNextLink or useMemo to produce the element) and pass that memoized component into Sidebar.Item's as prop instead, referencing Sidebar.Item, NextLink and item.url to locate the change so the link element is reused across renders.packages/chronicle/src/lib/openapi.ts (1)
131-143: Consider handling missingtypein v2 parameter schema.Line 141 casts
(p as JsonObject).type as string, but if a v2 parameter lacks an explicittype, this could result inundefinedbeing cast to string. While rare, some v2 specs may omit type.🛡️ Defensive fix
- schema: { type: (p as JsonObject).type as string, format: (p as JsonObject).format as string | undefined } as OpenAPIV3.SchemaObject, + schema: { type: ((p as JsonObject).type as string) ?? 'string', format: (p as JsonObject).format as string | undefined } as OpenAPIV3.SchemaObject,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/lib/openapi.ts` around lines 131 - 143, The conversion in convertV2Operation builds v3Params using (p as JsonObject).type cast to string which can be undefined for some v2 params; update the mapping for v3Params in convertV2Operation to defensively read the type and format from the parameter (e.g., const t = (p as JsonObject).type ?? 'string' and const f = (p as JsonObject).format ?? undefined) and use those values when constructing the OpenAPIV3.SchemaObject, ensuring required fallbacks rather than casting possibly undefined values; also keep the existing fields (name, in, required, description) unchanged.packages/chronicle/src/components/api/endpoint-page.tsx (1)
111-113: Consider logging the error for debugging.The catch block swallows the actual error, making debugging difficult when requests fail. Logging the error would help identify issues like network failures or CORS problems.
🔧 Suggested fix
- } catch { + } catch (err) { + console.error('API request failed:', err) setResponseBody({ status: 0, statusText: 'Error', body: 'Failed to send request' }) } finally {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/api/endpoint-page.tsx` around lines 111 - 113, The catch block that calls setResponseBody({ status: 0, statusText: 'Error', body: 'Failed to send request' }) is swallowing the thrown error; update the catch to accept the error (e.g., catch (err)) and log it for debugging (using console.error or the app's logger) so failures (network/CORS/etc.) are visible; keep the existing setResponseBody behavior but include error details in the log and optionally append short error info to the response body for the EndpointPage/send request flow.packages/chronicle/src/app/api/search/route.ts (1)
56-60: Synchronous file I/O in async context.
buildApiIndexescallsloadApiSpecs, which performs synchronous file reading. This is acceptable for build-time index generation but could block the event loop if called frequently or with many large specs. Consider async alternatives if this becomes a bottleneck.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/app/api/search/route.ts` around lines 56 - 60, buildApiIndexes currently calls loadApiSpecs which does synchronous file I/O and can block the event loop; change the implementation to use async file I/O: create an async loadApiSpecsAsync that uses fs.promises (or other async readers) and returns a Promise of specs, then make buildApiIndexes async (or add buildApiIndexesAsync) so it awaits loadApiSpecsAsync and returns Promise<AdvancedIndex[]>; update any callers of buildApiIndexes to await the new async function (or adapt to promise-based usage) and ensure types/signatures reflect the async change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/components/api/endpoint-page.tsx`:
- Around line 209-220: Remove the redundant hardcoded value="json" prop on the
outer CodeBlock in EndpointPage (the JSX block using CodeBlock,
CodeBlock.Header, CodeBlock.Content, CodeBlock.Code); keep only the
language="json" prop on CodeBlock.Code and remove the value prop from CodeBlock
so the language is driven by CodeBlock.Code (mirror the pattern used in
field-section.tsx). Ensure CodeBlock.Code retains language="json" and that
CopyButton/Content children remain unchanged.
---
Nitpick comments:
In `@packages/chronicle/src/app/api/search/route.ts`:
- Around line 56-60: buildApiIndexes currently calls loadApiSpecs which does
synchronous file I/O and can block the event loop; change the implementation to
use async file I/O: create an async loadApiSpecsAsync that uses fs.promises (or
other async readers) and returns a Promise of specs, then make buildApiIndexes
async (or add buildApiIndexesAsync) so it awaits loadApiSpecsAsync and returns
Promise<AdvancedIndex[]>; update any callers of buildApiIndexes to await the new
async function (or adapt to promise-based usage) and ensure types/signatures
reflect the async change.
In `@packages/chronicle/src/components/api/endpoint-page.tsx`:
- Around line 111-113: The catch block that calls setResponseBody({ status: 0,
statusText: 'Error', body: 'Failed to send request' }) is swallowing the thrown
error; update the catch to accept the error (e.g., catch (err)) and log it for
debugging (using console.error or the app's logger) so failures
(network/CORS/etc.) are visible; keep the existing setResponseBody behavior but
include error details in the log and optionally append short error info to the
response body for the EndpointPage/send request flow.
In `@packages/chronicle/src/lib/openapi.ts`:
- Around line 131-143: The conversion in convertV2Operation builds v3Params
using (p as JsonObject).type cast to string which can be undefined for some v2
params; update the mapping for v3Params in convertV2Operation to defensively
read the type and format from the parameter (e.g., const t = (p as
JsonObject).type ?? 'string' and const f = (p as JsonObject).format ??
undefined) and use those values when constructing the OpenAPIV3.SchemaObject,
ensuring required fallbacks rather than casting possibly undefined values; also
keep the existing fields (name, in, required, description) unchanged.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 97-105: The Sidebar.Item is recreating a JSX element each render
via as={<NextLink href={item.url ?? "#"} />}, which can hurt performance for
large nav trees; refactor by extracting a memoized Link component (e.g., create
a MemoNextLink or useMemo to produce the element) and pass that memoized
component into Sidebar.Item's as prop instead, referencing Sidebar.Item,
NextLink and item.url to locate the change so the link element is reused across
renders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log caught errors in try-it-out handler - Add defensive fallback for V2 param type - Memoize NextLink in SidebarNode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/components/api/endpoint-page.tsx`:
- Around line 89-126: The request-building logic currently only attaches
Content-Type and includes bodyValues for methods POST/PUT/PATCH; change these
checks to instead use the presence of the OpenAPI requestBody (use the existing
body and/or bodyJsonStr variables) so bodies are sent whenever a requestBody
exists (e.g., DELETE with requestBody). Update the fetch payload to include
body: bodyValues when body or bodyJsonStr is present (not just method checks),
set reqHeaders['Content-Type'] = body?.contentType ?? 'application/json' when
body or bodyJsonStr exists, and do the same for snippetHeaders (replace the
method-based checks with a body/bodyJsonStr presence check) so transport,
headers, and snippets are consistent with the actual body variable.
- Around line 83-85: The loop that copies headerValues into reqHeaders (for
(const [key, value] of Object.entries(headerValues)) { ... }) currently uses a
truthy check which drops valid falsy values like 0 or false; change the
condition to explicitly filter out empty/undefined/null values (e.g. check value
!== undefined && value !== null && value !== '') before assigning
reqHeaders[key] = String(value) so boolean/number headers are preserved while
still excluding absent/empty headers.
- Add aria-label to TrashIcon button in key-value editor - Use resp.status as fallback in response panel - Encode operationId with encodeURIComponent in api-routes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Body, Content-Type header, and snippets now check for requestBody instead of hard-coded method list. Also fix falsy header value check. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds full OpenAPI/Swagger API documentation support to Chronicle.
$refresolution/apis/[spec]/[operationId]with method-colored sidebar navigationchronicle.yaml, supports multiple specsTest plan
pnpm build— no type errorsexamples/basic, verify/apis/petstore/addPetrenders🤖 Generated with Claude Code