vector-framework 1.2.2 → 1.2.3

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.
Files changed (189) hide show
  1. package/README.md +18 -6
  2. package/dist/auth/protected.d.ts +4 -4
  3. package/dist/auth/protected.d.ts.map +1 -1
  4. package/dist/auth/protected.js +10 -7
  5. package/dist/auth/protected.js.map +1 -1
  6. package/dist/cache/manager.d.ts +2 -0
  7. package/dist/cache/manager.d.ts.map +1 -1
  8. package/dist/cache/manager.js +21 -4
  9. package/dist/cache/manager.js.map +1 -1
  10. package/dist/checkpoint/artifacts/compressor.d.ts +5 -0
  11. package/dist/checkpoint/artifacts/compressor.d.ts.map +1 -0
  12. package/dist/checkpoint/artifacts/compressor.js +24 -0
  13. package/dist/checkpoint/artifacts/compressor.js.map +1 -0
  14. package/dist/checkpoint/artifacts/decompress-worker.d.ts +2 -0
  15. package/dist/checkpoint/artifacts/decompress-worker.d.ts.map +1 -0
  16. package/dist/checkpoint/artifacts/decompress-worker.js +31 -0
  17. package/dist/checkpoint/artifacts/decompress-worker.js.map +1 -0
  18. package/dist/checkpoint/artifacts/hasher.d.ts +2 -0
  19. package/dist/checkpoint/artifacts/hasher.d.ts.map +1 -0
  20. package/dist/checkpoint/artifacts/hasher.js +7 -0
  21. package/dist/checkpoint/artifacts/hasher.js.map +1 -0
  22. package/dist/checkpoint/artifacts/manifest.d.ts +6 -0
  23. package/dist/checkpoint/artifacts/manifest.d.ts.map +1 -0
  24. package/dist/checkpoint/artifacts/manifest.js +55 -0
  25. package/dist/checkpoint/artifacts/manifest.js.map +1 -0
  26. package/dist/checkpoint/artifacts/materializer.d.ts +16 -0
  27. package/dist/checkpoint/artifacts/materializer.d.ts.map +1 -0
  28. package/dist/checkpoint/artifacts/materializer.js +168 -0
  29. package/dist/checkpoint/artifacts/materializer.js.map +1 -0
  30. package/dist/checkpoint/artifacts/packager.d.ts +12 -0
  31. package/dist/checkpoint/artifacts/packager.d.ts.map +1 -0
  32. package/dist/checkpoint/artifacts/packager.js +82 -0
  33. package/dist/checkpoint/artifacts/packager.js.map +1 -0
  34. package/dist/checkpoint/artifacts/repository.d.ts +11 -0
  35. package/dist/checkpoint/artifacts/repository.d.ts.map +1 -0
  36. package/dist/checkpoint/artifacts/repository.js +29 -0
  37. package/dist/checkpoint/artifacts/repository.js.map +1 -0
  38. package/dist/checkpoint/artifacts/store.d.ts +13 -0
  39. package/dist/checkpoint/artifacts/store.d.ts.map +1 -0
  40. package/dist/checkpoint/artifacts/store.js +85 -0
  41. package/dist/checkpoint/artifacts/store.js.map +1 -0
  42. package/dist/checkpoint/artifacts/types.d.ts +21 -0
  43. package/dist/checkpoint/artifacts/types.d.ts.map +1 -0
  44. package/dist/checkpoint/artifacts/types.js +2 -0
  45. package/dist/checkpoint/artifacts/types.js.map +1 -0
  46. package/dist/checkpoint/artifacts/worker-decompressor.d.ts +17 -0
  47. package/dist/checkpoint/artifacts/worker-decompressor.d.ts.map +1 -0
  48. package/dist/checkpoint/artifacts/worker-decompressor.js +148 -0
  49. package/dist/checkpoint/artifacts/worker-decompressor.js.map +1 -0
  50. package/dist/checkpoint/asset-store.d.ts +10 -0
  51. package/dist/checkpoint/asset-store.d.ts.map +1 -0
  52. package/dist/checkpoint/asset-store.js +46 -0
  53. package/dist/checkpoint/asset-store.js.map +1 -0
  54. package/dist/checkpoint/bundler.d.ts +15 -0
  55. package/dist/checkpoint/bundler.d.ts.map +1 -0
  56. package/dist/checkpoint/bundler.js +45 -0
  57. package/dist/checkpoint/bundler.js.map +1 -0
  58. package/dist/checkpoint/cli.d.ts +2 -0
  59. package/dist/checkpoint/cli.d.ts.map +1 -0
  60. package/dist/checkpoint/cli.js +157 -0
  61. package/dist/checkpoint/cli.js.map +1 -0
  62. package/dist/checkpoint/entrypoint-generator.d.ts +17 -0
  63. package/dist/checkpoint/entrypoint-generator.d.ts.map +1 -0
  64. package/dist/checkpoint/entrypoint-generator.js +251 -0
  65. package/dist/checkpoint/entrypoint-generator.js.map +1 -0
  66. package/dist/checkpoint/forwarder.d.ts +6 -0
  67. package/dist/checkpoint/forwarder.d.ts.map +1 -0
  68. package/dist/checkpoint/forwarder.js +74 -0
  69. package/dist/checkpoint/forwarder.js.map +1 -0
  70. package/dist/checkpoint/gateway.d.ts +11 -0
  71. package/dist/checkpoint/gateway.d.ts.map +1 -0
  72. package/dist/checkpoint/gateway.js +30 -0
  73. package/dist/checkpoint/gateway.js.map +1 -0
  74. package/dist/checkpoint/ipc.d.ts +12 -0
  75. package/dist/checkpoint/ipc.d.ts.map +1 -0
  76. package/dist/checkpoint/ipc.js +96 -0
  77. package/dist/checkpoint/ipc.js.map +1 -0
  78. package/dist/checkpoint/manager.d.ts +20 -0
  79. package/dist/checkpoint/manager.d.ts.map +1 -0
  80. package/dist/checkpoint/manager.js +214 -0
  81. package/dist/checkpoint/manager.js.map +1 -0
  82. package/dist/checkpoint/process-manager.d.ts +35 -0
  83. package/dist/checkpoint/process-manager.d.ts.map +1 -0
  84. package/dist/checkpoint/process-manager.js +203 -0
  85. package/dist/checkpoint/process-manager.js.map +1 -0
  86. package/dist/checkpoint/resolver.d.ts +25 -0
  87. package/dist/checkpoint/resolver.d.ts.map +1 -0
  88. package/dist/checkpoint/resolver.js +95 -0
  89. package/dist/checkpoint/resolver.js.map +1 -0
  90. package/dist/checkpoint/socket-path.d.ts +2 -0
  91. package/dist/checkpoint/socket-path.d.ts.map +1 -0
  92. package/dist/checkpoint/socket-path.js +51 -0
  93. package/dist/checkpoint/socket-path.js.map +1 -0
  94. package/dist/checkpoint/types.d.ts +54 -0
  95. package/dist/checkpoint/types.d.ts.map +1 -0
  96. package/dist/checkpoint/types.js +2 -0
  97. package/dist/checkpoint/types.js.map +1 -0
  98. package/dist/cli/index.js +10 -2
  99. package/dist/cli/index.js.map +1 -1
  100. package/dist/cli/option-resolution.d.ts +1 -1
  101. package/dist/cli/option-resolution.d.ts.map +1 -1
  102. package/dist/cli/option-resolution.js.map +1 -1
  103. package/dist/cli.js +3709 -328
  104. package/dist/core/config-loader.d.ts +1 -0
  105. package/dist/core/config-loader.d.ts.map +1 -1
  106. package/dist/core/config-loader.js +10 -2
  107. package/dist/core/config-loader.js.map +1 -1
  108. package/dist/core/router.d.ts +24 -3
  109. package/dist/core/router.d.ts.map +1 -1
  110. package/dist/core/router.js +398 -249
  111. package/dist/core/router.js.map +1 -1
  112. package/dist/core/server.d.ts +2 -0
  113. package/dist/core/server.d.ts.map +1 -1
  114. package/dist/core/server.js +22 -8
  115. package/dist/core/server.js.map +1 -1
  116. package/dist/core/vector.d.ts +3 -0
  117. package/dist/core/vector.d.ts.map +1 -1
  118. package/dist/core/vector.js +51 -1
  119. package/dist/core/vector.js.map +1 -1
  120. package/dist/dev/route-scanner.d.ts.map +1 -1
  121. package/dist/dev/route-scanner.js +2 -1
  122. package/dist/dev/route-scanner.js.map +1 -1
  123. package/dist/http.d.ts +32 -7
  124. package/dist/http.d.ts.map +1 -1
  125. package/dist/http.js +144 -13
  126. package/dist/http.js.map +1 -1
  127. package/dist/index.cjs +1297 -74
  128. package/dist/index.d.ts +3 -2
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -2
  131. package/dist/index.js.map +1 -1
  132. package/dist/index.mjs +1296 -73
  133. package/dist/middleware/manager.d.ts +3 -3
  134. package/dist/middleware/manager.d.ts.map +1 -1
  135. package/dist/middleware/manager.js +9 -8
  136. package/dist/middleware/manager.js.map +1 -1
  137. package/dist/openapi/docs-ui.d.ts.map +1 -1
  138. package/dist/openapi/docs-ui.js +1097 -61
  139. package/dist/openapi/docs-ui.js.map +1 -1
  140. package/dist/openapi/generator.d.ts +2 -1
  141. package/dist/openapi/generator.d.ts.map +1 -1
  142. package/dist/openapi/generator.js +240 -7
  143. package/dist/openapi/generator.js.map +1 -1
  144. package/dist/types/index.d.ts +71 -28
  145. package/dist/types/index.d.ts.map +1 -1
  146. package/dist/types/index.js +24 -1
  147. package/dist/types/index.js.map +1 -1
  148. package/dist/utils/validation.d.ts.map +1 -1
  149. package/dist/utils/validation.js +3 -2
  150. package/dist/utils/validation.js.map +1 -1
  151. package/package.json +2 -1
  152. package/src/auth/protected.ts +11 -8
  153. package/src/cache/manager.ts +23 -4
  154. package/src/checkpoint/artifacts/compressor.ts +30 -0
  155. package/src/checkpoint/artifacts/decompress-worker.ts +49 -0
  156. package/src/checkpoint/artifacts/hasher.ts +6 -0
  157. package/src/checkpoint/artifacts/manifest.ts +72 -0
  158. package/src/checkpoint/artifacts/materializer.ts +211 -0
  159. package/src/checkpoint/artifacts/packager.ts +100 -0
  160. package/src/checkpoint/artifacts/repository.ts +36 -0
  161. package/src/checkpoint/artifacts/store.ts +102 -0
  162. package/src/checkpoint/artifacts/types.ts +24 -0
  163. package/src/checkpoint/artifacts/worker-decompressor.ts +192 -0
  164. package/src/checkpoint/asset-store.ts +61 -0
  165. package/src/checkpoint/bundler.ts +64 -0
  166. package/src/checkpoint/cli.ts +177 -0
  167. package/src/checkpoint/entrypoint-generator.ts +275 -0
  168. package/src/checkpoint/forwarder.ts +84 -0
  169. package/src/checkpoint/gateway.ts +40 -0
  170. package/src/checkpoint/ipc.ts +107 -0
  171. package/src/checkpoint/manager.ts +254 -0
  172. package/src/checkpoint/process-manager.ts +250 -0
  173. package/src/checkpoint/resolver.ts +124 -0
  174. package/src/checkpoint/socket-path.ts +61 -0
  175. package/src/checkpoint/types.ts +63 -0
  176. package/src/cli/index.ts +11 -2
  177. package/src/cli/option-resolution.ts +5 -1
  178. package/src/core/config-loader.ts +11 -2
  179. package/src/core/router.ts +505 -264
  180. package/src/core/server.ts +36 -9
  181. package/src/core/vector.ts +60 -1
  182. package/src/dev/route-scanner.ts +2 -1
  183. package/src/http.ts +219 -19
  184. package/src/index.ts +3 -2
  185. package/src/middleware/manager.ts +10 -10
  186. package/src/openapi/docs-ui.ts +1097 -61
  187. package/src/openapi/generator.ts +265 -6
  188. package/src/types/index.ts +83 -30
  189. package/src/utils/validation.ts +5 -3
