# Capability Spec: api-openapi-ui-scalar **Package**: `@suaveplan/api-openapi-ui-scalar` **Status**: ACTIVE > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: Scalar UI handler renders the host's OpenAPI document The package SHALL expose `createScalarUiHandler({ openapiUrl, theme, title })` returning an HTTP handler that responds with the Scalar HTML shell. The shell MUST reference `openapiUrl` as its document source. #### Scenario: handler returns HTML pointing at openapiUrl - WHEN the handler is invoked with `openapiUrl = "/openapi.json"` - THEN the response Content-Type is `text/html` AND the body references `/openapi.json` exactly once in the Scalar configuration block **Verified by:** `src/handler/handler.test.ts::"handler returns HTML referencing openapiUrl"` --- ### Requirement: Per-plugin handler points at filtered URL The package SHALL expose `createPerPluginScalarUiHandler({ openapiUrlForPlugin, plugin })` whose shell references the plugin-filtered URL. #### Scenario: per-plugin handler uses filtered URL - WHEN `createPerPluginScalarUiHandler({ openapiUrlForPlugin: "/openapi.json?plugin=billing", plugin: "billing" })` is invoked - THEN the response body references that filtered URL **Verified by:** `src/per-plugin/per-plugin.test.ts::"per-plugin handler embeds filtered URL"` --- ### Requirement: Host config options validated by Zod The package SHALL validate `ScalarUiOptions` via a Zod schema. Invalid configuration raises `OPENAPI_UI_SCALAR_INVALID_OPTIONS` at host boot before the handler is registered. #### Scenario: invalid options rejected at boot - WHEN host config sets `theme` to a non-allowed value - THEN handler construction throws `OPENAPI_UI_SCALAR_INVALID_OPTIONS` **Verified by:** `src/options/options.test.ts::"invalid theme is rejected by schema"` --- ### Requirement: Asset strategy supports cdn and bundled modes The package SHALL resolve the Scalar runtime asset URL from `host.openapi.uiAssets`. `"cdn"` returns the upstream CDN URL; `"bundled"` returns the path to the embedded asset under `dist/assets/`. A missing bundled asset raises `OPENAPI_UI_SCALAR_ASSETS_MISSING`. #### Scenario: cdn mode returns CDN URL - WHEN `uiAssets = "cdn"` - THEN the resolved asset URL points at the documented Scalar CDN #### Scenario: bundled mode requires file present - WHEN `uiAssets = "bundled"` AND the bundled asset is missing from `dist/assets/` - THEN handler construction throws `OPENAPI_UI_SCALAR_ASSETS_MISSING` **Verified by:** `src/assets/assets.test.ts::"cdn mode returns documented CDN URL"` AND `src/assets/assets.test.ts::"bundled mode without asset raises ASSETS_MISSING"`