@djangocfg/ui-tools 2.1.287 → 2.1.290

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 (105) hide show
  1. package/README.md +14 -3
  2. package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
  3. package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
  4. package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
  5. package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
  6. package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
  7. package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
  9. package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
  10. package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
  11. package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
  12. package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
  13. package/dist/chunk-EFWOJPA6.mjs.map +1 -0
  14. package/dist/index.cjs +10 -10
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +34 -0
  17. package/dist/index.d.ts +34 -0
  18. package/dist/index.mjs +5 -5
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +21 -14
  21. package/src/components/markdown/MarkdownMessage.tsx +46 -0
  22. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
  23. package/src/tools/OpenapiViewer/README.md +114 -6
  24. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
  25. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
  26. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
  27. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
  39. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
  40. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
  41. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
  42. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
  43. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
  44. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
  45. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
  46. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
  47. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
  48. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
  49. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
  50. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
  51. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
  52. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
  53. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
  54. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
  55. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
  56. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
  57. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
  58. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
  59. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
  60. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
  61. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
  62. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
  63. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
  64. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
  65. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
  66. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
  67. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
  68. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
  69. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
  70. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
  71. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
  72. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
  73. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
  74. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
  75. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
  76. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
  77. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
  78. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
  79. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
  80. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
  81. package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
  82. package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
  83. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
  84. package/src/tools/OpenapiViewer/types.ts +46 -1
  85. package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
  86. package/src/tools/OpenapiViewer/utils/index.ts +3 -0
  87. package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
  88. package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
  89. package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
  90. package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
  91. package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
  92. package/src/tools/PrettyCode/index.tsx +13 -0
  93. package/src/tools/PrettyCode/lazy.tsx +5 -0
  94. package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
  95. package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
  96. package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
  97. package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
  98. package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
  99. package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
  100. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
  101. package/dist/chunk-IULI4XII.cjs.map +0 -1
  102. package/dist/chunk-VZGQC3NG.mjs.map +0 -1
  103. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
  104. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
  105. package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.287",
3
+ "version": "2.1.290",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -90,9 +90,10 @@
90
90
  "check": "tsc --noEmit"
91
91
  },
92
92
  "peerDependencies": {
93
- "@djangocfg/i18n": "^2.1.287",
94
- "@djangocfg/ui-core": "^2.1.287",
93
+ "@djangocfg/i18n": "^2.1.290",
94
+ "@djangocfg/ui-core": "^2.1.290",
95
95
  "consola": "^3.4.2",
96
+ "lodash-es": "^4.18.1",
96
97
  "lucide-react": "^0.545.0",
97
98
  "react": "^19.1.0",
98
99
  "react-dom": "^19.1.0",
@@ -100,30 +101,34 @@
100
101
  "zustand": "^5.0.0"
101
102
  },