@@ -133,6 +133,124 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
133
133
  .dark .json-number { color: #7dc9ff; }
134
134
  .dark .json-boolean { color: #93a4bf; }
135
135
  .dark .json-null { color: #7c8ba3; }
136
+ .param-row {
137
+ --param-row-bg-rgb: 255 255 255;
138
+ }
139
+ .dark .param-row {
140
+ --param-row-bg-rgb: 10 10 10;
141
+ }
142
+ .param-row-head {
143
+ display: grid;
144
+ grid-template-columns: minmax(0, 1fr) auto;
145
+ align-items: center;
146
+ gap: 0.5rem;
147
+ min-width: 0;
148
+ width: 100%;
149
+ }
150
+ .param-row-main {
151
+ min-width: 0;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 0.4rem;
155
+ overflow: hidden;
156
+ }
157
+ .param-tooltip-trigger {
158
+ border: 0;
159
+ margin: 0;
160
+ padding: 0;
161
+ background: transparent;
162
+ color: inherit;
163
+ cursor: pointer;
164
+ font: inherit;
165
+ text-align: left;
166
+ min-width: 0;
167
+ }
168
+ .param-tooltip-trigger:focus-visible {
169
+ outline: 2px solid rgba(0, 161, 255, 0.65);
170
+ outline-offset: 2px;
171
+ border-radius: 0.375rem;
172
+ }
173
+ .param-name-trigger {
174
+ min-width: 0;
175
+ flex: 1 1 auto;
176
+ overflow: hidden;
177
+ }
178
+ .param-name-text {
179
+ display: block;
180
+ max-width: 100%;
181
+ overflow: hidden;
182
+ text-overflow: ellipsis;
183
+ white-space: nowrap;
184
+ mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
185
+ -webkit-mask-image: linear-gradient(to right, #000 0%, #000 calc(100% - 18px), transparent 100%);
186
+ }
187
+ .param-type-fade {
188
+ position: relative;
189
+ z-index: 1;
190
+ display: block;
191
+ max-width: none;
192
+ overflow: visible;
193
+ text-overflow: clip;
194
+ padding-left: 1.25rem;
195
+ white-space: nowrap;
196
+ text-align: left;
197
+ justify-self: end;
198
+ background: linear-gradient(
199
+ 90deg,
200
+ rgba(var(--param-row-bg-rgb), 0) 0%,
201
+ rgba(var(--param-row-bg-rgb), 0.76) 36%,
202
+ rgba(var(--param-row-bg-rgb), 0.94) 68%,
203
+ rgba(var(--param-row-bg-rgb), 1) 100%
204
+ );
205
+ }
206
+ #param-value-tooltip {
207
+ position: fixed;
208
+ top: 0;
209
+ left: 0;
210
+ z-index: 70;
211
+ width: min(42rem, calc(100vw - 0.75rem));
212
+ border-radius: 0.5rem;
213
+ border: 1px solid rgba(15, 23, 42, 0.12);
214
+ background: rgba(255, 255, 255, 0.92);
215
+ color: #111111;
216
+ box-shadow: 0 10px 20px rgba(15, 23, 42, 0.14);
217
+ backdrop-filter: blur(12px) saturate(145%);
218
+ -webkit-backdrop-filter: blur(12px) saturate(145%);
219
+ padding: 0.4rem 0.6rem;
220
+ opacity: 0;
221
+ pointer-events: none;
222
+ transform: translateY(6px) scale(0.98);
223
+ transition:
224
+ opacity var(--motion-fast) var(--motion-ease),
225
+ transform var(--motion-fast) var(--motion-ease);
226
+ }
227
+ #param-value-tooltip.is-visible {
228
+ opacity: 1;
229
+ pointer-events: auto;
230
+ transform: translateY(0) scale(1);
231
+ }
232
+ .dark #param-value-tooltip {
233
+ border-color: rgba(148, 163, 184, 0.24);
234
+ background: rgba(17, 17, 17, 0.9);
235
+ color: #ededed;
236
+ box-shadow: 0 14px 30px rgba(0, 0, 0, 0.45);
237
+ }
238
+ #param-tooltip-line {
239
+ margin: 0;
240
+ font-size: 11px;
241
+ line-height: 1.3;
242
+ font-family: "JetBrains Mono", monospace;
243
+ white-space: normal;
244
+ word-break: break-word;
245
+ }
246
+ #param-tooltip-description {
247
+ margin: 0.2rem 0 0;
248
+ font-size: 11px;
249
+ line-height: 1.3;
250
+ opacity: 0.8;
251
+ white-space: normal;
252
+ word-break: break-word;
253
+ }
136
254
  </style>
137
255
  </head>
138
256
  <body class="bg-light-bg text-light-text dark:bg-dark-bg dark:text-dark-text font-sans antialiased flex h-screen overflow-hidden">
@@ -162,6 +280,16 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
162
280
  />
163
281
  </div>
164
282
  </div>
283
+ <div id="auth-panel" class="border-b border-light-border dark:border-dark-border">
284
+ <button id="auth-toggle" class="w-full flex items-center justify-between px-4 py-2.5 text-xs font-semibold uppercase tracking-wider opacity-60 hover:opacity-100 transition-opacity">
285
+ <span class="flex items-center gap-1.5">
286
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
287
+ Auth
288
+ </span>
289
+ <svg id="auth-chevron" class="w-3.5 h-3.5 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
290
+ </button>
291
+ <div id="auth-fields" class="px-4 pb-3 space-y-2"></div>
292
+ </div>
165
293
  <nav class="flex-1 overflow-y-auto px-3 py-2 space-y-6 text-sm" id="sidebar-nav"></nav>
166
294
  </aside>
167
295
 
@@ -195,6 +323,9 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
195
323
  <span id="endpoint-method" class="px-2.5 py-0.5 rounded-full text-xs font-mono font-medium"></span>
196
324
  <h2 class="text-xl font-semibold tracking-tight" id="endpoint-title">Operation</h2>
197
325
  </div>
326
+ <div id="deprecated-banner" class="hidden mb-4 px-3 py-2 rounded border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 text-xs font-medium">
327
+ ! This operation is deprecated
328
+ </div>
198
329
  <p class="text-sm opacity-80 mb-8 font-mono" id="endpoint-path">/</p>
199
330
  <div class="grid grid-cols-1 lg:grid-cols-12 gap-10">
200
331
  <div class="lg:col-span-5 space-y-8" id="params-column"></div>
@@ -290,6 +421,10 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
290
421
  <pre id="expand-viewer" class="hidden w-full h-[70vh] text-sm p-3 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-auto font-mono"></pre>
291
422
  </div>
292
423
  </div>
424
+ <div id="param-value-tooltip" aria-hidden="true" role="tooltip">
425
+ <p id="param-tooltip-line"></p>
426
+ <p id="param-tooltip-description" class="hidden"></p>
427
+ </div>
293
428
 
294
429
  <script>
295
430
  const spec = ${specJson};
@@ -360,14 +495,71 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
360
495
  return ops;
361
496
  }
362
497
 
