{"owner":"swagger-api","repo":"swagger-editor","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":5897,"categories":["claude-rule","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","docs/architecture.md"],"skills":{"CLAUDE.md":"# CLAUDE.md - SwaggerEditor Codebase Guide for AI Assistants\n\n**Version:** 5.0.6 | **Last Updated:** 2026-05-16\n\n---\n\n## Project Overview\n\nSwaggerEditor is a browser-based editor for API specifications supporting **OpenAPI 2.0/3.0/3.1/3.2**, **AsyncAPI 2.x/3.0**, **API Design Systems**, and **JSON Schema**. Built as a React app on SwaggerUI's plugin architecture with a split-pane Monaco Editor interface.\n\n**Core Technologies:** React 17+/18, SwaggerUI React, TypeScript (gradual), Immutable.js, Monaco Editor, ApiDOM, Vite 8, Vitest + Playwright\n\n**Philosophy:** Plugin-based architecture, minimal over-engineering, heavy E2E testing, gradual TypeScript adoption, app/ESM/UMD build artifacts.\n\n---\n\n## Codebase Structure\n\n```\nswagger-editor/\n├── vite/                 # Vite plugins, configs, and build scripts\n├── docs/                 # architecture.md, customization guides, migration guides\n├── public/               # Static assets, HTML template\n├── src/\n│   ├── App.tsx           # Main app component & plugin composition\n│   ├── index.tsx         # Browser entry point\n│   ├── plugins/          # 26 plugins (see Architecture section)\n│   ├── presets/\n│   │   ├── monaco/       # Full-featured preset (default)\n│   │   └── textarea/     # Lightweight fallback\n│   ├── styles/           # Global SCSS (index.scss)\n│   └── types/            # TypeScript declarations (*.d.ts)\n├── test/\n│   ├── playwright/\n│   │   ├── e2e/          # Test specs (*.spec.ts)\n│   │   ├── fixtures/     # Test data\n│   │   ├── helpers/      # Helper functions\n│   │   └── tsconfig.json\n│   └── setupTests.js     # Vitest setup (jest-dom/vitest, canvas-mock)\n├── build/                # Standalone app (generated)\n└── dist/esm|umd|types/   # Library bundles (generated)\n```\n\n---\n\n## Architecture & Design Patterns\n\n### Plugin Categories (26 total)\n\n**Editor implementations:**\n- `editor-textarea` — HTML `<textarea>` fallback\n- `editor-monaco` — Monaco Editor (advanced)\n- `editor-monaco-language-apidom` — ApiDOM language support\n- `editor-monaco-yaml-paste` — YAML paste transformations\n\n**Preview plugins:**\n- `editor-preview` — Base preview component\n- `editor-preview-swagger-ui` — OpenAPI rendering\n- `editor-preview-asyncapi` — AsyncAPI rendering\n- `editor-preview-api-design-systems` — ADS rendering\n\n**Editor support plugins:**\n- `editor-content-type` — Auto-detect content type (OpenAPI/AsyncAPI/JSON Schema)\n- `editor-content-persistence` — LocalStorage persistence\n- `editor-content-read-only` — Read-only mode\n- `editor-content-origin` — Track content source (URL, file, user)\n- `editor-content-fixtures` — Load example/fixture files\n- `editor-content-from-file` — File import\n\n**Generic feature plugins:**\n- `layout`, `top-bar`, `modals`, `dialogs`, `dropdown-menu`, `dropzone`, `splash-screen`, `editor-safe-render`, `swagger-ui-adapter`, `util`, `versions`, `props-change-watcher`\n\n### Component Hierarchy\n\n```\nApp.tsx (SwaggerUI wrapper)\n└── SwaggerEditorLayout\n    ├── SplashScreen\n    ├── TopBar (File/Edit/Generate menus)\n    └── Container\n        └── Dropzone\n            └── SplitPane (resizable)\n                ├── EditorPane\n                │   ├── EditorPaneBarTop\n                │   ├── MonacoEditor / TextareaEditor\n                │   └── ValidationPane (errors/warnings)\n                └── EditorPreviewPane\n                    └── EditorPreviewSwaggerUI / AsyncAPI / ApiDesignSystems\n```\n\n### Design Patterns\n\n- **Container/Presenter:** Containers connect to Redux (e.g., `MonacoEditorContainer.jsx`), presenters handle rendering (e.g., `MonacoEditor.jsx`)\n- **HOC via `wrapComponents`:** Plugins wrap existing components to enhance without forking\n- **`getComponent`:** Dynamically resolve components — `const C = getComponent('MonacoEditor')`\n- **FSM for async:** `idle → loading → success/failure` with request ID tracking to prevent race conditions\n\n---\n\n## Plugin System\n\n### Plugin Structure\n\n```javascript\n// src/plugins/plugin-name/index.js\nconst PluginName = ({ getSystem }) => ({\n  afterLoad: function,                   // Runs after plugin loads\n  components: {\n    ComponentName: Component,            // Register new components\n  },\n  wrapComponents: {\n    ComponentName: WrapperFn,            // Wrap/enhance existing components\n  },\n  rootInjects: {\n    utilityName: function,               // Inject utilities into system\n  },\n  statePlugins: {\n    pluginStateKey: {\n      actions: {},                       // Action creators\n      reducers: {},                      // Immutable.js reducers\n      selectors: {},                     // Reselect selectors\n      wrapActions: {},                   // Action middleware\n    },\n  },\n  fn: { utilityFunction: function },\n});\nexport default PluginName;\n```\n\n### Typical Plugin File Structure\n\n```\nplugin-name/\n├── index.js\n├── actions/index.js\n├── reducers.js\n├── selectors.js\n├── components/ComponentName.jsx\n├── components/ComponentName.scss\n├── extensions/other-plugin/wrap-components/ComponentWrapper.jsx\n├── after-load.js\n└── fn.js\n```\n\n### Component Wrapping Pattern\n\n```javascript\n// extensions/editor-preview/wrap-components/EditorPreviewWrapper.jsx\nconst EditorPreviewWrapper = (Original, system) => {\n  const EnhancedComponent = (props) => {\n    const isOpenAPI = system.editorSelectors.selectIsContentTypeOpenAPI();\n    if (isOpenAPI) return <EditorPreviewSwaggerUI />;\n    return <Original {...props} />;\n  };\n  return EnhancedComponent; // must return new component, not Original directly\n};\nexport default EditorPreviewWrapper;\n```\n\n### System Access in Plugins\n\n```javascript\nconst MyPlugin = (system) => {\n  const { getComponent, editorActions, editorSelectors, fn } = system;\n  const content = editorSelectors.selectEditorContent();\n  editorActions.setEditorContent('new content');\n  const MonacoEditor = getComponent('MonacoEditor');\n};\n```\n\n---\n\n## Development Workflows\n\n### Prerequisites\n- **Node.js** `>=24.22`, **npm** `>=11.19.1`, **Python 3.x** (node-gyp), **GLIBC** `>=2.29`\n- Optional: Docker or emscripten (for WASM builds)\n\n### npm Scripts\n\n| Script | Description |\n|--------|-------------|\n| `npm start` | Dev server on port 3000 (hot reload) |\n| `npm test` | Vitest unit tests (watch mode) |\n| `npm run test:run` | Vitest unit tests (single run, no watch) |\n| `npm run test:coverage` | Vitest unit tests with coverage report |\n| `npm run lint` | ESLint on all files |\n| `npm run lint:fix` | Auto-fix ESLint errors |\n| `npm run build` | Build all artifacts (app + bundles + types) |\n| `npm run build:app` | Standalone app → `/build` |\n| `npm run build:app:serve` | Serve built app on port 3050 |\n| `npm run build:bundle:esm` | ESM bundle → `/dist/esm` |\n| `npm run build:bundle:umd` | UMD bundle → `/dist/umd` |\n| `npm run build:definitions` | TypeScript definitions → `/dist/types` |\n| `npm run pw:test` | E2E tests (headless) |\n| `npm run pw:test:headed` | E2E with browser visible |\n| `npm run pw:test:ui` | Interactive Playwright UI mode |\n| `npm run pw:test:debug` | Playwright debug mode |\n| `npm run pw:report` | View test report |\n| `npm run clean` | Remove `/build` and `/dist` |\n\n### Environment Variables (`.env`, baked into build)\n\n| Variable | Description |\n|----------|-------------|\n| `VITE_VERSION` | App version displayed in the splash screen (defaults to `$npm_package_version`) |\n\n### Web Workers\n\nTwo workers handle background processing: `apidom.worker.js` (parsing/validation) and `editor.worker.js` (Monaco ops). Configure Monaco env **before** rendering:\n\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\n```\n\nWorkers must be accessible at runtime — either build them separately via webpack entry points, or copy pre-built files from `node_modules/swagger-editor/dist/umd/` using CopyWebpackPlugin.\n\n### OOM Fix for Large Builds\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\nnpm run build\n```\n\n---\n\n## Testing Strategy\n\n### Unit Testing (Vitest)\n- **Location:** `src/**/*.{spec,test}.{js,jsx,ts,tsx}`, run with `npm test` (watch) or `npm run test:run` (CI)\n- Config: `vitest.config.ts` — jsdom environment, globals enabled, `@codingame/monaco-vscode-api` inlined\n- Use `vi.fn()` for mocks; `@testing-library/jest-dom/vitest` for DOM matchers\n- ⚠️ Only 1 unit test exists: `ValidationPane.test.jsx` — heavy reliance on E2E\n\n### E2E Testing (Playwright)\n- **Location:** `test/playwright/e2e/*.spec.ts`, base URL `http://localhost:3000`\n- All tests written in TypeScript with full `@playwright/test` type safety\n\n**Existing test files:** `app.spec.ts`, `plugin.top-bar.spec.ts`, `plugin.editor-monaco.spec.ts`, `plugin.editor-monaco-yaml-paste.spec.ts`, `plugin.dropzone.spec.ts`, `plugin.validation-pane.spec.ts`, `plugin.editor-content-from-file.spec.ts`, `plugin.editor-persistence.spec.ts`, `plugin.editor-preview-*.spec.ts`, and more.\n\n**Helper functions** (`test/playwright/helpers/`):\n- Setup: `visitBlankPage()`, `waitForSplashScreen()`, `prepareAsyncAPI()`\n- Editor: `typeInEditor()`, `getAllEditorText()`, `selectAllEditorText()`\n- Menu: `clickMenu()`, `loadExample()`, `generateServer()`\n\n**E2E test template:**\n\n```typescript\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('Feature Name', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n\n  test('should do something', async ({ page }) => {\n    await page.locator('[data-testid=\"some-element\"]').click();\n    await expect(page.locator('text=Expected Text')).toBeVisible();\n  });\n});\n```\n\n---\n\n## Code Style & Conventions\n\n### ESLint (Airbnb + Prettier + jsx-a11y + @typescript-eslint)\n\nKey rules:\n- **Arrow functions** for named components — not `function` declarations\n- **File extensions required** on all JS/JSX imports: `./Component.jsx` ✅ `./Component` ❌ (`.ts`/`.tsx` exempt)\n- **JSX only in `.jsx`/`.tsx` files**\n- **Import groups:** external/builtin first (blank line), then internal\n\nFix violations: `npm run lint:fix`\n\n### Prettier\n`printWidth: 100`, `tabWidth: 2`, `semi: true`, `singleQuote: true`, `trailingComma: 'es5'`, `endOfLine: 'lf'`\n\n### Commit Messages (Conventional Commits)\n\n```\n<type>(<scope>): <subject>      ← max 69 characters\n```\n\n**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`\n\n**Scopes:** plugin name (`editor-monaco`, `top-bar`, `validation`) or area (`deps`, `build`, `release`)\n\nEnforced by commitlint via Husky pre-commit hook.\n\n### File Naming\n\n| Type | Convention | Example |\n|------|-----------|---------|\n| React Components | `PascalCase.jsx/tsx` | `MonacoEditor.jsx` |\n| Utilities | `kebab-case.js` | `import-url.js` |\n| Types | `kebab-case.d.ts` | `system.d.ts` |\n| Styles (partials) | `_kebab-case.scss` | `_monaco-editor.scss` |\n| Unit Tests | `ComponentName.test.jsx` | `ValidationPane.test.jsx` |\n| E2E Tests | `feature.spec.ts` | `plugin.editor-monaco.spec.ts` |\n\n### Styling\n- **SCSS** for all component styles; BEM-like naming (`.editor-pane__title`)\n- Partial files prefixed with `_`; global styles in `/src/styles/index.scss`\n\n### TypeScript\n- Strict mode **off** globally; gradual adoption via `typescript-strict-plugin`\n- Legacy files may use `// @ts-strict-ignore` at top; new files should aim for strictness\n- `allowJs: false` — TypeScript files only in tsconfig scope\n- Path aliases work: `import X from 'plugins/editor-monaco'` ✅\n\n---\n\n## State Management\n\nSwaggerUI's plugin-based Redux-like system with Immutable.js.\n\n### Actions\n\n```javascript\nexport const SET_EDITOR_CONTENT = 'editor_set_content';\nexport const setEditorContent = (content) => ({ type: SET_EDITOR_CONTENT, payload: content });\n\n// Async thunk\nexport const loadDefinition = (url) => async (system) => {\n  const { editorActions, fn } = system;\n  const requestId = generateRequestId();\n  editorActions.loadDefinitionRequest({ url, requestId });\n  try {\n    const content = await fn.fetchUrl(url);\n    editorActions.loadDefinitionSuccess({ content, requestId });\n  } catch (error) {\n    editorActions.loadDefinitionFailure({ error, requestId });\n  }\n};\n```\n\n### Reducers (Immutable.js)\n\n```javascript\nimport { Map } from 'immutable';\n\nexport const initialState = Map({ content: '', status: 'idle', error: null, requestId: null });\n\nconst loadSuccessReducer = (state, action) => {\n  if (state.get('requestId') !== action.payload.requestId) return state; // ignore stale\n  return state.merge({ status: 'success', content: action.payload.content, error: null });\n};\n\nexport default {\n  [SET_EDITOR_CONTENT]: (state, action) => state.set('content', action.payload),\n  [LOAD_DEFINITION_SUCCESS]: loadSuccessReducer,\n};\n```\n\n### Selectors (Reselect)\n\n```javascript\nimport { createSelector } from 'reselect';\n\nexport const selectEditorState = (state) => state.get('editor');\nexport const selectEditorContent = (state) => selectEditorState(state).get('content');\nexport const selectStatus = (state) => selectEditorState(state).get('status');\n\n// Always memoize derived state\nexport const selectValidationErrors = createSelector(\n  selectValidationResults,\n  (results) => results.filter((r) => r.severity === 'error')\n);\n```\n\n### State Access in Components\n\n```javascript\nconst MyComponent = () => {\n  const { editorSelectors, editorActions } = useSystem();\n  const content = editorSelectors.selectEditorContent();\n  const isLoading = editorSelectors.selectIsLoading();\n\n  return <div>{isLoading ? 'Loading...' : content}</div>;\n};\n```\n\n### Known Issue: Editor Content Storage\n\n> Editor content is stored in SwaggerUI `spec` plugin, causing parse/resolve/store on every keystroke → typing lag with large specs. **Future fix:** store content in editor plugin with FSM pattern in preview plugins.\n\n- Avoid unnecessary state updates in the editor\n- Debounce expensive validation triggers\n\n---\n\n## Common Tasks\n\n### Creating a New Plugin\n\n```javascript\n// src/plugins/my-plugin/index.js\nconst MyPlugin = () => ({\n  components: { MyComponent: () => <div>Hello from MyPlugin</div> },\n  statePlugins: {\n    myPlugin: {\n      initialState: Map({ data: null }),\n      actions: { myAction: (payload) => ({ type: 'MY_ACTION', payload }) },\n      reducers: { MY_ACTION: (state, action) => state.set('data', action.payload) },\n      selectors: { selectData: createSelector((s) => s.get('myPlugin'), (s) => s.get('data')) },\n    },\n  },\n});\nexport default MyPlugin;\n```\n\nThen import and add to `App.tsx` or your preset's plugin array.\n\n### Adding a Component Wrapper\n\n```javascript\n// src/plugins/my-plugin/extensions/top-bar/wrap-components/TopBarWrapper.jsx\nconst TopBarWrapper = (Original, system) => {\n  const Enhanced = (props) => {\n    const showBanner = system.myPluginSelectors.selectShowBanner();\n    return (\n      <>\n        {showBanner && <div className=\"banner\">Important Notice</div>}\n        <Original {...props} />\n      </>\n    );\n  };\n  return Enhanced;\n};\n// Register in plugin: wrapComponents: { TopBar: TopBarWrapper }\n```\n\n### Adding a New Content Type\n\n```javascript\n// Extend detection in editor-content-type plugin (order matters — specific first)\nconst detectContentType = (content) => {\n  if (/^openapi:\\s*[\"']?3\\.1/.test(content)) return 'openapi-3-1';\n  if (/^openapi:\\s*[\"']?3\\.0/.test(content)) return 'openapi-3-0';\n  if (/^swagger:\\s*[\"']?2\\.0/.test(content)) return 'openapi-2-0';\n  if (/^asyncapi:\\s*[\"']?2\\./.test(content)) return 'asyncapi-2';\n  if (/^myspec:\\s*[\"']?1\\.0/.test(content)) return 'myspec-1-0'; // custom\n  return 'unknown';\n};\n```\n\nThen create a preview plugin that wraps `EditorPreview` and conditionally renders based on `editorSelectors.selectEditorContentType()`.\n\n### Adding E2E Tests\n\n```typescript\n// test/playwright/e2e/plugin.my-feature.spec.ts\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('My Feature', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n  test('should perform action', async ({ page }) => {\n    await page.locator('[data-testid=\"my-button\"]').click();\n    await expect(page.locator('text=Expected Result')).toBeVisible();\n  });\n});\n```\n\n### Debugging Validation\n\n```javascript\nconst { editorSelectors } = useSystem();\nconsole.log('markers:', editorSelectors.selectEditorMarkers());\nconsole.log('diagnostics:', editorSelectors.selectDiagnostics());\nconsole.log('content type:', editorSelectors.selectEditorContentType());\nconsole.log('is OpenAPI:', editorSelectors.selectIsContentTypeOpenAPI());\n```\n\n---\n\n## Important Gotchas\n\n### 1. File Extensions Required\n\n```javascript\nimport Component from './Component.jsx';  // ✅\nimport Component from './Component';      // ❌ fails linting\n```\nException: `.ts`/`.tsx` files may omit extensions due to ESLint overrides.\n\n### 2. Immutable.js State Updates\n\n```javascript\nstate.data = newValue;           // ❌ mutates state\nreturn state.set('data', val);   // ✅\nreturn state.merge({ a, b });    // ✅ multiple fields\n```\n\n### 3. Component Wrapping Return Value\n\n```javascript\nconst Wrapper = (Original, system) => Original;              // ❌ no enhancement\nconst Wrapper = (Original, system) => (props) => <Original {...props} />; // ✅\n```\n\n### 4. Request ID Race Conditions\n\nAlways include `requestId` in async actions and guard in reducers:\n```javascript\nif (state.get('requestId') !== action.payload.requestId) return state;\n```\n\n### 5. Monaco Environment Configuration\n\nMust be set **before** rendering SwaggerEditor:\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\nReactDOM.render(<SwaggerEditor />, document.getElementById('root'));\n```\n\n### 6. Web Worker Path Issues\n\nWorkers must be accessible at runtime — build separately via webpack entries or copy pre-built files with CopyWebpackPlugin.\n\n### 7. Large Bundle / OOM Errors\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\n```\n\n### 8. Content Type Detection Order\n\nMore specific patterns must come first — detect `3.1` before `3.0`, `3.0` before `2.0`.\n\n### 9. Selector Memoization\n\n```javascript\n// ❌ recalculates every render\nexport const selectErrors = (state) => selectResults(state).filter(r => r.severity === 'error');\n\n// ✅ memoized with createSelector\nexport const selectErrors = createSelector(selectResults, (r) => r.filter(...));\n```\n\n### 10. TypeScript Strict Mode\n\nOff globally. Use `typescript-strict-plugin` per file. New files should aim for strictness (no `@ts-strict-ignore`). Legacy files may have it.\n\n---\n\n## Key Files Reference\n\n**Core:**\n| File | Purpose |\n|------|---------|\n| `/src/App.tsx` | Main component, plugin composition |\n| `/src/index.tsx` | Browser entry point |\n| `/public/index.html` | HTML template, MonacoEnvironment setup |\n\n**Config:**\n| File | Purpose |\n|------|---------|\n| `/package.json` | Dependencies and scripts |\n| `/tsconfig.json` | TypeScript compiler options |\n| `/.eslintrc` | ESLint rules (Airbnb + Prettier + @vitest) |\n| `/.prettierrc` | Formatting rules |\n| `/.commitlintrc.json` | Commit message linting |\n| `/vite.config.js` | Vite dev server config |\n| `/vite.config.app.js` | Vite app production build config |\n| `/vitest.config.ts` | Vitest unit test config |\n| `/playwright.config.ts` | Playwright E2E config |\n\n**Docs:**\n| File | Purpose |\n|------|---------|\n| `/docs/architecture.md` | High-level architecture overview |\n| `/docs/customization/plug-points/` | Plugin customization guides |\n| `/docs/migration*.md` | Migration guides from legacy version |\n\n**Testing:**\n| File | Purpose |\n|------|---------|\n| `/test/setupTests.js` | Vitest setup (jest-dom/vitest, vitest-canvas-mock) |\n| `/test/playwright/e2e/*.spec.ts` | E2E test specs |\n| `/test/playwright/helpers/` | Playwright helper functions |\n\n**Plugin Paths:**\n- Editor: `/src/plugins/editor-textarea/`, `/src/plugins/editor-monaco/`, `/src/plugins/editor-monaco-language-apidom/`, `/src/plugins/editor-monaco-yaml-paste/`\n- Preview: `/src/plugins/editor-preview*/`\n- Support: `/src/plugins/editor-content-*/`\n- Generic: `/src/plugins/layout/`, `/src/plugins/top-bar/`, `/src/plugins/modals/`, etc.\n- Presets: `/src/presets/monaco/` (default), `/src/presets/textarea/`\n\n---\n\n## Quick Reference for AI Assistants\n\n### Fixing a Bug\n1. Identify the plugin — most bugs are plugin-specific\n2. Check `test/playwright/e2e/plugin.<name>.spec.ts` for existing coverage\n3. Review actions/reducers/selectors in that plugin\n4. Add an E2E test to prevent regression\n\n### Adding a Feature\n1. Identify affected plugins; decide new vs. extend existing\n2. Plan state management needs (new actions/reducers?)\n3. Use component wrapping to enhance without forking\n4. Write E2E test first (TDD preferred); update README if user-facing\n\n### Refactoring\n1. Follow established patterns — don't tightly couple plugins\n2. Maintain Immutable.js correctness and selector memoization\n3. Run: `npm run lint:fix && npm test && npx playwright test`\n\n### When Stuck\n1. Read the relevant plugin source — most logic lives there\n2. Check `docs/architecture.md` for high-level overview\n3. Look at similar plugins for patterns to copy\n4. Check Playwright tests to see how features are exercised\n\n### Before Committing\n- [ ] `npm run lint:fix`\n- [ ] `npm test`\n- [ ] `npx playwright test`\n- [ ] Commit message: Conventional Commits format, max 69 chars header\n- [ ] `npm run build` succeeds\n\n---\n\n**Last Updated:** 2026-05-16 | **Version:** 5.0.6 | Update this file when architecture changes significantly\n","docs/architecture.md":"# High level architecture of SwaggerEditor@5\n\nThis document briefly describes high level architecture of SwaggerEditor@5.\n\n## Plugin architecture\n\nSwaggerEditor@5 is composed of number of plugins. These plugins are divided into four\ndistinct categories\n\n1. Plugins providing editor implementations\n2. Plugins providing preview of editor content\n3. Editor implementation support plugins\n4. Generic features plugins\n\n### Plugins providing editor implementations\n\nThese plugins include `editor-textarea` and `editor-moanco`.\n\n`editor-textarea` is a base plugin that other editor implementations build on.\nThis plugin provides basic editing experience using `<textarea />` HTML tag.\n\n`editor-monaco` builds on top of `editor-textarea` and provide advanced editing\nexperience using [Monaco Editor](https://microsoft.github.io/monaco-editor/).\n\n### Plugins providing preview of editor content\n\nThese plugins include `editor-preview-swagger-ui`, `editor-preview-asyncapi` and others.\nThe single responsibility of editor preview plugin is to render the editor content (text)\nto a set of UI components.\n\n### Editor implementation support plugins\n\nThese plugins include `editor-persistence`, `editor-read-only` and others.\nPlugins in this category are responsible to support plugins providing editor implementation\nwith additional enhanced capabilities in generic way.\n\n### Generic features plugins\n\nThese plugins include `dialogs`, `modals`, `layout` and others. Each plugin in this\ncategory provide features or enhancements to plugins in previous three categories.\n\n## Future architectural changes\n\nThis section describes opportunities to make the architecture of SwaggerEditor@5 better.\n\n### State management\n\nCurrently, editor content (text) is stored in SwaggerUI `spec` plugin. This causes various issues\nwhich manifests in decreased user experience of editing text. Every time the editor content\nchanges the `spec` plugin will try to parse it as JSON or YAML, resolve it and store it in\nredux state. This is absolutely not necessary. We can store editor content in editor implementation plugin\nand have preview plugins work as finite state machines and detect if they should process the editor content in any way.\nWhen this architectural change is implemented, the typing lag on big amount of editor content (text)\nwill significantly decrease.\n\n"},"files":{"CLAUDE.md":"# CLAUDE.md - SwaggerEditor Codebase Guide for AI Assistants\n\n**Version:** 5.0.6 | **Last Updated:** 2026-05-16\n\n---\n\n## Project Overview\n\nSwaggerEditor is a browser-based editor for API specifications supporting **OpenAPI 2.0/3.0/3.1/3.2**, **AsyncAPI 2.x/3.0**, **API Design Systems**, and **JSON Schema**. Built as a React app on SwaggerUI's plugin architecture with a split-pane Monaco Editor interface.\n\n**Core Technologies:** React 17+/18, SwaggerUI React, TypeScript (gradual), Immutable.js, Monaco Editor, ApiDOM, Vite 8, Vitest + Playwright\n\n**Philosophy:** Plugin-based architecture, minimal over-engineering, heavy E2E testing, gradual TypeScript adoption, app/ESM/UMD build artifacts.\n\n---\n\n## Codebase Structure\n\n```\nswagger-editor/\n├── vite/                 # Vite plugins, configs, and build scripts\n├── docs/                 # architecture.md, customization guides, migration guides\n├── public/               # Static assets, HTML template\n├── src/\n│   ├── App.tsx           # Main app component & plugin composition\n│   ├── index.tsx         # Browser entry point\n│   ├── plugins/          # 26 plugins (see Architecture section)\n│   ├── presets/\n│   │   ├── monaco/       # Full-featured preset (default)\n│   │   └── textarea/     # Lightweight fallback\n│   ├── styles/           # Global SCSS (index.scss)\n│   └── types/            # TypeScript declarations (*.d.ts)\n├── test/\n│   ├── playwright/\n│   │   ├── e2e/          # Test specs (*.spec.ts)\n│   │   ├── fixtures/     # Test data\n│   │   ├── helpers/      # Helper functions\n│   │   └── tsconfig.json\n│   └── setupTests.js     # Vitest setup (jest-dom/vitest, canvas-mock)\n├── build/                # Standalone app (generated)\n└── dist/esm|umd|types/   # Library bundles (generated)\n```\n\n---\n\n## Architecture & Design Patterns\n\n### Plugin Categories (26 total)\n\n**Editor implementations:**\n- `editor-textarea` — HTML `<textarea>` fallback\n- `editor-monaco` — Monaco Editor (advanced)\n- `editor-monaco-language-apidom` — ApiDOM language support\n- `editor-monaco-yaml-paste` — YAML paste transformations\n\n**Preview plugins:**\n- `editor-preview` — Base preview component\n- `editor-preview-swagger-ui` — OpenAPI rendering\n- `editor-preview-asyncapi` — AsyncAPI rendering\n- `editor-preview-api-design-systems` — ADS rendering\n\n**Editor support plugins:**\n- `editor-content-type` — Auto-detect content type (OpenAPI/AsyncAPI/JSON Schema)\n- `editor-content-persistence` — LocalStorage persistence\n- `editor-content-read-only` — Read-only mode\n- `editor-content-origin` — Track content source (URL, file, user)\n- `editor-content-fixtures` — Load example/fixture files\n- `editor-content-from-file` — File import\n\n**Generic feature plugins:**\n- `layout`, `top-bar`, `modals`, `dialogs`, `dropdown-menu`, `dropzone`, `splash-screen`, `editor-safe-render`, `swagger-ui-adapter`, `util`, `versions`, `props-change-watcher`\n\n### Component Hierarchy\n\n```\nApp.tsx (SwaggerUI wrapper)\n└── SwaggerEditorLayout\n    ├── SplashScreen\n    ├── TopBar (File/Edit/Generate menus)\n    └── Container\n        └── Dropzone\n            └── SplitPane (resizable)\n                ├── EditorPane\n                │   ├── EditorPaneBarTop\n                │   ├── MonacoEditor / TextareaEditor\n                │   └── ValidationPane (errors/warnings)\n                └── EditorPreviewPane\n                    └── EditorPreviewSwaggerUI / AsyncAPI / ApiDesignSystems\n```\n\n### Design Patterns\n\n- **Container/Presenter:** Containers connect to Redux (e.g., `MonacoEditorContainer.jsx`), presenters handle rendering (e.g., `MonacoEditor.jsx`)\n- **HOC via `wrapComponents`:** Plugins wrap existing components to enhance without forking\n- **`getComponent`:** Dynamically resolve components — `const C = getComponent('MonacoEditor')`\n- **FSM for async:** `idle → loading → success/failure` with request ID tracking to prevent race conditions\n\n---\n\n## Plugin System\n\n### Plugin Structure\n\n```javascript\n// src/plugins/plugin-name/index.js\nconst PluginName = ({ getSystem }) => ({\n  afterLoad: function,                   // Runs after plugin loads\n  components: {\n    ComponentName: Component,            // Register new components\n  },\n  wrapComponents: {\n    ComponentName: WrapperFn,            // Wrap/enhance existing components\n  },\n  rootInjects: {\n    utilityName: function,               // Inject utilities into system\n  },\n  statePlugins: {\n    pluginStateKey: {\n      actions: {},                       // Action creators\n      reducers: {},                      // Immutable.js reducers\n      selectors: {},                     // Reselect selectors\n      wrapActions: {},                   // Action middleware\n    },\n  },\n  fn: { utilityFunction: function },\n});\nexport default PluginName;\n```\n\n### Typical Plugin File Structure\n\n```\nplugin-name/\n├── index.js\n├── actions/index.js\n├── reducers.js\n├── selectors.js\n├── components/ComponentName.jsx\n├── components/ComponentName.scss\n├── extensions/other-plugin/wrap-components/ComponentWrapper.jsx\n├── after-load.js\n└── fn.js\n```\n\n### Component Wrapping Pattern\n\n```javascript\n// extensions/editor-preview/wrap-components/EditorPreviewWrapper.jsx\nconst EditorPreviewWrapper = (Original, system) => {\n  const EnhancedComponent = (props) => {\n    const isOpenAPI = system.editorSelectors.selectIsContentTypeOpenAPI();\n    if (isOpenAPI) return <EditorPreviewSwaggerUI />;\n    return <Original {...props} />;\n  };\n  return EnhancedComponent; // must return new component, not Original directly\n};\nexport default EditorPreviewWrapper;\n```\n\n### System Access in Plugins\n\n```javascript\nconst MyPlugin = (system) => {\n  const { getComponent, editorActions, editorSelectors, fn } = system;\n  const content = editorSelectors.selectEditorContent();\n  editorActions.setEditorContent('new content');\n  const MonacoEditor = getComponent('MonacoEditor');\n};\n```\n\n---\n\n## Development Workflows\n\n### Prerequisites\n- **Node.js** `>=24.22`, **npm** `>=11.19.1`, **Python 3.x** (node-gyp), **GLIBC** `>=2.29`\n- Optional: Docker or emscripten (for WASM builds)\n\n### npm Scripts\n\n| Script | Description |\n|--------|-------------|\n| `npm start` | Dev server on port 3000 (hot reload) |\n| `npm test` | Vitest unit tests (watch mode) |\n| `npm run test:run` | Vitest unit tests (single run, no watch) |\n| `npm run test:coverage` | Vitest unit tests with coverage report |\n| `npm run lint` | ESLint on all files |\n| `npm run lint:fix` | Auto-fix ESLint errors |\n| `npm run build` | Build all artifacts (app + bundles + types) |\n| `npm run build:app` | Standalone app → `/build` |\n| `npm run build:app:serve` | Serve built app on port 3050 |\n| `npm run build:bundle:esm` | ESM bundle → `/dist/esm` |\n| `npm run build:bundle:umd` | UMD bundle → `/dist/umd` |\n| `npm run build:definitions` | TypeScript definitions → `/dist/types` |\n| `npm run pw:test` | E2E tests (headless) |\n| `npm run pw:test:headed` | E2E with browser visible |\n| `npm run pw:test:ui` | Interactive Playwright UI mode |\n| `npm run pw:test:debug` | Playwright debug mode |\n| `npm run pw:report` | View test report |\n| `npm run clean` | Remove `/build` and `/dist` |\n\n### Environment Variables (`.env`, baked into build)\n\n| Variable | Description |\n|----------|-------------|\n| `VITE_VERSION` | App version displayed in the splash screen (defaults to `$npm_package_version`) |\n\n### Web Workers\n\nTwo workers handle background processing: `apidom.worker.js` (parsing/validation) and `editor.worker.js` (Monaco ops). Configure Monaco env **before** rendering:\n\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\n```\n\nWorkers must be accessible at runtime — either build them separately via webpack entry points, or copy pre-built files from `node_modules/swagger-editor/dist/umd/` using CopyWebpackPlugin.\n\n### OOM Fix for Large Builds\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\nnpm run build\n```\n\n---\n\n## Testing Strategy\n\n### Unit Testing (Vitest)\n- **Location:** `src/**/*.{spec,test}.{js,jsx,ts,tsx}`, run with `npm test` (watch) or `npm run test:run` (CI)\n- Config: `vitest.config.ts` — jsdom environment, globals enabled, `@codingame/monaco-vscode-api` inlined\n- Use `vi.fn()` for mocks; `@testing-library/jest-dom/vitest` for DOM matchers\n- ⚠️ Only 1 unit test exists: `ValidationPane.test.jsx` — heavy reliance on E2E\n\n### E2E Testing (Playwright)\n- **Location:** `test/playwright/e2e/*.spec.ts`, base URL `http://localhost:3000`\n- All tests written in TypeScript with full `@playwright/test` type safety\n\n**Existing test files:** `app.spec.ts`, `plugin.top-bar.spec.ts`, `plugin.editor-monaco.spec.ts`, `plugin.editor-monaco-yaml-paste.spec.ts`, `plugin.dropzone.spec.ts`, `plugin.validation-pane.spec.ts`, `plugin.editor-content-from-file.spec.ts`, `plugin.editor-persistence.spec.ts`, `plugin.editor-preview-*.spec.ts`, and more.\n\n**Helper functions** (`test/playwright/helpers/`):\n- Setup: `visitBlankPage()`, `waitForSplashScreen()`, `prepareAsyncAPI()`\n- Editor: `typeInEditor()`, `getAllEditorText()`, `selectAllEditorText()`\n- Menu: `clickMenu()`, `loadExample()`, `generateServer()`\n\n**E2E test template:**\n\n```typescript\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('Feature Name', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n\n  test('should do something', async ({ page }) => {\n    await page.locator('[data-testid=\"some-element\"]').click();\n    await expect(page.locator('text=Expected Text')).toBeVisible();\n  });\n});\n```\n\n---\n\n## Code Style & Conventions\n\n### ESLint (Airbnb + Prettier + jsx-a11y + @typescript-eslint)\n\nKey rules:\n- **Arrow functions** for named components — not `function` declarations\n- **File extensions required** on all JS/JSX imports: `./Component.jsx` ✅ `./Component` ❌ (`.ts`/`.tsx` exempt)\n- **JSX only in `.jsx`/`.tsx` files**\n- **Import groups:** external/builtin first (blank line), then internal\n\nFix violations: `npm run lint:fix`\n\n### Prettier\n`printWidth: 100`, `tabWidth: 2`, `semi: true`, `singleQuote: true`, `trailingComma: 'es5'`, `endOfLine: 'lf'`\n\n### Commit Messages (Conventional Commits)\n\n```\n<type>(<scope>): <subject>      ← max 69 characters\n```\n\n**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`\n\n**Scopes:** plugin name (`editor-monaco`, `top-bar`, `validation`) or area (`deps`, `build`, `release`)\n\nEnforced by commitlint via Husky pre-commit hook.\n\n### File Naming\n\n| Type | Convention | Example |\n|------|-----------|---------|\n| React Components | `PascalCase.jsx/tsx` | `MonacoEditor.jsx` |\n| Utilities | `kebab-case.js` | `import-url.js` |\n| Types | `kebab-case.d.ts` | `system.d.ts` |\n| Styles (partials) | `_kebab-case.scss` | `_monaco-editor.scss` |\n| Unit Tests | `ComponentName.test.jsx` | `ValidationPane.test.jsx` |\n| E2E Tests | `feature.spec.ts` | `plugin.editor-monaco.spec.ts` |\n\n### Styling\n- **SCSS** for all component styles; BEM-like naming (`.editor-pane__title`)\n- Partial files prefixed with `_`; global styles in `/src/styles/index.scss`\n\n### TypeScript\n- Strict mode **off** globally; gradual adoption via `typescript-strict-plugin`\n- Legacy files may use `// @ts-strict-ignore` at top; new files should aim for strictness\n- `allowJs: false` — TypeScript files only in tsconfig scope\n- Path aliases work: `import X from 'plugins/editor-monaco'` ✅\n\n---\n\n## State Management\n\nSwaggerUI's plugin-based Redux-like system with Immutable.js.\n\n### Actions\n\n```javascript\nexport const SET_EDITOR_CONTENT = 'editor_set_content';\nexport const setEditorContent = (content) => ({ type: SET_EDITOR_CONTENT, payload: content });\n\n// Async thunk\nexport const loadDefinition = (url) => async (system) => {\n  const { editorActions, fn } = system;\n  const requestId = generateRequestId();\n  editorActions.loadDefinitionRequest({ url, requestId });\n  try {\n    const content = await fn.fetchUrl(url);\n    editorActions.loadDefinitionSuccess({ content, requestId });\n  } catch (error) {\n    editorActions.loadDefinitionFailure({ error, requestId });\n  }\n};\n```\n\n### Reducers (Immutable.js)\n\n```javascript\nimport { Map } from 'immutable';\n\nexport const initialState = Map({ content: '', status: 'idle', error: null, requestId: null });\n\nconst loadSuccessReducer = (state, action) => {\n  if (state.get('requestId') !== action.payload.requestId) return state; // ignore stale\n  return state.merge({ status: 'success', content: action.payload.content, error: null });\n};\n\nexport default {\n  [SET_EDITOR_CONTENT]: (state, action) => state.set('content', action.payload),\n  [LOAD_DEFINITION_SUCCESS]: loadSuccessReducer,\n};\n```\n\n### Selectors (Reselect)\n\n```javascript\nimport { createSelector } from 'reselect';\n\nexport const selectEditorState = (state) => state.get('editor');\nexport const selectEditorContent = (state) => selectEditorState(state).get('content');\nexport const selectStatus = (state) => selectEditorState(state).get('status');\n\n// Always memoize derived state\nexport const selectValidationErrors = createSelector(\n  selectValidationResults,\n  (results) => results.filter((r) => r.severity === 'error')\n);\n```\n\n### State Access in Components\n\n```javascript\nconst MyComponent = () => {\n  const { editorSelectors, editorActions } = useSystem();\n  const content = editorSelectors.selectEditorContent();\n  const isLoading = editorSelectors.selectIsLoading();\n\n  return <div>{isLoading ? 'Loading...' : content}</div>;\n};\n```\n\n### Known Issue: Editor Content Storage\n\n> Editor content is stored in SwaggerUI `spec` plugin, causing parse/resolve/store on every keystroke → typing lag with large specs. **Future fix:** store content in editor plugin with FSM pattern in preview plugins.\n\n- Avoid unnecessary state updates in the editor\n- Debounce expensive validation triggers\n\n---\n\n## Common Tasks\n\n### Creating a New Plugin\n\n```javascript\n// src/plugins/my-plugin/index.js\nconst MyPlugin = () => ({\n  components: { MyComponent: () => <div>Hello from MyPlugin</div> },\n  statePlugins: {\n    myPlugin: {\n      initialState: Map({ data: null }),\n      actions: { myAction: (payload) => ({ type: 'MY_ACTION', payload }) },\n      reducers: { MY_ACTION: (state, action) => state.set('data', action.payload) },\n      selectors: { selectData: createSelector((s) => s.get('myPlugin'), (s) => s.get('data')) },\n    },\n  },\n});\nexport default MyPlugin;\n```\n\nThen import and add to `App.tsx` or your preset's plugin array.\n\n### Adding a Component Wrapper\n\n```javascript\n// src/plugins/my-plugin/extensions/top-bar/wrap-components/TopBarWrapper.jsx\nconst TopBarWrapper = (Original, system) => {\n  const Enhanced = (props) => {\n    const showBanner = system.myPluginSelectors.selectShowBanner();\n    return (\n      <>\n        {showBanner && <div className=\"banner\">Important Notice</div>}\n        <Original {...props} />\n      </>\n    );\n  };\n  return Enhanced;\n};\n// Register in plugin: wrapComponents: { TopBar: TopBarWrapper }\n```\n\n### Adding a New Content Type\n\n```javascript\n// Extend detection in editor-content-type plugin (order matters — specific first)\nconst detectContentType = (content) => {\n  if (/^openapi:\\s*[\"']?3\\.1/.test(content)) return 'openapi-3-1';\n  if (/^openapi:\\s*[\"']?3\\.0/.test(content)) return 'openapi-3-0';\n  if (/^swagger:\\s*[\"']?2\\.0/.test(content)) return 'openapi-2-0';\n  if (/^asyncapi:\\s*[\"']?2\\./.test(content)) return 'asyncapi-2';\n  if (/^myspec:\\s*[\"']?1\\.0/.test(content)) return 'myspec-1-0'; // custom\n  return 'unknown';\n};\n```\n\nThen create a preview plugin that wraps `EditorPreview` and conditionally renders based on `editorSelectors.selectEditorContentType()`.\n\n### Adding E2E Tests\n\n```typescript\n// test/playwright/e2e/plugin.my-feature.spec.ts\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('My Feature', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n  test('should perform action', async ({ page }) => {\n    await page.locator('[data-testid=\"my-button\"]').click();\n    await expect(page.locator('text=Expected Result')).toBeVisible();\n  });\n});\n```\n\n### Debugging Validation\n\n```javascript\nconst { editorSelectors } = useSystem();\nconsole.log('markers:', editorSelectors.selectEditorMarkers());\nconsole.log('diagnostics:', editorSelectors.selectDiagnostics());\nconsole.log('content type:', editorSelectors.selectEditorContentType());\nconsole.log('is OpenAPI:', editorSelectors.selectIsContentTypeOpenAPI());\n```\n\n---\n\n## Important Gotchas\n\n### 1. File Extensions Required\n\n```javascript\nimport Component from './Component.jsx';  // ✅\nimport Component from './Component';      // ❌ fails linting\n```\nException: `.ts`/`.tsx` files may omit extensions due to ESLint overrides.\n\n### 2. Immutable.js State Updates\n\n```javascript\nstate.data = newValue;           // ❌ mutates state\nreturn state.set('data', val);   // ✅\nreturn state.merge({ a, b });    // ✅ multiple fields\n```\n\n### 3. Component Wrapping Return Value\n\n```javascript\nconst Wrapper = (Original, system) => Original;              // ❌ no enhancement\nconst Wrapper = (Original, system) => (props) => <Original {...props} />; // ✅\n```\n\n### 4. Request ID Race Conditions\n\nAlways include `requestId` in async actions and guard in reducers:\n```javascript\nif (state.get('requestId') !== action.payload.requestId) return state;\n```\n\n### 5. Monaco Environment Configuration\n\nMust be set **before** rendering SwaggerEditor:\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\nReactDOM.render(<SwaggerEditor />, document.getElementById('root'));\n```\n\n### 6. Web Worker Path Issues\n\nWorkers must be accessible at runtime — build separately via webpack entries or copy pre-built files with CopyWebpackPlugin.\n\n### 7. Large Bundle / OOM Errors\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\n```\n\n### 8. Content Type Detection Order\n\nMore specific patterns must come first — detect `3.1` before `3.0`, `3.0` before `2.0`.\n\n### 9. Selector Memoization\n\n```javascript\n// ❌ recalculates every render\nexport const selectErrors = (state) => selectResults(state).filter(r => r.severity === 'error');\n\n// ✅ memoized with createSelector\nexport const selectErrors = createSelector(selectResults, (r) => r.filter(...));\n```\n\n### 10. TypeScript Strict Mode\n\nOff globally. Use `typescript-strict-plugin` per file. New files should aim for strictness (no `@ts-strict-ignore`). Legacy files may have it.\n\n---\n\n## Key Files Reference\n\n**Core:**\n| File | Purpose |\n|------|---------|\n| `/src/App.tsx` | Main component, plugin composition |\n| `/src/index.tsx` | Browser entry point |\n| `/public/index.html` | HTML template, MonacoEnvironment setup |\n\n**Config:**\n| File | Purpose |\n|------|---------|\n| `/package.json` | Dependencies and scripts |\n| `/tsconfig.json` | TypeScript compiler options |\n| `/.eslintrc` | ESLint rules (Airbnb + Prettier + @vitest) |\n| `/.prettierrc` | Formatting rules |\n| `/.commitlintrc.json` | Commit message linting |\n| `/vite.config.js` | Vite dev server config |\n| `/vite.config.app.js` | Vite app production build config |\n| `/vitest.config.ts` | Vitest unit test config |\n| `/playwright.config.ts` | Playwright E2E config |\n\n**Docs:**\n| File | Purpose |\n|------|---------|\n| `/docs/architecture.md` | High-level architecture overview |\n| `/docs/customization/plug-points/` | Plugin customization guides |\n| `/docs/migration*.md` | Migration guides from legacy version |\n\n**Testing:**\n| File | Purpose |\n|------|---------|\n| `/test/setupTests.js` | Vitest setup (jest-dom/vitest, vitest-canvas-mock) |\n| `/test/playwright/e2e/*.spec.ts` | E2E test specs |\n| `/test/playwright/helpers/` | Playwright helper functions |\n\n**Plugin Paths:**\n- Editor: `/src/plugins/editor-textarea/`, `/src/plugins/editor-monaco/`, `/src/plugins/editor-monaco-language-apidom/`, `/src/plugins/editor-monaco-yaml-paste/`\n- Preview: `/src/plugins/editor-preview*/`\n- Support: `/src/plugins/editor-content-*/`\n- Generic: `/src/plugins/layout/`, `/src/plugins/top-bar/`, `/src/plugins/modals/`, etc.\n- Presets: `/src/presets/monaco/` (default), `/src/presets/textarea/`\n\n---\n\n## Quick Reference for AI Assistants\n\n### Fixing a Bug\n1. Identify the plugin — most bugs are plugin-specific\n2. Check `test/playwright/e2e/plugin.<name>.spec.ts` for existing coverage\n3. Review actions/reducers/selectors in that plugin\n4. Add an E2E test to prevent regression\n\n### Adding a Feature\n1. Identify affected plugins; decide new vs. extend existing\n2. Plan state management needs (new actions/reducers?)\n3. Use component wrapping to enhance without forking\n4. Write E2E test first (TDD preferred); update README if user-facing\n\n### Refactoring\n1. Follow established patterns — don't tightly couple plugins\n2. Maintain Immutable.js correctness and selector memoization\n3. Run: `npm run lint:fix && npm test && npx playwright test`\n\n### When Stuck\n1. Read the relevant plugin source — most logic lives there\n2. Check `docs/architecture.md` for high-level overview\n3. Look at similar plugins for patterns to copy\n4. Check Playwright tests to see how features are exercised\n\n### Before Committing\n- [ ] `npm run lint:fix`\n- [ ] `npm test`\n- [ ] `npx playwright test`\n- [ ] Commit message: Conventional Commits format, max 69 chars header\n- [ ] `npm run build` succeeds\n\n---\n\n**Last Updated:** 2026-05-16 | **Version:** 5.0.6 | Update this file when architecture changes significantly\n","docs/architecture.md":"# High level architecture of SwaggerEditor@5\n\nThis document briefly describes high level architecture of SwaggerEditor@5.\n\n## Plugin architecture\n\nSwaggerEditor@5 is composed of number of plugins. These plugins are divided into four\ndistinct categories\n\n1. Plugins providing editor implementations\n2. Plugins providing preview of editor content\n3. Editor implementation support plugins\n4. Generic features plugins\n\n### Plugins providing editor implementations\n\nThese plugins include `editor-textarea` and `editor-moanco`.\n\n`editor-textarea` is a base plugin that other editor implementations build on.\nThis plugin provides basic editing experience using `<textarea />` HTML tag.\n\n`editor-monaco` builds on top of `editor-textarea` and provide advanced editing\nexperience using [Monaco Editor](https://microsoft.github.io/monaco-editor/).\n\n### Plugins providing preview of editor content\n\nThese plugins include `editor-preview-swagger-ui`, `editor-preview-asyncapi` and others.\nThe single responsibility of editor preview plugin is to render the editor content (text)\nto a set of UI components.\n\n### Editor implementation support plugins\n\nThese plugins include `editor-persistence`, `editor-read-only` and others.\nPlugins in this category are responsible to support plugins providing editor implementation\nwith additional enhanced capabilities in generic way.\n\n### Generic features plugins\n\nThese plugins include `dialogs`, `modals`, `layout` and others. Each plugin in this\ncategory provide features or enhancements to plugins in previous three categories.\n\n## Future architectural changes\n\nThis section describes opportunities to make the architecture of SwaggerEditor@5 better.\n\n### State management\n\nCurrently, editor content (text) is stored in SwaggerUI `spec` plugin. This causes various issues\nwhich manifests in decreased user experience of editing text. Every time the editor content\nchanges the `spec` plugin will try to parse it as JSON or YAML, resolve it and store it in\nredux state. This is absolutely not necessary. We can store editor content in editor implementation plugin\nand have preview plugins work as finite state machines and detect if they should process the editor content in any way.\nWhen this architectural change is implemented, the typing lag on big amount of editor content (text)\nwill significantly decrease.\n\n"},"items":[{"name":"architecture.md","path":"docs/architecture.md","rawUrl":"https://raw.githubusercontent.com/swagger-api/swagger-editor/HEAD/docs/architecture.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"# High level architecture of SwaggerEditor@5\n\nThis document briefly describes high level architecture of SwaggerEditor@5.\n\n## Plugin architecture\n\nSwaggerEditor@5 is composed of number of plugins. These plugins are divided into four\ndistinct categories\n\n1. Plugins providing editor implementations\n2. Plugins providing preview of editor content\n3. Editor implementation support plugins\n4. Generic features plugins\n\n### Plugins providing editor implementations\n\nThese plugins include `editor-textarea` and `editor-moanco`.\n\n`editor-textarea` is a base plugin that other editor implementations build on.\nThis plugin provides basic editing experience using `<textarea />` HTML tag.\n\n`editor-monaco` builds on top of `editor-textarea` and provide advanced editing\nexperience using [Monaco Editor](https://microsoft.github.io/monaco-editor/).\n\n### Plugins providing preview of editor content\n\nThese plugins include `editor-preview-swagger-ui`, `editor-preview-asyncapi` and others.\nThe single responsibility of editor preview plugin is to render the editor content (text)\nto a set of UI components.\n\n### Editor implementation support plugins\n\nThese plugins include `editor-persistence`, `editor-read-only` and others.\nPlugins in this category are responsible to support plugins providing editor implementation\nwith additional enhanced capabilities in generic way.\n\n### Generic features plugins\n\nThese plugins include `dialogs`, `modals`, `layout` and others. Each plugin in this\ncategory provide features or enhancements to plugins in previous three categories.\n\n## Future architectural changes\n\nThis section describes opportunities to make the architecture of SwaggerEditor@5 better.\n\n### State management\n\nCurrently, editor content (text) is stored in SwaggerUI `spec` plugin. This causes various issues\nwhich manifests in decreased user experience of editing text. Every time the editor content\nchanges the `spec` plugin will try to parse it as JSON or YAML, resolve it and store it in\nredux state. This is absolutely not necessary. We can store editor content in editor implementation plugin\nand have preview plugins work as finite state machines and detect if they should process the editor content in any way.\nWhen this architectural change is implemented, the typing lag on big amount of editor content (text)\nwill significantly decrease.\n\n","isInternal":false,"tokens":447,"sizeBytes":2342},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/swagger-api/swagger-editor/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md - SwaggerEditor Codebase Guide for AI Assistants\n\n**Version:** 5.0.6 | **Last Updated:** 2026-05-16\n\n---\n\n## Project Overview\n\nSwaggerEditor is a browser-based editor for API specifications supporting **OpenAPI 2.0/3.0/3.1/3.2**, **AsyncAPI 2.x/3.0**, **API Design Systems**, and **JSON Schema**. Built as a React app on SwaggerUI's plugin architecture with a split-pane Monaco Editor interface.\n\n**Core Technologies:** React 17+/18, SwaggerUI React, TypeScript (gradual), Immutable.js, Monaco Editor, ApiDOM, Vite 8, Vitest + Playwright\n\n**Philosophy:** Plugin-based architecture, minimal over-engineering, heavy E2E testing, gradual TypeScript adoption, app/ESM/UMD build artifacts.\n\n---\n\n## Codebase Structure\n\n```\nswagger-editor/\n├── vite/                 # Vite plugins, configs, and build scripts\n├── docs/                 # architecture.md, customization guides, migration guides\n├── public/               # Static assets, HTML template\n├── src/\n│   ├── App.tsx           # Main app component & plugin composition\n│   ├── index.tsx         # Browser entry point\n│   ├── plugins/          # 26 plugins (see Architecture section)\n│   ├── presets/\n│   │   ├── monaco/       # Full-featured preset (default)\n│   │   └── textarea/     # Lightweight fallback\n│   ├── styles/           # Global SCSS (index.scss)\n│   └── types/            # TypeScript declarations (*.d.ts)\n├── test/\n│   ├── playwright/\n│   │   ├── e2e/          # Test specs (*.spec.ts)\n│   │   ├── fixtures/     # Test data\n│   │   ├── helpers/      # Helper functions\n│   │   └── tsconfig.json\n│   └── setupTests.js     # Vitest setup (jest-dom/vitest, canvas-mock)\n├── build/                # Standalone app (generated)\n└── dist/esm|umd|types/   # Library bundles (generated)\n```\n\n---\n\n## Architecture & Design Patterns\n\n### Plugin Categories (26 total)\n\n**Editor implementations:**\n- `editor-textarea` — HTML `<textarea>` fallback\n- `editor-monaco` — Monaco Editor (advanced)\n- `editor-monaco-language-apidom` — ApiDOM language support\n- `editor-monaco-yaml-paste` — YAML paste transformations\n\n**Preview plugins:**\n- `editor-preview` — Base preview component\n- `editor-preview-swagger-ui` — OpenAPI rendering\n- `editor-preview-asyncapi` — AsyncAPI rendering\n- `editor-preview-api-design-systems` — ADS rendering\n\n**Editor support plugins:**\n- `editor-content-type` — Auto-detect content type (OpenAPI/AsyncAPI/JSON Schema)\n- `editor-content-persistence` — LocalStorage persistence\n- `editor-content-read-only` — Read-only mode\n- `editor-content-origin` — Track content source (URL, file, user)\n- `editor-content-fixtures` — Load example/fixture files\n- `editor-content-from-file` — File import\n\n**Generic feature plugins:**\n- `layout`, `top-bar`, `modals`, `dialogs`, `dropdown-menu`, `dropzone`, `splash-screen`, `editor-safe-render`, `swagger-ui-adapter`, `util`, `versions`, `props-change-watcher`\n\n### Component Hierarchy\n\n```\nApp.tsx (SwaggerUI wrapper)\n└── SwaggerEditorLayout\n    ├── SplashScreen\n    ├── TopBar (File/Edit/Generate menus)\n    └── Container\n        └── Dropzone\n            └── SplitPane (resizable)\n                ├── EditorPane\n                │   ├── EditorPaneBarTop\n                │   ├── MonacoEditor / TextareaEditor\n                │   └── ValidationPane (errors/warnings)\n                └── EditorPreviewPane\n                    └── EditorPreviewSwaggerUI / AsyncAPI / ApiDesignSystems\n```\n\n### Design Patterns\n\n- **Container/Presenter:** Containers connect to Redux (e.g., `MonacoEditorContainer.jsx`), presenters handle rendering (e.g., `MonacoEditor.jsx`)\n- **HOC via `wrapComponents`:** Plugins wrap existing components to enhance without forking\n- **`getComponent`:** Dynamically resolve components — `const C = getComponent('MonacoEditor')`\n- **FSM for async:** `idle → loading → success/failure` with request ID tracking to prevent race conditions\n\n---\n\n## Plugin System\n\n### Plugin Structure\n\n```javascript\n// src/plugins/plugin-name/index.js\nconst PluginName = ({ getSystem }) => ({\n  afterLoad: function,                   // Runs after plugin loads\n  components: {\n    ComponentName: Component,            // Register new components\n  },\n  wrapComponents: {\n    ComponentName: WrapperFn,            // Wrap/enhance existing components\n  },\n  rootInjects: {\n    utilityName: function,               // Inject utilities into system\n  },\n  statePlugins: {\n    pluginStateKey: {\n      actions: {},                       // Action creators\n      reducers: {},                      // Immutable.js reducers\n      selectors: {},                     // Reselect selectors\n      wrapActions: {},                   // Action middleware\n    },\n  },\n  fn: { utilityFunction: function },\n});\nexport default PluginName;\n```\n\n### Typical Plugin File Structure\n\n```\nplugin-name/\n├── index.js\n├── actions/index.js\n├── reducers.js\n├── selectors.js\n├── components/ComponentName.jsx\n├── components/ComponentName.scss\n├── extensions/other-plugin/wrap-components/ComponentWrapper.jsx\n├── after-load.js\n└── fn.js\n```\n\n### Component Wrapping Pattern\n\n```javascript\n// extensions/editor-preview/wrap-components/EditorPreviewWrapper.jsx\nconst EditorPreviewWrapper = (Original, system) => {\n  const EnhancedComponent = (props) => {\n    const isOpenAPI = system.editorSelectors.selectIsContentTypeOpenAPI();\n    if (isOpenAPI) return <EditorPreviewSwaggerUI />;\n    return <Original {...props} />;\n  };\n  return EnhancedComponent; // must return new component, not Original directly\n};\nexport default EditorPreviewWrapper;\n```\n\n### System Access in Plugins\n\n```javascript\nconst MyPlugin = (system) => {\n  const { getComponent, editorActions, editorSelectors, fn } = system;\n  const content = editorSelectors.selectEditorContent();\n  editorActions.setEditorContent('new content');\n  const MonacoEditor = getComponent('MonacoEditor');\n};\n```\n\n---\n\n## Development Workflows\n\n### Prerequisites\n- **Node.js** `>=24.22`, **npm** `>=11.19.1`, **Python 3.x** (node-gyp), **GLIBC** `>=2.29`\n- Optional: Docker or emscripten (for WASM builds)\n\n### npm Scripts\n\n| Script | Description |\n|--------|-------------|\n| `npm start` | Dev server on port 3000 (hot reload) |\n| `npm test` | Vitest unit tests (watch mode) |\n| `npm run test:run` | Vitest unit tests (single run, no watch) |\n| `npm run test:coverage` | Vitest unit tests with coverage report |\n| `npm run lint` | ESLint on all files |\n| `npm run lint:fix` | Auto-fix ESLint errors |\n| `npm run build` | Build all artifacts (app + bundles + types) |\n| `npm run build:app` | Standalone app → `/build` |\n| `npm run build:app:serve` | Serve built app on port 3050 |\n| `npm run build:bundle:esm` | ESM bundle → `/dist/esm` |\n| `npm run build:bundle:umd` | UMD bundle → `/dist/umd` |\n| `npm run build:definitions` | TypeScript definitions → `/dist/types` |\n| `npm run pw:test` | E2E tests (headless) |\n| `npm run pw:test:headed` | E2E with browser visible |\n| `npm run pw:test:ui` | Interactive Playwright UI mode |\n| `npm run pw:test:debug` | Playwright debug mode |\n| `npm run pw:report` | View test report |\n| `npm run clean` | Remove `/build` and `/dist` |\n\n### Environment Variables (`.env`, baked into build)\n\n| Variable | Description |\n|----------|-------------|\n| `VITE_VERSION` | App version displayed in the splash screen (defaults to `$npm_package_version`) |\n\n### Web Workers\n\nTwo workers handle background processing: `apidom.worker.js` (parsing/validation) and `editor.worker.js` (Monaco ops). Configure Monaco env **before** rendering:\n\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\n```\n\nWorkers must be accessible at runtime — either build them separately via webpack entry points, or copy pre-built files from `node_modules/swagger-editor/dist/umd/` using CopyWebpackPlugin.\n\n### OOM Fix for Large Builds\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\nnpm run build\n```\n\n---\n\n## Testing Strategy\n\n### Unit Testing (Vitest)\n- **Location:** `src/**/*.{spec,test}.{js,jsx,ts,tsx}`, run with `npm test` (watch) or `npm run test:run` (CI)\n- Config: `vitest.config.ts` — jsdom environment, globals enabled, `@codingame/monaco-vscode-api` inlined\n- Use `vi.fn()` for mocks; `@testing-library/jest-dom/vitest` for DOM matchers\n- ⚠️ Only 1 unit test exists: `ValidationPane.test.jsx` — heavy reliance on E2E\n\n### E2E Testing (Playwright)\n- **Location:** `test/playwright/e2e/*.spec.ts`, base URL `http://localhost:3000`\n- All tests written in TypeScript with full `@playwright/test` type safety\n\n**Existing test files:** `app.spec.ts`, `plugin.top-bar.spec.ts`, `plugin.editor-monaco.spec.ts`, `plugin.editor-monaco-yaml-paste.spec.ts`, `plugin.dropzone.spec.ts`, `plugin.validation-pane.spec.ts`, `plugin.editor-content-from-file.spec.ts`, `plugin.editor-persistence.spec.ts`, `plugin.editor-preview-*.spec.ts`, and more.\n\n**Helper functions** (`test/playwright/helpers/`):\n- Setup: `visitBlankPage()`, `waitForSplashScreen()`, `prepareAsyncAPI()`\n- Editor: `typeInEditor()`, `getAllEditorText()`, `selectAllEditorText()`\n- Menu: `clickMenu()`, `loadExample()`, `generateServer()`\n\n**E2E test template:**\n\n```typescript\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('Feature Name', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n\n  test('should do something', async ({ page }) => {\n    await page.locator('[data-testid=\"some-element\"]').click();\n    await expect(page.locator('text=Expected Text')).toBeVisible();\n  });\n});\n```\n\n---\n\n## Code Style & Conventions\n\n### ESLint (Airbnb + Prettier + jsx-a11y + @typescript-eslint)\n\nKey rules:\n- **Arrow functions** for named components — not `function` declarations\n- **File extensions required** on all JS/JSX imports: `./Component.jsx` ✅ `./Component` ❌ (`.ts`/`.tsx` exempt)\n- **JSX only in `.jsx`/`.tsx` files**\n- **Import groups:** external/builtin first (blank line), then internal\n\nFix violations: `npm run lint:fix`\n\n### Prettier\n`printWidth: 100`, `tabWidth: 2`, `semi: true`, `singleQuote: true`, `trailingComma: 'es5'`, `endOfLine: 'lf'`\n\n### Commit Messages (Conventional Commits)\n\n```\n<type>(<scope>): <subject>      ← max 69 characters\n```\n\n**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`\n\n**Scopes:** plugin name (`editor-monaco`, `top-bar`, `validation`) or area (`deps`, `build`, `release`)\n\nEnforced by commitlint via Husky pre-commit hook.\n\n### File Naming\n\n| Type | Convention | Example |\n|------|-----------|---------|\n| React Components | `PascalCase.jsx/tsx` | `MonacoEditor.jsx` |\n| Utilities | `kebab-case.js` | `import-url.js` |\n| Types | `kebab-case.d.ts` | `system.d.ts` |\n| Styles (partials) | `_kebab-case.scss` | `_monaco-editor.scss` |\n| Unit Tests | `ComponentName.test.jsx` | `ValidationPane.test.jsx` |\n| E2E Tests | `feature.spec.ts` | `plugin.editor-monaco.spec.ts` |\n\n### Styling\n- **SCSS** for all component styles; BEM-like naming (`.editor-pane__title`)\n- Partial files prefixed with `_`; global styles in `/src/styles/index.scss`\n\n### TypeScript\n- Strict mode **off** globally; gradual adoption via `typescript-strict-plugin`\n- Legacy files may use `// @ts-strict-ignore` at top; new files should aim for strictness\n- `allowJs: false` — TypeScript files only in tsconfig scope\n- Path aliases work: `import X from 'plugins/editor-monaco'` ✅\n\n---\n\n## State Management\n\nSwaggerUI's plugin-based Redux-like system with Immutable.js.\n\n### Actions\n\n```javascript\nexport const SET_EDITOR_CONTENT = 'editor_set_content';\nexport const setEditorContent = (content) => ({ type: SET_EDITOR_CONTENT, payload: content });\n\n// Async thunk\nexport const loadDefinition = (url) => async (system) => {\n  const { editorActions, fn } = system;\n  const requestId = generateRequestId();\n  editorActions.loadDefinitionRequest({ url, requestId });\n  try {\n    const content = await fn.fetchUrl(url);\n    editorActions.loadDefinitionSuccess({ content, requestId });\n  } catch (error) {\n    editorActions.loadDefinitionFailure({ error, requestId });\n  }\n};\n```\n\n### Reducers (Immutable.js)\n\n```javascript\nimport { Map } from 'immutable';\n\nexport const initialState = Map({ content: '', status: 'idle', error: null, requestId: null });\n\nconst loadSuccessReducer = (state, action) => {\n  if (state.get('requestId') !== action.payload.requestId) return state; // ignore stale\n  return state.merge({ status: 'success', content: action.payload.content, error: null });\n};\n\nexport default {\n  [SET_EDITOR_CONTENT]: (state, action) => state.set('content', action.payload),\n  [LOAD_DEFINITION_SUCCESS]: loadSuccessReducer,\n};\n```\n\n### Selectors (Reselect)\n\n```javascript\nimport { createSelector } from 'reselect';\n\nexport const selectEditorState = (state) => state.get('editor');\nexport const selectEditorContent = (state) => selectEditorState(state).get('content');\nexport const selectStatus = (state) => selectEditorState(state).get('status');\n\n// Always memoize derived state\nexport const selectValidationErrors = createSelector(\n  selectValidationResults,\n  (results) => results.filter((r) => r.severity === 'error')\n);\n```\n\n### State Access in Components\n\n```javascript\nconst MyComponent = () => {\n  const { editorSelectors, editorActions } = useSystem();\n  const content = editorSelectors.selectEditorContent();\n  const isLoading = editorSelectors.selectIsLoading();\n\n  return <div>{isLoading ? 'Loading...' : content}</div>;\n};\n```\n\n### Known Issue: Editor Content Storage\n\n> Editor content is stored in SwaggerUI `spec` plugin, causing parse/resolve/store on every keystroke → typing lag with large specs. **Future fix:** store content in editor plugin with FSM pattern in preview plugins.\n\n- Avoid unnecessary state updates in the editor\n- Debounce expensive validation triggers\n\n---\n\n## Common Tasks\n\n### Creating a New Plugin\n\n```javascript\n// src/plugins/my-plugin/index.js\nconst MyPlugin = () => ({\n  components: { MyComponent: () => <div>Hello from MyPlugin</div> },\n  statePlugins: {\n    myPlugin: {\n      initialState: Map({ data: null }),\n      actions: { myAction: (payload) => ({ type: 'MY_ACTION', payload }) },\n      reducers: { MY_ACTION: (state, action) => state.set('data', action.payload) },\n      selectors: { selectData: createSelector((s) => s.get('myPlugin'), (s) => s.get('data')) },\n    },\n  },\n});\nexport default MyPlugin;\n```\n\nThen import and add to `App.tsx` or your preset's plugin array.\n\n### Adding a Component Wrapper\n\n```javascript\n// src/plugins/my-plugin/extensions/top-bar/wrap-components/TopBarWrapper.jsx\nconst TopBarWrapper = (Original, system) => {\n  const Enhanced = (props) => {\n    const showBanner = system.myPluginSelectors.selectShowBanner();\n    return (\n      <>\n        {showBanner && <div className=\"banner\">Important Notice</div>}\n        <Original {...props} />\n      </>\n    );\n  };\n  return Enhanced;\n};\n// Register in plugin: wrapComponents: { TopBar: TopBarWrapper }\n```\n\n### Adding a New Content Type\n\n```javascript\n// Extend detection in editor-content-type plugin (order matters — specific first)\nconst detectContentType = (content) => {\n  if (/^openapi:\\s*[\"']?3\\.1/.test(content)) return 'openapi-3-1';\n  if (/^openapi:\\s*[\"']?3\\.0/.test(content)) return 'openapi-3-0';\n  if (/^swagger:\\s*[\"']?2\\.0/.test(content)) return 'openapi-2-0';\n  if (/^asyncapi:\\s*[\"']?2\\./.test(content)) return 'asyncapi-2';\n  if (/^myspec:\\s*[\"']?1\\.0/.test(content)) return 'myspec-1-0'; // custom\n  return 'unknown';\n};\n```\n\nThen create a preview plugin that wraps `EditorPreview` and conditionally renders based on `editorSelectors.selectEditorContentType()`.\n\n### Adding E2E Tests\n\n```typescript\n// test/playwright/e2e/plugin.my-feature.spec.ts\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('My Feature', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n  test('should perform action', async ({ page }) => {\n    await page.locator('[data-testid=\"my-button\"]').click();\n    await expect(page.locator('text=Expected Result')).toBeVisible();\n  });\n});\n```\n\n### Debugging Validation\n\n```javascript\nconst { editorSelectors } = useSystem();\nconsole.log('markers:', editorSelectors.selectEditorMarkers());\nconsole.log('diagnostics:', editorSelectors.selectDiagnostics());\nconsole.log('content type:', editorSelectors.selectEditorContentType());\nconsole.log('is OpenAPI:', editorSelectors.selectIsContentTypeOpenAPI());\n```\n\n---\n\n## Important Gotchas\n\n### 1. File Extensions Required\n\n```javascript\nimport Component from './Component.jsx';  // ✅\nimport Component from './Component';      // ❌ fails linting\n```\nException: `.ts`/`.tsx` files may omit extensions due to ESLint overrides.\n\n### 2. Immutable.js State Updates\n\n```javascript\nstate.data = newValue;           // ❌ mutates state\nreturn state.set('data', val);   // ✅\nreturn state.merge({ a, b });    // ✅ multiple fields\n```\n\n### 3. Component Wrapping Return Value\n\n```javascript\nconst Wrapper = (Original, system) => Original;              // ❌ no enhancement\nconst Wrapper = (Original, system) => (props) => <Original {...props} />; // ✅\n```\n\n### 4. Request ID Race Conditions\n\nAlways include `requestId` in async actions and guard in reducers:\n```javascript\nif (state.get('requestId') !== action.payload.requestId) return state;\n```\n\n### 5. Monaco Environment Configuration\n\nMust be set **before** rendering SwaggerEditor:\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\nReactDOM.render(<SwaggerEditor />, document.getElementById('root'));\n```\n\n### 6. Web Worker Path Issues\n\nWorkers must be accessible at runtime — build separately via webpack entries or copy pre-built files with CopyWebpackPlugin.\n\n### 7. Large Bundle / OOM Errors\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\n```\n\n### 8. Content Type Detection Order\n\nMore specific patterns must come first — detect `3.1` before `3.0`, `3.0` before `2.0`.\n\n### 9. Selector Memoization\n\n```javascript\n// ❌ recalculates every render\nexport const selectErrors = (state) => selectResults(state).filter(r => r.severity === 'error');\n\n// ✅ memoized with createSelector\nexport const selectErrors = createSelector(selectResults, (r) => r.filter(...));\n```\n\n### 10. TypeScript Strict Mode\n\nOff globally. Use `typescript-strict-plugin` per file. New files should aim for strictness (no `@ts-strict-ignore`). Legacy files may have it.\n\n---\n\n## Key Files Reference\n\n**Core:**\n| File | Purpose |\n|------|---------|\n| `/src/App.tsx` | Main component, plugin composition |\n| `/src/index.tsx` | Browser entry point |\n| `/public/index.html` | HTML template, MonacoEnvironment setup |\n\n**Config:**\n| File | Purpose |\n|------|---------|\n| `/package.json` | Dependencies and scripts |\n| `/tsconfig.json` | TypeScript compiler options |\n| `/.eslintrc` | ESLint rules (Airbnb + Prettier + @vitest) |\n| `/.prettierrc` | Formatting rules |\n| `/.commitlintrc.json` | Commit message linting |\n| `/vite.config.js` | Vite dev server config |\n| `/vite.config.app.js` | Vite app production build config |\n| `/vitest.config.ts` | Vitest unit test config |\n| `/playwright.config.ts` | Playwright E2E config |\n\n**Docs:**\n| File | Purpose |\n|------|---------|\n| `/docs/architecture.md` | High-level architecture overview |\n| `/docs/customization/plug-points/` | Plugin customization guides |\n| `/docs/migration*.md` | Migration guides from legacy version |\n\n**Testing:**\n| File | Purpose |\n|------|---------|\n| `/test/setupTests.js` | Vitest setup (jest-dom/vitest, vitest-canvas-mock) |\n| `/test/playwright/e2e/*.spec.ts` | E2E test specs |\n| `/test/playwright/helpers/` | Playwright helper functions |\n\n**Plugin Paths:**\n- Editor: `/src/plugins/editor-textarea/`, `/src/plugins/editor-monaco/`, `/src/plugins/editor-monaco-language-apidom/`, `/src/plugins/editor-monaco-yaml-paste/`\n- Preview: `/src/plugins/editor-preview*/`\n- Support: `/src/plugins/editor-content-*/`\n- Generic: `/src/plugins/layout/`, `/src/plugins/top-bar/`, `/src/plugins/modals/`, etc.\n- Presets: `/src/presets/monaco/` (default), `/src/presets/textarea/`\n\n---\n\n## Quick Reference for AI Assistants\n\n### Fixing a Bug\n1. Identify the plugin — most bugs are plugin-specific\n2. Check `test/playwright/e2e/plugin.<name>.spec.ts` for existing coverage\n3. Review actions/reducers/selectors in that plugin\n4. Add an E2E test to prevent regression\n\n### Adding a Feature\n1. Identify affected plugins; decide new vs. extend existing\n2. Plan state management needs (new actions/reducers?)\n3. Use component wrapping to enhance without forking\n4. Write E2E test first (TDD preferred); update README if user-facing\n\n### Refactoring\n1. Follow established patterns — don't tightly couple plugins\n2. Maintain Immutable.js correctness and selector memoization\n3. Run: `npm run lint:fix && npm test && npx playwright test`\n\n### When Stuck\n1. Read the relevant plugin source — most logic lives there\n2. Check `docs/architecture.md` for high-level overview\n3. Look at similar plugins for patterns to copy\n4. Check Playwright tests to see how features are exercised\n\n### Before Committing\n- [ ] `npm run lint:fix`\n- [ ] `npm test`\n- [ ] `npx playwright test`\n- [ ] Commit message: Conventional Commits format, max 69 chars header\n- [ ] `npm run build` succeeds\n\n---\n\n**Last Updated:** 2026-05-16 | **Version:** 5.0.6 | Update this file when architecture changes significantly\n","isInternal":false,"tokens":5450,"sizeBytes":21927}],"systemPromptSnippet":"<yakaai_skills repo=\"swagger-api/swagger-editor\">\n<!-- File: docs/architecture.md (Tokens: ~447 | Category: project-spec) -->\n# High level architecture of SwaggerEditor@5\n\nThis document briefly describes high level architecture of SwaggerEditor@5.\n\n## Plugin architecture\n\nSwaggerEditor@5 is composed of number of plugins. These plugins are divided into four\ndistinct categories\n\n1. Plugins providing editor implementations\n2. Plugins providing preview of editor content\n3. Editor implementation support plugins\n4. Generic features plugins\n\n### Plugins providing editor implementations\n\nThese plugins include `editor-textarea` and `editor-moanco`.\n\n`editor-textarea` is a base plugin that other editor implementations build on.\nThis plugin provides basic editing experience using `<textarea />` HTML tag.\n\n`editor-monaco` builds on top of `editor-textarea` and provide advanced editing\nexperience using [Monaco Editor](https://microsoft.github.io/monaco-editor/).\n\n### Plugins providing preview of editor content\n\nThese plugins include `editor-preview-swagger-ui`, `editor-preview-asyncapi` and others.\nThe single responsibility of editor preview plugin is to render the editor content (text)\nto a set of UI components.\n\n### Editor implementation support plugins\n\nThese plugins include `editor-persistence`, `editor-read-only` and others.\nPlugins in this category are responsible to support plugins providing editor implementation\nwith additional enhanced capabilities in generic way.\n\n### Generic features plugins\n\nThese plugins include `dialogs`, `modals`, `layout` and others. Each plugin in this\ncategory provide features or enhancements to plugins in previous three categories.\n\n## Future architectural changes\n\nThis section describes opportunities to make the architecture of SwaggerEditor@5 better.\n\n### State management\n\nCurrently, editor content (text) is stored in SwaggerUI `spec` plugin. This causes various issues\nwhich manifests in decreased user experience of editing text. Every time the editor content\nchanges the `spec` plugin will try to parse it as JSON or YAML, resolve it and store it in\nredux state. This is absolutely not necessary. We can store editor content in editor implementation plugin\nand have preview plugins work as finite state machines and detect if they should process the editor content in any way.\nWhen this architectural change is implemented, the typing lag on big amount of editor content (text)\nwill significantly decrease.\n\n<!-- File: CLAUDE.md (Tokens: ~5450 | Category: claude-rule) -->\n# CLAUDE.md - SwaggerEditor Codebase Guide for AI Assistants\n\n**Version:** 5.0.6 | **Last Updated:** 2026-05-16\n\n---\n\n## Project Overview\n\nSwaggerEditor is a browser-based editor for API specifications supporting **OpenAPI 2.0/3.0/3.1/3.2**, **AsyncAPI 2.x/3.0**, **API Design Systems**, and **JSON Schema**. Built as a React app on SwaggerUI's plugin architecture with a split-pane Monaco Editor interface.\n\n**Core Technologies:** React 17+/18, SwaggerUI React, TypeScript (gradual), Immutable.js, Monaco Editor, ApiDOM, Vite 8, Vitest + Playwright\n\n**Philosophy:** Plugin-based architecture, minimal over-engineering, heavy E2E testing, gradual TypeScript adoption, app/ESM/UMD build artifacts.\n\n---\n\n## Codebase Structure\n\n```\nswagger-editor/\n├── vite/                 # Vite plugins, configs, and build scripts\n├── docs/                 # architecture.md, customization guides, migration guides\n├── public/               # Static assets, HTML template\n├── src/\n│   ├── App.tsx           # Main app component & plugin composition\n│   ├── index.tsx         # Browser entry point\n│   ├── plugins/          # 26 plugins (see Architecture section)\n│   ├── presets/\n│   │   ├── monaco/       # Full-featured preset (default)\n│   │   └── textarea/     # Lightweight fallback\n│   ├── styles/           # Global SCSS (index.scss)\n│   └── types/            # TypeScript declarations (*.d.ts)\n├── test/\n│   ├── playwright/\n│   │   ├── e2e/          # Test specs (*.spec.ts)\n│   │   ├── fixtures/     # Test data\n│   │   ├── helpers/      # Helper functions\n│   │   └── tsconfig.json\n│   └── setupTests.js     # Vitest setup (jest-dom/vitest, canvas-mock)\n├── build/                # Standalone app (generated)\n└── dist/esm|umd|types/   # Library bundles (generated)\n```\n\n---\n\n## Architecture & Design Patterns\n\n### Plugin Categories (26 total)\n\n**Editor implementations:**\n- `editor-textarea` — HTML `<textarea>` fallback\n- `editor-monaco` — Monaco Editor (advanced)\n- `editor-monaco-language-apidom` — ApiDOM language support\n- `editor-monaco-yaml-paste` — YAML paste transformations\n\n**Preview plugins:**\n- `editor-preview` — Base preview component\n- `editor-preview-swagger-ui` — OpenAPI rendering\n- `editor-preview-asyncapi` — AsyncAPI rendering\n- `editor-preview-api-design-systems` — ADS rendering\n\n**Editor support plugins:**\n- `editor-content-type` — Auto-detect content type (OpenAPI/AsyncAPI/JSON Schema)\n- `editor-content-persistence` — LocalStorage persistence\n- `editor-content-read-only` — Read-only mode\n- `editor-content-origin` — Track content source (URL, file, user)\n- `editor-content-fixtures` — Load example/fixture files\n- `editor-content-from-file` — File import\n\n**Generic feature plugins:**\n- `layout`, `top-bar`, `modals`, `dialogs`, `dropdown-menu`, `dropzone`, `splash-screen`, `editor-safe-render`, `swagger-ui-adapter`, `util`, `versions`, `props-change-watcher`\n\n### Component Hierarchy\n\n```\nApp.tsx (SwaggerUI wrapper)\n└── SwaggerEditorLayout\n    ├── SplashScreen\n    ├── TopBar (File/Edit/Generate menus)\n    └── Container\n        └── Dropzone\n            └── SplitPane (resizable)\n                ├── EditorPane\n                │   ├── EditorPaneBarTop\n                │   ├── MonacoEditor / TextareaEditor\n                │   └── ValidationPane (errors/warnings)\n                └── EditorPreviewPane\n                    └── EditorPreviewSwaggerUI / AsyncAPI / ApiDesignSystems\n```\n\n### Design Patterns\n\n- **Container/Presenter:** Containers connect to Redux (e.g., `MonacoEditorContainer.jsx`), presenters handle rendering (e.g., `MonacoEditor.jsx`)\n- **HOC via `wrapComponents`:** Plugins wrap existing components to enhance without forking\n- **`getComponent`:** Dynamically resolve components — `const C = getComponent('MonacoEditor')`\n- **FSM for async:** `idle → loading → success/failure` with request ID tracking to prevent race conditions\n\n---\n\n## Plugin System\n\n### Plugin Structure\n\n```javascript\n// src/plugins/plugin-name/index.js\nconst PluginName = ({ getSystem }) => ({\n  afterLoad: function,                   // Runs after plugin loads\n  components: {\n    ComponentName: Component,            // Register new components\n  },\n  wrapComponents: {\n    ComponentName: WrapperFn,            // Wrap/enhance existing components\n  },\n  rootInjects: {\n    utilityName: function,               // Inject utilities into system\n  },\n  statePlugins: {\n    pluginStateKey: {\n      actions: {},                       // Action creators\n      reducers: {},                      // Immutable.js reducers\n      selectors: {},                     // Reselect selectors\n      wrapActions: {},                   // Action middleware\n    },\n  },\n  fn: { utilityFunction: function },\n});\nexport default PluginName;\n```\n\n### Typical Plugin File Structure\n\n```\nplugin-name/\n├── index.js\n├── actions/index.js\n├── reducers.js\n├── selectors.js\n├── components/ComponentName.jsx\n├── components/ComponentName.scss\n├── extensions/other-plugin/wrap-components/ComponentWrapper.jsx\n├── after-load.js\n└── fn.js\n```\n\n### Component Wrapping Pattern\n\n```javascript\n// extensions/editor-preview/wrap-components/EditorPreviewWrapper.jsx\nconst EditorPreviewWrapper = (Original, system) => {\n  const EnhancedComponent = (props) => {\n    const isOpenAPI = system.editorSelectors.selectIsContentTypeOpenAPI();\n    if (isOpenAPI) return <EditorPreviewSwaggerUI />;\n    return <Original {...props} />;\n  };\n  return EnhancedComponent; // must return new component, not Original directly\n};\nexport default EditorPreviewWrapper;\n```\n\n### System Access in Plugins\n\n```javascript\nconst MyPlugin = (system) => {\n  const { getComponent, editorActions, editorSelectors, fn } = system;\n  const content = editorSelectors.selectEditorContent();\n  editorActions.setEditorContent('new content');\n  const MonacoEditor = getComponent('MonacoEditor');\n};\n```\n\n---\n\n## Development Workflows\n\n### Prerequisites\n- **Node.js** `>=24.22`, **npm** `>=11.19.1`, **Python 3.x** (node-gyp), **GLIBC** `>=2.29`\n- Optional: Docker or emscripten (for WASM builds)\n\n### npm Scripts\n\n| Script | Description |\n|--------|-------------|\n| `npm start` | Dev server on port 3000 (hot reload) |\n| `npm test` | Vitest unit tests (watch mode) |\n| `npm run test:run` | Vitest unit tests (single run, no watch) |\n| `npm run test:coverage` | Vitest unit tests with coverage report |\n| `npm run lint` | ESLint on all files |\n| `npm run lint:fix` | Auto-fix ESLint errors |\n| `npm run build` | Build all artifacts (app + bundles + types) |\n| `npm run build:app` | Standalone app → `/build` |\n| `npm run build:app:serve` | Serve built app on port 3050 |\n| `npm run build:bundle:esm` | ESM bundle → `/dist/esm` |\n| `npm run build:bundle:umd` | UMD bundle → `/dist/umd` |\n| `npm run build:definitions` | TypeScript definitions → `/dist/types` |\n| `npm run pw:test` | E2E tests (headless) |\n| `npm run pw:test:headed` | E2E with browser visible |\n| `npm run pw:test:ui` | Interactive Playwright UI mode |\n| `npm run pw:test:debug` | Playwright debug mode |\n| `npm run pw:report` | View test report |\n| `npm run clean` | Remove `/build` and `/dist` |\n\n### Environment Variables (`.env`, baked into build)\n\n| Variable | Description |\n|----------|-------------|\n| `VITE_VERSION` | App version displayed in the splash screen (defaults to `$npm_package_version`) |\n\n### Web Workers\n\nTwo workers handle background processing: `apidom.worker.js` (parsing/validation) and `editor.worker.js` (Monaco ops). Configure Monaco env **before** rendering:\n\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\n```\n\nWorkers must be accessible at runtime — either build them separately via webpack entry points, or copy pre-built files from `node_modules/swagger-editor/dist/umd/` using CopyWebpackPlugin.\n\n### OOM Fix for Large Builds\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\nnpm run build\n```\n\n---\n\n## Testing Strategy\n\n### Unit Testing (Vitest)\n- **Location:** `src/**/*.{spec,test}.{js,jsx,ts,tsx}`, run with `npm test` (watch) or `npm run test:run` (CI)\n- Config: `vitest.config.ts` — jsdom environment, globals enabled, `@codingame/monaco-vscode-api` inlined\n- Use `vi.fn()` for mocks; `@testing-library/jest-dom/vitest` for DOM matchers\n- ⚠️ Only 1 unit test exists: `ValidationPane.test.jsx` — heavy reliance on E2E\n\n### E2E Testing (Playwright)\n- **Location:** `test/playwright/e2e/*.spec.ts`, base URL `http://localhost:3000`\n- All tests written in TypeScript with full `@playwright/test` type safety\n\n**Existing test files:** `app.spec.ts`, `plugin.top-bar.spec.ts`, `plugin.editor-monaco.spec.ts`, `plugin.editor-monaco-yaml-paste.spec.ts`, `plugin.dropzone.spec.ts`, `plugin.validation-pane.spec.ts`, `plugin.editor-content-from-file.spec.ts`, `plugin.editor-persistence.spec.ts`, `plugin.editor-preview-*.spec.ts`, and more.\n\n**Helper functions** (`test/playwright/helpers/`):\n- Setup: `visitBlankPage()`, `waitForSplashScreen()`, `prepareAsyncAPI()`\n- Editor: `typeInEditor()`, `getAllEditorText()`, `selectAllEditorText()`\n- Menu: `clickMenu()`, `loadExample()`, `generateServer()`\n\n**E2E test template:**\n\n```typescript\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('Feature Name', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n\n  test('should do something', async ({ page }) => {\n    await page.locator('[data-testid=\"some-element\"]').click();\n    await expect(page.locator('text=Expected Text')).toBeVisible();\n  });\n});\n```\n\n---\n\n## Code Style & Conventions\n\n### ESLint (Airbnb + Prettier + jsx-a11y + @typescript-eslint)\n\nKey rules:\n- **Arrow functions** for named components — not `function` declarations\n- **File extensions required** on all JS/JSX imports: `./Component.jsx` ✅ `./Component` ❌ (`.ts`/`.tsx` exempt)\n- **JSX only in `.jsx`/`.tsx` files**\n- **Import groups:** external/builtin first (blank line), then internal\n\nFix violations: `npm run lint:fix`\n\n### Prettier\n`printWidth: 100`, `tabWidth: 2`, `semi: true`, `singleQuote: true`, `trailingComma: 'es5'`, `endOfLine: 'lf'`\n\n### Commit Messages (Conventional Commits)\n\n```\n<type>(<scope>): <subject>      ← max 69 characters\n```\n\n**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`\n\n**Scopes:** plugin name (`editor-monaco`, `top-bar`, `validation`) or area (`deps`, `build`, `release`)\n\nEnforced by commitlint via Husky pre-commit hook.\n\n### File Naming\n\n| Type | Convention | Example |\n|------|-----------|---------|\n| React Components | `PascalCase.jsx/tsx` | `MonacoEditor.jsx` |\n| Utilities | `kebab-case.js` | `import-url.js` |\n| Types | `kebab-case.d.ts` | `system.d.ts` |\n| Styles (partials) | `_kebab-case.scss` | `_monaco-editor.scss` |\n| Unit Tests | `ComponentName.test.jsx` | `ValidationPane.test.jsx` |\n| E2E Tests | `feature.spec.ts` | `plugin.editor-monaco.spec.ts` |\n\n### Styling\n- **SCSS** for all component styles; BEM-like naming (`.editor-pane__title`)\n- Partial files prefixed with `_`; global styles in `/src/styles/index.scss`\n\n### TypeScript\n- Strict mode **off** globally; gradual adoption via `typescript-strict-plugin`\n- Legacy files may use `// @ts-strict-ignore` at top; new files should aim for strictness\n- `allowJs: false` — TypeScript files only in tsconfig scope\n- Path aliases work: `import X from 'plugins/editor-monaco'` ✅\n\n---\n\n## State Management\n\nSwaggerUI's plugin-based Redux-like system with Immutable.js.\n\n### Actions\n\n```javascript\nexport const SET_EDITOR_CONTENT = 'editor_set_content';\nexport const setEditorContent = (content) => ({ type: SET_EDITOR_CONTENT, payload: content });\n\n// Async thunk\nexport const loadDefinition = (url) => async (system) => {\n  const { editorActions, fn } = system;\n  const requestId = generateRequestId();\n  editorActions.loadDefinitionRequest({ url, requestId });\n  try {\n    const content = await fn.fetchUrl(url);\n    editorActions.loadDefinitionSuccess({ content, requestId });\n  } catch (error) {\n    editorActions.loadDefinitionFailure({ error, requestId });\n  }\n};\n```\n\n### Reducers (Immutable.js)\n\n```javascript\nimport { Map } from 'immutable';\n\nexport const initialState = Map({ content: '', status: 'idle', error: null, requestId: null });\n\nconst loadSuccessReducer = (state, action) => {\n  if (state.get('requestId') !== action.payload.requestId) return state; // ignore stale\n  return state.merge({ status: 'success', content: action.payload.content, error: null });\n};\n\nexport default {\n  [SET_EDITOR_CONTENT]: (state, action) => state.set('content', action.payload),\n  [LOAD_DEFINITION_SUCCESS]: loadSuccessReducer,\n};\n```\n\n### Selectors (Reselect)\n\n```javascript\nimport { createSelector } from 'reselect';\n\nexport const selectEditorState = (state) => state.get('editor');\nexport const selectEditorContent = (state) => selectEditorState(state).get('content');\nexport const selectStatus = (state) => selectEditorState(state).get('status');\n\n// Always memoize derived state\nexport const selectValidationErrors = createSelector(\n  selectValidationResults,\n  (results) => results.filter((r) => r.severity === 'error')\n);\n```\n\n### State Access in Components\n\n```javascript\nconst MyComponent = () => {\n  const { editorSelectors, editorActions } = useSystem();\n  const content = editorSelectors.selectEditorContent();\n  const isLoading = editorSelectors.selectIsLoading();\n\n  return <div>{isLoading ? 'Loading...' : content}</div>;\n};\n```\n\n### Known Issue: Editor Content Storage\n\n> Editor content is stored in SwaggerUI `spec` plugin, causing parse/resolve/store on every keystroke → typing lag with large specs. **Future fix:** store content in editor plugin with FSM pattern in preview plugins.\n\n- Avoid unnecessary state updates in the editor\n- Debounce expensive validation triggers\n\n---\n\n## Common Tasks\n\n### Creating a New Plugin\n\n```javascript\n// src/plugins/my-plugin/index.js\nconst MyPlugin = () => ({\n  components: { MyComponent: () => <div>Hello from MyPlugin</div> },\n  statePlugins: {\n    myPlugin: {\n      initialState: Map({ data: null }),\n      actions: { myAction: (payload) => ({ type: 'MY_ACTION', payload }) },\n      reducers: { MY_ACTION: (state, action) => state.set('data', action.payload) },\n      selectors: { selectData: createSelector((s) => s.get('myPlugin'), (s) => s.get('data')) },\n    },\n  },\n});\nexport default MyPlugin;\n```\n\nThen import and add to `App.tsx` or your preset's plugin array.\n\n### Adding a Component Wrapper\n\n```javascript\n// src/plugins/my-plugin/extensions/top-bar/wrap-components/TopBarWrapper.jsx\nconst TopBarWrapper = (Original, system) => {\n  const Enhanced = (props) => {\n    const showBanner = system.myPluginSelectors.selectShowBanner();\n    return (\n      <>\n        {showBanner && <div className=\"banner\">Important Notice</div>}\n        <Original {...props} />\n      </>\n    );\n  };\n  return Enhanced;\n};\n// Register in plugin: wrapComponents: { TopBar: TopBarWrapper }\n```\n\n### Adding a New Content Type\n\n```javascript\n// Extend detection in editor-content-type plugin (order matters — specific first)\nconst detectContentType = (content) => {\n  if (/^openapi:\\s*[\"']?3\\.1/.test(content)) return 'openapi-3-1';\n  if (/^openapi:\\s*[\"']?3\\.0/.test(content)) return 'openapi-3-0';\n  if (/^swagger:\\s*[\"']?2\\.0/.test(content)) return 'openapi-2-0';\n  if (/^asyncapi:\\s*[\"']?2\\./.test(content)) return 'asyncapi-2';\n  if (/^myspec:\\s*[\"']?1\\.0/.test(content)) return 'myspec-1-0'; // custom\n  return 'unknown';\n};\n```\n\nThen create a preview plugin that wraps `EditorPreview` and conditionally renders based on `editorSelectors.selectEditorContentType()`.\n\n### Adding E2E Tests\n\n```typescript\n// test/playwright/e2e/plugin.my-feature.spec.ts\nimport { test, expect } from '@playwright/test';\nimport { visitBlankPage, waitForSplashScreen } from '../helpers';\n\ntest.describe('My Feature', () => {\n  test.beforeEach(async ({ page }) => {\n    await visitBlankPage(page);\n    await waitForSplashScreen(page);\n  });\n  test('should perform action', async ({ page }) => {\n    await page.locator('[data-testid=\"my-button\"]').click();\n    await expect(page.locator('text=Expected Result')).toBeVisible();\n  });\n});\n```\n\n### Debugging Validation\n\n```javascript\nconst { editorSelectors } = useSystem();\nconsole.log('markers:', editorSelectors.selectEditorMarkers());\nconsole.log('diagnostics:', editorSelectors.selectDiagnostics());\nconsole.log('content type:', editorSelectors.selectEditorContentType());\nconsole.log('is OpenAPI:', editorSelectors.selectIsContentTypeOpenAPI());\n```\n\n---\n\n## Important Gotchas\n\n### 1. File Extensions Required\n\n```javascript\nimport Component from './Component.jsx';  // ✅\nimport Component from './Component';      // ❌ fails linting\n```\nException: `.ts`/`.tsx` files may omit extensions due to ESLint overrides.\n\n### 2. Immutable.js State Updates\n\n```javascript\nstate.data = newValue;           // ❌ mutates state\nreturn state.set('data', val);   // ✅\nreturn state.merge({ a, b });    // ✅ multiple fields\n```\n\n### 3. Component Wrapping Return Value\n\n```javascript\nconst Wrapper = (Original, system) => Original;              // ❌ no enhancement\nconst Wrapper = (Original, system) => (props) => <Original {...props} />; // ✅\n```\n\n### 4. Request ID Race Conditions\n\nAlways include `requestId` in async actions and guard in reducers:\n```javascript\nif (state.get('requestId') !== action.payload.requestId) return state;\n```\n\n### 5. Monaco Environment Configuration\n\nMust be set **before** rendering SwaggerEditor:\n```javascript\nself.MonacoEnvironment = { baseUrl: `${document.baseURI || location.href}dist/` };\nReactDOM.render(<SwaggerEditor />, document.getElementById('root'));\n```\n\n### 6. Web Worker Path Issues\n\nWorkers must be accessible at runtime — build separately via webpack entries or copy pre-built files with CopyWebpackPlugin.\n\n### 7. Large Bundle / OOM Errors\n\n```bash\nexport NODE_OPTIONS=\"--max_old_space_size=4096\"\n```\n\n### 8. Content Type Detection Order\n\nMore specific patterns must come first — detect `3.1` before `3.0`, `3.0` before `2.0`.\n\n### 9. Selector Memoization\n\n```javascript\n// ❌ recalculates every render\nexport const selectErrors = (state) => selectResults(state).filter(r => r.severity === 'error');\n\n// ✅ memoized with createSelector\nexport const selectErrors = createSelector(selectResults, (r) => r.filter(...));\n```\n\n### 10. TypeScript Strict Mode\n\nOff globally. Use `typescript-strict-plugin` per file. New files should aim for strictness (no `@ts-strict-ignore`). Legacy files may have it.\n\n---\n\n## Key Files Reference\n\n**Core:**\n| File | Purpose |\n|------|---------|\n| `/src/App.tsx` | Main component, plugin composition |\n| `/src/index.tsx` | Browser entry point |\n| `/public/index.html` | HTML template, MonacoEnvironment setup |\n\n**Config:**\n| File | Purpose |\n|------|---------|\n| `/package.json` | Dependencies and scripts |\n| `/tsconfig.json` | TypeScript compiler options |\n| `/.eslintrc` | ESLint rules (Airbnb + Prettier + @vitest) |\n| `/.prettierrc` | Formatting rules |\n| `/.commitlintrc.json` | Commit message linting |\n| `/vite.config.js` | Vite dev server config |\n| `/vite.config.app.js` | Vite app production build config |\n| `/vitest.config.ts` | Vitest unit test config |\n| `/playwright.config.ts` | Playwright E2E config |\n\n**Docs:**\n| File | Purpose |\n|------|---------|\n| `/docs/architecture.md` | High-level architecture overview |\n| `/docs/customization/plug-points/` | Plugin customization guides |\n| `/docs/migration*.md` | Migration guides from legacy version |\n\n**Testing:**\n| File | Purpose |\n|------|---------|\n| `/test/setupTests.js` | Vitest setup (jest-dom/vitest, vitest-canvas-mock) |\n| `/test/playwright/e2e/*.spec.ts` | E2E test specs |\n| `/test/playwright/helpers/` | Playwright helper functions |\n\n**Plugin Paths:**\n- Editor: `/src/plugins/editor-textarea/`, `/src/plugins/editor-monaco/`, `/src/plugins/editor-monaco-language-apidom/`, `/src/plugins/editor-monaco-yaml-paste/`\n- Preview: `/src/plugins/editor-preview*/`\n- Support: `/src/plugins/editor-content-*/`\n- Generic: `/src/plugins/layout/`, `/src/plugins/top-bar/`, `/src/plugins/modals/`, etc.\n- Presets: `/src/presets/monaco/` (default), `/src/presets/textarea/`\n\n---\n\n## Quick Reference for AI Assistants\n\n### Fixing a Bug\n1. Identify the plugin — most bugs are plugin-specific\n2. Check `test/playwright/e2e/plugin.<name>.spec.ts` for existing coverage\n3. Review actions/reducers/selectors in that plugin\n4. Add an E2E test to prevent regression\n\n### Adding a Feature\n1. Identify affected plugins; decide new vs. extend existing\n2. Plan state management needs (new actions/reducers?)\n3. Use component wrapping to enhance without forking\n4. Write E2E test first (TDD preferred); update README if user-facing\n\n### Refactoring\n1. Follow established patterns — don't tightly couple plugins\n2. Maintain Immutable.js correctness and selector memoization\n3. Run: `npm run lint:fix && npm test && npx playwright test`\n\n### When Stuck\n1. Read the relevant plugin source — most logic lives there\n2. Check `docs/architecture.md` for high-level overview\n3. Look at similar plugins for patterns to copy\n4. Check Playwright tests to see how features are exercised\n\n### Before Committing\n- [ ] `npm run lint:fix`\n- [ ] `npm test`\n- [ ] `npx playwright test`\n- [ ] Commit message: Conventional Commits format, max 69 chars header\n- [ ] `npm run build` succeeds\n\n---\n\n**Last Updated:** 2026-05-16 | **Version:** 5.0.6 | Update this file when architecture changes significantly\n</yakaai_skills>"}