Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

api-docs.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

api-docs.tsxBlame700 lines · 1 contributor
45e31d0Claude1/**
2 * API Documentation — interactive docs page.
93812e4Claude3 *
4 * 2026 polish: scoped `.apidocs-*` class system gives every endpoint group
5 * its own card, every endpoint its own row with a color-coded method pill,
6 * and every code example a solid-white spec block with a Copy button
7 * (recipe lifted from `build-agent-spec.tsx`). Nothing outside `.apidocs-*`
8 * is touched and the route surface is unchanged.
45e31d0Claude9 */
10
11import { Hono } from "hono";
12import { Layout } from "../views/layout";
13import { softAuth } from "../middleware/auth";
14import type { AuthEnv } from "../middleware/auth";
15
16const apiDocs = new Hono<AuthEnv>();
17
93812e4Claude18// ─── Scoped CSS (.apidocs-*) ────────────────────────────────────────────────
19const apiDocsStyles = `
eed4684Claude20 .apidocs-wrap { max-width: 1680px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }
93812e4Claude21
22 /* ─── Header ─── */
23 .apidocs-head { margin-bottom: var(--space-5); }
24 .apidocs-eyebrow {
25 display: inline-flex;
26 align-items: center;
27 gap: 8px;
28 text-transform: uppercase;
29 font-family: var(--font-mono);
30 font-size: 11px;
31 letter-spacing: 0.16em;
32 color: var(--text-muted);
33 font-weight: 600;
34 margin-bottom: 10px;
35 }
36 .apidocs-eyebrow-dot {
37 width: 8px; height: 8px;
38 border-radius: 9999px;
39 background: linear-gradient(135deg, #8c6dff, #36c5d6);
40 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
41 }
42 .apidocs-title {
43 font-family: var(--font-display);
44 font-size: clamp(26px, 3.6vw, 40px);
45 font-weight: 800;
46 letter-spacing: -0.028em;
47 line-height: 1.05;
48 margin: 0 0 6px;
49 color: var(--text-strong);
50 }
51 .apidocs-title-grad {
52 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
53 -webkit-background-clip: text;
54 background-clip: text;
55 -webkit-text-fill-color: transparent;
56 color: transparent;
57 }
58 .apidocs-sub {
59 margin: 0;
60 font-size: 14.5px;
61 color: var(--text-muted);
62 line-height: 1.5;
63 max-width: 720px;
64 }
65 .apidocs-sub a { color: var(--accent); text-decoration: none; }
66 .apidocs-sub a:hover { text-decoration: underline; }
67
68 /* ─── Section card per endpoint group ─── */
69 .apidocs-section {
70 margin-bottom: var(--space-5);
71 background: var(--bg-elevated);
72 border: 1px solid var(--border);
73 border-radius: 14px;
74 overflow: hidden;
75 position: relative;
76 }
77 .apidocs-section::before {
78 content: '';
79 position: absolute;
80 top: 0; left: 0; right: 0;
81 height: 2px;
82 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
83 opacity: 0.55;
84 pointer-events: none;
85 }
86 .apidocs-section-head {
87 padding: var(--space-4) var(--space-5) var(--space-3);
88 border-bottom: 1px solid var(--border);
89 }
90 .apidocs-section-title {
91 margin: 0;
92 font-family: var(--font-display);
93 font-size: 17px;
94 font-weight: 700;
95 letter-spacing: -0.018em;
96 color: var(--text-strong);
97 }
98 .apidocs-section-sub {
99 margin: 6px 0 0;
100 font-size: 12.5px;
101 color: var(--text-muted);
102 line-height: 1.45;
103 }
104 .apidocs-section-sub code,
105 .apidocs-sub code {
106 font-family: var(--font-mono);
107 font-size: 12px;
108 background: rgba(255,255,255,0.04);
109 padding: 1px 6px;
110 border-radius: 4px;
111 }
112 .apidocs-section-body {
113 padding: var(--space-3) var(--space-5) var(--space-4);
114 display: flex;
115 flex-direction: column;
116 gap: 10px;
117 }
118
119 /* ─── Per-endpoint card ─── */
120 .apidocs-endpoint {
121 display: grid;
122 grid-template-columns: 64px 1fr auto;
123 column-gap: 14px;
124 row-gap: 6px;
125 align-items: start;
126 padding: 12px 14px;
127 background: rgba(255,255,255,0.018);
128 border: 1px solid var(--border);
129 border-radius: 11px;
130 transition: border-color 120ms ease, background 120ms ease;
131 }
132 .apidocs-endpoint:hover {
133 border-color: var(--border-strong);
134 background: rgba(255,255,255,0.03);
135 }
136 @media (max-width: 720px) {
137 .apidocs-endpoint {
138 grid-template-columns: 1fr;
139 grid-template-areas: "method" "path" "desc" "badges";
140 }
141 .apidocs-method { grid-area: method; justify-self: start; }
142 .apidocs-path { grid-area: path; }
143 .apidocs-desc { grid-area: desc; }
144 .apidocs-badges { grid-area: badges; justify-self: start; }
145 }
146 .apidocs-method {
147 display: inline-flex;
148 align-items: center;
149 justify-content: center;
150 padding: 3px 8px;
151 min-width: 56px;
152 font-family: var(--font-mono);
153 font-size: 11px;
154 font-weight: 700;
155 letter-spacing: 0.04em;
156 border-radius: 6px;
157 text-align: center;
158 text-transform: uppercase;
159 line-height: 1.4;
160 }
161 .apidocs-method.m-get {
162 background: rgba(52,211,153,0.14);
163 color: #6ee7b7;
164 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.32);
165 }
166 .apidocs-method.m-post {
167 background: rgba(140,109,255,0.16);
168 color: #c4b5fd;
169 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.32);
170 }
171 .apidocs-method.m-put,
172 .apidocs-method.m-patch {
173 background: rgba(251,191,36,0.14);
174 color: #fde68a;
175 box-shadow: inset 0 0 0 1px rgba(251,191,36,0.32);
176 }
177 .apidocs-method.m-delete {
178 background: rgba(248,113,113,0.14);
179 color: #fecaca;
180 box-shadow: inset 0 0 0 1px rgba(248,113,113,0.32);
181 }
182 .apidocs-path {
183 font-family: var(--font-mono);
184 font-size: 13px;
185 color: #e9e2ff;
186 word-break: break-all;
187 line-height: 1.5;
188 }
189 .apidocs-desc {
190 grid-column: 2 / 3;
191 font-size: 12.5px;
192 color: var(--text-muted);
193 line-height: 1.5;
194 }
195 .apidocs-desc code {
196 font-family: var(--font-mono);
197 font-size: 11.5px;
198 background: rgba(255,255,255,0.04);
199 padding: 1px 5px;
200 border-radius: 4px;
201 }
202 .apidocs-params {
203 display: block;
204 margin-top: 4px;
205 font-size: 11.5px;
206 color: var(--text-muted);
207 }
208 .apidocs-badges {
209 display: inline-flex;
210 flex-direction: column;
211 gap: 4px;
212 align-items: flex-end;
213 }
214 @media (max-width: 720px) {
215 .apidocs-badges { flex-direction: row; flex-wrap: wrap; }
216 }
217 .apidocs-badge {
218 display: inline-flex;
219 align-items: center;
220 gap: 4px;
221 padding: 2px 8px;
222 border-radius: 9999px;
223 font-size: 10.5px;
224 font-weight: 600;
225 letter-spacing: 0.04em;
226 text-transform: uppercase;
227 line-height: 1.4;
228 }
229 .apidocs-badge.is-auth {
230 background: rgba(54,197,214,0.14);
231 color: #67e8f9;
232 box-shadow: inset 0 0 0 1px rgba(54,197,214,0.32);
233 }
234 .apidocs-badge.is-scope {
235 background: rgba(52,211,153,0.14);
236 color: #6ee7b7;
237 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.32);
238 }
239 .apidocs-badge .dot { width: 5px; height: 5px; border-radius: 9999px; background: currentColor; }
240
241 /* ─── Rate-limit table ─── */
242 .apidocs-table-wrap {
243 margin: 0;
244 border: 1px solid var(--border);
245 border-radius: 11px;
246 overflow: hidden;
247 }
248 .apidocs-table {
249 width: 100%;
250 border-collapse: collapse;
251 font-size: 13px;
252 }
253 .apidocs-table th {
254 text-align: left;
255 padding: 10px 14px;
256 font-size: 11px;
257 font-weight: 600;
258 text-transform: uppercase;
259 letter-spacing: 0.06em;
260 color: var(--text-muted);
261 background: rgba(255,255,255,0.025);
262 border-bottom: 1px solid var(--border);
263 }
264 .apidocs-table td {
265 padding: 10px 14px;
266 color: var(--text);
267 border-bottom: 1px solid var(--border);
268 font-variant-numeric: tabular-nums;
269 }
270 .apidocs-table tr:last-child td { border-bottom: none; }
271
272 /* ─── Solid-white spec block for examples (build-agent-spec recipe) ─── */
273 .apidocs-spec {
274 background: #ffffff;
275 color: #0a0a0a;
276 border: 1px solid #e5e7eb;
277 border-radius: 12px;
278 overflow: hidden;
279 box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 6px 20px rgba(0,0,0,0.16);
280 }
281 .apidocs-spec-head {
282 display: flex;
283 align-items: center;
284 justify-content: space-between;
285 gap: 12px;
286 padding: 10px 14px;
287 background: #f9fafb;
288 border-bottom: 1px solid #e5e7eb;
289 flex-wrap: wrap;
290 }
291 .apidocs-spec-title {
292 display: flex;
293 align-items: center;
294 gap: 9px;
295 margin: 0;
296 font-family: var(--font-display, system-ui, sans-serif);
297 font-size: 13px;
298 font-weight: 700;
299 color: #111827;
300 letter-spacing: -0.005em;
301 }
302 .apidocs-spec-dot {
303 width: 7px; height: 7px;
304 border-radius: 9999px;
305 background: linear-gradient(135deg, #8c6dff, #36c5d6);
306 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
307 }
308 .apidocs-spec-copy {
309 display: inline-flex;
310 align-items: center;
311 gap: 6px;
312 padding: 5px 11px;
313 font-size: 12px;
314 font-weight: 600;
315 color: #111827;
316 background: #ffffff;
317 border: 1px solid #d1d5db;
318 border-radius: 7px;
319 cursor: pointer;
320 font-family: inherit;
321 transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
322 }
323 .apidocs-spec-copy:hover {
324 background: #f3f4f6;
325 border-color: #9ca3af;
326 }
327 .apidocs-spec-copy.is-copied {
328 background: #ecfdf5;
329 border-color: #6ee7b7;
330 color: #047857;
331 }
332 .apidocs-spec-copy svg { display: block; }
333 .apidocs-spec-pre {
334 margin: 0;
335 padding: 16px 18px;
336 font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace);
337 font-size: 12.5px;
338 line-height: 1.65;
339 color: #0a0a0a;
340 background: #ffffff;
341 white-space: pre;
342 overflow-x: auto;
343 tab-size: 2;
344 }
345
346 /* ─── Footer / kbd ─── */
347 .apidocs-foot {
348 margin-top: var(--space-5);
349 padding: var(--space-4);
350 text-align: center;
351 color: var(--text-muted);
352 font-size: 13px;
353 border: 1px dashed var(--border);
354 border-radius: 12px;
355 }
356 .apidocs-foot code {
357 font-family: var(--font-mono);
358 font-size: 12px;
359 background: rgba(255,255,255,0.04);
360 padding: 1px 6px;
361 border-radius: 4px;
362 color: var(--text-strong);
363 }
364 .apidocs-kbd {
365 display: inline-block;
366 padding: 2px 6px;
367 font-family: var(--font-mono);
368 font-size: 11px;
369 color: var(--text-strong);
370 background: rgba(255,255,255,0.04);
371 border: 1px solid var(--border-strong);
372 border-bottom-width: 2px;
373 border-radius: 4px;
374 }
375`;
376
45e31d0Claude377apiDocs.get("/api/docs", softAuth, (c) => {
378 const user = c.get("user");
379
380 return c.html(
381 <Layout title="API Documentation" user={user}>
93812e4Claude382 <div class="apidocs-wrap">
383 <header class="apidocs-head">
384 <div class="apidocs-eyebrow">
385 <span class="apidocs-eyebrow-dot" aria-hidden="true" />
386 REST API · v2 · Public reference
387 </div>
388 <h1 class="apidocs-title">
389 <span class="apidocs-title-grad">gluecron API.</span>
390 </h1>
391 <p class="apidocs-sub">
392 Complete REST surface for programmatic access to repositories,
393 issues, pull requests, and more. Shapes match GitHub REST v3 — a
394 base-URL swap reuses most existing integrations. Build a token at{" "}
395 <a href="/settings/tokens">/settings/tokens</a>.
396 </p>
397 </header>
45e31d0Claude398
399 <ApiSection
400 title="Authentication"
401 description="All API requests require authentication via a personal access token."
402 >
403 <CodeExample
93812e4Claude404 id="auth"
45e31d0Claude405 title="Using a Bearer token"
406 code={`curl -H "Authorization: Bearer glue_your_token_here" \\
407 https://gluecron.com/api/v2/user`}
408 />
93812e4Claude409 <p class="apidocs-section-sub">
410 Create a token at <a href="/settings/tokens" style="color:var(--accent);text-decoration:none">/settings/tokens</a>. Tokens support scopes: <code>repo</code>, <code>user</code>, <code>admin</code>.
45e31d0Claude411 </p>
412 </ApiSection>
413
414 <ApiSection title="Rate Limits" description="Rate limits are applied per IP address.">
415 <EndpointTable
416 rows={[
417 ["API routes", "100 req/min"],
418 ["Search", "30 req/min"],
419 ["Authentication", "10 req/min"],
420 ["Git operations", "60 req/min"],
421 ]}
422 headers={["Scope", "Limit"]}
423 />
93812e4Claude424 <p class="apidocs-section-sub" style="margin-top:10px">
425 Rate-limit info is included in response headers: <code>X-RateLimit-Limit</code>, <code>X-RateLimit-Remaining</code>, <code>X-RateLimit-Reset</code>.
45e31d0Claude426 </p>
427 </ApiSection>
428
429 <ApiSection title="Users">
430 <Endpoint method="GET" path="/api/v2/user" description="Get authenticated user" auth />
431 <Endpoint method="GET" path="/api/v2/users/:username" description="Get user by username" />
432 <Endpoint method="PATCH" path="/api/v2/user" description="Update profile (displayName, bio, avatarUrl)" auth scope="user" />
433 </ApiSection>
434
435 <ApiSection title="Repositories">
436 <Endpoint method="GET" path="/api/v2/users/:username/repos" description="List user repositories" params="sort=updated|stars|name" />
437 <Endpoint method="POST" path="/api/v2/repos" description="Create repository" auth scope="repo" />
438 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo" description="Get repository details" />
439 <Endpoint method="PATCH" path="/api/v2/repos/:owner/:repo" description="Update repository (description, visibility)" auth scope="repo" />
440 <Endpoint method="DELETE" path="/api/v2/repos/:owner/:repo" description="Delete repository" auth scope="admin" />
441 </ApiSection>
442
443 <ApiSection title="Branches">
444 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/branches" description="List all branches" />
445 </ApiSection>
446
447 <ApiSection title="Commits">
448 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/commits" description="List commits" params="ref, limit, offset" />
449 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/commits/:sha" description="Get commit with diff" />
450 </ApiSection>
451
452 <ApiSection title="File Contents">
453 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/tree/:ref" description="Get file tree at ref" params="path" />
454 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/contents/:path" description="Get file contents" params="ref" />
455 </ApiSection>
456
457 <ApiSection title="Issues">
458 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/issues" description="List issues" params="state=open|closed, limit" />
459 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/issues" description="Create issue" auth scope="repo" />
460 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/issues/:number" description="Get issue with comments" />
461 <Endpoint method="PATCH" path="/api/v2/repos/:owner/:repo/issues/:number" description="Update issue (title, body, state)" auth scope="repo" />
462 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/issues/:number/comments" description="Add comment to issue" auth scope="repo" />
463 </ApiSection>
464
465 <ApiSection title="Pull Requests">
466 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/pulls" description="List pull requests" params="state=open|closed|merged" />
467 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/pulls" description="Create pull request" auth scope="repo" />
468 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/pulls/:number" description="Get PR with comments" />
469 </ApiSection>
470
471 <ApiSection title="Stars">
472 <Endpoint method="PUT" path="/api/v2/repos/:owner/:repo/star" description="Star a repository" auth />
473 <Endpoint method="DELETE" path="/api/v2/repos/:owner/:repo/star" description="Unstar a repository" auth />
474 </ApiSection>
475
476 <ApiSection title="Labels">
477 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/labels" description="List labels" />
478 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/labels" description="Create label" auth scope="repo" />
479 </ApiSection>
480
481 <ApiSection title="Search">
482 <Endpoint method="GET" path="/api/v2/search/repos" description="Search repositories" params="q (required), sort, limit" />
483 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/search/code" description="Search code in repository" params="q (required)" />
484 </ApiSection>
485
486 <ApiSection title="Topics">
487 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/topics" description="Get repository topics" />
488 <Endpoint method="PUT" path="/api/v2/repos/:owner/:repo/topics" description="Set repository topics" auth scope="repo" />
489 </ApiSection>
490
491 <ApiSection title="Webhooks">
492 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/webhooks" description="List webhooks" auth scope="repo" />
493 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/webhooks" description="Create webhook" auth scope="admin" />
494 </ApiSection>
495
496 <ApiSection title="Activity Feed">
497 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/activity" description="Get activity feed" params="limit" />
498 </ApiSection>
499
500 <ApiSection title="Status Checks (CI Integration)">
501 <Endpoint method="POST" path="/api/v2/repos/:owner/:repo/statuses/:sha" description="Create status check" auth scope="repo" />
502 <Endpoint method="GET" path="/api/v2/repos/:owner/:repo/statuses/:sha" description="Get status checks for commit" />
503 <CodeExample
93812e4Claude504 id="status-ci"
45e31d0Claude505 title="Report CI status"
506 code={`curl -X POST -H "Authorization: Bearer glue_xxx" \\
507 -H "Content-Type: application/json" \\
508 -d '{"context":"ci/build","state":"success","targetUrl":"https://ci.example.com/build/123"}' \\
509 https://gluecron.com/api/v2/repos/user/repo/statuses/abc123`}
510 />
511 </ApiSection>
512
93812e4Claude513 <div class="apidocs-foot">
514 API index: <code>GET /api/v2</code> returns a machine-readable endpoint listing.
515 Press <span class="apidocs-kbd">?</span> for keyboard shortcuts.
45e31d0Claude516 </div>
517 </div>
93812e4Claude518 <style dangerouslySetInnerHTML={{ __html: apiDocsStyles }} />
519 <script
520 dangerouslySetInnerHTML={{
521 __html: `
522 (function(){
523 var btns = document.querySelectorAll('[data-apidocs-copy]');
524 btns.forEach(function(btn){
525 var pre = btn.parentNode && btn.parentNode.parentNode
526 ? btn.parentNode.parentNode.querySelector('[data-apidocs-pre]')
527 : null;
528 var label = btn.querySelector('[data-apidocs-copy-label]');
529 if (!pre || !label) return;
530 btn.addEventListener('click', function(){
531 var text = pre.textContent || '';
532 var done = function(){
533 btn.classList.add('is-copied');
534 label.textContent = 'Copied';
535 setTimeout(function(){
536 btn.classList.remove('is-copied');
537 label.textContent = 'Copy';
538 }, 1800);
539 };
540 if (navigator.clipboard && navigator.clipboard.writeText) {
541 navigator.clipboard.writeText(text).then(done).catch(function(){
542 var ta = document.createElement('textarea');
543 ta.value = text; ta.style.position='fixed'; ta.style.left='-9999px';
544 document.body.appendChild(ta); ta.select();
545 try { document.execCommand('copy'); done(); } catch(e){}
546 document.body.removeChild(ta);
547 });
548 } else {
549 var ta = document.createElement('textarea');
550 ta.value = text; ta.style.position='fixed'; ta.style.left='-9999px';
551 document.body.appendChild(ta); ta.select();
552 try { document.execCommand('copy'); done(); } catch(e){}
553 document.body.removeChild(ta);
554 }
555 });
556 });
557 })();
558 `,
559 }}
560 />
45e31d0Claude561 </Layout>
562 );
563});
564
565// ─── Documentation Components ────────────────────────────────────────────
566
567const ApiSection = ({
568 title,
569 description,
570 children,
571}: {
572 title: string;
573 description?: string;
574 children: any;
575}) => (
93812e4Claude576 <section class="apidocs-section">
577 <header class="apidocs-section-head">
578 <h2 class="apidocs-section-title">{title}</h2>
579 {description && (
580 <p class="apidocs-section-sub">{description}</p>
581 )}
582 </header>
583 <div class="apidocs-section-body">{children}</div>
584 </section>
45e31d0Claude585);
586
587const Endpoint = ({
588 method,
589 path,
590 description,
591 params,
592 auth,
593 scope,
594}: {
595 method: string;
596 path: string;
597 description: string;
598 params?: string;
599 auth?: boolean;
600 scope?: string;
601}) => {
93812e4Claude602 const methodClass =
603 method === "GET" ? "m-get" :
604 method === "POST" ? "m-post" :
605 method === "PUT" ? "m-put" :
606 method === "PATCH" ? "m-patch" :
607 method === "DELETE" ? "m-delete" : "m-get";
45e31d0Claude608
609 return (
93812e4Claude610 <div class="apidocs-endpoint">
611 <span class={`apidocs-method ${methodClass}`}>{method}</span>
612 <code class="apidocs-path">{path}</code>
613 <span class="apidocs-badges">
45e31d0Claude614 {auth && (
93812e4Claude615 <span class="apidocs-badge is-auth">
616 <span class="dot" aria-hidden="true" />
617 Auth
45e31d0Claude618 </span>
619 )}
620 {scope && (
93812e4Claude621 <span class="apidocs-badge is-scope">
622 <span class="dot" aria-hidden="true" />
45e31d0Claude623 {scope}
624 </span>
625 )}
626 </span>
93812e4Claude627 <span class="apidocs-desc">
628 {description}
629 {params && (
630 <span class="apidocs-params">
631 Params: <code>{params}</code>
632 </span>
633 )}
634 </span>
45e31d0Claude635 </div>
636 );
637};
638
93812e4Claude639const CodeExample = ({
640 id,
641 title,
642 code,
643}: {
644 id?: string;
645 title: string;
646 code: string;
647}) => (
648 <div class="apidocs-spec" aria-labelledby={id ? `apidocs-spec-${id}` : undefined}>
649 <header class="apidocs-spec-head">
650 <p class="apidocs-spec-title" id={id ? `apidocs-spec-${id}` : undefined}>
651 <span class="apidocs-spec-dot" aria-hidden="true" />
652 {title}
653 </p>
45e31d0Claude654 <button
655 type="button"
93812e4Claude656 class="apidocs-spec-copy"
657 data-apidocs-copy
658 aria-label={`Copy ${title} example to clipboard`}
45e31d0Claude659 >
93812e4Claude660 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
661 <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
662 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
663 </svg>
664 <span data-apidocs-copy-label>Copy</span>
45e31d0Claude665 </button>
93812e4Claude666 </header>
667 <pre class="apidocs-spec-pre" data-apidocs-pre>{code}</pre>
45e31d0Claude668 </div>
669);
670
671const EndpointTable = ({
672 headers,
673 rows,
674}: {
675 headers: string[];
676 rows: string[][];
677}) => (
93812e4Claude678 <div class="apidocs-table-wrap">
679 <table class="apidocs-table">
680 <thead>
45e31d0Claude681 <tr>
93812e4Claude682 {headers.map((h) => (
683 <th>{h}</th>
45e31d0Claude684 ))}
685 </tr>
93812e4Claude686 </thead>
687 <tbody>
688 {rows.map((row) => (
689 <tr>
690 {row.map((cell) => (
691 <td>{cell}</td>
692 ))}
693 </tr>
694 ))}
695 </tbody>
696 </table>
697 </div>
45e31d0Claude698);
699
700export default apiDocs;