102
103
  "dependencies": {
103
- "@tiptap/core": "^3.20.1",
104
- "@tiptap/react": "^3.20.1",
105
- "@tiptap/starter-kit": "^3.20.1",
106
- "@tiptap/extension-placeholder": "^3.20.1",
107
- "@tiptap/extension-mention": "^3.20.1",
108
- "@tiptap/suggestion": "^3.20.1",
109
- "@tiptap/markdown": "^3.20.1",
110
- "@tiptap/pm": "^3.20.1",
111
104
  "@rjsf/core": "^6.1.2",
112
105
  "@rjsf/utils": "^6.1.2",
113
106
  "@rjsf/validator-ajv8": "^6.1.2",
114
107
  "@rpldy/uploady": "^1.8.5",
108
+ "@tiptap/core": "^3.20.1",
109
+ "@tiptap/extension-mention": "^3.20.1",
110
+ "@tiptap/extension-placeholder": "^3.20.1",
111
+ "@tiptap/markdown": "^3.20.1",
112
+ "@tiptap/pm": "^3.20.1",
113
+ "@tiptap/react": "^3.20.1",
114
+ "@tiptap/starter-kit": "^3.20.1",
115
+ "@tiptap/suggestion": "^3.20.1",
115
116
  "@vidstack/react": "next",
116
117
  "@wavesurfer/react": "^1.0.12",
117
118
  "maplibre-gl": "^4.7.1",
118
119
  "media-icons": "next",
119
120
  "mermaid": "^11.12.0",
120
121
  "monaco-editor": "^0.55.1",
122
+ "openapi-sampler": "^1.7.2",
121
123
  "prism-react-renderer": "^2.4.1",
124
+ "prismjs": "^1.30.0",
122
125
  "react-json-tree": "^0.20.0",
123
126
  "react-lottie-player": "^2.1.0",
124
127
  "react-map-gl": "^8.1.0",
125
128
  "react-markdown": "10.1.0",
126
129
  "react-zoom-pan-pinch": "^3.7.0",
130
+ "rehype-raw": "^7.0.0",
131
+ "rehype-sanitize": "^6.0.0",
127
132
  "remark-gfm": "4.0.1",
128
133
  "vidstack": "next",
129
134
  "wavesurfer.js": "^7.12.1"
@@ -133,14 +138,16 @@
133
138
  "@maplibre/maplibre-gl-geocoder": "^1.7.0"
134
139
  },
