# @comvi/core/devtools

API reference for @comvi/core/devtools: devtools(), attachDevtools, the window.__COMVI__ discovery queue, and the @comvi/core/editor-bridge helpers.

Canonical: https://comvi.io/docs/i18n/api/core-devtools

Browser-extension discovery for a [`@comvi/core`](/docs/i18n/api/core) instance, plus the
editor bridge the in-context editor and the SSR adapters share.

## Exports

- `@comvi/core/devtools`: `devtools`, `attachDevtools`; types `DevtoolsOptions`, `ComviQueue`,
`ComviQueueEntry`, `ComviHook`.
- `@comvi/core/editor-bridge`: `EDITOR_MAPPINGS_GLOBAL`, `EDITOR_INITIAL_MAPPINGS_GLOBAL`,
`readEditorMappings`, `toRecordOfNumbers`; type `InContextEditorMappings`.

## `devtools(options?)`

```ts
function devtools(options?: DevtoolsOptions): <T extends I18n<any>>(i18n: T) => T
function attachDevtools<T extends I18n<any>>(i18n: T, options?: DevtoolsOptions): T
```

| Option | Type | Default | Meaning |
| --- | --- | --- | --- |
| `instanceId` | `string` | `comvi-<n>` when exposing | Stable id for the instance |
| `exposeGlobal` | `boolean` | `true` in a browser, `false` under SSR | Publish the instance on `window.__COMVI__`; `false` also leaves `instanceId` unset |

When exposing, assigns `instanceId`, publishes the instance on the discovery queue and removes it
on `destroy()`; with `exposeGlobal: true` and no `window` it assigns the id and publishes nothing.
It adds no public member, so the instance type comes back unchanged. An instance
that already has an id keeps it. One composed with `exposeGlobal: false` can be exposed later
by a second `.use(devtools({ exposeGlobal: true }))`.

`src/devtools-instance.ts`

```ts

export const i18n = createI18n({ locale: 'en' }).use(devtools({ instanceId: 'app' }));

i18n.instanceId; // "app" in a browser, undefined under SSR
```

## `window.__COMVI__`

```ts
interface ComviQueueEntry {
v: string; // core version that published the entry
i: I18n;
}
interface ComviHook {
push(entry: ComviQueueEntry): void;
remove(entry: ComviQueueEntry): void;
}
type ComviQueue = ComviQueueEntry[] | ComviHook
```

The global is an array of entries, or a hook object an extension swapped in. Removal on
`destroy()` is by entry identity. A failure inside discovery is swallowed, so it never breaks
construction or teardown.

## `@comvi/core/editor-bridge`

```ts
const EDITOR_MAPPINGS_GLOBAL: '__comviInContextEditorMappings'
const EDITOR_INITIAL_MAPPINGS_GLOBAL: '__comviInContextEditorInitialMappings'
function readEditorMappings(instance: unknown): InContextEditorMappings | undefined
function toRecordOfNumbers(value: unknown): Record<string, number> | undefined

interface InContextEditorMappings {
getKeyMappings(): Record<string, number>;
loadKeyMappings(mappings: Record<string, number>): void;
}
```

| Export | Meaning |
| --- | --- |
| `EDITOR_MAPPINGS_GLOBAL` | Property on the instance the editor plugin writes the bridge under |
| `EDITOR_INITIAL_MAPPINGS_GLOBAL` | Property on the instance an SSR adapter writes server mappings under, consumed once |
| `readEditorMappings(i18n)` | The bridge, or `undefined` when absent or malformed |
| `toRecordOfNumbers(value)` | Validates untrusted payload state; `undefined` if any entry is not a finite number |

## Read next

- [`@comvi/core`](/docs/i18n/api/core): the i18n instance this capability composes onto.
- [In-context editor](/docs/i18n/plugins/in-context-editor): the plugin that uses discovery and the editor bridge.
