Skip to content

chore(ts): migrate editor-bridge to TypeScript#48

Merged
oxyc merged 1 commit into
mainfrom
chore/ts-editor-bridge
May 29, 2026
Merged

chore(ts): migrate editor-bridge to TypeScript#48
oxyc merged 1 commit into
mainfrom
chore/ts-editor-bridge

Conversation

@oxyc

@oxyc oxyc commented May 29, 2026

Copy link
Copy Markdown
Member

PR 2 of 4 for #41. No behaviour change — pure type addition over the existing logic. editor-bridge.jseditor-bridge.ts.

wp.* globals

window.wp.data / window.wp.blocks typed with the narrow surface we actually touch (getBlock, replaceBlocks, insertBlocks, updateBlockAttributes, replaceBlock, getSettings, parse, serialize, createBlock, getBlockType). Keyed select() / dispatch() overloads return the precise store type for the core/block-editor / core/editor / core/edit-post cases and unknown otherwise.

The official @types/wordpress__data is too abstract for direct select("core/block-editor") use (it just says dispatch<T> / select<T>) — this local declaration is just what we read here.

Tool I/O

Discriminated input + result types so future call-sites get autocomplete and consumers can refine on kind / error:

  • EditorToolName union of all 9 editor__* names
  • Per-tool input interfaces (ReplaceBlocksInput, InsertBlocksInput, UpdateBlockAttributesInput, UpdatePostInput, RecoverBlockInput, QueryDomInput, FocusInput, OpenSidebarInput)
  • EditorToolResult — common { error?, ok?, diff?, ...rest }
  • DiffSnapshot — the before/after the tool-call card renders
  • EditorContext — the selection summary sent with each chat request

Internal helpers (Block, MediaItem, ResolvedMedia, OutlineEntry, SidebarEntry, etc.) get local interfaces too.

Verification

  • npm run typecheck — clean
  • npm run build — bundle includes editor__* tool names and the executeClientTool dispatch
  • npm run lint:js — clean on this file. Two warnings (no-nested-ternary line 1067, no-unused-vars-before-return line 1271) carried over from the JS source; they were already there.

use-runtime-adapter.js imports { getEditorContext, executeClientTool } without an extension — webpack + TS resolve it to the .ts file automatically, no caller changes needed.

