## Review Desk\:&#32;a native panel and portable dialogs

Imani’s standard editor works in both terminal and RPC review flows\.&#32;Now she wants a compact local panel for quickly accepting or rejecting a long note\.

Her obstacle is portability\.&#32;A terminal component is not automatically a remotely inspectable form\.

She keeps the domain tool and standard dialogs\,&#32;then adds the panel as an optional TUI presentation\.

### Milestone\:&#32;open a focused native overlay

**Human OMP slash command—TUI only\:**

~~~text
/review-desk overlay
~~~

The supplied&#32;`ReviewPanel`&#32;supports\:

- Up\/Down to scroll\;
- `a`&#32;to accept locally\;
- `r`&#32;to reject\;
- Escape to cancel\.

The overlay does not edit the note\.&#32;Accepting from it retains the existing text\.

The body shows an eight\-line scrolling window\.&#32;Text rows are bounded to the smaller of the available width and 100 columns\.

**Exact excerpt—untrusted text handling in&#32;[panel\.ts](<https://present-sketch-tp94.here.now/examples/review-desk/panel.ts>)\:**

~~~ts
export function safeText(text: string): string {
  return replaceTabs(Bun.stripANSI(text)).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, "");
}
~~~

The note is data\.&#32;It is not trusted ANSI styling\,&#32;terminal control\,&#32;clipboard control or bidirectional layout instruction\.

#### Inspect progress

**Observed\:**

- The loader\-registered expanded custom renderer was checked at widths&#32;`0`\,&#32;`1`\,&#32;`4`\,&#32;`24`\,&#32;`80`&#32;and&#32;`160`\.
- Every rendered row stayed within the applicable bound\.
- Injected control sequences were removed\.
- Real controller\/TUI focus dispatch through a terminal emulator accepted the overlay\.
- Cleanup hid and disposed it\.
- The surrounding composer text remained unchanged\.
- Aborting a custom UI signal rejected the promise and removed the overlay\.

This was real controller and terminal\-emulator behavior with a surrounding mode fixture\.&#32;It was not a physical\-terminal visual audit or a full Seed Desk composer smoke\.

**Exercise\:**&#32;open the overlay\,&#32;scroll\,&#32;then Escape\.

**Checkpoint\:**&#32;the panel closes\;&#32;the domain records a cancelled review\.&#32;An externally aborted presentation caused by revocation\/navigation instead must not commit to another branch\.

### Choose semantic dialogs before custom controls

The portable baseline is\:

| Method | Result | Cancellation distinction |
| --- | --- | --- |
| `select(title, options, dialogOptions?)` | Selected&#32;**label**\,&#32;even for an option object | `undefined`&#32;on dismissal\/cancellation |
| `confirm(title, message, dialogOptions?)` | Boolean | Decline and cancellation both resolve false |
| `input(title, placeholder?, dialogOptions?)` | Text | `undefined`&#32;means no answer\;&#32;an empty string is a separate value |
| `editor(title, prefill?, dialogOptions?, editorOptions?)` | Multiline text | `undefined`&#32;means cancelled |
| Optional&#32;`askDialog(questions, dialogOptions?)` | A structured ask result | Can be cancelled\,&#32;submitted\,&#32;or redirected to chat |

`input`’s second argument is a placeholder\,&#32;not an initial document\.&#32;`editor`’s second argument is the prefill\.&#32;Its fourth argument may set&#32;`promptStyle`\.

Feature\-detect&#32;`askDialog`\;&#32;do not assume it exists because&#32;`hasUI`&#32;is true\.

### Rich ask data

A rich ask question contains\:

- `id`\:&#32;stable question identity\;
- `question`\:&#32;full question text\;
- optional&#32;`header`\;
- `options`\;
- optional&#32;`multi`\;
- optional zero\-based&#32;`recommended`\.

Each option has&#32;`label`\,&#32;optional&#32;`description`\,&#32;and optional&#32;`preview`\.

A submitted result has&#32;`kind: "submit"`&#32;and ordered&#32;`results`\.&#32;Each result item contains\:

- `id`\,&#32;`question`\;
- `options`&#32;as labels\;
- `multi`\;
- `selectedOptions`\;
- optional&#32;`customInput`\,&#32;`note`\,&#32;`timedOut`\.

`kind: "chat"`&#32;means the user chose to discuss the question\.&#32;It is not cancellation and it is not an answer\.

ACP’s rich ask adapter can produce recommended fallback answers marked&#32;`timedOut: true`\.&#32;A permission system must not interpret such a fallback as explicit consent\.

Descriptions and previews are presentation capabilities\,&#32;not a guarantee that every protocol adapter transmits them\.&#32;For example\,&#32;the supplied ACP basic select translates labels to an enum\;&#32;its rich ask path carries richer descriptions\.

### Dialog options are not universally portable

`ExtensionUIDialogOptions`&#32;includes all of the following\:

| Family | Members | Use |
| --- | --- | --- |
| Cancellation and timing | `signal`\,&#32;`timeout`\,&#32;`onTimeout`\,&#32;`onTimeoutStart`\,&#32;`onTimeoutReset` | Cancel work and observe a host\-managed timeout |
| Initial selector presentation | `initialIndex`\,&#32;`outline`\,&#32;`helpText` | Position and explain a TUI selector |
| Selector actions | `onLeft`\,&#32;`onRight`\,&#32;`onExternalEditor` | TUI\-specific callbacks |
| Selection markers | `selectionMarker`\,&#32;`checkedIndices`\,&#32;`markableCount` | Radio\/checkbox presentation for leading options |