498
+ const AUTH_STATE_KEY = "vector-docs-auth-v1";
499
+ const AUTH_SELECTION_KEY = "vector-docs-auth-selection-v1";
500
+ const HEADERS_STATE_KEY = "vector-docs-headers-v1";
501
+
502
+ function loadSavedHeaders() {
503
+ try {
504
+ const raw = localStorage.getItem(HEADERS_STATE_KEY);
505
+ if (raw) {
506
+ const parsed = JSON.parse(raw);
507
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed;
508
+ }
509
+ } catch {}
510
+ return [{ key: "", value: "" }];
511
+ }
512
+
513
+ function saveHeaders() {
514
+ try { localStorage.setItem(HEADERS_STATE_KEY, JSON.stringify(requestHeaders)); } catch {}
515
+ }
516
+
517
+ function loadAuthState() {
518
+ try {
519
+ const raw = localStorage.getItem(AUTH_STATE_KEY);
520
+ if (raw) return JSON.parse(raw);
521
+ } catch {}
522
+ return {};
523
+ }
524
+
525
+ function saveAuthState() {
526
+ try { localStorage.setItem(AUTH_STATE_KEY, JSON.stringify(authState)); } catch {}
527
+ }
528
+
529
+ function loadAuthSelectionState() {
530
+ try {
531
+ const raw = localStorage.getItem(AUTH_SELECTION_KEY);
532
+ if (raw) {
533
+ const parsed = JSON.parse(raw);
534
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
535
+ return parsed;
536
+ }
537
+ }
538
+ } catch {}
539
+ return {};
540
+ }
541
+
542
+ function saveAuthSelectionState() {
543
+ try { localStorage.setItem(AUTH_SELECTION_KEY, JSON.stringify(authSelectionState)); } catch {}
544
+ }
545
+
546
+ const authSchemes = (spec.components && spec.components.securitySchemes) || {};
547
+ let authState = loadAuthState();
548
+ let authSelectionState = loadAuthSelectionState();
549
+
363
550
  const operations = getOperations();
364
551
  let selected = operations[0] || null;
365
552
  const operationParamValues = new Map();
366
553
  const operationBodyDrafts = new Map();
367
- const requestHeaders = [{ key: "Authorization", value: "" }];
554
+ const requestHeaders = loadSavedHeaders();
368
555
  let expandModalMode = null;
369
556
  let isMobileSidebarOpen = false;
370
557
  let sidebarSearchQuery = "";
558
+ const paramTooltipRoot = document.getElementById("param-value-tooltip");
559
+ const paramTooltipLine = document.getElementById("param-tooltip-line");
560
+ const paramTooltipDescription = document.getElementById("param-tooltip-description");
561
+ let activeParamTooltipTrigger = null;
562
+ let paramTooltipHideTimer = null;
371
563
 
372
564
  function setMobileSidebarOpen(open) {
373
565
  const sidebar = document.getElementById("docs-sidebar");
@@ -387,6 +579,29 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
387
579
  return op.method + " " + op.path;
388
580
  }
389
581
 
582
+ function getOpHash(op) {
583
+ var tag = op.tag || "default";
584
+ var id = (op.operation && op.operation.operationId)
585
+ ? op.operation.operationId
586
+ : op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
587
+ return "#/" + encodeURIComponent(tag) + "/" + encodeURIComponent(id);
588
+ }
589
+
590
+ function findOpByHash(hash) {
591
+ if (!hash || hash.length <= 1) return null;
592
+ var parts = hash.slice(1).split("/").filter(Boolean);
593
+ if (parts.length < 2) return null;
594
+ var hashTag = decodeURIComponent(parts[0]);
595
+ var hashId = decodeURIComponent(parts[1]);
596
+ return operations.find(function(op) {
597
+ if (op.tag !== hashTag) return false;
598
+ var id = (op.operation && op.operation.operationId)
599
+ ? op.operation.operationId
600
+ : op.method.toLowerCase() + "_" + op.path.split("/").filter(Boolean).join("_").replace(/[{}]/g, "");
601
+ return id === hashId;
602
+ }) || null;
603
+ }
604
+
390
605
  function getOperationParameterGroups(op) {
391
606
  const params =
392
607
  op &&
@@ -436,7 +651,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
436
651
  return resolved;
437
652
  }
438
653
 
439
- function buildRequestPath(op, pathParams, queryParams, values) {
654
+ function buildRequestPath(op, pathParams, queryParams, values, extraQuery) {
440
655
  const resolvedPath = resolvePath(op.path, pathParams, values);
441
656
  const query = new URLSearchParams();
442
657
 
@@ -448,6 +663,13 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
448
663
  query.append(param.name, String(rawValue));
449
664
  }
450
665
 
666
+ if (extraQuery) {
667
+ for (const key of Object.keys(extraQuery)) {
668
+ const val = extraQuery[key];
669
+ if (val) query.set(key, String(val));
670
+ }
671
+ }
672
+
451
673
  const queryString = query.toString();
452
674
  return queryString ? resolvedPath + "?" + queryString : resolvedPath;
453
675
  }
@@ -587,14 +809,25 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
587
809
 
588
810
  const name = document.createElement("span");
589
811
  name.textContent = op.name;
812
+ if (op.operation && op.operation.deprecated) {
813
+ name.style.textDecoration = "line-through";
814
+ name.style.opacity = "0.5";
815
+ }
590
816
 
591
817
  row.appendChild(method);
592
818
  row.appendChild(name);
819
+ if (op.operation && op.operation.deprecated) {
820
+ const badge = document.createElement("span");
821
+ badge.className = "text-[9px] px-1 py-0.5 rounded bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-500 font-semibold shrink-0";
822
+ badge.textContent = "deprecated";
823
+ row.appendChild(badge);
824
+ }
593
825
  a.appendChild(row);
594
826
 
595
827
  a.onclick = (e) => {
596
828
  e.preventDefault();
597
829
  selected = op;
830
+ history.pushState(null, "", getOpHash(op));
598
831
  renderSidebar();
599
832
  renderEndpoint();
600
833
  if (window.innerWidth < 768) {
@@ -608,51 +841,257 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
608
841
  }
609
842
  }
610
843
 