135
140
  "devDependencies": {
136
- "@djangocfg/i18n": "^2.1.287",
141
+ "@djangocfg/i18n": "^2.1.290",
137
142
  "@djangocfg/playground": "workspace:*",
138
- "@djangocfg/typescript-config": "^2.1.287",
139
- "@djangocfg/ui-core": "^2.1.287",
143
+ "@djangocfg/typescript-config": "^2.1.290",
144
+ "@djangocfg/ui-core": "^2.1.290",
145
+ "@types/lodash-es": "^4.17.12",
140
146
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
141
147
  "@types/node": "^24.7.2",
142
148
  "@types/react": "^19.1.0",
143
149
  "@types/react-dom": "^19.1.0",
150
+ "lodash-es": "^4.18.1",
144
151
  "lucide-react": "^0.545.0",
145
152
  "react": "^19.1.0",
146
153
  "react-dom": "^19.1.0",
@@ -2,8 +2,37 @@
2
2
 
3
3
  import React from 'react';
4
4
  import ReactMarkdown from 'react-markdown';
5
+ import rehypeRaw from 'rehype-raw';
6
+ import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
5
7
  import remarkGfm from 'remark-gfm';
6
8
 
9
+ // Allow-list HTML that OpenAPI descriptions commonly use — we want
10
+ // ``<b>``/``<code>``/``<br>`` to render, not to appear as literal text.
11
+ // Built on top of ``rehype-sanitize``'s default schema so we keep the
12
+ // XSS protection (no ``<script>``, no ``on*`` handlers, no ``javascript:``
13
+ // URLs); we just extend the tag allow-list with safe presentational
14
+ // elements that are in wide use in API docs.
15
+ const HTML_SCHEMA = {
16
+ ...defaultSchema,
17
+ tagNames: [
18
+ ...(defaultSchema.tagNames ?? []),
19
+ 'br',
20
+ 'b',
21
+ 'i',
22
+ 'u',
23
+ 's',
24
+ 'sub',
25
+ 'sup',
26
+ 'small',
27
+ 'mark',
28
+ 'kbd',
29
+ 'code',
30
+ 'pre',
31
+ 'details',
32
+ 'summary',
33
+ ],
34
+ };
35
+
7
36
  import { CopyButton } from '@djangocfg/ui-core/components';
8
37
  import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
9
38
 
@@ -331,6 +360,16 @@ const hasMarkdownSyntax = (text: string): boolean => {
331
360
  return true;
332
361
  }
333
362
 
363
+ // Inline HTML tags (``<br>``, ``<b>``, ``<code>``, …) — used widely
364
+ // in OpenAPI descriptions. Without this branch the plain-text fast
365
+ // path would escape them and the user sees literal angle brackets.
366
+ // The regex matches opening, closing, and self-closing tags without
367
+ // being too clever about malformed HTML; it's an affordance test,
368
+ // not a validator.
369
+ if (/<\/?[a-zA-Z][a-zA-Z0-9-]*(\s[^>]*)?\/?>/.test(text)) {
370
+ return true;
371
+ }
372
+
334
373
  // Common markdown patterns
335
374
  const markdownPatterns = [
336
375
  /^#{1,6}\s/m, // Headers
@@ -549,6 +588,13 @@ export const MarkdownMessage: React.FC<MarkdownMessageProps> = ({
549
588
  >
550
589
  <ReactMarkdown
551
590
  remarkPlugins={[remarkGfm]}
591
+ // ``rehype-raw`` parses inline HTML in the source (OpenAPI
592
+ // ``description`` fields often contain ``<br>`` / ``<b>`` /
593
+ // ``<code>``). ``rehype-sanitize`` with our extended schema
594
+ // runs after it so the allowed tags pass through while
595
+ // anything risky (scripts, event handlers, javascript: urls)
596
+ // is stripped.
597
+ rehypePlugins={[rehypeRaw, [rehypeSanitize, HTML_SCHEMA]]}
552
598
  components={components}
553
599
  >
554
600
  {displayContent}
@@ -1,4 +1,4 @@
1
- import { defineStory, useSelect, useValue } from '@djangocfg/playground';
1
+ import { defineStory, useSelect, useValue, useBoolean } from '@djangocfg/playground';
2
2
  import { Playground } from './index';
3
3
  import { LazyOpenapiViewer } from './lazy';
4
4
  import type { PlaygroundConfig } from './types';
@@ -11,13 +11,18 @@ export default defineStory({
11
11
  });
12
12
 
13
13
  // ============================================================================
14
- // Schema configs
14
+ // Schema fixtures
15
15
  // ============================================================================
16
16
 
17
+ // Petstore's own ``servers[0].url`` is just ``/api/v3`` (relative),
18
+ // which makes code samples look like they target ``localhost`` when
19
+ // viewed from the playground. Override with the canonical public host
20
+ // so the generated curl/fetch/etc snippets are runnable as-is.
17
21
  const PETSTORE_SCHEMA = {
18
22
  id: 'petstore',
19
23
  name: 'Petstore API',
20
24
  url: 'https://petstore3.swagger.io/api/v3/openapi.json',
25
+ baseUrl: 'https://petstore3.swagger.io/api/v3',
21
26
  } as const;
22
27
 
23
28
  const HTTPBIN_SCHEMA = {
@@ -28,15 +33,18 @@ const HTTPBIN_SCHEMA = {
28
33
 
29
34
  const MULTI_SCHEMAS = [PETSTORE_SCHEMA, HTTPBIN_SCHEMA] as const;
30
35
 
31
- // Simulates a real multi-group setup (like cmdop with 5+ API groups)
36
+ // Simulates a real multi-group setup (like cmdop with 5+ API groups).
37
+ // All clones share the canonical Petstore host so code samples
38
+ // render with a real runnable URL.
39
+ const PETSTORE_BASE = 'https://petstore3.swagger.io/api/v3';
32
40
  const MANY_SCHEMAS = [
33
41
  PETSTORE_SCHEMA,
34
42
  HTTPBIN_SCHEMA,
35
- { id: 'machines', name: 'Machines API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
36
- { id: 'terminal', name: 'Terminal API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
37
- { id: 'skills', name: 'Skills API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
38
- { id: 'system', name: 'System API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
39
- { id: 'site', name: 'Site API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
43
+ { id: 'machines', name: 'Machines API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
44
+ { id: 'terminal', name: 'Terminal API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
45
+ { id: 'skills', name: 'Skills API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
46
+ { id: 'system', name: 'System API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
47
+ { id: 'site', name: 'Site API', url: 'https://petstore3.swagger.io/api/v3/openapi.json', baseUrl: PETSTORE_BASE },
40
48
  ] as const;
41
49
 
42
50
  // ============================================================================
@@ -44,60 +52,34 @@ const MANY_SCHEMAS = [
44
52
  // ============================================================================
45
53
 
46
54
  /**
47
- * Interactivedefault schema is adjustable.
55
+ * Defaultsingle-schema mode with the most common configuration knobs
56
+ * exposed as toggles. Replaces the earlier Interactive / SingleSchema /
57
+ * WithBaseUrl / NoDefault / CustomUrl stories, which were all "this
58
+ * component with different props" variations.
48
59
  */
49
- export const Interactive = () => {
50
- const [defaultSchema] = useSelect('defaultSchema', {
51
- options: ['petstore', 'httpbin'] as const,
52
- defaultValue: 'petstore',
53
- label: 'Default Schema',
54
- description: 'Which schema to load first',
60
+ export const Default = () => {
61
+ const [schemaUrl] = useValue('schemaUrl', {
62
+ defaultValue: PETSTORE_SCHEMA.url,
63
+ label: 'Schema URL',
64
+ description: 'Any OpenAPI 3.x JSON endpoint',
55
65
  });
56
66
 
57
- const config: PlaygroundConfig = {
58
- schemas: [...MULTI_SCHEMAS],
59
- defaultSchemaId: defaultSchema,
60
- };
61
-
62
- return (
63
- <div className="min-h-[600px]">
64
- <Playground config={config} />
65
- </div>
66
- );
67
- };
68
-
69
- /**
70
- * With baseUrl override — forces a specific request host, ignoring
71
- * `schema.servers[0].url`. Useful when docs are hosted on a different
72
- * origin than the API, or when the schema has no servers entry.
73
- */
74
- export const WithBaseUrl = () => {
75
67
  const [baseUrl] = useValue('baseUrl', {
76
- defaultValue: 'https://petstore3.swagger.io/api/v3',
77
- label: 'Base URL',
78
- description: 'Overrides schema.servers[0].url for every request',
68
+ defaultValue: '',
69
+ label: 'Base URL override',
70
+ description: 'Empty = use servers[0].url from the schema',
79
71
  });
80
72
 
81
- const config: PlaygroundConfig = {
82
- schemas: [PETSTORE_SCHEMA],
83
- defaultSchemaId: 'petstore',
84
- baseUrl,
85
- };
86
-
87
- return (
88
- <div className="min-h-[600px]">
89
- <Playground config={config} />
90
- </div>
91
- );
92
- };
73
+ const [autoSelect] = useBoolean('autoSelectDefault', {
74
+ defaultValue: true,
75
+ label: 'Auto-select default schema',
76
+ description: 'When off, tests the "no defaultSchemaId" code path',
77
+ });
93
78
 
94
- /**
95
- * Single schema — no Combobox switcher shown (schemas.length === 1).
96
- */
97
- export const SingleSchema = () => {
98
79
  const config: PlaygroundConfig = {
99
- schemas: [PETSTORE_SCHEMA],
100
- defaultSchemaId: 'petstore',
80
+ schemas: [{ id: 'custom', name: 'Schema', url: schemaUrl }],
81
+ defaultSchemaId: autoSelect ? 'custom' : undefined,
82
+ baseUrl: baseUrl || undefined,
101
83
  };
102
84
 
103
85
  return (
@@ -108,29 +90,20 @@ export const SingleSchema = () => {
108
90
  };
109
91
 
110
92
  /**
111
- * Multiple schemas with Combobox switcher.
112
- * Switch between Petstore and HTTPBin to see different endpoint sets.
93
+ * MultiSchema — multiple schemas with the Combobox switcher. Count
94
+ * is adjustable to exercise both the short-list layout and the
95
+ * long-list behaviour (cmdop-style setups with 7+ APIs).
113
96
  */
114
- export const MultipleSchemas = () => {
115
- const config: PlaygroundConfig = {
116
- schemas: [...MULTI_SCHEMAS],
117
- defaultSchemaId: 'petstore',
118
- };
119
-
120
- return (
121
- <div className="min-h-[600px]">
122
- <Playground config={config} />
123
- </div>
124
- );
125
- };
97
+ export const MultiSchema = () => {
98
+ const [count] = useSelect('count', {
99
+ options: ['2', '7'] as const,
100
+ defaultValue: '2',
101
+ label: 'Schema count',
102
+ description: '2 uses Petstore+HTTPBin; 7 stress-tests the Combobox',
103
+ });
126
104
 
127
- /**
128
- * Many schemas (7+) — tests Combobox with a long list.
129
- * Simulates a real setup like cmdop with machines, terminal, skills, etc.
130
- */
131
- export const ManySchemas = () => {
132
105
  const config: PlaygroundConfig = {
133
- schemas: [...MANY_SCHEMAS],
106
+ schemas: count === '2' ? [...MULTI_SCHEMAS] : [...MANY_SCHEMAS],
134
107
  defaultSchemaId: 'petstore',
135
108
  };
136
109
 
@@ -142,58 +115,23 @@ export const ManySchemas = () => {
142
115
  };
143
116
 
144
117
  /**
145
- * No defaultSchemaId should auto-select the first schema.
118
+ * SectionsGroupingalternate layout: every schema's endpoints get
119
+ * rendered on the same page as top-level sections, with a sidebar
120
+ * that stacks them. URL hash sync writes ``#<schemaId>/<anchor>``
121
+ * as the user scrolls so deep-linking keeps working.
146
122
  */
147
- export const NoDefault = () => {
148
- const config: PlaygroundConfig = {
149
- schemas: [...MULTI_SCHEMAS],
150
- };
151
-
152
- return (
153
- <div className="min-h-[600px]">
154
- <Playground config={config} />
155
- </div>
156
- );
157
- };
123
+ export const SectionsGrouping = () => {
124
+ const [urlSync] = useBoolean('urlSync', {
125
+ defaultValue: true,
126
+ label: 'URL hash sync',
127
+ description: 'Writes #<schemaId>/<anchor> as you scroll',
128
+ });
158
129
 
159
- /**
160
- * Error state — invalid schema URL that will fail to fetch.
161
- * Tests error handling and UI feedback.
162
- */
163
- export const ErrorState = () => {
164
130
  const config: PlaygroundConfig = {
165
- schemas: [
166
- {
167
- id: 'broken',
168
- name: 'Broken API',
169
- url: 'https://httpbin.org/status/404',
170
- },
171
- ],
172
- defaultSchemaId: 'broken',
173
- };
174
-
175
- return (
176
- <div className="min-h-[600px]">
177
- <Playground config={config} />
178
- </div>
179
- );
180
- };
181
-
182
- /**
183
- * Mixed valid + broken — one schema works, one fails.
184
- * Tests switching between working and broken schemas.
185
- */
186
- export const MixedValidBroken = () => {
187
- const config: PlaygroundConfig = {
188
- schemas: [
189
- PETSTORE_SCHEMA,
190
- {
191
- id: 'broken',
192
- name: 'Broken API (404)',
193
- url: 'https://httpbin.org/status/404',
194
- },
195
- ],
131
+ schemas: [...MULTI_SCHEMAS],
196
132
  defaultSchemaId: 'petstore',
133
+ schemaGrouping: 'sections',
134
+ urlSync,
197
135
  };
198
136
 
199
137
  return (
@@ -204,12 +142,37 @@ export const MixedValidBroken = () => {
204
142
  };
205
143
 
206
144
  /**
207
- * Empty schemas array edge case, no schemas provided.
145
+ * Errors consolidated failure paths. Pick a scenario via the toggle:
146
+ * - ``broken``: single schema that 404s → full-panel error state.
147
+ * - ``mixed``: valid + broken side-by-side → tests switching between.
148
+ * - ``empty``: no schemas configured at all → empty-state UI.
208
149
  */
209
- export const EmptySchemas = () => {
210
- const config: PlaygroundConfig = {
211
- schemas: [],
212
- };
150
+ export const Errors = () => {
151
+ const [scenario] = useSelect('scenario', {
152
+ options: ['broken', 'mixed', 'empty'] as const,
153
+ defaultValue: 'broken',
154
+ label: 'Failure scenario',
155
+ });
156
+
157
+ const config: PlaygroundConfig = (() => {
158
+ switch (scenario) {
159
+ case 'broken':
160
+ return {
161
+ schemas: [{ id: 'broken', name: 'Broken API', url: 'https://httpbin.org/status/404' }],
162
+ defaultSchemaId: 'broken',
163
+ };
164
+ case 'mixed':
165
+ return {
166
+ schemas: [
167
+ PETSTORE_SCHEMA,
168
+ { id: 'broken', name: 'Broken API (404)', url: 'https://httpbin.org/status/404' },
169
+ ],
170
+ defaultSchemaId: 'petstore',
171
+ };
172
+ case 'empty':
173
+ return { schemas: [] };
174
+ }
175
+ })();
213
176
 
214
177
  return (
215
178
  <div className="min-h-[600px]">
@@ -219,8 +182,9 @@ export const EmptySchemas = () => {
219
182
  };
220
183
 
221
184
  /**
222
- * LazyOpenapiViewerthe production lazy-loaded variant.
223
- * Shows loading spinner while ~400KB of OpenAPI components load.
185
+ * Lazy — production lazy-loaded variant. Shows the loading spinner
186
+ * while the ~400 KB OpenAPI viewer chunk is fetched. Only path you
187
+ * should use in an actual app bundle.
224
188
  */
225
189
  export const Lazy = () => {
226
190
  const config: PlaygroundConfig = {
@@ -234,31 +198,3 @@ export const Lazy = () => {
234
198
  </div>
235
199
  );
236
200
  };
237
-
238
- /**
239
- * Custom schema URL — paste any OpenAPI 3.x schema URL to test.
240
- */
241
- export const CustomUrl = () => {
242
- const [url] = useValue('schemaUrl', {
243
- defaultValue: 'https://petstore3.swagger.io/api/v3/openapi.json',
244
- label: 'Schema URL',
245
- description: 'Full URL to an OpenAPI 3.x JSON schema',
246
- });
247
-
248
- const config: PlaygroundConfig = {
249
- schemas: [
250
- {
251
- id: 'custom',
252
- name: 'Custom Schema',
253
- url,
254
- },
255
- ],
256
- defaultSchemaId: 'custom',
257
- };
258
-
259
- return (
260
- <div className="min-h-[600px]">
261
- <Playground config={config} />
262
- </div>
263
- );
264
- };
@@ -101,6 +101,9 @@ OpenapiViewer/
101
101
  ├── utils/
102
102
  │ ├── url.ts # UrlBuilder + path/query/baseUrl helpers
103
103
  │ ├── schemaExport.ts # Dereferenced/compact/markdown export for LLMs
104
+ │ ├── sampler.ts # Schema → example value (openapi-sampler wrap)
105
+ │ ├── operationToHar.ts # ApiEndpoint → HAR request shape
106
+ │ ├── codeSamples.ts # HAR → curl / fetch / python / … renderers
104
107
  │ ├── formatters.ts # HTTP status / method helpers, JSON validation
105
108
  │ ├── versionManager.ts
106
109
  │ ├── apiKeyManager.ts
@@ -110,28 +113,126 @@ OpenapiViewer/
110
113
  ├── index.ts # Re-exports DocsLayout
111
114
  ├── DocsLayout/ # Default (only) layout
112
115
  │ ├── index.tsx # Root: sidebar + docs + slide-in playground
113
- │ ├── Sidebar.tsx # Grouped endpoint list, scrollspy highlight
114
116
  │ ├── DocsView.tsx # Longread scroll container + scrollspy
115
- │ ├── EndpointDoc.tsx # One endpoint as a prose section + fields table
116
117
  │ ├── ApiIntroSection.tsx # Top intro + Copy-for-AI dropdown
117
118
  │ ├── SchemaCopyMenu.tsx # Per-schema copy flavours
118
119
  │ ├── SlideInPlayground.tsx# Right slide-in overlay
119
120
  │ ├── TryItSheet.tsx # Mobile/tablet fallback via ResponsiveSheet
120
121
  │ ├── anchor.ts # Stable section anchors for deep-linking
121
122
  │ ├── grouping.ts # Shared group+sort used by sidebar and docs
122
- │ ├── schemaFields.ts # JSON Schema → flat field rows
123
- └── sidebarLabel.ts # Common-prefix strip + tooltip helpers
123
+ │ ├── schemaFields.ts # JSON Schema → flat field rows (legacy helper)
124
+ ├── sidebarLabel.ts # Common-prefix strip + tooltip helpers
125
+ │ │
126
+ │ ├── Sidebar/ # Grouped endpoint list + toolbar
127
+ │ │ ├── index.tsx # Orchestrator
128
+ │ │ ├── types.ts # VM types + METHOD_FILTERS
129
+ │ │ ├── buildVM.ts # Pure view-model builders (flat + sections)
130
+ │ │ ├── useDebouncedValue.ts
131
+ │ │ ├── BrandHeader.tsx # Title + version + Copy-for-AI
132
+ │ │ ├── Toolbar.tsx # Schema combobox + search + method chips
133
+ │ │ ├── SearchInput.tsx
134
+ │ │ ├── MethodChips.tsx
135
+ │ │ ├── SidebarBody.tsx # flat vs sections switch
136
+ │ │ ├── SchemaSection.tsx
137
+ │ │ ├── CategoryBlock.tsx
138
+ │ │ └── EndpointRow.tsx
139
+ │ │
140
+ │ └── EndpointDoc/ # One endpoint as a prose card
141
+ │ ├── index.tsx # Orchestrator: header + sections
142
+ │ ├── types.ts # SectionId union
143
+ │ ├── context.tsx # Identity context (endpointId + method)
144
+ │ │
145
+ │ ├── store/ # Zustand — open sections + active code tab
146
+ │ │ ├── index.ts # create() + sessionStorage persist
147
+ │ │ └── selectors.ts
148
+ │ │
149
+ │ ├── hooks/
150
+ │ │ └── useSectionHash.ts # #section=<id>.<sec> deep-link router
151
+ │ │
152
+ │ ├── Header/ # Meta row (badge + actions + Try it) + path
153
+ │ │ ├── index.tsx
154
+ │ │ ├── MetaActions.tsx # Copy link / markdown / expand all
155
+ │ │ ├── MethodBadge.tsx
156
+ │ │ └── PathDisplay.tsx
157
+ │ │
158
+ │ ├── Section/ # Collapsible section wrapper
159
+ │ │ ├── index.tsx
160
+ │ │ ├── SectionHeader.tsx # Chevron + title + badge + anchor
161
+ │ │ └── defaults.ts # Per-method open-by-default rules
162
+ │ │
163
+ │ ├── Parameters/ # Path + Query parameters block
164
+ │ │ ├── index.tsx
165
+ │ │ ├── ParamGroup.tsx
166
+ │ │ └── ParamRow.tsx
167
+ │ │
168
+ │ ├── RequestBody/ # Body preview
169
+ │ │ └── index.tsx
170
+ │ │
171
+ │ ├── SchemaFields/ # Tree-view of JSON Schema properties
172
+ │ │ ├── index.tsx
173
+ │ │ ├── FieldRow.tsx
174
+ │ │ ├── buildTree.ts
175
+ │ │ └── types.ts
176
+ │ │
177
+ │ ├── Responses/ # Status-code rows + example body
178
+ │ │ ├── index.tsx
179
+ │ │ ├── ResponseRow.tsx
180
+ │ │ ├── ResponseBody.tsx
181
+ │ │ └── StatusTag.tsx
182
+ │ │
183
+ │ └── CodeSamples/ # Language tabs + generated snippet
184
+ │ ├── index.tsx
185
+ │ ├── LanguageTabs.tsx
186
+ │ └── useCodeSnippet.ts
124
187
 
125
188
  └── shared/ # Panels reused by SlideInPlayground + TryItSheet
126
189
  ├── RequestPanel.tsx
127
- ├── ResponsePanel.tsx
128
190
  ├── SendButton.tsx
129
191
  ├── BodyFormEditor.tsx # Recursive JSON-Schema form renderer
130
192
  ├── EndpointDraftSync.tsx# Headless draft ↔ context sync
131
193
  ├── EndpointResetButton.tsx
132
- └── ui.tsx # MethodBadge, StatusBadge, Panel, atoms
194
+ ├── ui.tsx # MethodBadge, StatusBadge, Panel, atoms
195
+
196
+ └── ResponsePanel/ # Response viewer with Pretty / Raw / Preview tabs
197
+ ├── index.tsx # Orchestrator + mode state + guards
198
+ ├── types.ts # ViewMode + ContentKind
199
+ ├── detectContent.ts # Content-Type → Prism language inference
200
+ ├── useResponseView.ts
201
+ ├── StatusBar.tsx # Status / size / duration / Copy
202
+ ├── ViewTabs.tsx
203
+ ├── PrettyView.tsx # JsonTree or syntax-highlighted code
204
+ ├── RawView.tsx # Plain <pre>
205
+ └── PreviewView.tsx # Sandboxed iframe for HTML responses
133
206
  ```
134
207
 
208
+ ## Endpoint doc — sections & deep-linking
209
+
210
+ Each endpoint card splits into four collapsible sections: **Parameters**,
211
+ **Request body**, **Code samples**, **Responses**. Defaults are
212
+ method-aware — GET opens Parameters + Responses, POST/PUT/PATCH opens
213
+ Request body + Responses, etc. User overrides are persisted per
214
+ endpoint in `sessionStorage` (zustand `persist` middleware) so a tab you
215
+ opened once stays open as you scroll.
216
+
217
+ A hover-revealed anchor button on each section header copies a shareable
218
+ URL like `…#section=ep-get-api-v3-pet-findbystatus.responses` —
219
+ following that link lands on the endpoint, expands the referenced
220
+ section, and scrolls it into view.
221
+
222
+ ## Response panel — Pretty / Raw / Preview
223
+
224
+ The response panel picks a default view based on `Content-Type`:
225
+
226
+ - **JSON** → `Pretty` (interactive JsonTree)
227
+ - **HTML** → `Preview` (sandboxed iframe, scripts disabled) with `Pretty`
228
+ as the syntax-highlighted source fallback
229
+ - **XML / CSS / JS / text** → `Pretty` using Prism
230
+ - Everything has a **Raw** tab for a literal `<pre>` dump
231
+
232
+ The HTML preview detects single-page-app shells (empty `<body>` + mount
233
+ div + `<script>`) and shows an explanatory empty-state instead of a
234
+ blank iframe, since scripts can't run in the sandbox.
235
+
135
236
  ## Config Reference
136
237
 
137
238
  ```ts
@@ -145,6 +246,13 @@ interface PlaygroundConfig {
145
246
  /** Optional API keys for the X-API-Key picker. */
146
247
  apiKeys?: ApiKey[];
147
248
  apiKeysLoading?: boolean;
249
+ /** Layout mode. ``'selector'`` (default) shows one schema at a time,
250
+ * picked via a Combobox. ``'sections'`` flattens every schema into
251
+ * the longread as top-level sections. */
252
+ schemaGrouping?: 'selector' | 'sections';
253
+ /** Sync the active endpoint anchor to ``window.location.hash`` as
254
+ * the user scrolls (sections mode). Default: off. */
255
+ urlSync?: boolean;
148
256
  }
149
257
 
150
258
  interface SchemaSource {