Timeouts are milliseconds\.&#32;`timeoutStartsOnPresentation`&#32;is an optional UI capability\:&#32;the TUI sets it true so a queued selector’s timeout need not expire before it appears\.

Do not infer that every option applies to every method\.&#32;In particular\,&#32;the supplied standard multiline editor path honors cancellation but does not implement the same dialog timeout plumbing as the selector\/input paths\.

### RPC is semantic UI\,&#32;not a terminal

The current wired RPC extension context reports&#32;`hasUI: true`&#32;and&#32;`mode: "rpc"`\.&#32;This outranks the stale type comment saying RPC has no UI\.

The protocol uses requests and correlated responses\.

**RPC client input—request the human review command\:**

~~~json
{"type":"prompt","id":"review-1","message":"/review-desk review"}
~~~

The client must continue reading output\,&#32;present the emitted editor\/selector requests\,&#32;and reply using each request’s actual ID\.&#32;It must not wait for the review to finish before processing the dialog requests needed to finish it\.

**Illustrative RPC response shapes—`dialog-1`&#32;represents an ID received from the server\,&#32;not a reusable workbook ID\:**

~~~json
{"type":"extension_ui_response","id":"dialog-1","value":"Accept locally"}
~~~

~~~json
{"type":"extension_ui_response","id":"dialog-1","confirmed":true}
~~~

~~~json
{"type":"extension_ui_response","id":"dialog-1","cancelled":true}
~~~

Use the appropriate variant\,&#32;not all three\.

RPC specifics\:

- Select sends string&#32;`options`\;&#32;optional&#32;`optionDetails`&#32;align descriptions by position\.
- Select returns a label\,&#32;not an index\.
- Select\,&#32;confirm and input transmit timeout information\.
- Editor transmits&#32;`title`\,&#32;`prefill`&#32;and optional&#32;`promptStyle`\;&#32;it has no timeout field or local editor timeout scheduler in this adapter\.
- Aborting an active dialog emits&#32;`method: "cancel"`&#32;with&#32;`targetId`&#32;equal to the original request ID\.
- Pre\-aborted signals suppress presentation\.
- Disconnect rejects pending and future requests\.
- Local select\/confirm\/input timer expiry can settle without a cancel frame\,&#32;so clients must honor the transmitted timeout too\.

RPC supports fire\-and\-forget notifications\,&#32;status\,&#32;string\-array widgets and editor\-text requests\.&#32;Title emission is opt\-in through&#32;`PI_RPC_EMIT_TITLE=1`\.

It does not serialize TUI component factories\.&#32;`custom()`&#32;returns without invoking the factory\.&#32;Component widgets\,&#32;terminal listeners\,&#32;custom editor replacement and autocomplete factories are unsupported there\.

### Passive presentation is not persistence

| UI method | Useful role | Current implementation boundary |
| --- | --- | --- |
| `notify` | Brief result or warning | Not a durable domain record |
| `setStatus(key, text)` | Small named status indicator | Clear with&#32;`undefined` |
| `setWorkingMessage(message?)` | Streaming activity wording | TUI support\;&#32;RPC\/ACP inert |
| `setWidget(key, content, options?)` | Summary above\/below editor | TUI supports strings or factories\;&#32;RPC supports strings only |
| `setTitle` | Terminal\/window title | Not the persisted session name |
| `setFooter`\,&#32;`setHeader` | Advertised component replacement surfaces | No\-op in the supplied TUI controller\,&#32;as well as RPC\/ACP |
| `custom(factory, options?)` | Focused terminal component | Guard with&#32;`ctx.mode === "tui"` |

For string widgets\,&#32;the TUI takes the first ten supplied strings and adds a truncation notice when necessary\.&#32;This is not a promise that arbitrary long strings wrap into only ten terminal rows\.

`ExtensionCustomOptions`&#32;contains\:

- `overlay`\;
- static or lazy&#32;`overlayOptions`\;
- `onHandle`\,&#32;receiving an overlay handle\;
- `signal`\.

A component should implement&#32;`render(width)`&#32;and&#32;`invalidate()`\,&#32;optionally input handling and&#32;`dispose()`\.&#32;Cleanup must be idempotent\.&#32;Review Desk’s panel becomes inert after disposal\.

### What changes next\?

If Imani needs screen\-reader\-friendly or remotely operated review controls\,&#32;she should extend the&#32;**semantic domain interface**&#32;or use host\-supported forms\.&#32;A custom terminal drawing does not become accessible merely because it is visible\.

`review_desk inspect/query/act`&#32;is the agent\-facing interface\.&#32;The panel is one human\-facing view of that interface\.

*Source\,&#32;snapshot 2026\-08\-29\:&#32;`packages/coding-agent/src/extensibility/extensions/types.ts`\,&#32;UI interfaces\;&#32;`packages/coding-agent/src/modes/controllers/extension-ui-controller.ts`\,&#32;`showHookCustom`\,&#32;`#presentDialog`\;&#32;`packages/coding-agent/src/modes/rpc/rpc-mode.ts`\;&#32;linked Review Desk panel\.*