844
+ function hideParamTooltip() {
845
+ if (!paramTooltipRoot) return;
846
+ if (paramTooltipHideTimer) {
847
+ window.clearTimeout(paramTooltipHideTimer);
848
+ paramTooltipHideTimer = null;
849
+ }
850
+ paramTooltipRoot.classList.remove("is-visible");
851
+ paramTooltipRoot.setAttribute("aria-hidden", "true");
852
+ if (activeParamTooltipTrigger) {
853
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
854
+ }
855
+ activeParamTooltipTrigger = null;
856
+ }
857
+
858
+ function scheduleParamTooltipHide() {
859
+ if (paramTooltipHideTimer) {
860
+ window.clearTimeout(paramTooltipHideTimer);
861
+ }
862
+ paramTooltipHideTimer = window.setTimeout(() => {
863
+ hideParamTooltip();
864
+ }, 95);
865
+ }
866
+
867
+ function positionParamTooltip(trigger) {
868
+ if (!paramTooltipRoot || !trigger) return;
869
+ const viewportPadding = 8;
870
+ const spacing = 10;
871
+ const triggerRect = trigger.getBoundingClientRect();
872
+ const tooltipRect = paramTooltipRoot.getBoundingClientRect();
873
+ let left = triggerRect.left + (triggerRect.width / 2) - (tooltipRect.width / 2);
874
+ left = Math.max(viewportPadding, Math.min(left, window.innerWidth - tooltipRect.width - viewportPadding));
875
+ let top = triggerRect.top - tooltipRect.height - spacing;
876
+ if (top < viewportPadding) {
877
+ top = triggerRect.bottom + spacing;
878
+ }
879
+ if (top + tooltipRect.height > window.innerHeight - viewportPadding) {
880
+ top = window.innerHeight - tooltipRect.height - viewportPadding;
881
+ }
882
+ paramTooltipRoot.style.left = Math.round(left) + "px";
883
+ paramTooltipRoot.style.top = Math.round(top) + "px";
884
+ }
885
+
886
+ function showParamTooltip(trigger) {
887
+ if (
888
+ !paramTooltipRoot ||
889
+ !paramTooltipLine ||
890
+ !paramTooltipDescription ||
891
+ !trigger
892
+ ) {
893
+ return;
894
+ }
895
+ if (paramTooltipHideTimer) {
896
+ window.clearTimeout(paramTooltipHideTimer);
897
+ paramTooltipHideTimer = null;
898
+ }
899
+ const label = trigger.getAttribute("data-param-tooltip-label") || "Value";
900
+ const value = trigger.getAttribute("data-param-tooltip-value") || "";
901
+ const related = trigger.getAttribute("data-param-tooltip-related") || "";
902
+ const description = trigger.getAttribute("data-param-tooltip-description") || "";
903
+ if (activeParamTooltipTrigger && activeParamTooltipTrigger !== trigger) {
904
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "false");
905
+ }
906
+ activeParamTooltipTrigger = trigger;
907
+ activeParamTooltipTrigger.setAttribute("aria-expanded", "true");
908
+ const pathLabel = related ? " | path: " + related : "";
909
+ paramTooltipLine.textContent = label + ": " + value + pathLabel;
910
+ if (description.trim()) {
911
+ paramTooltipDescription.textContent = description;
912
+ paramTooltipDescription.classList.remove("hidden");
913
+ } else {
914
+ paramTooltipDescription.textContent = "";
915
+ paramTooltipDescription.classList.add("hidden");
916
+ }
917
+ paramTooltipRoot.classList.add("is-visible");
918
+ paramTooltipRoot.setAttribute("aria-hidden", "false");
919
+ positionParamTooltip(trigger);
920
+ }
921
+
922
+ function registerParamTooltipTargets(scope) {
923
+ if (!scope) return;
924
+ const targets = scope.querySelectorAll("[data-param-tooltip-value]");
925
+ for (const target of targets) {
926
+ target.addEventListener("click", (event) => {
927
+ event.preventDefault();
928
+ if (
929
+ activeParamTooltipTrigger === target &&
930
+ paramTooltipRoot &&
931
+ paramTooltipRoot.classList.contains("is-visible")
932
+ ) {
933
+ hideParamTooltip();
934
+ return;
935
+ }
936
+ showParamTooltip(target);
937
+ });
938
+ target.addEventListener("mouseenter", () => {
939
+ showParamTooltip(target);
940
+ });
941
+ target.addEventListener("mouseleave", (event) => {
942
+ const related = event.relatedTarget;
943
+ if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
944
+ scheduleParamTooltipHide();
945
+ });
946
+ target.addEventListener("focus", () => {
947
+ showParamTooltip(target);
948
+ });
949
+ target.addEventListener("blur", (event) => {
950
+ const related = event.relatedTarget;
951
+ if (paramTooltipRoot && related && paramTooltipRoot.contains(related)) return;
952
+ scheduleParamTooltipHide();
953
+ });
954
+ }
955
+ }
956
+
611
957
  function renderParamSection(title, params) {
612
958
  if (!params.length) return "";
613
959
  let rows = "";
614
960
  for (const p of params) {
615
- const type = escapeHtml((p.schema && p.schema.type) || "unknown");
616
- const name = escapeHtml(p.name || "");
617
- rows += '<div class="py-2 flex justify-between border-b border-light-border/50 dark:border-dark-border/50"><div><code class="text-sm font-mono">' + name + '</code><span class="text-xs text-brand ml-2">' + (p.required ? "required" : "optional") + '</span></div><span class="text-xs font-mono opacity-60">' + type + '</span></div>';
961
+ const schema = resolveSchemaRef(p.schema || {});
962
+ const typeRaw = getSchemaTypeLabel(schema);
963
+ const type = escapeHtml(typeRaw);
964
+ const nameRaw = p.name || "";
965
+ const name = escapeHtml(nameRaw);
966
+ const tooltipName = escapeHtmlAttribute(nameRaw);
967
+ const tooltipType = escapeHtmlAttribute(typeRaw);
968
+ const tooltipDescription = (typeof p.description === "string" && p.description.trim())
969
+ ? escapeHtmlAttribute(p.description.trim())
970
+ : (typeof schema.description === "string" && schema.description.trim())
971
+ ? escapeHtmlAttribute(schema.description.trim())
972
+ : "";
973
+ const desc = (typeof p.description === "string" && p.description.trim())
974
+ ? '<p class="text-xs opacity-60 mt-0.5 leading-snug">' + renderMarkdown(p.description.trim()) + '</p>'
975
+ : "";
976
+ const extra = buildSchemaExtra(schema);
977
+ rows +=
978
+ '<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50">' +
979
+ '<div class="param-row-head">' +
980
+ '<div class="param-row-main">' +
981
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Parameter" data-param-tooltip-value="' +
982
+ tooltipName +
983
+ '" data-param-tooltip-description="' +
984
+ tooltipDescription +
985
+ '" aria-expanded="false">' +
986
+ '<code class="text-sm font-mono param-name-text">' +
987
+ name +
988
+ "</code></button>" +
989
+ '<span class="text-xs text-brand shrink-0">' +
990
+ (p.required ? "required" : "optional") +
991
+ "</span></div>" +
992
+ '<button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
993
+ tooltipType +
994
+ '" data-param-tooltip-description="' +
995
+ tooltipDescription +
996
+ '" aria-expanded="false">' +
997
+ type +
998
+ "</button></div>" +
999
+ desc +
1000
+ extra +
1001
+ "</div>";
618
1002
  }
619
1003
  return '<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">' + escapeHtml(title) + "</h3>" + rows + "</div>";
620
1004
  }
621
1005
 
622
1006
  function getSchemaTypeLabel(schema) {
623
- if (!schema || typeof schema !== "object") return "unknown";
624
- if (Array.isArray(schema.type)) return schema.type.join(" | ");
625
- if (schema.type) return String(schema.type);
626
- if (schema.properties) return "object";
627
- if (schema.items) return "array";
628
- if (Array.isArray(schema.oneOf)) return "oneOf";
629
- if (Array.isArray(schema.anyOf)) return "anyOf";
630
- if (Array.isArray(schema.allOf)) return "allOf";
1007
+ const resolved = resolveSchemaRef(schema);
1008
+ if (!resolved || typeof resolved !== "object") return "unknown";
1009
+ if (Array.isArray(resolved.type)) return resolved.type.join(" | ");
1010
+ if (resolved.type) return String(resolved.type);
1011
+ if (resolved.properties) return "object";
1012
+ if (resolved.items) return "array";
1013
+ if (Array.isArray(resolved.oneOf)) return "oneOf";
1014
+ if (Array.isArray(resolved.anyOf)) return "anyOf";
1015
+ if (Array.isArray(resolved.allOf)) return "allOf";
631
1016
  return "unknown";
632
1017
  }
633
1018
 