Follow-up (tracked in #41)

  • PR 3: types/runtime.ts shared module
  • PR 4: use-runtime-adapter.ts

🤖 Generated with Claude Code

PR 2 of 4 for #41. No behaviour change — pure type addition over the
existing logic. Renames `editor-bridge.js` → `editor-bridge.ts` and adds
inline types for:

## wp.* globals

`window.wp.data` / `window.wp.blocks` typed with the narrow surface we
actually touch (`getBlock`, `replaceBlocks`, `insertBlocks`,
`updateBlockAttributes`, `replaceBlock`, `getSettings`, `parse`,
`serialize`, `createBlock`, `getBlockType`). Keyed `select()` /
`dispatch()` overloads return the precise store type for the
`core/block-editor` / `core/editor` / `core/edit-post` cases and
`unknown` otherwise. The official `@types/wordpress__data` is too
abstract for direct `select("core/block-editor")` use — this local
declaration is just what we read.

## Tool I/O

Discriminated input + result types so future call-sites get autocomplete
and consumers can refine on `kind`/`error`:

- `EditorToolName` union of all 9 `editor__*` names
- Per-tool input interfaces (`ReplaceBlocksInput`, `InsertBlocksInput`,
  `UpdateBlockAttributesInput`, `UpdatePostInput`, `RecoverBlockInput`,
  `QueryDomInput`, `FocusInput`, `OpenSidebarInput`)
- `EditorToolResult` — common `{ error?, ok?, diff?, ...rest }`
- `DiffSnapshot` — the before/after the tool-call card renders
- `EditorContext` — the selection summary sent with each chat request

Internal helpers (`Block`, `MediaItem`, `ResolvedMedia`, `OutlineEntry`,
`SidebarEntry`, etc.) get local interfaces too.

## Verification

- `npm run typecheck` — clean
- `npm run build` — bundle includes `editor__*` tool names and the
  `executeClientTool` dispatch (`grep "editor__replace_blocks"
  build/admin-chat.js` finds them)
- `npm run lint:js` — clean on this file; the two `editor-bridge.ts`
  warnings (`no-nested-ternary` line 1067 + `no-unused-vars-before-
  return` line 1271) carry over from the JS source unchanged and live
  on as pre-existing warnings in the JS source too

`use-runtime-adapter.js` imports
`{ getEditorContext, executeClientTool }` without an extension — webpack
+ TS resolve it to the `.ts` file automatically, no caller changes
needed.

## Follow-up

- PR 3 (#41): `types/runtime.ts` shared module
- PR 4 (#41): `use-runtime-adapter.ts`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxyc oxyc merged commit 151e9ff into main May 29, 2026
3 checks passed
@oxyc oxyc deleted the chore/ts-editor-bridge branch May 29, 2026 21:55
oxyc added a commit that referenced this pull request May 29, 2026
PR 4 of 4 for #41 — the big one. No behaviour change; pure type
addition over the existing logic. Renames
`use-runtime-adapter.js` → `use-runtime-adapter.ts` and wires in the
shared `RuntimeEvent`, `UiMessage`, `WireMessage`, `UiContentPart`,
`WireContentBlock`, and `SessionUsageSnapshot` types from PR 3.

## Hook signature

`useAssistantRuntime()` now returns a typed `AssistantRuntimeHandle`:

```ts
interface AssistantRuntimeHandle {
  runtime: ReturnType<typeof useExternalStoreRuntime>;
  loadConversation: (uuid: string) => Promise<void>;
  approveToolCall: (options?: { trustHost?: boolean }) => void;
  denyToolCall: () => void;
  pendingApprovals: PendingApproval[];
  undoableActions: Record<string, UndoableActionState>;
  undoAction: (toolCallId: string, auditId: number) => Promise<void>;
  retryToolCall: (toolCallId: string) => Promise<void>;
  retryingIds: Set<string>;
}
```

## Globals

`window.gdsAssistant` is now declared via `declare global` with the
`GdsAssistantGlobal` interface: localised config (`restUrl`, `restBase`,
`nonce`, `modelPricing`) plus the two hooks we attach ourselves
(`sendChatMessage`, `openChat`). Eliminates the silent `as any` access
pattern.

## SSE event narrowing

The big `for await (const event of parseSSE(...))` switch now narrows
on `event.type` against the discriminated `RuntimeEvent` union, so each
case's `event.data` is the precise per-event shape (no more `event.data
.input_tokens` reads against `mixed`). `parseSSE` itself returns
`AsyncGenerator<RuntimeEvent>` rather than a loose object stream.

## Type interop boundary

assistant-ui's `ExternalStoreAdapter` types `onNew`/`onEdit` with its
own (narrower) `AppendMessage` shape. Our adapter accepts a superset
(control messages, client-tool-result resumes), so we cast at the
`useExternalStoreRuntime(adapter as …)` boundary rather than weakening
the internal `OnNewMessage` type. Comment explains why.

## Test helper

`restorePendingApprovalsFromHistory` keeps its existing signature
(`WireMessage[] | unknown`) and the Jest unit test continues to pass
unchanged.

## Consumers

`app.jsx`, `components/Composer.jsx`, `components/SidePanels.jsx`,
`components/Messages.jsx`, `components/Thread.jsx` all import without
an extension — webpack + TS resolve to the `.ts` file automatically. No
caller changes needed.

## Verification

- `npm run typecheck` — clean
- `npm run build` — bundle includes `useAssistantRuntime`,
  `loadConversation`, `approveToolCall`
- `npm run test:unit` — 17/17 pass
- `npm run lint:js` — only the pre-existing `no-nested-ternary` /
  `no-unused-vars-before-return` warnings carried over from the JS
  source; same in `editor-bridge.ts`

## Wraps up #41

PR 1 (#47): setup + UndoContext smoke
PR 2 (#48): editor-bridge.ts
PR 3 (#49): types/runtime.ts shared module
PR 4 (this): use-runtime-adapter.ts

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant