raffel 1.1.48 → 1.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1214,60 +1214,191 @@ function generateExampleFromSchema(schema) {
1214
1214
  default: return null;
1215
1215
  }
1216
1216
  }
1217
+ function httpBaseUrl() {
1218
+ return String(spec?.servers?.[0]?.url ?? 'http://localhost:3000').replace(/\/$/, '');
1219
+ }
1220
+ // Collect JSON-Schema validation constraints as readable chips.
1221
+ function schemaConstraintChips(schema) {
1222
+ if (!schema || typeof schema !== 'object')
1223
+ return [];
1224
+ const chips = [];
1225
+ if (schema.format)
1226
+ chips.push(`format: ${schema.format}`);
1227
+ if (typeof schema.minLength === 'number')
1228
+ chips.push(`min length: ${schema.minLength}`);
1229
+ if (typeof schema.maxLength === 'number')
1230
+ chips.push(`max length: ${schema.maxLength}`);
1231
+ if (typeof schema.minimum === 'number')
1232
+ chips.push(`>= ${schema.minimum}`);
1233
+ if (typeof schema.exclusiveMinimum === 'number')
1234
+ chips.push(`> ${schema.exclusiveMinimum}`);
1235
+ if (typeof schema.maximum === 'number')
1236
+ chips.push(`<= ${schema.maximum}`);
1237
+ if (typeof schema.exclusiveMaximum === 'number')
1238
+ chips.push(`< ${schema.exclusiveMaximum}`);
1239
+ if (typeof schema.multipleOf === 'number')
1240
+ chips.push(`multiple of ${schema.multipleOf}`);
1241
+ if (typeof schema.minItems === 'number')
1242
+ chips.push(`min items: ${schema.minItems}`);
1243
+ if (typeof schema.maxItems === 'number')
1244
+ chips.push(`max items: ${schema.maxItems}`);
1245
+ if (schema.uniqueItems)
1246
+ chips.push('unique items');
1247
+ if (typeof schema.pattern === 'string')
1248
+ chips.push(`pattern: ${schema.pattern}`);
1249
+ if (schema.nullable)
1250
+ chips.push('nullable');
1251
+ if (schema.readOnly)
1252
+ chips.push('read-only');
1253
+ if (schema.writeOnly)
1254
+ chips.push('write-only');
1255
+ return chips;
1256
+ }
1257
+ // Append constraint / default / enum chips to a row element.
1258
+ function appendConstraintChips(row, schema) {
1259
+ if (!schema || typeof schema !== 'object')
1260
+ return;
1261
+ const chips = schemaConstraintChips(schema);
1262
+ const hasDefault = schema.default !== undefined;
1263
+ const enumValues = Array.isArray(schema.enum) ? schema.enum : [];
1264
+ if (!chips.length && !hasDefault && enumValues.length === 0)
1265
+ return;
1266
+ const wrap = doc.createElement('div');
1267
+ wrap.className = 'constraint-chips';
1268
+ if (hasDefault) {
1269
+ const chip = doc.createElement('span');
1270
+ chip.className = 'constraint-chip';
1271
+ chip.textContent = `default: ${JSON.stringify(schema.default)}`;
1272
+ wrap.appendChild(chip);
1273
+ }
1274
+ chips.forEach(text => {
1275
+ const chip = doc.createElement('span');
1276
+ chip.className = 'constraint-chip';
1277
+ chip.textContent = text;
1278
+ wrap.appendChild(chip);
1279
+ });
1280
+ enumValues.forEach(value => {
1281
+ const chip = doc.createElement('span');
1282
+ chip.className = 'constraint-chip constraint-enum';
1283
+ chip.textContent = String(value);
1284
+ wrap.appendChild(chip);
1285
+ });
1286
+ row.appendChild(wrap);
1287
+ }
1288
+ // Render a JSON value as a Python literal (for the Python request sample).
1289
+ function pyLiteral(value) {
1290
+ if (value === null || value === undefined)
1291
+ return 'None';
1292
+ if (value === true)
1293
+ return 'True';
1294
+ if (value === false)
1295
+ return 'False';
1296
+ if (typeof value === 'number')
1297
+ return String(value);
1298
+ if (typeof value === 'string')
1299
+ return JSON.stringify(value);
1300
+ if (Array.isArray(value))
1301
+ return `[${value.map(pyLiteral).join(', ')}]`;
1302
+ if (typeof value === 'object') {
1303
+ return `{${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)}: ${pyLiteral(v)}`).join(', ')}}`;
1304
+ }
1305
+ return 'None';
1306
+ }
1307
+ // Build request samples in cURL, JavaScript, Python and Rust.
1308
+ function buildHttpSamples(method, url, body) {
1309
+ const hasBody = body !== null && body !== undefined && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
1310
+ const jsonPretty = hasBody ? JSON.stringify(body, null, 2) : '';
1311
+ const samples = {};
1312
+ let curl = `curl -X ${method} "${url}"`;
1313
+ if (hasBody)
1314
+ curl += ` \\\n -H "Content-Type: application/json" \\\n -d '${JSON.stringify(body)}'`;
1315
+ samples.curl = curl;
1316
+ let js = `const res = await fetch(${JSON.stringify(url)}, {\n method: ${JSON.stringify(method)},`;
1317
+ if (hasBody)
1318
+ js += `\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(${jsonPretty.split('\n').join('\n ')}),`;
1319
+ js += `\n});\nconst data = await res.json();\nconsole.log(data);`;
1320
+ samples.javascript = js;
1321
+ let py = `import requests\n\nres = requests.${method.toLowerCase()}(\n ${JSON.stringify(url)},`;
1322
+ if (hasBody)
1323
+ py += `\n json=${pyLiteral(body)},`;
1324
+ py += `\n)\nprint(res.json())`;
1325
+ samples.python = py;
1326
+ let rust = `use reqwest::Client;\n\nlet client = Client::new();\nlet res = client\n .${method.toLowerCase()}(${JSON.stringify(url)})`;
1327
+ if (hasBody)
1328
+ rust += `\n .json(&serde_json::json!(${jsonPretty.split('\n').join('\n ')}))`;
1329
+ rust += `\n .send()\n .await?;\nlet body = res.text().await?;\nprintln!("{}", body);`;
1330
+ samples.rust = rust;
1331
+ return samples;
1332
+ }
1217
1333
  function renderCodeExamples(endpoint, data) {
1218
- const container = doc.createElement('div');
1219
- container.style.marginTop = '20px';
1220
- container.style.paddingTop = '16px';
1221
- container.style.borderTop = '1px solid var(--border-color)';
1222
- // cURL example
1223
- const method = endpoint.method?.toUpperCase() || 'GET';
1224
- const path = endpoint.path || '/';
1225
- const baseUrl = 'http://localhost:3000';
1226
- const curlSection = doc.createElement('div');
1227
- curlSection.style.marginBottom = '20px';
1228
- curlSection.innerHTML = '<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px;">cURL EXAMPLE</div>';
1229
- const curlCode = doc.createElement('pre');
1230
- curlCode.style.backgroundColor = 'var(--bg-secondary)';
1231
- curlCode.style.padding = '12px';
1232
- curlCode.style.borderRadius = '4px';
1233
- curlCode.style.fontSize = '12px';
1234
- curlCode.style.overflow = 'auto';
1235
- let curlCmd = `curl -X ${method} "${baseUrl}${path}"`;
1236
- const reqBody = data.requestBody?.content?.[Object.keys(data.requestBody.content)[0]];
1237
- if (reqBody && ['POST', 'PUT', 'PATCH'].includes(method)) {
1238
- const example = generateExampleFromSchema(reqBody.schema);
1239
- curlCmd += ` \\\n -H "Content-Type: application/json" \\\n -d '${JSON.stringify(example, null, 2).split('\n').join('\n ')}'`;
1240
- }
1241
- curlCode.textContent = curlCmd;
1242
- curlSection.appendChild(curlCode);
1243
- container.appendChild(curlSection);
1244
- // Response example
1245
- if (data.responses) {
1246
- const responses = Object.entries(data.responses);
1247
- if (responses.length > 0) {
1248
- const [status, resp] = responses[0];
1249
- const respSection = doc.createElement('div');
1250
- respSection.innerHTML = `<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px;">RESPONSE ${status}</div>`;
1251
- const respCode = doc.createElement('pre');
1252
- respCode.style.backgroundColor = 'var(--bg-secondary)';
1253
- respCode.style.padding = '12px';
1254
- respCode.style.borderRadius = '4px';
1255
- respCode.style.fontSize = '12px';
1256
- respCode.style.overflow = 'auto';
1257
- if (resp.content) {
1258
- const contentType = Object.keys(resp.content)[0];
1259
- const schema = resp.content[contentType]?.schema;
1260
- const example = generateExampleFromSchema(schema);
1261
- respCode.textContent = JSON.stringify(example, null, 2);
1262
- }
1263
- else {
1264
- respCode.textContent = `{}`;
1265
- }
1266
- respSection.appendChild(respCode);
1267
- container.appendChild(respSection);
1334
+ const method = (endpoint.method || 'GET').toUpperCase();
1335
+ const url = `${httpBaseUrl()}${endpoint.path || '/'}`;
1336
+ const reqContent = data.requestBody?.content;
1337
+ const reqSchema = reqContent ? reqContent[Object.keys(reqContent)[0]]?.schema : null;
1338
+ const bodyExample = reqSchema ? generateExampleFromSchema(reqSchema) : null;
1339
+ const samples = buildHttpSamples(method, url, bodyExample);
1340
+ const langs = [['curl', 'cURL'], ['javascript', 'JavaScript'], ['python', 'Python'], ['rust', 'Rust']];
1341
+ const wrap = doc.createElement('div');
1342
+ wrap.className = 'http-code-samples';
1343
+ const tabs = doc.createElement('div');
1344
+ tabs.className = 'code-tabs';
1345
+ const contents = doc.createElement('div');
1346
+ contents.className = 'code-contents';
1347
+ langs.forEach(([key, label], index) => {
1348
+ const tab = doc.createElement('button');
1349
+ tab.type = 'button';
1350
+ tab.className = `code-tab${index === 0 ? ' active' : ''}`;
1351
+ tab.textContent = label;
1352
+ const content = doc.createElement('div');
1353
+ content.className = `code-content${index === 0 ? ' active' : ''}`;
1354
+ const pre = doc.createElement('pre');
1355
+ pre.className = 'http-code-sample-pre';
1356
+ pre.textContent = samples[key];
1357
+ content.appendChild(pre);
1358
+ tab.onclick = () => {
1359
+ tabs.querySelectorAll('.code-tab').forEach((t) => t.classList.remove('active'));
1360
+ contents.querySelectorAll('.code-content').forEach((c) => c.classList.remove('active'));
1361
+ tab.classList.add('active');
1362
+ content.classList.add('active');
1363
+ };
1364
+ tabs.appendChild(tab);
1365
+ contents.appendChild(content);
1366
+ });
1367
+ const copy = doc.createElement('button');
1368
+ copy.type = 'button';
1369
+ copy.className = 'http-code-copy';
1370
+ copy.textContent = 'Copy';
1371
+ copy.onclick = () => {
1372
+ const active = contents.querySelector('.code-content.active pre');
1373
+ const text = active?.textContent ?? '';
1374
+ const clip = globalThis.navigator?.clipboard;
1375
+ if (clip?.writeText) {
1376
+ clip.writeText(text).then(() => {
1377
+ copy.textContent = 'Copied';
1378
+ setTimeout(() => { copy.textContent = 'Copy'; }, 1200);
1379
+ });
1268
1380
  }
1269
- }
1270
- return container;
1381
+ };
1382
+ tabs.appendChild(copy);
1383
+ wrap.appendChild(tabs);
1384
+ wrap.appendChild(contents);
1385
+ return wrap;
1386
+ }
1387
+ // Append a JSON example block to a container.
1388
+ function appendJsonExample(container, value, label = 'Example') {
1389
+ if (value === null || value === undefined)
1390
+ return;
1391
+ const head = doc.createElement('div');
1392
+ head.className = 'response-subhead';
1393
+ head.style.marginTop = '12px';
1394
+ head.textContent = label;
1395
+ container.appendChild(head);
1396
+ const pre = doc.createElement('pre');
1397
+ pre.className = 'http-code-sample-pre';
1398
+ pre.style.border = '1px solid var(--border-color)';
1399
+ pre.style.borderRadius = '8px';
1400
+ pre.textContent = JSON.stringify(value, null, 2);
1401
+ container.appendChild(pre);
1271
1402
  }
1272
1403
  function renderSchemaTree(parent, schema, depth = 0) {
1273
1404
  if (!schema)
@@ -1290,8 +1421,7 @@ function renderSchemaTree(parent, schema, depth = 0) {
1290
1421
  row.style.paddingLeft = depth === 0 ? '8px' : '0';
1291
1422
  const type = prop.type || 'any';
1292
1423
  const required = schema.required?.includes(key) ? '<span style="color: #ef4444; margin-left: 4px;">*</span>' : '';
1293
- const format = prop.format ? ` (${prop.format})` : '';
1294
- row.innerHTML = `<div style="font-weight: 500; color: var(--text-primary); display: flex; align-items: center;">${esc(key)}${required} <span style="color: var(--text-secondary); font-weight: normal; margin-left: 8px;">${type}${format}</span></div>`;
1424
+ row.innerHTML = `<div style="font-weight: 500; color: var(--text-primary); display: flex; align-items: center;">${esc(key)}${required} <span class="schema-type type-${esc(prop.type || 'null')}" style="margin-left: 8px;">${esc(type)}</span></div>`;
1295
1425
  if (prop.description) {
1296
1426
  const desc = doc.createElement('div');
1297
1427
  desc.style.fontSize = '12px';
@@ -1300,33 +1430,20 @@ function renderSchemaTree(parent, schema, depth = 0) {
1300
1430
  desc.textContent = prop.description;
1301
1431
  row.appendChild(desc);
1302
1432
  }
1303
- if (prop.example !== undefined) {
1304
- const ex = doc.createElement('div');
1305
- ex.style.fontSize = '11px';
1306
- ex.style.color = '#888';
1307
- ex.style.marginTop = '2px';
1308
- ex.style.fontFamily = 'monospace';
1309
- ex.textContent = `Example: ${JSON.stringify(prop.example)}`;
1310
- row.appendChild(ex);
1311
- }
1312
- if (prop.enum) {
1313
- const en = doc.createElement('div');
1314
- en.style.fontSize = '11px';
1315
- en.style.color = '#888';
1316
- en.style.marginTop = '2px';
1317
- en.textContent = `Values: ${prop.enum.join(', ')}`;
1318
- row.appendChild(en);
1319
- }
1433
+ appendConstraintChips(row, prop);
1320
1434
  div.appendChild(row);
1321
- if (prop.properties) {
1435
+ if (prop.type === 'object' && prop.properties) {
1322
1436
  renderSchemaTree(div, prop, depth + 1);
1323
1437
  }
1438
+ else if (prop.type === 'array' && prop.items?.properties) {
1439
+ renderSchemaTree(div, prop.items, depth + 1);
1440
+ }
1324
1441
  });
1325
1442
  }
1326
1443
  else if (schema.type === 'array' && schema.items) {
1327
1444
  const row = doc.createElement('div');
1328
1445
  row.style.padding = '6px 0';
1329
- row.innerHTML = `<span style="color: var(--text-primary); font-weight: 500;">array</span> <span style="color: var(--text-secondary);">of ${schema.items.type || 'object'}</span>`;
1446
+ row.innerHTML = `<span style="color: var(--text-primary); font-weight: 500;">array</span> <span style="color: var(--text-secondary);">of ${esc(schema.items.type || 'object')}</span>`;
1330
1447
  div.appendChild(row);
1331
1448
  if (schema.items.properties) {
1332
1449
  renderSchemaTree(div, schema.items, depth + 1);
@@ -1336,10 +1453,108 @@ function renderSchemaTree(parent, schema, depth = 0) {
1336
1453
  const row = doc.createElement('div');
1337
1454
  row.style.padding = '6px 0';
1338
1455
  row.innerHTML = `<span style="color: var(--text-secondary);">${esc(schema.type || 'any')}</span>`;
1456
+ appendConstraintChips(row, schema);
1339
1457
  div.appendChild(row);
1340
1458
  }
1341
1459
  parent.appendChild(div);
1342
1460
  }
1461
+ // Render one HTTP parameter group (path/query/header/cookie) ReDoc-style.
1462
+ function renderParamGroup(title, params) {
1463
+ const group = doc.createElement('div');
1464
+ group.className = 'http-param-group';
1465
+ const heading = doc.createElement('div');
1466
+ heading.className = 'http-param-group-title';
1467
+ heading.textContent = title;
1468
+ group.appendChild(heading);
1469
+ const list = doc.createElement('div');
1470
+ list.className = 'http-params';
1471
+ params.forEach(param => {
1472
+ const item = doc.createElement('div');
1473
+ item.className = 'http-param';
1474
+ const schema = param.schema || {};
1475
+ const typeName = schema.type === 'array' ? `${schema.items?.type || 'any'}[]` : (schema.type || 'string');
1476
+ const head = doc.createElement('div');
1477
+ head.className = 'http-param-head';
1478
+ head.innerHTML = `<span class="http-param-name">${esc(param.name)}</span>` +
1479
+ `<span class="schema-type type-${esc(schema.type || 'string')}">${esc(typeName)}</span>` +
1480
+ (param.required ? '<span class="http-param-required">required</span>' : '') +
1481
+ (param.deprecated ? '<span class="http-param-deprecated">deprecated</span>' : '');
1482
+ item.appendChild(head);
1483
+ const description = param.description || schema.description;
1484
+ if (description) {
1485
+ const desc = doc.createElement('div');
1486
+ desc.className = 'http-param-desc';
1487
+ desc.textContent = description;
1488
+ item.appendChild(desc);
1489
+ }
1490
+ appendConstraintChips(item, schema);
1491
+ list.appendChild(item);
1492
+ });
1493
+ group.appendChild(list);
1494
+ return group;
1495
+ }
1496
+ // Render a single response as a collapsible accordion (ReDoc-style).
1497
+ function renderResponseAccordion(status, resp, openByDefault) {
1498
+ const statusClass = status.startsWith('2') ? 'status-2xx'
1499
+ : status.startsWith('3') ? 'status-3xx'
1500
+ : status.startsWith('4') ? 'status-4xx'
1501
+ : 'status-5xx';
1502
+ const acc = doc.createElement('div');
1503
+ acc.className = `response-accordion${openByDefault ? ' open' : ''}`;
1504
+ const header = doc.createElement('button');
1505
+ header.type = 'button';
1506
+ header.className = 'response-accordion-header';
1507
+ header.innerHTML = `<span class="response-accordion-caret">▶</span>` +
1508
+ `<span class="response-status-dot ${statusClass}"></span>` +
1509
+ `<span class="response-status-code">${esc(status)}</span>` +
1510
+ `<span class="response-status-desc">${esc(resp.description || '')}</span>`;
1511
+ const body = doc.createElement('div');
1512
+ body.className = 'response-accordion-body';
1513
+ if (resp.headers && Object.keys(resp.headers).length > 0) {
1514
+ const block = doc.createElement('div');
1515
+ block.className = 'response-block';
1516
+ const sub = doc.createElement('div');
1517
+ sub.className = 'response-subhead';
1518
+ sub.textContent = 'Response Headers';
1519
+ block.appendChild(sub);
1520
+ const list = doc.createElement('div');
1521
+ list.className = 'http-params';
1522
+ Object.entries(resp.headers).forEach(([name, def]) => {
1523
+ const item = doc.createElement('div');
1524
+ item.className = 'http-param';
1525
+ const hschema = def.schema || {};
1526
+ item.innerHTML = `<div class="http-param-head"><span class="http-param-name">${esc(name)}</span><span class="schema-type type-${esc(hschema.type || 'string')}">${esc(hschema.type || 'string')}</span></div>` +
1527
+ (def.description ? `<div class="http-param-desc">${esc(def.description)}</div>` : '');
1528
+ list.appendChild(item);
1529
+ });
1530
+ block.appendChild(list);
1531
+ body.appendChild(block);
1532
+ }
1533
+ const content = resp.content;
1534
+ const contentType = content ? Object.keys(content)[0] : null;
1535
+ const schema = contentType ? content[contentType]?.schema : null;
1536
+ if (schema) {
1537
+ const block = doc.createElement('div');
1538
+ block.className = 'response-block';
1539
+ const sub = doc.createElement('div');
1540
+ sub.className = 'response-subhead';
1541
+ sub.textContent = `Response Body${contentType ? ` · ${contentType}` : ''}`;
1542
+ block.appendChild(sub);
1543
+ renderSchemaTree(block, schema);
1544
+ appendJsonExample(block, generateExampleFromSchema(schema));
1545
+ body.appendChild(block);
1546
+ }
1547
+ if (!body.children.length) {
1548
+ const empty = doc.createElement('div');
1549
+ empty.className = 'response-desc-only';
1550
+ empty.textContent = resp.description || 'No content.';
1551
+ body.appendChild(empty);
1552
+ }
1553
+ header.onclick = () => acc.classList.toggle('open');
1554
+ acc.appendChild(header);
1555
+ acc.appendChild(body);
1556
+ return acc;
1557
+ }
1343
1558
  function renderEndpointDetails(endpoint) {
1344
1559
  const container = doc.createElement('div');
1345
1560
  container.className = 'endpoint-details';
@@ -1352,22 +1567,29 @@ function renderEndpointDetails(endpoint) {
1352
1567
  const section = doc.createElement('div');
1353
1568
  section.className = 'endpoint-subsection';
1354
1569
  section.innerHTML = '<div class="subsection-label">PARAMETERS</div>';
1355
- const grid = doc.createElement('div');
1356
- grid.className = 'info-grid';
1357
- params.forEach(p => {
1358
- grid.innerHTML += `<div class="info-card"><div class="info-card-title">${esc(p.name)}</div><div class="info-card-value">${esc(p.in || 'query')} • ${esc(p.schema?.type || 'string')}${p.required ? ' *' : ''}</div></div>`;
1570
+ const groups = [
1571
+ ['path', 'Path Parameters'],
1572
+ ['query', 'Query Parameters'],
1573
+ ['header', 'Header Parameters'],
1574
+ ['cookie', 'Cookie Parameters'],
1575
+ ];
1576
+ groups.forEach(([location, title]) => {
1577
+ const inGroup = params.filter(p => (p.in || 'query') === location);
1578
+ if (inGroup.length > 0)
1579
+ section.appendChild(renderParamGroup(title, inGroup));
1359
1580
  });
1360
- section.appendChild(grid);
1361
1581
  container.appendChild(section);
1362
1582
  }
1363
1583
  const reqBody = data.requestBody;
1364
1584
  if (reqBody?.content) {
1365
1585
  const section = doc.createElement('div');
1366
1586
  section.className = 'endpoint-subsection';
1367
- section.innerHTML = '<div class="subsection-label">REQUEST BODY</div>';
1368
- const content = reqBody.content[Object.keys(reqBody.content)[0]];
1587
+ const contentType = Object.keys(reqBody.content)[0];
1588
+ section.innerHTML = `<div class="subsection-label">REQUEST BODY${reqBody.required ? ' <span style="color:#ef4444">required</span>' : ''}${contentType ? ` · ${esc(contentType)}` : ''}</div>`;
1589
+ const content = reqBody.content[contentType];
1369
1590
  if (content?.schema) {
1370
1591
  renderSchemaTree(section, content.schema);
1592
+ appendJsonExample(section, generateExampleFromSchema(content.schema));
1371
1593
  }
1372
1594
  container.appendChild(section);
1373
1595
  }
@@ -1376,51 +1598,25 @@ function renderEndpointDetails(endpoint) {
1376
1598
  const section = doc.createElement('div');
1377
1599
  section.className = 'endpoint-subsection';
1378
1600
  section.innerHTML = '<div class="subsection-label">RESPONSES</div>';
1379
- Object.entries(responses).forEach(([status, resp]) => {
1380
- const statusClass = status.startsWith('2') ? 'status-2xx' : status.startsWith('4') ? 'status-4xx' : 'status-5xx';
1381
- const item = doc.createElement('div');
1382
- item.style.marginBottom = '16px';
1383
- item.style.padding = '12px';
1384
- item.style.borderRadius = '6px';
1385
- item.style.backgroundColor = 'var(--bg-secondary)';
1386
- item.innerHTML = `<div style="color: var(--text-secondary); font-size: 14px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;"><span class="badge badge-${statusClass}">${status}</span> <strong>${esc(resp.description || '')}</strong></div>`;
1387
- // Headers
1388
- if (resp.headers && Object.keys(resp.headers).length > 0) {
1389
- const headersDiv = doc.createElement('div');
1390
- headersDiv.style.marginBottom = '12px';
1391
- headersDiv.innerHTML = '<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px;">HEADERS</div>';
1392
- const headersList = doc.createElement('div');
1393
- headersList.style.fontSize = '12px';
1394
- Object.entries(resp.headers).forEach(([headerName, headerDef]) => {
1395
- const headerRow = doc.createElement('div');
1396
- headerRow.style.padding = '4px 0';
1397
- headerRow.style.borderLeft = '2px solid var(--border-color)';
1398
- headerRow.style.paddingLeft = '8px';
1399
- const headerType = headerDef.schema?.type || 'string';
1400
- headerRow.innerHTML = `<div style="font-weight: 500; color: var(--text-primary);">${esc(headerName)}</div><div style="color: var(--text-muted); font-size: 11px;">${headerType}</div>${headerDef.description ? `<div style="color: var(--text-muted); font-size: 11px; margin-top: 2px;">${esc(headerDef.description)}</div>` : ''}`;
1401
- headersList.appendChild(headerRow);
1402
- });
1403
- headersDiv.appendChild(headersList);
1404
- item.appendChild(headersDiv);
1405
- }
1406
- // Schema
1407
- if (resp.content) {
1408
- const contentType = Object.keys(resp.content)[0];
1409
- const schema = resp.content[contentType]?.schema;
1410
- if (schema) {
1411
- const schemaDiv = doc.createElement('div');
1412
- schemaDiv.innerHTML = '<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px;">SCHEMA</div>';
1413
- renderSchemaTree(schemaDiv, schema);
1414
- item.appendChild(schemaDiv);
1415
- }
1416
- }
1417
- section.appendChild(item);
1601
+ const entries = Object.entries(responses);
1602
+ let opened = false;
1603
+ entries.forEach(([status, resp]) => {
1604
+ const open = !opened && status.startsWith('2');
1605
+ if (open)
1606
+ opened = true;
1607
+ section.appendChild(renderResponseAccordion(status, resp, open));
1418
1608
  });
1609
+ if (!opened) {
1610
+ const first = section.querySelector('.response-accordion');
1611
+ if (first)
1612
+ first.classList.add('open');
1613
+ }
1419
1614
  container.appendChild(section);
1420
1615
  }
1421
- // Code examples
1616
+ // Request samples (cURL / JavaScript / Python / Rust)
1422
1617
  const examplesSection = doc.createElement('div');
1423
1618
  examplesSection.className = 'endpoint-subsection';
1619
+ examplesSection.innerHTML = '<div class="subsection-label">REQUEST SAMPLES</div>';
1424
1620
  examplesSection.appendChild(renderCodeExamples(endpoint, data));
1425
1621
  container.appendChild(examplesSection);
1426
1622
  }