1019
+ function buildSchemaExtra(schema) {
1020
+ const resolved = resolveSchemaRef(schema);
1021
+ if (!resolved || typeof resolved !== "object") return "";
1022
+ const chips = [];
1023
+ if (resolved.format) chips.push(escapeHtml(String(resolved.format)));
1024
+ if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
1025
+ const shown = resolved.enum.slice(0, 5).map(function(v) { return escapeHtml(JSON.stringify(v)); });
1026
+ chips.push(shown.join(" | ") + (resolved.enum.length > 5 ? " …" : ""));
1027
+ }
1028
+ if (resolved.minimum !== undefined) chips.push("min: " + resolved.minimum);
1029
+ if (resolved.maximum !== undefined) chips.push("max: " + resolved.maximum);
1030
+ if (typeof resolved.exclusiveMinimum === "number") chips.push("&gt;" + resolved.exclusiveMinimum);
1031
+ if (typeof resolved.exclusiveMaximum === "number") chips.push("&lt;" + resolved.exclusiveMaximum);
1032
+ if (resolved.minLength !== undefined) chips.push("minLen: " + resolved.minLength);
1033
+ if (resolved.maxLength !== undefined) chips.push("maxLen: " + resolved.maxLength);
1034
+ if (resolved.minItems !== undefined) chips.push("minItems: " + resolved.minItems);
1035
+ if (resolved.maxItems !== undefined) chips.push("maxItems: " + resolved.maxItems);
1036
+ if (resolved.uniqueItems) chips.push("unique");
1037
+ if (resolved.pattern) chips.push("/" + escapeHtml(String(resolved.pattern)) + "/");
1038
+ if (!chips.length) return "";
1039
+ return '<div class="flex flex-wrap gap-1 mt-1.5">' +
1040
+ chips.map(function(c) {
1041
+ return '<span class="text-[10px] px-1.5 py-0.5 rounded bg-black/5 dark:bg-white/5 font-mono opacity-80">' + c + '</span>';
1042
+ }).join("") +
1043
+ '</div>';
1044
+ }
1045
+
1046
+ function resolveSchemaRef(schema, visitedRefs) {
1047
+ if (!schema || typeof schema !== "object") return schema;
1048
+ const ref = typeof schema.$ref === "string" ? schema.$ref : "";
1049
+ if (!ref || !ref.startsWith("#/components/schemas/")) {
1050
+ return schema;
1051
+ }
1052
+
1053
+ const seen = visitedRefs || new Set();
1054
+ if (seen.has(ref)) return schema;
1055
+ seen.add(ref);
1056
+
1057
+ const parts = ref.split("/");
1058
+ const schemaName = parts[parts.length - 1];
1059
+ const referenced = spec && spec.components && spec.components.schemas && spec.components.schemas[schemaName];
1060
+ if (!referenced || typeof referenced !== "object") return schema;
1061
+
1062
+ const merged = Object.assign({}, referenced, schema);
1063
+ delete merged.$ref;
1064
+ return resolveSchemaRef(merged, seen);
1065
+ }
1066
+
634
1067
  function buildSchemaChildren(schema) {
635
- if (!schema || typeof schema !== "object") return [];
1068
+ const resolved = resolveSchemaRef(schema);
1069
+ if (!resolved || typeof resolved !== "object") return [];
636
1070
 
637
1071
  const children = [];
638
1072
 
639
- if (schema.properties && typeof schema.properties === "object") {
1073
+ if (resolved.properties && typeof resolved.properties === "object") {
640
1074
  const requiredSet = new Set(
641
- Array.isArray(schema.required) ? schema.required : [],
1075
+ Array.isArray(resolved.required) ? resolved.required : [],
642
1076
  );
643
- for (const [name, childSchema] of Object.entries(schema.properties)) {
1077
+ for (const [name, childSchema] of Object.entries(resolved.properties)) {
1078
+ const childDef = childSchema || {};
1079
+ const isArrayType = Array.isArray(childDef.type)
1080
+ ? childDef.type.includes("array")
1081
+ : childDef.type === "array";
1082
+ const isArrayLike = isArrayType || childDef.items !== undefined;
644
1083
  children.push({
645
- name,
646
- schema: childSchema || {},
1084
+ name: isArrayLike ? (name + "[]") : name,
1085
+ schema: childDef,
647
1086
  required: requiredSet.has(name),
648
1087
  });
649
1088
  }
650
1089
  }
651
1090
 
652
- if (schema.items) {
1091
+ if (resolved.items) {
653
1092
  children.push({
654
- name: "items[]",
655
- schema: schema.items,
1093
+ name: getArrayItemNodeName(resolved.items),
1094
+ schema: resolved.items,
656
1095
  required: true,
657
1096
  });
658
1097
  }
@@ -660,45 +1099,100 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
660
1099
  return children;
661
1100
  }
662
1101
 
663
- function renderSchemaFieldNode(field, depth) {
664
- const schema = field.schema || {};
665
- const name = escapeHtml(field.name || "field");
1102
+ function getArrayItemNodeName(itemSchema) {
1103
+ if (!itemSchema || typeof itemSchema !== "object") return "item";
1104
+ const title =
1105
+ typeof itemSchema.title === "string" && itemSchema.title.trim()
1106
+ ? itemSchema.title.trim()
1107
+ : "";
1108
+ if (title) return title;
1109
+
1110
+ const ref =
1111
+ typeof itemSchema.$ref === "string" && itemSchema.$ref.trim()
1112
+ ? itemSchema.$ref.trim()
1113
+ : "";
1114
+ if (ref) {
1115
+ const parts = ref.split("/").filter(Boolean);
1116
+ const last = parts[parts.length - 1];
1117
+ if (last) return last;
1118
+ }
1119
+
1120
+ const typeLabel = getSchemaTypeLabel(itemSchema);
1121
+ if (typeLabel && typeLabel !== "unknown") return typeLabel;
1122
+ return "type";
1123
+ }
1124
+
1125
+ function renderSchemaFieldNode(field, depth, parentPath) {
1126
+ const schema = resolveSchemaRef(field.schema || {});
1127
+ const nameRaw = field.name || "field";
1128
+ const name = escapeHtml(nameRaw);
666
1129
  const requiredLabel = field.required ? "required" : "optional";
667
- const type = escapeHtml(getSchemaTypeLabel(schema));
1130
+ const typeRaw = getSchemaTypeLabel(schema);
1131
+ const type = escapeHtml(typeRaw);
1132
+ const tooltipName = escapeHtmlAttribute(nameRaw);
1133
+ const tooltipType = escapeHtmlAttribute(typeRaw);
1134
+ const fieldPath = parentPath ? (parentPath + "." + nameRaw) : nameRaw;
1135
+ const tooltipPath = escapeHtmlAttribute(fieldPath);
1136
+ const tooltipDescription = (typeof schema.description === "string" && schema.description.trim())
1137
+ ? escapeHtmlAttribute(schema.description.trim())
1138
+ : "";
668
1139
  const children = buildSchemaChildren(schema);
669
1140
  const padding = depth * 14;
1141
+ const extra = buildSchemaExtra(schema);
670
1142
 
671
1143
  if (!children.length) {
672
1144
  return (
673
- '<div class="py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
1145
+ '<div class="param-row py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
674
1146
  padding +
675
- 'px"><div class="flex justify-between"><div><code class="text-sm font-mono">' +
1147
+ 'px"><div class="param-row-head"><div class="param-row-main">' +
1148
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
1149
+ tooltipName +
1150
+ '" data-param-tooltip-related="' +
1151
+ tooltipPath +
1152
+ '" data-param-tooltip-description="' +
1153
+ tooltipDescription +
1154
+ '" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
676
1155
  name +
677
- '</code><span class="text-xs text-brand ml-2">' +
1156
+ '</code></button><span class="text-xs text-brand shrink-0">' +
678
1157
  requiredLabel +
679
- '</span></div><span class="text-xs font-mono opacity-60">' +
1158
+ '</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
1159
+ tooltipType +
1160
+ '" data-param-tooltip-description="' +
1161
+ tooltipDescription +
1162
+ '" aria-expanded="false">' +
680
1163
  type +
681
- "</span></div></div>"
1164
+ "</button></div>" + extra + "</div>"
682
1165
  );
683
1166
  }
684
1167
 
685
1168
  let nested = "";
686
1169
  for (const child of children) {
687
- nested += renderSchemaFieldNode(child, depth + 1);
1170
+ nested += renderSchemaFieldNode(child, depth + 1, fieldPath);
688
1171
  }
689
1172
 
690
1173
  return (
691
- '<details class="border-b border-light-border/50 dark:border-dark-border/50" open>' +
692
- '<summary class="list-none cursor-pointer py-2 flex justify-between items-center" style="padding-left:' +
1174
+ '<details open>' +
1175
+ '<summary class="list-none cursor-pointer py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
693
1176
  padding +
694
- 'px"><div class="flex items-center gap-2"><span class="text-xs opacity-70">▾</span><code class="text-sm font-mono">' +
1177
+ 'px"><div class="param-row-head"><div class="param-row-main"><span class="text-xs opacity-70 shrink-0">▾</span>' +
1178
+ '<button type="button" class="param-tooltip-trigger param-name-trigger" data-param-tooltip-label="Field" data-param-tooltip-value="' +
1179
+ tooltipName +
1180
+ '" data-param-tooltip-related="' +
1181
+ tooltipPath +
1182
+ '" data-param-tooltip-description="' +
1183
+ tooltipDescription +
1184
+ '" aria-expanded="false"><code class="text-sm font-mono param-name-text">' +
695
1185
  name +
696
- '</code><span class="text-xs text-brand">' +
1186
+ '</code></button><span class="text-xs text-brand shrink-0">' +
697
1187
  requiredLabel +
698
- '</span></div><span class="text-xs font-mono opacity-60">' +
1188
+ '</span></div><button type="button" class="param-tooltip-trigger param-type-fade text-xs font-mono opacity-60" data-param-tooltip-label="Type" data-param-tooltip-value="' +
1189
+ tooltipType +
1190
+ '" data-param-tooltip-description="' +
1191
+ tooltipDescription +
1192
+ '" aria-expanded="false">' +
699
1193
  type +
700
- "</span></summary>" +
701
- '<div class="pb-1">' +
1194
+ "</button></div>" + extra + "</summary>" +
1195
+ "<div>" +
702
1196
  nested +
703
1197
  "</div></details>"
704
1198
  );
@@ -711,7 +1205,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
711
1205
 
712
1206
  let rows = "";
713
1207
  for (const child of rootChildren) {
714
- rows += renderSchemaFieldNode(child, 0);
1208
+ rows += renderSchemaFieldNode(child, 0, "");
715
1209
  }
716
1210
 
717
1211
  return (
@@ -738,27 +1232,40 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
738
1232
  const responseDef = responses[statusCode];
739
1233
  if (!responseDef || typeof responseDef !== "object") continue;
740
1234
 
1235
+ const responseDesc = (typeof responseDef.description === "string" && responseDef.description.trim())
1236
+ ? responseDef.description.trim()
1237
+ : "";
1238
+
741
1239
  const jsonSchema =
742
1240
  responseDef.content &&
743
1241
  responseDef.content["application/json"] &&
744
1242
  responseDef.content["application/json"].schema;
745
1243
 
746
- if (!jsonSchema || typeof jsonSchema !== "object") continue;
747
-
748
- const rootChildren = buildSchemaChildren(jsonSchema);
749
- if (!rootChildren.length) continue;
750
-
751
1244
  let rows = "";
752
- for (const child of rootChildren) {
753
- rows += renderSchemaFieldNode(child, 0);
1245
+ if (jsonSchema && typeof jsonSchema === "object") {
1246
+ const rootChildren = buildSchemaChildren(jsonSchema);
1247
+ for (const child of rootChildren) {
1248
+ rows += renderSchemaFieldNode(child, 0, "");
1249
+ }
754
1250
  }
755
1251
 
1252
+ if (!responseDesc && !rows) continue;
1253
+
1254
+ const descHtml = responseDesc
1255
+ ? ' <span class="normal-case font-sans opacity-70 ml-1">— ' + escapeHtml(responseDesc) + '</span>'
1256
+ : "";
1257
+ const contentHtml = rows || '<p class="text-xs opacity-60 mt-1">No schema fields</p>';
1258
+
756
1259
  sections +=
757
- '<div class="mb-4"><h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
1260
+ '<details class="mb-4">' +
1261
+ '<summary class="list-none cursor-pointer">' +
1262
+ '<h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
758
1263
  escapeHtml(statusCode) +
1264
+ descHtml +
759
1265
  "</h4>" +
760
- rows +
761
- "</div>";
1266
+ "</summary>" +
1267
+ contentHtml +
1268
+ "</details>";
762
1269
  }
763
1270
 
764
1271
  if (!sections) return "";
@@ -850,6 +1357,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
850
1357
  "w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
851
1358
  keyInput.addEventListener("input", () => {
852
1359
  entry.key = keyInput.value;
1360
+ saveHeaders();
853
1361
  updateRequestPreview();
854
1362
  });
855
1363
 
@@ -864,6 +1372,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
864
1372
  "w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
865
1373
  valueInput.addEventListener("input", () => {
866
1374
  entry.value = valueInput.value;
1375
+ saveHeaders();
867
1376
  updateRequestPreview();
868
1377
  });
869
1378
 
@@ -877,6 +1386,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
877
1386
  '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h12"></path></svg>';
878
1387
  removeButton.addEventListener("click", () => {
879
1388
  requestHeaders.splice(index, 1);
1389
+ saveHeaders();
880
1390
  renderHeaderInputs();
881
1391
  updateRequestPreview();
882
1392
  });
@@ -893,15 +1403,32 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
893
1403
  return Object.keys(headers).some((key) => key.toLowerCase() === target);
894
1404
  }
895
1405
 
896
- function getRequestHeadersObject() {
897
- const headers = {};
1406
+ function buildCookieHeaderValue(cookieValues) {
1407
+ const entries = Object.entries(cookieValues);
1408
+ if (!entries.length) return "";
1409
+ return entries
1410
+ .map(([name, value]) => String(name) + "=" + encodeURIComponent(String(value)))
1411
+ .join("; ");
1412
+ }
1413
+
1414
+ function getRequestHeadersObject(op) {
1415
+ const auth = getAuthHeaders(op);
1416
+ const authCookies = getAuthCookieParams(op);
1417
+ if (Object.keys(authCookies).length > 0) {
1418
+ const cookieHeader = buildCookieHeaderValue(authCookies);
1419
+ if (cookieHeader) {
1420
+ auth["Cookie"] = cookieHeader;
1421
+ }
1422
+ }
1423
+ const manual = {};
898
1424
  for (const entry of requestHeaders) {
899
1425
  const key = String(entry.key || "").trim();
900
1426
  const value = String(entry.value || "").trim();
901
1427
  if (!key || !value) continue;
902
- headers[key] = value;
1428
+ manual[key] = value;
903
1429
  }
904
- return headers;
1430
+ // Auth provides defaults; manual headers win on conflict
1431
+ return Object.assign({}, auth, manual);
905
1432
  }
906
1433
 
907
1434
  function buildCurl(op, headers, body, requestPath) {
@@ -938,6 +1465,33 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
938
1465
  .replace(/>/g, "&gt;");
939
1466
  }
940
1467
 
1468
+ function escapeHtmlAttribute(value) {
1469
+ return String(value)
1470
+ .replace(/&/g, "&amp;")
1471
+ .replace(/</g, "&lt;")
1472
+ .replace(/>/g, "&gt;")
1473
+ .replace(/"/g, "&quot;")
1474
+ .replace(/'/g, "&#39;");
1475
+ }
1476
+
1477
+ function renderMarkdown(text) {
1478
+ if (!text || typeof text !== "string") return "";
1479
+ var s = escapeHtml(text);
1480
+ // inline code — process first to protect content inside backticks
1481
+ s = s.replace(/\`([^\`\\n]+)\`/g, '<code class="text-xs font-mono bg-black/5 dark:bg-white/5 px-1 py-0.5 rounded">$1</code>');
1482
+ // bold **text**
1483
+ s = s.replace(/\\*\\*([^*\\n]+)\\*\\*/g, '<strong>$1</strong>');
1484
+ // italic *text*
1485
+ s = s.replace(/\\*([^*\\n]+)\\*/g, '<em>$1</em>');
1486
+ // links [text](url)
1487
+ s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, function(m, txt, url) {
1488
+ var lc = url.toLowerCase().replace(/\\s/g, "");
1489
+ if (lc.indexOf("javascript:") === 0 || lc.indexOf("data:") === 0 || lc.indexOf("vbscript:") === 0) return txt;
1490
+ return '<a href="' + url.replace(/"/g, '&quot;') + '" target="_blank" rel="noopener noreferrer" class="text-brand hover:underline">' + txt + '</a>';
1491
+ });
1492
+ return s;
1493
+ }
1494
+
941
1495
  function toPrettyJson(value) {
942
1496
  const trimmed = (value || "").trim();
943
1497
  if (!trimmed) return null;
@@ -1071,10 +1625,10 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1071
1625
 
1072
1626
  const { path, query } = getOperationParameterGroups(selected);
1073
1627
  const values = getParameterValues(selected);
1074
- const requestPath = buildRequestPath(selected, path, query, values);
1628
+ const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
1075
1629
  const bodyInput = document.getElementById("body-input");
1076
1630
  const body = bodyInput ? bodyInput.value.trim() : "";
1077
- const headers = getRequestHeadersObject();
1631
+ const headers = getRequestHeadersObject(selected);
1078
1632
  if (body && !hasHeaderName(headers, "Content-Type")) {
1079
1633
  headers["Content-Type"] = "application/json";
1080
1634
  }
@@ -1125,8 +1679,13 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1125
1679
  }
1126
1680
  setResponseContent("", "", false);
1127
1681
 
1682
+ const deprecatedBanner = document.getElementById("deprecated-banner");
1683
+ if (deprecatedBanner) {
1684
+ deprecatedBanner.classList.toggle("hidden", !op.deprecated);
1685
+ }
1686
+
1128
1687
  document.getElementById("tag-title").textContent = selected.tag;
1129
- document.getElementById("tag-description").textContent = op.description || "Interactive API documentation.";
1688
+ document.getElementById("tag-description").innerHTML = op.description ? renderMarkdown(op.description) : "Interactive API documentation.";
1130
1689
  const methodNode = document.getElementById("endpoint-method");
1131
1690
  methodNode.textContent = selected.method;
1132
1691
  methodNode.className = "px-2.5 py-0.5 rounded-full text-xs font-mono font-medium " + (methodBadge[selected.method] || methodBadgeDefault);
@@ -1143,7 +1702,13 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1143
1702
 
1144
1703
  html += renderRequestBodySchemaSection(reqSchema);
1145
1704
  html += renderResponseSchemasSection(op.responses);
1146
- document.getElementById("params-column").innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
1705
+ const paramsColumn = document.getElementById("params-column");
1706
+ if (paramsColumn) {
1707
+ hideParamTooltip();
1708
+ paramsColumn.innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
1709
+ registerParamTooltipTargets(paramsColumn);
1710
+ }
1711
+ renderAuthPanel();
1147
1712
  renderTryItParameterInputs(path, query);
1148
1713
  renderHeaderInputs();
1149
1714
  updateRequestPreview();
@@ -1154,6 +1719,24 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1154
1719
  document.getElementById("copy-curl").addEventListener("click", async () => {
1155
1720
  try { await navigator.clipboard.writeText(document.getElementById("curl-code").textContent || ""); } catch {}
1156
1721
  });
1722
+ if (paramTooltipRoot) {
1723
+ paramTooltipRoot.addEventListener("mouseenter", () => {
1724
+ if (paramTooltipHideTimer) {
1725
+ window.clearTimeout(paramTooltipHideTimer);
1726
+ paramTooltipHideTimer = null;
1727
+ }
1728
+ });
1729
+ paramTooltipRoot.addEventListener("mouseleave", () => {
1730
+ scheduleParamTooltipHide();
1731
+ });
1732
+ }
1733
+ document.addEventListener("pointerdown", (event) => {
1734
+ if (!paramTooltipRoot) return;
1735
+ const target = event.target;
1736
+ if (target && paramTooltipRoot.contains(target)) return;
1737
+ if (target && target.closest && target.closest("[data-param-tooltip-value]")) return;
1738
+ hideParamTooltip();
1739
+ });
1157
1740
  document.getElementById("sidebar-search").addEventListener("input", (event) => {
1158
1741
  sidebarSearchQuery = event.currentTarget.value || "";
1159
1742
  renderSidebar();
@@ -1179,7 +1762,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1179
1762
  return;
1180
1763
  }
1181
1764
 
1182
- const requestPath = buildRequestPath(selected, path, query, values);
1765
+ const requestPath = buildRequestPath(selected, path, query, values, getAuthQueryParams(selected));
1183
1766
  formatBodyJsonInput();
1184
1767
  updateBodyJsonPresentation();
1185
1768
  const op = selected.operation || {};
@@ -1188,7 +1771,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1188
1771
  const bodyInput = document.getElementById("body-input");
1189
1772
  const body =
1190
1773
  supportsBody && bodyInput ? bodyInput.value.trim() : "";
1191
- const headers = getRequestHeadersObject();
1774
+ const headers = getRequestHeadersObject(selected);
1192
1775
  if (body && !hasHeaderName(headers, "Content-Type")) {
1193
1776
  headers["Content-Type"] = "application/json";
1194
1777
  }
@@ -1196,7 +1779,13 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1196
1779
  setSubmitLoading(true);
1197
1780
  try {
1198
1781
  const requestStart = performance.now();
1199
- const response = await fetch(requestPath, { method: selected.method, headers, body: body || undefined });
1782
+ applyAuthCookies(selected);
1783
+ const response = await fetch(requestPath, {
1784
+ method: selected.method,
1785
+ headers,
1786
+ body: body || undefined,
1787
+ credentials: "same-origin",
1788
+ });
1200
1789
  const text = await response.text();
1201
1790
  const responseTimeMs = Math.round(performance.now() - requestStart);
1202
1791
  const contentType = response.headers.get("content-type") || "unknown";
@@ -1272,6 +1861,7 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1272
1861
 
1273
1862
  document.getElementById("add-header-btn").addEventListener("click", () => {
1274
1863
  requestHeaders.push({ key: "", value: "" });
1864
+ saveHeaders();
1275
1865
  renderHeaderInputs();
1276
1866
  updateRequestPreview();
1277
1867
  });
@@ -1385,12 +1975,21 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1385
1975
  setMobileSidebarOpen(false);
1386
1976
  });
1387
1977
  window.addEventListener("resize", () => {
1978
+ if (activeParamTooltipTrigger) {
1979
+ positionParamTooltip(activeParamTooltipTrigger);
1980
+ }
1388
1981
  if (window.innerWidth >= 768 && isMobileSidebarOpen) {
1389
1982
  setMobileSidebarOpen(false);
1390
1983
  }
1391
1984
  });
1985
+ window.addEventListener("scroll", () => {
1986
+ if (activeParamTooltipTrigger) {
1987
+ positionParamTooltip(activeParamTooltipTrigger);
1988
+ }
1989
+ }, true);
1392
1990
  document.addEventListener("keydown", (event) => {
1393
1991
  if (event.key === "Escape") {
1992
+ hideParamTooltip();
1394
1993
  if (isMobileSidebarOpen) {
1395
1994
  setMobileSidebarOpen(false);
1396
1995
  }
@@ -1415,7 +2014,444 @@ export function renderOpenAPIDocsHtml(spec, openapiPath, tailwindScriptPath, log
1415
2014
  }
1416
2015
  });
1417
2016
 
2017
+ function getOperationSecurityRequirements(op) {
2018
+ const operationSecurity = op && op.operation && Array.isArray(op.operation.security)
2019
+ ? op.operation.security
2020
+ : null;
2021
+ if (operationSecurity) {
2022
+ return operationSecurity;
2023
+ }
2024
+ return Array.isArray(spec.security) ? spec.security : [];
2025
+ }
2026
+
2027
+ function getAuthSelectionKeyForOperation(op) {
2028
+ if (!op) return "";
2029
+ return getOperationKey(op);
2030
+ }
2031
+
2032
+ function getAuthSchemeOptionsForOperation(op) {
2033
+ const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
2034
+ requirement && typeof requirement === "object" && !Array.isArray(requirement)
2035
+ );
2036
+ if (!requirements.length) return [];
2037
+
2038
+ const seen = new Set();
2039
+ const options = [];
2040
+ for (const requirement of requirements) {
2041
+ for (const schemeName of Object.keys(requirement)) {
2042
+ if (!Object.prototype.hasOwnProperty.call(authSchemes, schemeName)) continue;
2043
+ if (seen.has(schemeName)) continue;
2044
+ seen.add(schemeName);
2045
+ options.push(schemeName);
2046
+ }
2047
+ }
2048
+ return options;
2049
+ }
2050
+
2051
+ function getSelectedAuthSchemeForOperation(op) {
2052
+ const selectionKey = getAuthSelectionKeyForOperation(op);
2053
+ if (!selectionKey) return null;
2054
+
2055
+ const selectedScheme = authSelectionState[selectionKey];
2056
+ if (!selectedScheme || typeof selectedScheme !== "string") return null;
2057
+
2058
+ const options = Object.keys(authSchemes);
2059
+ if (!options.includes(selectedScheme)) {
2060
+ delete authSelectionState[selectionKey];
2061
+ saveAuthSelectionState();
2062
+ return null;
2063
+ }
2064
+
2065
+ return selectedScheme;
2066
+ }
2067
+
2068
+ function setSelectedAuthSchemeForOperation(op, schemeName) {
2069
+ const selectionKey = getAuthSelectionKeyForOperation(op);
2070
+ if (!selectionKey) return;
2071
+
2072
+ if (!schemeName) {
2073
+ delete authSelectionState[selectionKey];
2074
+ } else {
2075
+ authSelectionState[selectionKey] = schemeName;
2076
+ }
2077
+ saveAuthSelectionState();
2078
+ }
2079
+
2080
+ function hasAuthStateForScheme(schemeName) {
2081
+ const scheme = authSchemes[schemeName];
2082
+ if (!scheme) return false;
2083
+
2084
+ const state = authState[schemeName] || {};
2085
+ const type = (scheme.type || "").toLowerCase();
2086
+ const httpScheme = (scheme.scheme || "").toLowerCase();
2087
+
2088
+ if (type === "http" && httpScheme === "basic") {
2089
+ return Boolean(state.username && state.password);
2090
+ }
2091
+ if (type === "http") {
2092
+ return Boolean(state.token);
2093
+ }
2094
+ if (type === "apikey") {
2095
+ return Boolean(state.value);
2096
+ }
2097
+ if (type === "oauth2" || type === "openidconnect") {
2098
+ return Boolean(state.token);
2099
+ }
2100
+
2101
+ return false;
2102
+ }
2103
+
2104
+ function chooseOperationSecurityRequirement(op) {
2105
+ const requirements = getOperationSecurityRequirements(op).filter((requirement) =>
2106
+ requirement && typeof requirement === "object" && !Array.isArray(requirement)
2107
+ );
2108
+ if (!requirements.length) return null;
2109
+
2110
+ const selectedScheme = getSelectedAuthSchemeForOperation(op);
2111
+ if (selectedScheme) {
2112
+ const selectedRequirement = requirements.find((requirement) =>
2113
+ Object.prototype.hasOwnProperty.call(requirement, selectedScheme)
2114
+ );
2115
+ if (selectedRequirement) return selectedRequirement;
2116
+ }
2117
+
2118
+ let bestRequirement = null;
2119
+ let bestScore = -1;
2120
+
2121
+ for (const requirement of requirements) {
2122
+ const schemeNames = Object.keys(requirement).filter((schemeName) =>
2123
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
2124
+ );
2125
+ if (!schemeNames.length) continue;
2126
+
2127
+ const providedCount = schemeNames.filter((schemeName) => hasAuthStateForScheme(schemeName)).length;
2128
+ const isComplete = providedCount === schemeNames.length;
2129
+ const score = isComplete ? 1000 + providedCount : providedCount;
2130
+
2131
+ if (score > bestScore) {
2132
+ bestScore = score;
2133
+ bestRequirement = requirement;
2134
+ }
2135
+ }
2136
+
2137
+ return bestRequirement || requirements[0];
2138
+ }
2139
+
2140
+ function getAuthSchemeNamesForOperation(op) {
2141
+ const schemeNames = Object.keys(authSchemes);
2142
+ if (!schemeNames.length) return [];
2143
+
2144
+ const selectedScheme = getSelectedAuthSchemeForOperation(op);
2145
+ if (selectedScheme) {
2146
+ const requirement = chooseOperationSecurityRequirement(op);
2147
+ if (requirement && Object.prototype.hasOwnProperty.call(requirement, selectedScheme)) {
2148
+ return Object.keys(requirement).filter((schemeName) =>
2149
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
2150
+ );
2151
+ }
2152
+ return [selectedScheme];
2153
+ }
2154
+
2155
+ const requirement = chooseOperationSecurityRequirement(op);
2156
+ if (!requirement) return [];
2157
+
2158
+ return Object.keys(requirement).filter((schemeName) =>
2159
+ Object.prototype.hasOwnProperty.call(authSchemes, schemeName)
2160
+ );
2161
+ }
2162
+
2163
+ function getAuthHeaders(op) {
2164
+ const headers = {};
2165
+ const schemeNames = getAuthSchemeNamesForOperation(op);
2166
+ const allSchemeNames = Object.keys(authSchemes);
2167
+
2168
+ if (!allSchemeNames.length) {
2169
+ const state = authState["__default__"] || {};
2170
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
2171
+ return headers;
2172
+ }
2173
+ if (!schemeNames.length) return headers;
2174
+
2175
+ for (const schemeName of schemeNames) {
2176
+ const scheme = authSchemes[schemeName];
2177
+ const state = authState[schemeName] || {};
2178
+ const type = (scheme.type || "").toLowerCase();
2179
+ const httpScheme = (scheme.scheme || "").toLowerCase();
2180
+
2181
+ if (type === "http" && httpScheme === "basic") {
2182
+ if (state.username && state.password) {
2183
+ try {
2184
+ headers["Authorization"] = "Basic " + btoa(state.username + ":" + state.password);
2185
+ } catch {}
2186
+ }
2187
+ } else if (type === "http") {
2188
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
2189
+ } else if (type === "apikey" && (scheme.in || "").toLowerCase() === "header") {
2190
+ if (state.value && scheme.name) headers[scheme.name] = state.value;
2191
+ } else if (type === "oauth2" || type === "openidconnect") {
2192
+ if (state.token) headers["Authorization"] = "Bearer " + state.token;
2193
+ }
2194
+ }
2195
+ return headers;
2196
+ }
2197
+
2198
+ function getAuthQueryParams(op) {
2199
+ const params = {};
2200
+ for (const schemeName of getAuthSchemeNamesForOperation(op)) {
2201
+ const scheme = authSchemes[schemeName];
2202
+ if ((scheme.type || "").toLowerCase() === "apikey" && (scheme.in || "").toLowerCase() === "query") {
2203
+ const state = authState[schemeName] || {};
2204
+ if (state.value && scheme.name) params[scheme.name] = state.value;
2205
+ }
2206
+ }
2207
+ return params;
2208
+ }
2209
+
2210
+ function getAuthCookieParams(op) {
2211
+ const cookies = {};
2212
+ for (const schemeName of getAuthSchemeNamesForOperation(op)) {
2213
+ const scheme = authSchemes[schemeName];
2214
+ if ((scheme.type || "").toLowerCase() !== "apikey") continue;
2215
+ if ((scheme.in || "").toLowerCase() !== "cookie") continue;
2216
+ const state = authState[schemeName] || {};
2217
+ if (state.value && scheme.name) {
2218
+ cookies[scheme.name] = state.value;
2219
+ }
2220
+ }
2221
+ return cookies;
2222
+ }
2223
+
2224
+ function applyAuthCookies(op) {
2225
+ const cookies = getAuthCookieParams(op);
2226
+ for (const [name, value] of Object.entries(cookies)) {
2227
+ try {
2228
+ document.cookie = encodeURIComponent(String(name)) + "=" + encodeURIComponent(String(value)) + "; path=/";
2229
+ } catch {}
2230
+ }
2231
+ }
2232
+
2233
+ function renderAuthPanel() {
2234
+ const fields = document.getElementById("auth-fields");
2235
+ if (!fields) return;
2236
+ fields.innerHTML = "";
2237
+
2238
+ const schemeNames = Object.keys(authSchemes);
2239
+ const op = selected;
2240
+ const operationSchemeOptions = op ? getAuthSchemeOptionsForOperation(op) : [];
2241
+ const availableSchemeOptions = Object.keys(authSchemes);
2242
+ const selectedScheme = op ? getSelectedAuthSchemeForOperation(op) : null;
2243
+
2244
+ function getAuthSchemeDisplayLabel(schemeName) {
2245
+ const scheme = authSchemes[schemeName] || {};
2246
+ const type = (scheme.type || "").toLowerCase();
2247
+ const httpScheme = (scheme.scheme || "").toLowerCase();
2248
+ const location = (scheme.in || "").toLowerCase();
2249
+
2250
+ if (type === "http" && httpScheme === "basic") return "HTTP Basic";
2251
+ if (type === "http" && httpScheme === "bearer") return "HTTP Bearer";
2252
+ if (type === "http" && httpScheme === "digest") return "HTTP Digest";
2253
+ if (type === "http") return "HTTP " + (httpScheme || "Token");
2254
+ if (type === "apikey") return "API Key" + (location ? " (" + location + ")" : "");
2255
+ if (type === "oauth2") return "OAuth 2.0";
2256
+ if (type === "openidconnect") return "OpenID Connect";
2257
+ if (type === "mutualtls") return "Mutual TLS";
2258
+ return schemeName;
2259
+ }
2260
+
2261
+ function makeInput(placeholder, value, onInput, type) {
2262
+ const inp = document.createElement("input");
2263
+ inp.type = type || "text";
2264
+ inp.value = value || "";
2265
+ inp.placeholder = placeholder;
2266
+ inp.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
2267
+ inp.addEventListener("input", onInput);
2268
+ return inp;
2269
+ }
2270
+
2271
+ function makeLabel(text, small) {
2272
+ const el = document.createElement("p");
2273
+ el.className = small
2274
+ ? "text-[10px] font-medium uppercase tracking-wider opacity-55"
2275
+ : "text-[10px] font-semibold uppercase tracking-wider opacity-55";
2276
+ el.textContent = text;
2277
+ return el;
2278
+ }
2279
+
2280
+ function makeField(label, input) {
2281
+ const wrapper = document.createElement("div");
2282
+ wrapper.className = "space-y-1";
2283
+ wrapper.appendChild(makeLabel(label, true));
2284
+ wrapper.appendChild(input);
2285
+ return wrapper;
2286
+ }
2287
+
2288
+ function makeSchemeCard(title, subtitle) {
2289
+ const card = document.createElement("div");
2290
+ card.className = "space-y-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg/40 dark:bg-dark-bg/40 p-2.5";
2291
+
2292
+ const titleRow = document.createElement("div");
2293
+ titleRow.className = "flex items-center justify-between gap-2";
2294
+
2295
+ const heading = document.createElement("p");
2296
+ heading.className = "text-[11px] font-semibold tracking-wide";
2297
+ heading.textContent = title;
2298
+ titleRow.appendChild(heading);
2299
+
2300
+ if (subtitle) {
2301
+ const note = document.createElement("span");
2302
+ note.className = "text-[10px] opacity-60 font-mono";
2303
+ note.textContent = subtitle;
2304
+ titleRow.appendChild(note);
2305
+ }
2306
+
2307
+ card.appendChild(titleRow);
2308
+ return card;
2309
+ }
2310
+
2311
+ if (!schemeNames.length) {
2312
+ if (!authState["__default__"]) authState["__default__"] = {};
2313
+ const defaultCard = makeSchemeCard("Default Auth", "bearer");
2314
+ defaultCard.appendChild(makeField("Token", makeInput("Enter token…", authState["__default__"].token, function(e) {
2315
+ authState["__default__"].token = e.target.value;
2316
+ saveAuthState();
2317
+ updateRequestPreview();
2318
+ })));
2319
+ fields.appendChild(defaultCard);
2320
+ return;
2321
+ }
2322
+
2323
+ if (op && availableSchemeOptions.length > 0) {
2324
+ const selectorWrap = document.createElement("div");
2325
+ selectorWrap.className = "space-y-1";
2326
+ selectorWrap.appendChild(makeLabel("Auth Type"));
2327
+ const select = document.createElement("select");
2328
+ select.className = "w-full text-xs px-2.5 py-2 rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
2329
+
2330
+ const autoOption = document.createElement("option");
2331
+ autoOption.value = "";
2332
+ autoOption.textContent = "Auto";
2333
+ select.appendChild(autoOption);
2334
+
2335
+ for (const schemeName of availableSchemeOptions) {
2336
+ const option = document.createElement("option");
2337
+ option.value = schemeName;
2338
+ const isOperationScheme = operationSchemeOptions.includes(schemeName);
2339
+ const label = getAuthSchemeDisplayLabel(schemeName);
2340
+ option.textContent = isOperationScheme
2341
+ ? label
2342
+ : (label + " • override");
2343
+ select.appendChild(option);
2344
+ }
2345
+
2346
+ select.value = selectedScheme || "";
2347
+ select.addEventListener("change", function(e) {
2348
+ setSelectedAuthSchemeForOperation(op, e.target.value || "");
2349
+ renderAuthPanel();
2350
+ updateRequestPreview();
2351
+ });
2352
+ selectorWrap.appendChild(select);
2353
+ fields.appendChild(selectorWrap);
2354
+ }
2355
+
2356
+ const schemesToRender = selectedScheme
2357
+ ? [selectedScheme]
2358
+ : (operationSchemeOptions.length ? operationSchemeOptions : schemeNames);
2359
+
2360
+ for (const schemeName of schemesToRender) {
2361
+ const scheme = authSchemes[schemeName];
2362
+ if (!authState[schemeName]) authState[schemeName] = {};
2363
+ const state = authState[schemeName];
2364
+ const type = (scheme.type || "").toLowerCase();
2365
+ const httpScheme = (scheme.scheme || "").toLowerCase();
2366
+ const card = makeSchemeCard(getAuthSchemeDisplayLabel(schemeName), schemeName);
2367
+
2368
+ if (type === "http" && httpScheme === "basic") {
2369
+ card.appendChild(makeField("Username", makeInput("Username", state.username, function(e) {
2370
+ authState[schemeName].username = e.target.value;
2371
+ saveAuthState();
2372
+ updateRequestPreview();
2373
+ })));
2374
+ card.appendChild(makeField("Password", makeInput("Password", state.password, function(e) {
2375
+ authState[schemeName].password = e.target.value;
2376
+ saveAuthState();
2377
+ updateRequestPreview();
2378
+ }, "password")));
2379
+ } else if (type === "apikey") {
2380
+ const paramName = scheme.name || "key";
2381
+ const location = (scheme.in || "header").toLowerCase();
2382
+ card.appendChild(makeField("API Key", makeInput(paramName + " (" + location + ")", state.value, function(e) {
2383
+ authState[schemeName].value = e.target.value;
2384
+ saveAuthState();
2385
+ updateRequestPreview();
2386
+ })));
2387
+ } else if (type === "oauth2") {
2388
+ card.appendChild(makeField("Access Token", makeInput("OAuth2 access token…", state.token, function(e) {
2389
+ authState[schemeName].token = e.target.value;
2390
+ saveAuthState();
2391
+ updateRequestPreview();
2392
+ })));
2393
+ } else if (type === "openidconnect") {
2394
+ card.appendChild(makeField("ID Token / Access Token", makeInput("OpenID Connect token…", state.token, function(e) {
2395
+ authState[schemeName].token = e.target.value;
2396
+ saveAuthState();
2397
+ updateRequestPreview();
2398
+ })));
2399
+ } else if (type === "http" && httpScheme === "digest") {
2400
+ card.appendChild(makeField("Digest Credential", makeInput("Digest token…", state.token, function(e) {
2401
+ authState[schemeName].token = e.target.value;
2402
+ saveAuthState();
2403
+ updateRequestPreview();
2404
+ })));
2405
+ } else if (type === "http" && httpScheme === "bearer") {
2406
+ card.appendChild(makeField("Bearer Token", makeInput("Bearer token…", state.token, function(e) {
2407
+ authState[schemeName].token = e.target.value;
2408
+ saveAuthState();
2409
+ updateRequestPreview();
2410
+ })));
2411
+ } else if (type === "mutualtls") {
2412
+ const hint = document.createElement("p");
2413
+ hint.className = "text-xs opacity-70 leading-relaxed";
2414
+ hint.textContent = "Configured by your client certificate. No token input required.";
2415
+ card.appendChild(hint);
2416
+ } else {
2417
+ card.appendChild(makeField("Token", makeInput("Token…", state.token, function(e) {
2418
+ authState[schemeName].token = e.target.value;
2419
+ saveAuthState();
2420
+ updateRequestPreview();
2421
+ })));
2422
+ }
2423
+ fields.appendChild(card);
2424
+ }
2425
+ }
2426
+
2427
+ let authPanelOpen = true;
2428
+ document.getElementById("auth-toggle").addEventListener("click", function() {
2429
+ authPanelOpen = !authPanelOpen;
2430
+ const fieldsEl = document.getElementById("auth-fields");
2431
+ const chevron = document.getElementById("auth-chevron");
2432
+ if (fieldsEl) fieldsEl.classList.toggle("hidden", !authPanelOpen);
2433
+ if (chevron) chevron.style.transform = authPanelOpen ? "" : "rotate(-90deg)";
2434
+ });
2435
+
2436
+ // Restore selected operation from URL hash, or set hash for the default selection
2437
+ var initMatch = findOpByHash(window.location.hash);
2438
+ if (initMatch) {
2439
+ selected = initMatch;
2440
+ } else if (selected) {
2441
+ history.replaceState(null, "", getOpHash(selected));
2442
+ }
2443
+
2444
+ window.addEventListener("popstate", function() {
2445
+ var match = findOpByHash(window.location.hash);
2446
+ if (match) {
2447
+ selected = match;
2448
+ renderSidebar();
2449
+ renderEndpoint();
2450
+ }
2451
+ });
2452
+
1418
2453
  setMobileSidebarOpen(false);
2454
+ renderAuthPanel();
1419
2455
  renderSidebar();
1420
2456
  renderEndpoint();
1421
2457
  </script>