@molecule/app-ide-react 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,2751 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:51:04.596Z
7
+ -->
8
+
9
+ # @molecule/app-ide-react
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ `@molecule/app-ide-react` — React components for an AI-powered IDE
16
+ workspace: `WorkspaceLayout` (resizable panel row), `ChatPanel` (streaming
17
+ AI chat with tool-call cards, @ file mentions, / commands), `EditorPanel`
18
+ (tabbed Monaco editor), `PreviewPanel` (live-preview iframe with device
19
+ frames + crash/blank recovery), `FileExplorer`, `CommandPalette`,
20
+ `QuickOpen`, `TabBar`, plus `registerCustomEventCard()` for app-specific
21
+ chat cards and `useKeyboardShortcuts()`.
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { ChatPanel, EditorPanel, PreviewPanel, WorkspaceLayout } from '@molecule/app-ide-react'
27
+
28
+ ;<WorkspaceLayout>
29
+ <ChatPanel
30
+ projectId="proj_abc123"
31
+ onFileOpen={(path) => console.log('open', path)}
32
+ onFileChange={(path, content) => console.log('changed', path, content.length)}
33
+ onReadyToBuild={() => console.log('boot sandbox')}
34
+ />
35
+ <EditorPanel
36
+ onActiveFileChange={(path) => console.log('active', path)}
37
+ onFixWithAI={(req) => console.log('fix', req)}
38
+ />
39
+ <PreviewPanel onPreviewError={(errs) => console.error(errs)} />
40
+ </WorkspaceLayout>
41
+ ```
42
+
43
+ ## Type
44
+
45
+ `feature`
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ npm install @molecule/app-ide-react @molecule/app-ai-chat @molecule/app-ai-models @molecule/app-ai-voice @molecule/app-code-editor @molecule/app-country-flags @molecule/app-i18n @molecule/app-icons @molecule/app-ide @molecule/app-live-preview @molecule/app-logger @molecule/app-react @molecule/app-storage @molecule/app-ui @molecule/app-ui-react material-file-icons react react-dom
51
+ npm install -D @types/react
52
+ ```
53
+
54
+ ## API
55
+
56
+ ### Interfaces
57
+
58
+ #### `Activity`
59
+
60
+ A single captured activity. Mirrors the SSE `activity.activity` payload; the
61
+ REST list endpoint additionally returns `payload` and `result` for the
62
+ expanded detail view.
63
+
64
+ ```typescript
65
+ interface Activity {
66
+ id: string
67
+ type: ActivityType
68
+ status: ActivityStatus
69
+ recipient?: string
70
+ summary?: string
71
+ /** ISO 8601 timestamp. */
72
+ timestamp: string
73
+ /** Full captured payload — only present on the REST detail response (dev only). */
74
+ payload?: unknown
75
+ /** Provider result / synthetic success record — only present on the REST detail response. */
76
+ result?: unknown
77
+ }
78
+ ```
79
+
80
+ #### `ActivityCardProps`
81
+
82
+ Props for the inline {@link ActivityCard}.
83
+
84
+ ```typescript
85
+ interface ActivityCardProps {
86
+ /** The captured activity to render. */
87
+ activity: Activity
88
+ /** Called when the card is clicked — should open the Activity panel filtered to this activity. */
89
+ onActivityClick?: (activity: Activity) => void
90
+ }
91
+ ```
92
+
93
+ #### `AutoCommitState`
94
+
95
+ The countdown's state.
96
+
97
+ `intervalSeconds` is the configured cadence (`0` = disabled). `remaining` is
98
+ the live count: a positive number while counting down, `0` at the instant a
99
+ commit is due, and `null` while disabled or paused (after a commit, awaiting
100
+ the next file change to re-arm).
101
+
102
+ ```typescript
103
+ interface AutoCommitState {
104
+ /** Configured countdown length in seconds; `0` when auto-commit is off. */
105
+ intervalSeconds: number
106
+ /** Seconds left until the next auto-commit; `null` when disabled or paused. */
107
+ remaining: number | null
108
+ }
109
+ ```
110
+
111
+ #### `ChatEventCard`
112
+
113
+ A chat system card: a short message with an optional action (or actions). Mirrors
114
+ the system-card shape ChatPanel renders for upgrade prompts, guest reminders, etc.
115
+
116
+ ```typescript
117
+ interface ChatEventCard {
118
+ /** The card's text. */
119
+ text: string
120
+ /** An optional action button (or buttons): a link (`href`) and/or a click handler. */
121
+ action?: ChatEventCardAction | ChatEventCardAction[]
122
+ /**
123
+ * Composable inline body for a `tone` (tip) card: an ordered list of segments rendered
124
+ * in sequence — plain strings as text, {@link ChatEventCardAction}s as inline underlined
125
+ * links — so prose and links interleave freely (e.g. text → an inline link → a trailing
126
+ * period). When set, the renderer uses this INSTEAD of `text` + appended `action`s, so a
127
+ * link can sit mid-sentence rather than only at the end. Segments carry their own spacing
128
+ * (no auto-space is inserted between them). Keep `text` populated with a plain-text
129
+ * equivalent for accessibility / non-toned consumers. Only honored when `tone` is set.
130
+ */
131
+ content?: ChatEventCardSegment[]
132
+ /**
133
+ * When true, ChatPanel renders the card as a stand-out tip box rather than muted inline
134
+ * text. Prefer setting {@link ChatEventCard.tone} (which implies emphasis AND picks the
135
+ * accent colour + icon); `emphasized` without a `tone` falls back to the neutral `info`
136
+ * tone. The app opts in; the shared package never infers emphasis from a card's copy.
137
+ */
138
+ emphasized?: boolean
139
+ /**
140
+ * The card's tip TONE — picks its accent colour + default icon so every notice card
141
+ * shares ONE consistent box (icon + tinted body + a uniform 1px border + actions),
142
+ * differing only by colour/icon per kind:
143
+ * - `info` — blue, info glyph (neutral notice)
144
+ * - `gold` — amber, lightbulb (an honest tip / onboarding note)
145
+ * - `upgrade` — amber, clock (a plan/limit/budget nudge)
146
+ * - `success` — green, check (a completed action, e.g. a saved script)
147
+ * - `signup` — primary, sign-in (an auth nudge)
148
+ *
149
+ * Setting `tone` implies emphasis. Cards that supply composable {@link ChatEventCard.content}
150
+ * render their inline links in the box; cards that supply `action`(s) render them as a
151
+ * consistent row of accent buttons. Omit `tone` (and `emphasized`) for a plain muted line.
152
+ */
153
+ tone?: 'info' | 'gold' | 'upgrade' | 'success' | 'signup'
154
+ /**
155
+ * Optional icon-name override (a `@molecule/app-icons` glyph) — defaults to the tone's
156
+ * icon. Use only a name that exists in the bonded set (`getIcon` throws otherwise);
157
+ * sets with extra glyphs register them via `CustomIconNames` augmentation.
158
+ */
159
+ icon?: IconName
160
+ }
161
+ ```
162
+
163
+ #### `ChatEventCardAction`
164
+
165
+ A single call-to-action on a chat card: a labelled link (`href`) and/or click
166
+ handler. The app supplies any route/copy — the shared package never hardcodes one.
167
+
168
+ ```typescript
169
+ interface ChatEventCardAction {
170
+ /** Button label (already localized by the app). */
171
+ label: string
172
+ /** Link target. App-owned — e.g. the host's own pricing/auth route. */
173
+ href?: string
174
+ /** Click handler (alternative to, or alongside, `href`). */
175
+ onClick?: () => void
176
+ /**
177
+ * Render the action's label as inline monospace code — a command/identifier like
178
+ * `/report` or a skill name — so it stands out from prose while staying clickable.
179
+ */
180
+ code?: boolean
181
+ }
182
+ ```
183
+
184
+ #### `ChatEventCardCode`
185
+
186
+ A non-interactive inline monospace code span in a card body — a command or identifier
187
+ the prose refers to (`/report`, a skill name) that should read as code but isn't
188
+ clickable. For a _clickable_ command, use {@link ChatEventCardAction} with `code: true`.
189
+
190
+ ```typescript
191
+ interface ChatEventCardCode {
192
+ /** The code text, rendered monospaced/tinted. */
193
+ code: string
194
+ }
195
+ ```
196
+
197
+ #### `ChatMessageItemProps`
198
+
199
+ Properties for the chat message item component.
200
+
201
+ ```typescript
202
+ interface ChatMessageItemProps {
203
+ message: ChatMessage
204
+ className?: string
205
+ }
206
+ ```
207
+
208
+ #### `ChatPanelProps`
209
+
210
+ Props for the {@link ChatPanel} component — the IDE chat surface plus the
211
+ callbacks the host app uses to react to AI activity (file changes, boot, client
212
+ actions, etc.).
213
+
214
+ ```typescript
215
+ interface ChatPanelProps {
216
+ projectId: string
217
+ endpoint?: string
218
+ /** If provided, auto-send this message once on mount (e.g., prompt from landing page). */
219
+ initialMessage?: string
220
+ /** Called after the initial message has been sent — used to clear router state. */
221
+ onInitialMessageSent?: () => void
222
+ /** Called to open a file as a preview tab. `opts.focus === false` opens it quietly (no pane switch) — e.g. a saved plan or a system-initiated open while the user is busy. */
223
+ onFileOpen?: (path: string, opts?: { focus?: boolean }) => void
224
+ /** Called when a filename in a tool call is double-clicked — should pin the tab. */
225
+ onFileDoubleClick?: (path: string) => void
226
+ /** Called when a file in the uncommitted list is clicked for diff view. */
227
+ onFileDiff?: (path: string, diff?: { original: string; modified: string }) => void
228
+ /** Called to undo/redo a file change — writes the given content to the file path. */
229
+ onFileRevert?: (path: string, content: string) => Promise<void>
230
+ /** Called when the AI creates or modifies a file — should refresh the editor if the file is open. */
231
+ onFileChange?: (path: string, content: string) => void
232
+ /** Called when a file is removed from disk (e.g. reverting an untracked file). */
233
+ onFileDeleted?: (path: string) => void
234
+ /** Called after a successful commit — should refresh file explorer git status. */
235
+ onCommit?: () => void
236
+ /** Called when an inline activity card is clicked — should open the Activity panel filtered to this activity. */
237
+ onActivityClick?: (activity: ActivityFromCard) => void
238
+ /**
239
+ * Called when a user avatar in the chat timeline is clicked — the host opens
240
+ * that user's profile (e.g. molecule.dev's profile modal). Receives the clicked
241
+ * user's {@link ChatUserIdentity}. Omit it (the default) to render the avatars
242
+ * non-interactive (static image/icon, exactly as before). Only real user
243
+ * avatars are clickable — the molecule glyph on auto-sent messages is not.
244
+ */
245
+ onProfileClick?: (user: ChatUserIdentity) => void
246
+ /** Called when the server signals (via the `ready_to_build` stream event) that discovery is complete and the sandbox should boot. */
247
+ onReadyToBuild?: () => void
248
+ /**
249
+ * True while the plan has finished streaming but the sandbox is still booting
250
+ * (after `ready_to_build`, before the post-boot build kickoff). When set and no
251
+ * message is actively streaming, the chat shows a "waiting for the development
252
+ * environment" indicator so the conversation doesn't appear to silently stall.
253
+ */
254
+ awaitingSandboxBoot?: boolean
255
+ /** Called when the agent requests a UI action via the `client_action` stream event (reload/navigate the preview, open a file). */
256
+ onClientAction?: (action: IdeClientAction) => void
257
+ /** Called on each stream `done` — host uses it to keep the boot view up until the parallel during-boot plan stream finishes. */
258
+ onTurnComplete?: () => void
259
+ /**
260
+ * Called whenever the chat's loading state changes — true when a turn (plan or build) is in
261
+ * progress, false when idle. The host uses this as the authoritative "the agent is actively
262
+ * building" signal to drive the preview's "Building your app…" overlay, so a half-built /
263
+ * blank preview during a long build always shows progress instead of a bare white screen.
264
+ */
265
+ onLoadingChange?: (loading: boolean) => void
266
+ /**
267
+ * Navigates the live preview to a route path. Wired so a `[label](/route)` markdown link in
268
+ * an assistant message (e.g. the agent's "your app is ready" handoff) jumps the preview to
269
+ * that page on click. User-initiated, so the host should navigate unconditionally (it is not
270
+ * the rate-limited agent `navigate_preview` action).
271
+ */
272
+ onNavigatePreview?: (path: string) => void
273
+ /**
274
+ * Called on mount with a handler the parent invokes to deliver a broadcast chat event
275
+ * from another project member (the push channel); called with null on unmount.
276
+ */
277
+ onRegisterPushHandler?: (
278
+ handler: ((conversationId: string, event: ChatStreamEvent) => void) | null,
279
+ ) => void
280
+ /** Changing this value submits the current input draft — used to send a prefilled prompt after the prompt→chat morph docks. */
281
+ autoSubmitSignal?: number
282
+ /** Seeds the input with this text on mount (prompt→chat morph), so the chat input shows the prompt before it is sent. */
283
+ initialInputValue?: string
284
+ /** Hide the conversation-selector header (e.g. during discovery, before any history is worth showing). */
285
+ hideConversationMenu?: boolean
286
+ /**
287
+ * Whether to render the built-in conversation header (the picker + searchable
288
+ * history dropdown, the share / bug-report / settings buttons, and the
289
+ * new-chat "+"). Defaults to `true` (the package owns that chrome). Pass
290
+ * `false` to operate **headless** — the host renders those controls itself
291
+ * (e.g. molecule.dev's Workspace top bar) and drives the chat through the
292
+ * controlled props below: {@link ChatPanelProps.conversationId} /
293
+ * {@link ChatPanelProps.chatKey} / {@link ChatPanelProps.onConversationId} for
294
+ * the conversation, and {@link ChatPanelProps.openShareSignal} /
295
+ * {@link ChatPanelProps.openReportSignal} to open the in-chat modals.
296
+ */
297
+ renderConversationHeader?: boolean
298
+ /**
299
+ * Host-controlled active conversation id (headless mode). Drives the chat
300
+ * endpoint's `?conversationId=`. When `undefined` (the default) the panel owns
301
+ * the active conversation internally (localStorage-backed). `null` is a valid
302
+ * controlled value meaning "no conversation yet".
303
+ */
304
+ conversationId?: string | null
305
+ /**
306
+ * Host-controlled remount key for the inner chat (headless mode). Changing it
307
+ * remounts the conversation timeline (a new chat or a switch); the backend
308
+ * assigning an id mid-stream must NOT change it (that would drop in-flight
309
+ * messages). Falls back to the internal key when omitted.
310
+ */
311
+ chatKey?: string
312
+ /**
313
+ * Called whenever the active conversation id changes — the backend assigns one
314
+ * mid-stream and the host needs it to keep its own picker in sync WITHOUT
315
+ * remounting (do not change {@link ChatPanelProps.chatKey} in response).
316
+ */
317
+ onConversationId?: (id: string | null) => void
318
+ /** Changing this opens the in-chat `/share` modal (host-driven, e.g. a top-bar share button). Overrides the built-in header's share button signal. */
319
+ openShareSignal?: number
320
+ /** Changing this opens the in-chat `/report` modal (host-driven). Overrides the built-in header's bug-report button signal. */
321
+ openReportSignal?: number
322
+ /** Changing this opens the in-chat `/settings` view (host-driven). Overrides the built-in header's settings button signal. */
323
+ openSettingsSignal?: number
324
+ /**
325
+ * When provided, the `/model` picker shows an "Add or manage your own
326
+ * models…" row at the bottom of the list; choosing it closes the picker and
327
+ * invokes this callback (the host opens its own custom-provider management
328
+ * surface). Omit to hide the row — the shared package stays host-agnostic.
329
+ */
330
+ onManageCustomModels?: () => void
331
+ /** Spinner/busy indicator node to show for in-chat loading states (e.g. the "designing" indicator). Falls back to a built-in dots animation. */
332
+ spinner?: ReactNode
333
+ /** Path of the currently focused file in the editor (shown first in @ picker). */
334
+ activeFile?: string | null
335
+ /** Paths of all open editor tabs (shown after active file in @ picker). */
336
+ openTabs?: string[]
337
+ /** Incremented to trigger a git status refresh (e.g. after file create/rename/delete). */
338
+ gitStatusTick?: number
339
+ /** Message to auto-send (e.g. from "Fix with AI"). Sent when pendingMessageKey changes. */
340
+ pendingMessage?: string
341
+ /** Incremented to trigger sending pendingMessage. */
342
+ pendingMessageKey?: number
343
+ /** When true, the pending message is sent on the user's behalf (e.g. the post-boot build kickoff) and is NOT shown as a user bubble — phase markers convey what's happening instead. */
344
+ pendingMessageSuppressUser?: boolean
345
+ /**
346
+ * When true, the pending message was directly requested by the user (e.g. the
347
+ * editor's or broken-preview overlay's "Fix with AI" button) rather than
348
+ * dispatched autonomously (preview-health / preview-error auto-fix). A user
349
+ * Stop suppresses autonomous automatic sends until the user re-engages; a
350
+ * user-initiated pending message IS that re-engagement, so it always sends.
351
+ */
352
+ pendingMessageUserInitiated?: boolean
353
+ /** File path the user just edited in the editor — triggers auto-deletion of queued autofix messages. */
354
+ userEditedFile?: string
355
+ /** Incremented to trigger the user-edit check (same path may be edited multiple times). */
356
+ userEditedFileKey?: number
357
+ /**
358
+ * Whether the current user is anonymous. The shared IDE no longer renders any
359
+ * built-in sign-up/guest card itself — guest reminders now arrive as a `custom`
360
+ * stream event the host registers via {@link registerCustomEventCard}, and upgrade
361
+ * call-to-actions come from {@link ChatPanelProps.buildUpgradeCta}. Retained so the
362
+ * host can still pass it; the host's own `buildUpgradeCta` closure decides whether
363
+ * an anonymous user should sign up vs. upgrade.
364
+ */
365
+ isAnonymous?: boolean
366
+ /** When true, user has a paid plan and can use all models (drives locked-model display). */
367
+ isPro?: boolean
368
+ /**
369
+ * Retained for call-site compatibility. The periodic "sign up to keep your work"
370
+ * reminder is no longer generated client-side — the host's backend decides when to
371
+ * emit it as a `guest_reminder` `custom` stream event (so it can be suppressed during
372
+ * discovery server-side). This prop no longer drives any built-in behavior.
373
+ * @deprecated Guest reminders moved to the host-emitted `custom` event + registry.
374
+ */
375
+ suppressGuestReminder?: boolean
376
+ /**
377
+ * Builds the call-to-action button(s) shown when the chat surfaces an upgrade /
378
+ * sign-in nudge — a locked model the user can't select, or a usage/resource limit
379
+ * the backend reported. The shared IDE owns NO pricing or auth routes, so the host
380
+ * supplies the button(s) here (e.g. its own `/pricing` or `/signup`). Return
381
+ * `null`/`undefined` (the default) to render the nudge text with no button.
382
+ * `requiresSignup`, when set, is the backend's flag that the user must sign up
383
+ * rather than upgrade an existing plan; when unset the host's own auth state decides.
384
+ */
385
+ buildUpgradeCta?: (context: {
386
+ requiresSignup?: boolean
387
+ }) => ChatEventCardAction | ChatEventCardAction[] | null | undefined
388
+ /**
389
+ * Optional app-specific section appended to the `/help` output — e.g. a plan /
390
+ * upgrade blurb. The shared IDE has no pricing or plan copy, so the host supplies
391
+ * the (already-localized) lines plus any call-to-action. Return `null` (the default)
392
+ * to append nothing.
393
+ */
394
+ buildHelpUpgradeSection?: () =>
395
+ { lines: string[]; action?: ChatEventCardAction | ChatEventCardAction[] } | null | undefined
396
+ /**
397
+ * The signed-in user's profile avatar (SOC1) — an inline `data:image/*` URI or
398
+ * an `http(s)` URL — rendered beside their own messages in the chat timeline.
399
+ * The host passes whatever value its user metadata holds; the shared IDE gates
400
+ * it (`resolveUserAvatar`) so only a safe, renderable source reaches the DOM and
401
+ * falls back to a generic icon otherwise. Omit it (the default) to always show
402
+ * the icon.
403
+ */
404
+ userAvatar?: string | null
405
+ /**
406
+ * Display name of the AI coding agent, interpolated into all shared chat copy
407
+ * that refers to it (the stalled-stream notice, sound-event descriptions, the
408
+ * `/help` body, tips, `/settings` and command descriptions, the `/scripts`
409
+ * empty state). The shared IDE owns NO product branding, so the host passes its
410
+ * own agent brand name. Defaults to the neutral `'the assistant'`
411
+ * (`DEFAULT_AGENT_NAME` from `@molecule/app-react`) so the package alone never
412
+ * names a specific product.
413
+ */
414
+ agentName?: string
415
+ /**
416
+ * Display name of the host product / IDE, interpolated into shared chat copy
417
+ * that refers to the product (the `/help` intro, the report-confirmation and
418
+ * report-modal subheading, the command-menu version line). The host passes its
419
+ * own product brand name; defaults to the neutral `'the IDE'`
420
+ * (`DEFAULT_PRODUCT_NAME` from `@molecule/app-react`).
421
+ */
422
+ productName?: string
423
+ /**
424
+ * The host's current app/build version (e.g. `'0.1.0'`), shown in the `/version`
425
+ * command's menu description and its output. The shared IDE has no build version
426
+ * of its own, so when omitted it falls back to the package default constant.
427
+ */
428
+ version?: string
429
+ /**
430
+ * URL the command-menu "Report a problem" link points at (the host's own issue
431
+ * tracker / feedback page). The shared IDE owns no product URLs, so when this
432
+ * is omitted (the default) the link is not rendered. The in-chat `/report`
433
+ * modal — which POSTs to the project's own backend — is unaffected.
434
+ */
435
+ feedbackUrl?: string
436
+ className?: string
437
+ }
438
+ ```
439
+
440
+ #### `ChatUserIdentity`
441
+
442
+ Identity of the user whose avatar was clicked in the chat timeline, passed to
443
+ {@link ChatPanelProps.onProfileClick} so the host can open that user's profile.
444
+
445
+ Today the chat is solo — the only avatar shown is the signed-in user's own —
446
+ so the only known field is the avatar value. The interface is intentionally
447
+ forward-compatible: collaborator fields (id, name) can be added here when
448
+ multi-user chat lands, without changing the callback signature.
449
+
450
+ ```typescript
451
+ interface ChatUserIdentity {
452
+ /** The clicked user's avatar value (data-URI / URL), if any. */
453
+ avatar?: string | null
454
+ }
455
+ ```
456
+
457
+ #### `ClientInfo`
458
+
459
+ Client-side diagnostics attached to a report so triage can see the running
460
+ environment without asking the user. Every field is optional — only what
461
+ could be read in the current environment is present (see
462
+ {@link collectClientInfo}).
463
+
464
+ ```typescript
465
+ interface ClientInfo {
466
+ /** The running build version. */
467
+ appVersion?: string
468
+ /** `navigator.userAgent` (browser + OS). */
469
+ userAgent?: string
470
+ /** `navigator.platform`. */
471
+ platform?: string
472
+ /** `navigator.language`. */
473
+ language?: string
474
+ /** Inner viewport size, `${innerWidth}×${innerHeight}`. */
475
+ viewport?: string
476
+ /** Physical screen size, `${screen.width}×${screen.height}`. */
477
+ screen?: string
478
+ /** Active theme — `'light'` or `'dark'`. */
479
+ theme?: string
480
+ /** The current page URL (`window.location.href`). */
481
+ url?: string
482
+ }
483
+ ```
484
+
485
+ #### `Command`
486
+
487
+ A command available in the command palette.
488
+
489
+ ```typescript
490
+ interface Command {
491
+ /** Unique identifier. */
492
+ id: string
493
+ /** Display label. */
494
+ label: string
495
+ /** Keyboard shortcut hint (e.g. "Cmd+P"). */
496
+ shortcut?: string
497
+ /** Handler invoked when the command is executed. */
498
+ execute: () => void
499
+ /** Category prefix (e.g. "View", "File"). */
500
+ category?: string
501
+ }
502
+ ```
503
+
504
+ #### `CommandCategory`
505
+
506
+ A command category with its display label.
507
+
508
+ ```typescript
509
+ interface CommandCategory {
510
+ /** Stable category key referenced by {@link CommandDef.category}. */
511
+ key: CommandCategoryKey
512
+ /** Human-readable category heading (English default; wrapped in `t()` at render). */
513
+ label: string
514
+ }
515
+ ```
516
+
517
+ #### `CommandDef`
518
+
519
+ Metadata describing a single slash command.
520
+
521
+ ```typescript
522
+ interface CommandDef {
523
+ /** Command id (the part after the slash, e.g. `'help'`). */
524
+ id: string
525
+ /** Display label including the leading slash (e.g. `'/help'`). */
526
+ label: string
527
+ /**
528
+ * Short description shown in the menu and in `/help` (English default). May
529
+ * contain the `{{agentName}}` interpolation token, filled in by the render
530
+ * sites (command menu, `/help`, `/settings` card) from the host's agent
531
+ * identity (neutral default: "the assistant").
532
+ */
533
+ description: string
534
+ /** Category this command is grouped under. */
535
+ category: CommandCategoryKey
536
+ /**
537
+ * Argument syntax for commands that take options, shown in the `/settings`
538
+ * command reference (English default). `[…]` = optional, `<…>` = required.
539
+ * Omit for commands that take no arguments.
540
+ */
541
+ usage?: string
542
+ }
543
+ ```
544
+
545
+ #### `CommandGroup`
546
+
547
+ A category paired with the commands that belong to it.
548
+
549
+ ```typescript
550
+ interface CommandGroup {
551
+ /** The category metadata (key + label). */
552
+ category: CommandCategory
553
+ /** Commands in this category, in registry order. */
554
+ commands: CommandDef[]
555
+ }
556
+ ```
557
+
558
+ #### `CommandPaletteProps`
559
+
560
+ Properties for the command palette.
561
+
562
+ ```typescript
563
+ interface CommandPaletteProps {
564
+ /** Available commands. */
565
+ commands: Command[]
566
+ /** Called when the palette is dismissed. */
567
+ onDismiss: () => void
568
+ }
569
+ ```
570
+
571
+ #### `DeviceDimensions`
572
+
573
+ Per-frame iframe sizing. `width`/`height` are the PORTRAIT CSS sizes; a
574
+ fixed-frame (`rotatable`) device swaps them in landscape. `'100%'` width with
575
+ a `null` height means "fluid" — fill the available preview area (responsive /
576
+ desktop have no fixed frame to rotate).
577
+
578
+ ```typescript
579
+ interface DeviceDimensions {
580
+ /** Portrait CSS width (e.g. `'768px'`, or `'100%'` for a fluid frame). */
581
+ readonly width: string
582
+ /** Portrait CSS height in px (e.g. `'1024px'`), or `null` to fill the area. */
583
+ readonly height: string | null
584
+ /** Whether the frame has a fixed size that can be rotated portrait ⇄ landscape. */
585
+ readonly rotatable: boolean
586
+ }
587
+ ```
588
+
589
+ #### `DeviceFrameSelectorProps`
590
+
591
+ Properties for device frame selector.
592
+
593
+ ```typescript
594
+ interface DeviceFrameSelectorProps {
595
+ current: DeviceFrame
596
+ onChange: (device: DeviceFrame) => void
597
+ className?: string
598
+ }
599
+ ```
600
+
601
+ #### `EditorPanelProps`
602
+
603
+ Properties for the editor panel component.
604
+
605
+ ```typescript
606
+ interface EditorPanelProps {
607
+ className?: string
608
+ /** Called whenever the active file changes (tab switch, file open, file close). */
609
+ onActiveFileChange?: (path: string | null) => void
610
+ /** Called once after the editor is fully mounted and ready to accept files. */
611
+ onEditorReady?: () => void
612
+ /** Called whenever the open tab list changes (file opened or closed). */
613
+ onTabsChange?: (paths: string[]) => void
614
+ /** Maps file path to git status for coloring tab filenames. */
615
+ fileStatuses?: Record<string, string>
616
+ /** Path of the file currently being formatted, for visual indicator. */
617
+ formattingFile?: string | null
618
+ /** Path of the file with an active save debounce countdown. */
619
+ countdownFile?: string | null
620
+ /** Incremented each keystroke to restart the countdown animation. */
621
+ countdownKey?: number
622
+ /** Estimated format duration in ms (rolling average, default 2000). */
623
+ formatEstimate?: number
624
+ /** Called when the user triggers "Fix with AI" from the editor's lightbulb or context menu. */
625
+ onFixWithAI?: (request: FixWithAIRequest) => void
626
+ /** Override double-click on a tab. Return `true` to skip the default pin behavior. */
627
+ onTabDoubleClick?: (path: string) => boolean
628
+ }
629
+ ```
630
+
631
+ #### `FileExplorerProps`
632
+
633
+ Properties for file explorer.
634
+
635
+ ```typescript
636
+ interface FileExplorerProps {
637
+ files: FileNode[]
638
+ onFileSelect: (path: string) => void
639
+ onFileDoubleClick?: (path: string) => void
640
+ onDirExpand?: (path: string) => void
641
+ /** Called when the user chooses "Rename" from the context menu. */
642
+ onRename?: (path: string) => void
643
+ /** Called when the user chooses "Delete" from the context menu. */
644
+ onDelete?: (path: string) => void
645
+ /** Called when the user deletes multiple selected files/folders via context menu or keyboard. */
646
+ onDeleteMultiple?: (paths: string[]) => void
647
+ /** Called when the user moves files via drag-and-drop or cut+paste. */
648
+ onMoveFiles?: (moves: Array<{ oldPath: string; newPath: string }>) => void
649
+ /** Called when the user chooses "New File" from the context menu. */
650
+ onNewFile?: (dirPath: string) => void
651
+ /** Called when the user chooses "New Folder" from the context menu. */
652
+ onNewFolder?: (dirPath: string) => void
653
+ /** Called when the user chooses "Collapse All" from the context menu. */
654
+ onCollapseAll?: () => void
655
+ className?: string
656
+ /** localStorage key for persisting expand/collapse state across reloads. */
657
+ persistKey?: string
658
+ /** Path of the currently active file — highlighted in the tree. */
659
+ activeFile?: string | null
660
+ /** Maps file path to git status — used to color directory names by highest-priority child status. */
661
+ fileStatuses?: Record<string, string>
662
+ }
663
+ ```
664
+
665
+ #### `FileNode`
666
+
667
+ File Node interface.
668
+
669
+ ```typescript
670
+ interface FileNode {
671
+ name: string
672
+ path: string
673
+ type: 'file' | 'directory'
674
+ children?: FileNode[]
675
+ isDimmed?: boolean
676
+ gitStatus?: 'modified' | 'added' | 'deleted' | 'untracked'
677
+ /** If this entry is a symlink, the target it points to. */
678
+ symlinkTarget?: string
679
+ }
680
+ ```
681
+
682
+ #### `IconProps`
683
+
684
+ Props for {@link Icon}. Extends `SVGProps` so callers can forward any SVG/HTML
685
+ attribute (`data-mol-id`, `aria-*`, `role`, event handlers, `style`) to the
686
+ root `<svg>` without the component enumerating them.
687
+
688
+ ```typescript
689
+ interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'width' | 'height' | 'viewBox' | 'fill'> {
690
+ /** Name of the glyph to look up in the bonded icon set (e.g. `'sync'`). */
691
+ name: IconName
692
+ /** Width and height of the rendered SVG in pixels. Defaults to 16. */
693
+ size?: number
694
+ /** Class name forwarded to the root `<svg>`. */
695
+ className?: string
696
+ }
697
+ ```
698
+
699
+ #### `IdeClientAction`
700
+
701
+ A non-mutating UI action the AI agent asks the IDE to perform — reload or
702
+ navigate the live preview, open a file in the editor, or drive the preview's
703
+ interaction bridge (`preview_ui`). Delivered via the `client_action`
704
+ chat-stream event (and, for `preview_ui`, also via the host's collab socket
705
+ so a mid-build tab reload can't orphan it).
706
+
707
+ ```typescript
708
+ interface IdeClientAction {
709
+ action: 'reload_preview' | 'navigate_preview' | 'open_file' | 'preview_ui'
710
+ /** navigate_preview: a URL path (e.g. "/dashboard"). open_file: a file path. */
711
+ path?: string
712
+ /** preview_ui: correlates the command with its ui-result round-trip. */
713
+ requestId?: string
714
+ /** preview_ui: the interaction the preview bridge should perform. */
715
+ command?: 'snapshot' | 'click' | 'fill' | 'select' | 'waitFor'
716
+ /** preview_ui: the `data-mol-id` of the target element (preferred). */
717
+ molId?: string
718
+ /** preview_ui: CSS-selector fallback when no molId is available. */
719
+ selector?: string
720
+ /** preview_ui: visible-label match for apps whose elements carry no molId. */
721
+ text?: string
722
+ /** preview_ui: value to set for fill/select. */
723
+ value?: string
724
+ }
725
+ ```
726
+
727
+ #### `KeyboardShortcut`
728
+
729
+ A keyboard shortcut definition.
730
+
731
+ ```typescript
732
+ interface KeyboardShortcut {
733
+ /** Key combo string, e.g. `"mod+p"`, `"mod+shift+f"`. `mod` = Cmd (Mac) / Ctrl (others). */
734
+ keys: string
735
+ /** Handler invoked when the shortcut fires. */
736
+ handler: () => void
737
+ /** If true, fires even when an `<input>` / `<textarea>` is focused. */
738
+ allowInInput?: boolean
739
+ /** If true, fires even when the Monaco editor is focused. */
740
+ allowInEditor?: boolean
741
+ /** Human-readable label for display in the command palette. */
742
+ label?: string
743
+ }
744
+ ```
745
+
746
+ #### `KeyboardShortcutsPanelProps`
747
+
748
+ Properties for the keyboard shortcuts reference panel.
749
+
750
+ ```typescript
751
+ interface KeyboardShortcutsPanelProps {
752
+ /** List of shortcuts to display. */
753
+ shortcuts: ShortcutEntry[]
754
+ /** Called when the panel is dismissed. */
755
+ onDismiss: () => void
756
+ }
757
+ ```
758
+
759
+ #### `PreviewPanelProps`
760
+
761
+ Props for the {@link PreviewPanel} — the live app preview (iframe + device frame + URL bar).
762
+
763
+ ```typescript
764
+ interface PreviewPanelProps {
765
+ /** Custom loading indicator shown while the dev server is starting. */
766
+ loadingIndicator?: ReactNode
767
+ /**
768
+ * The current UI command the host wants performed in the preview iframe (AI-driven
769
+ * end-to-end verification). The panel posts it to the iframe's interaction bridge when it
770
+ * CHANGES (keyed on `id`, so each new command fires exactly once). The panel only relays it;
771
+ * the host owns what to send and what to do with the result.
772
+ */
773
+ uiCommand?: PreviewUiCommand | null
774
+ /** Called when the iframe replies to a {@link PreviewUiCommand}, keyed by the command `id`. */
775
+ onUiResult?: (id: string, result: PreviewUiResult) => void
776
+ /** Custom loading indicator shown when the dev server restarts mid-session. Falls back to loadingIndicator if not provided. */
777
+ restartingIndicator?: ReactNode
778
+ /** Called when the preview iframe reports runtime JS errors. */
779
+ onPreviewError?: (
780
+ errors: Array<{ message: string; source?: string; line?: number; column?: number }>,
781
+ ) => void
782
+ /** Incremented when AI edits files. Triggers an iframe reload only when the preview is broken. */
783
+ fileChangeTick?: number
784
+ /**
785
+ * Active-build hint (e.g. a basename like `GuestMenu.tsx`) the host sets while the
786
+ * AI is editing files. When non-null the overlay is forced on — covering the
787
+ * blank-white iframe reload a build triggers — and shows "Updating `<hint>`…" so
788
+ * the user sees what's being worked on. Null when no build edit is in flight.
789
+ */
790
+ buildingHint?: string | null
791
+ /**
792
+ * Whether the AI agent is actively building right now (a chat turn is in progress).
793
+ * The host derives this from the chat's loading state. While true, the preview keeps a
794
+ * "Building your app…" status overlay up whenever the app has NOT confirmed it rendered
795
+ * content (no `molecule:ready`) — so a half-built / blank / white iframe during a long
796
+ * build always shows progress instead of a bare white screen. A confirmed render still
797
+ * reveals the live app (HMR updates stay visible), so this never hides a working preview.
798
+ */
799
+ isBuilding?: boolean
800
+ /**
801
+ * Timestamp (ms since epoch) of when the preview's backing server/sandbox was last
802
+ * woken from sleep or restarted, or 0/undefined when it never was. While this is
803
+ * recent, the panel treats the preview like a fresh cold boot: the dev server behind
804
+ * it is restarting and recompiling, so a document that reloads to blank (or a
805
+ * transient error page that never runs the bridge) is EXPECTED for a while and must
806
+ * NOT trip the fast "preview is blank" accusation — the honest starting/loading
807
+ * status stays up, and only the generous never-rendered ceiling can accuse. A real
808
+ * render (`molecule:ready`) clears the patience immediately, so a healthy wake
809
+ * reveals as fast as ever.
810
+ */
811
+ wakeAt?: number
812
+ /**
813
+ * Called when the preview gives up showing the running app — after exhausting reload
814
+ * recovery, at the absolute readiness ceiling, OR when the heartbeat watchdog detects a
815
+ * frozen (locked-thread) app. Receives a {@link PreviewStuckReport} (failure class +
816
+ * route) so the host can drive recovery UI AND hand the agent an actionable, targeted
817
+ * fix request. The argument is optional for backward compatibility with no-arg callers.
818
+ */
819
+ onPreviewStuck?: (report?: PreviewStuckReport) => void
820
+ /**
821
+ * Called when the preview's render verdict changes ({@link PreviewRenderState}) — and
822
+ * with the current location so the host can report WHERE. The host forwards this to the
823
+ * server so Synthase's post-loop verification can confirm the app actually rendered (not
824
+ * just that it compiled + served) before calling a build done.
825
+ */
826
+ onRenderState?: (state: PreviewRenderState, url?: string) => void
827
+ className?: string
828
+ }
829
+ ```
830
+
831
+ #### `PreviewStuckReport`
832
+
833
+ Structured report passed to {@link PreviewPanelProps.onPreviewStuck} when the preview
834
+ gives up. Carries the failure class + the route it happened on so the host can compose
835
+ an actionable, agent-fixable message instead of a bare "preview is stuck".
836
+
837
+ ```typescript
838
+ interface PreviewStuckReport {
839
+ /** The failure class — what left the preview unable to show the running app. */
840
+ reason: PreviewStuckReason
841
+ /** The preview's current location (route) when the failure was detected, if known. */
842
+ url?: string
843
+ }
844
+ ```
845
+
846
+ #### `PreviewUiCommand`
847
+
848
+ A live-preview interaction the host asks the panel to perform inside the iframe, so an AI
849
+ agent can verify a feature end-to-end by DRIVING the app the user is watching (no headless
850
+ browser). The panel just relays it to the iframe's interaction bridge — generic, so it
851
+ carries no host/API specifics.
852
+
853
+ ```typescript
854
+ interface PreviewUiCommand {
855
+ /** Correlates this command with its result; the host round-trips on it. */
856
+ id: string
857
+ /** `snapshot` the interactive UI, or act on an element. */
858
+ action: 'snapshot' | 'click' | 'fill' | 'select' | 'waitFor'
859
+ /** `data-mol-id` of the target element (preferred over selector). */
860
+ molId?: string
861
+ /** CSS-selector fallback when no molId is available. */
862
+ selector?: string
863
+ /** Visible-label match — targets apps whose elements carry no `data-mol-id`. */
864
+ text?: string
865
+ /** Value to set for `fill` / `select`. */
866
+ value?: string
867
+ /**
868
+ * `snapshot` only — the moment the host pointed the preview at a new URL (`Date.now()`).
869
+ * Only a document that loaded at or after it may answer, so a navigation snapshot can never
870
+ * come from the OUTGOING page still sitting in the iframe. Set it ONLY when a new document
871
+ * is genuinely loading (the URL actually changed) — otherwise nothing can satisfy it and the
872
+ * command goes unanswered. Omit for a plain read.
873
+ */
874
+ minLoadedAt?: number
875
+ }
876
+ ```
877
+
878
+ #### `PreviewUiResult`
879
+
880
+ The preview interaction bridge's reply to a {@link PreviewUiCommand}.
881
+
882
+ ```typescript
883
+ interface PreviewUiResult {
884
+ ok: boolean
885
+ /** Interactive-element list + url/title from the preview (present on a snapshot / success). */
886
+ snapshot?: unknown
887
+ found?: boolean
888
+ error?: string
889
+ /**
890
+ * Failed network requests from the last ~10s (method, url, status, bounded response body),
891
+ * captured in-page — so a click that 4xx'd explains itself in the same result.
892
+ */
893
+ recentNetworkErrors?: string[]
894
+ /**
895
+ * Present when the bridge gave up on a settle budget instead of observing the page settle —
896
+ * it names what was still pending (document parsing, an empty root, an unreached route,
897
+ * in-flight requests). The snapshot may be incomplete, so a race stays distinguishable from
898
+ * a genuinely broken page.
899
+ */
900
+ stillSettling?: string
901
+ }
902
+ ```
903
+
904
+ #### `QuickOpenProps`
905
+
906
+ Properties for the quick-open file finder.
907
+
908
+ ```typescript
909
+ interface QuickOpenProps {
910
+ /** Project ID used for API calls. */
911
+ projectId: string
912
+ /** Called when the user selects a file. */
913
+ onFileOpen: (path: string) => void
914
+ /** Called when the picker is dismissed. */
915
+ onDismiss: () => void
916
+ }
917
+ ```
918
+
919
+ #### `QuickPickerItem`
920
+
921
+ An item in the quick picker list.
922
+
923
+ ```typescript
924
+ interface QuickPickerItem {
925
+ /** Unique identifier. */
926
+ id: string
927
+ /** Primary label. */
928
+ label: string
929
+ /** Secondary text shown beside the label. */
930
+ detail?: string
931
+ /** Optional icon element. */
932
+ icon?: ReactNode
933
+ }
934
+ ```
935
+
936
+ #### `QuickPickerProps`
937
+
938
+ Properties for the reusable quick picker overlay.
939
+
940
+ ```typescript
941
+ interface QuickPickerProps {
942
+ /** Items to display and filter. */
943
+ items: QuickPickerItem[]
944
+ /** Placeholder text for the search input. */
945
+ placeholder?: string
946
+ /** Called when the user selects an item. */
947
+ onSelect: (item: QuickPickerItem) => void
948
+ /** Called when the user dismisses the picker (Escape or backdrop click). */
949
+ onDismiss: () => void
950
+ /** Show a loading indicator. */
951
+ loading?: boolean
952
+ /** Pre-fill the search input. */
953
+ initialQuery?: string
954
+ className?: string
955
+ }
956
+ ```
957
+
958
+ #### `ReportFormState`
959
+
960
+ The report modal's form state.
961
+
962
+ ```typescript
963
+ interface ReportFormState {
964
+ /** Short summary / issue title. */
965
+ title: string
966
+ /** Detailed description of the problem or request. */
967
+ description: string
968
+ /** Optional reproduction steps (free text). */
969
+ steps: string
970
+ /** Whether to attach the recent conversation to the report. */
971
+ includeChat: boolean
972
+ }
973
+ ```
974
+
975
+ #### `ReportPayload`
976
+
977
+ The `POST /projects/:id/report` request body.
978
+
979
+ ```typescript
980
+ interface ReportPayload {
981
+ /** Short summary / issue title. */
982
+ title: string
983
+ /** Detailed description. */
984
+ description: string
985
+ /** Reproduction steps — omitted entirely when blank. */
986
+ steps?: string
987
+ /** Whether the backend should attach the recent conversation. */
988
+ includeChat: boolean
989
+ /** Client diagnostics — omitted entirely when none could be collected. */
990
+ clientInfo?: ClientInfo
991
+ }
992
+ ```
993
+
994
+ #### `ReportResult`
995
+
996
+ The `POST /projects/:id/report` response.
997
+
998
+ ```typescript
999
+ interface ReportResult {
1000
+ /** Whether the report was recorded. */
1001
+ ok: boolean
1002
+ /** Link to the created issue, when one was filed. */
1003
+ url?: string
1004
+ /** The persisted DB row id. */
1005
+ id?: string
1006
+ }
1007
+ ```
1008
+
1009
+ #### `ResizeHandleProps`
1010
+
1011
+ Properties for resize handle.
1012
+
1013
+ ```typescript
1014
+ interface ResizeHandleProps {
1015
+ onResize: (delta: number) => void
1016
+ direction?: 'horizontal' | 'vertical'
1017
+ className?: string
1018
+ }
1019
+ ```
1020
+
1021
+ #### `SearchPanelProps`
1022
+
1023
+ Properties for the search-in-files panel.
1024
+
1025
+ ```typescript
1026
+ interface SearchPanelProps {
1027
+ /** Project ID used for API calls. */
1028
+ projectId: string
1029
+ /** Called when the user clicks a search result. */
1030
+ onResultClick?: (path: string, line: number) => void
1031
+ className?: string
1032
+ /**
1033
+ * The project's excluded directory names (VS Code `search.exclude`
1034
+ * semantics). Displayed and editable in the panel; the backend applies the
1035
+ * SAME set server-side to every search surface (panel + AI tools), so this
1036
+ * prop is display/edit state — searches don't send it per query. When
1037
+ * omitted, the panel shows {@link DEFAULT_SEARCH_EXCLUDED_DIRS}.
1038
+ */
1039
+ excludedDirs?: string[]
1040
+ /** Persist an edited excluded-dir set (the host owns storage). */
1041
+ onExcludedDirsChange?: (dirs: string[]) => void
1042
+ }
1043
+ ```
1044
+
1045
+ #### `SearchResponse`
1046
+
1047
+ Response from the search API endpoint.
1048
+
1049
+ ```typescript
1050
+ interface SearchResponse {
1051
+ /** The search pattern used. */
1052
+ pattern: string
1053
+ /** Grouped results by file. */
1054
+ results: SearchResult[]
1055
+ /** Total number of matches across all files. */
1056
+ totalCount: number
1057
+ /** Whether results were truncated. */
1058
+ truncated: boolean
1059
+ }
1060
+ ```
1061
+
1062
+ #### `SearchResult`
1063
+
1064
+ A single file's search results.
1065
+
1066
+ ```typescript
1067
+ interface SearchResult {
1068
+ /** Relative file path. */
1069
+ file: string
1070
+ /** Matching lines within the file. */
1071
+ matches: Array<{ line: number; content: string }>
1072
+ }
1073
+ ```
1074
+
1075
+ #### `SettingMeta`
1076
+
1077
+ Canonical, value-free metadata for a single user-controllable setting.
1078
+
1079
+ ```typescript
1080
+ interface SettingMeta {
1081
+ /** Stable id (also the i18n key suffix, e.g. `'effort'`). */
1082
+ id: SettingKey
1083
+ /** Human-readable label (English default; wrapped in `t()` at render). */
1084
+ label: string
1085
+ /**
1086
+ * One-line explanation of what the setting does (English default). May
1087
+ * contain the `{{agentName}}` interpolation token, filled in at render from
1088
+ * the host's agent identity (neutral default: "the assistant").
1089
+ */
1090
+ description: string
1091
+ /**
1092
+ * The slash command that edits this setting client-side. Drives the inline
1093
+ * "Edit" affordance and cross-links the setting to its command. Omitted only
1094
+ * for read-only settings.
1095
+ */
1096
+ editCommand?: CommandId
1097
+ /**
1098
+ * The exact slash-command input to prefill when editing, for settings whose
1099
+ * bare {@link SettingMeta.editCommand} is not specific enough — e.g. the
1100
+ * per-mode model rows both run the `model` command but must scope it to a
1101
+ * mode (`/model --plan`, `/model --execute`). Omit when running the bare
1102
+ * command suffices.
1103
+ */
1104
+ editInput?: string
1105
+ }
1106
+ ```
1107
+
1108
+ #### `ShareLinkResult`
1109
+
1110
+ The `POST /projects/:projectId/shares` response — the created public link.
1111
+ Mirrors the relevant fields of the `@molecule/api-resource-share` `ShareLink`.
1112
+
1113
+ ```typescript
1114
+ interface ShareLinkResult {
1115
+ /** The link's unique id (used by the revoke route). */
1116
+ id?: string
1117
+ /** Opaque slug embedded in the public URL. */
1118
+ slug: string
1119
+ /** The role this link grants. */
1120
+ role: ShareRole
1121
+ /**
1122
+ * A fully-qualified share URL, when the backend supplies one. Preferred over
1123
+ * client-side construction so the canonical origin (e.g. a custom domain)
1124
+ * wins over the current page origin.
1125
+ */
1126
+ url?: string
1127
+ }
1128
+ ```
1129
+
1130
+ #### `SharePayload`
1131
+
1132
+ The `POST /projects/:projectId/shares` request body.
1133
+
1134
+ ```typescript
1135
+ interface SharePayload {
1136
+ /** Role granted to anyone who opens the link. */
1137
+ role: ShareRole
1138
+ }
1139
+ ```
1140
+
1141
+ #### `ShortcutEntry`
1142
+
1143
+ A shortcut entry for display in the keyboard shortcuts panel.
1144
+
1145
+ ```typescript
1146
+ interface ShortcutEntry {
1147
+ /** Human-readable label describing the action. */
1148
+ label: string
1149
+ /** Display string for the key combo (e.g. "⌘P", "⌘⇧F"). */
1150
+ keys: string
1151
+ /** Optional grouping category. */
1152
+ category?: string
1153
+ /** Handler invoked when the row is clicked. */
1154
+ execute?: () => void
1155
+ }
1156
+ ```
1157
+
1158
+ #### `SidebarTabsProps`
1159
+
1160
+ Properties for the sidebar tab switcher.
1161
+
1162
+ ```typescript
1163
+ interface SidebarTabsProps {
1164
+ /** Currently active sidebar tab. */
1165
+ activeTab: 'files' | 'search'
1166
+ /** Called when the user switches tabs. */
1167
+ onTabChange: (tab: 'files' | 'search') => void
1168
+ /** Tab content rendered below the tab buttons. */
1169
+ children: ReactNode
1170
+ className?: string
1171
+ }
1172
+ ```
1173
+
1174
+ #### `TabBarProps`
1175
+
1176
+ Properties for tab bar.
1177
+
1178
+ ```typescript
1179
+ interface TabBarProps {
1180
+ tabs: EditorTab[]
1181
+ activeFile: string | null
1182
+ onSelect: (path: string) => void
1183
+ onClose: (path: string) => void
1184
+ onDoubleClick?: (path: string) => void
1185
+ /** Maps file path to git status for coloring tab filenames. */
1186
+ fileStatuses?: Record<string, string>
1187
+ className?: string
1188
+ }
1189
+ ```
1190
+
1191
+ #### `ToolCallCardProps`
1192
+
1193
+ Properties for tool call card.
1194
+
1195
+ ```typescript
1196
+ interface ToolCallCardProps {
1197
+ id: string
1198
+ name: string
1199
+ input?: unknown
1200
+ output?: unknown
1201
+ status: 'pending' | 'running' | 'done' | 'error'
1202
+ /** Snapshot of original/modified file content captured at tool-call time. */
1203
+ fileDiff?: { original: string; modified: string }
1204
+ /** Externally controlled undo state — when true, the card displays as undone. */
1205
+ isUndone?: boolean
1206
+ /** Called when the undo/redo button is toggled on this tool call. */
1207
+ onUndoToggle?: (id: string, undone: boolean) => void
1208
+ /** Called when a filename in the card is clicked — should open the file as a preview tab. */
1209
+ onFileOpen?: (path: string) => void
1210
+ /** Called when a filename in the card is double-clicked — should pin the tab. */
1211
+ onFileDoubleClick?: (path: string) => void
1212
+ /** Called when a file-changing card is clicked — should open the file diff in the editor. */
1213
+ onFileDiff?: (path: string, diff?: { original: string; modified: string }) => void
1214
+ /** Called to undo/redo a file change — writes the given content to the file path. */
1215
+ onFileRevert?: (path: string, content: string) => Promise<void>
1216
+ /** Called when the user responds to an `ask_user` tool call (clicks an option or submits free text). */
1217
+ onAskUserResponse?: (response: string) => void
1218
+ className?: string
1219
+ }
1220
+ ```
1221
+
1222
+ #### `UserAvatarProps`
1223
+
1224
+ Props for {@link UserAvatar}.
1225
+
1226
+ ```typescript
1227
+ interface UserAvatarProps {
1228
+ /**
1229
+ * The signed-in user's avatar — an inline `data:image/*` URI or an `http(s)`
1230
+ * URL from their profile metadata. Unsafe / unset / oversized values are
1231
+ * ignored (see {@link resolveUserAvatar}) and the generic icon is shown.
1232
+ */
1233
+ userAvatar?: string | null
1234
+ /** Diameter of the avatar in pixels. Defaults to 24. */
1235
+ size?: number
1236
+ /**
1237
+ * Optional click handler. When supplied, the avatar becomes an interactive
1238
+ * button (pointer cursor, hover/focus ring, keyboard- and screen-reader
1239
+ * accessible) that opens the user's profile — the host decides what to show.
1240
+ * When omitted (the default) the avatar renders exactly as before: a static,
1241
+ * non-interactive image/icon, so existing call sites are unaffected.
1242
+ */
1243
+ onClick?: () => void
1244
+ }
1245
+ ```
1246
+
1247
+ #### `WorkspaceLayoutProps`
1248
+
1249
+ Properties for workspace layout.
1250
+
1251
+ ```typescript
1252
+ interface WorkspaceLayoutProps {
1253
+ children: ReactNode
1254
+ className?: string
1255
+ }
1256
+ ```
1257
+
1258
+ ### Types
1259
+
1260
+ #### `ActivityStatus`
1261
+
1262
+ Lifecycle status of a captured activity.
1263
+
1264
+ ```typescript
1265
+ type ActivityStatus = 'captured' | 'sent' | 'delivered' | 'failed'
1266
+ ```
1267
+
1268
+ #### `ActivityType`
1269
+
1270
+ Channel categories a captured activity can belong to.
1271
+
1272
+ ```typescript
1273
+ type ActivityType = 'email' | 'sms' | 'push' | 'webhook' | 'channel'
1274
+ ```
1275
+
1276
+ #### `AutoCommitAction`
1277
+
1278
+ Actions the countdown reducer accepts.
1279
+
1280
+ - `set` — apply a `/autocommit <seconds>` command (`seconds <= 0` disables);
1281
+ arms AND starts a fresh countdown (an explicit, just-now user choice).
1282
+ - `hydrate` — restore a cadence persisted on the project (e.g. on reload),
1283
+ enabled but PAUSED (`seconds <= 0` disables). Unlike `set`, it does NOT
1284
+ start counting down — the countdown only re-arms on the next file change —
1285
+ so reopening a project never auto-commits a tree the user hasn't touched.
1286
+ - `reset` — a file changed; restart the full countdown (no-op when disabled).
1287
+ - `tick` — one second elapsed; decrement toward zero (no-op when paused).
1288
+ - `fired` — a commit was just dispatched; pause until the next file change.
1289
+
1290
+ ```typescript
1291
+ type AutoCommitAction =
1292
+ | { type: 'set'; seconds: number }
1293
+ | { type: 'hydrate'; seconds: number }
1294
+ | { type: 'reset' }
1295
+ | { type: 'tick' }
1296
+ | { type: 'fired' }
1297
+ ```
1298
+
1299
+ #### `ChatEventCardFactory`
1300
+
1301
+ Turns a custom event's `data` payload into a chat card, or returns null to render
1302
+ nothing for that event.
1303
+
1304
+ ```typescript
1305
+ type ChatEventCardFactory = (data: Record<string, unknown> | undefined) => ChatEventCard | null
1306
+ ```
1307
+
1308
+ #### `ChatEventCardSegment`
1309
+
1310
+ One inline segment of a card's composable body: literal text, an inline monospace
1311
+ {@link ChatEventCardCode} span, or a labelled link/action ({@link ChatEventCardAction}).
1312
+ See {@link ChatEventCard.content}.
1313
+
1314
+ ```typescript
1315
+ type ChatEventCardSegment = string | ChatEventCardCode | ChatEventCardAction
1316
+ ```
1317
+
1318
+ #### `CommandCategoryKey`
1319
+
1320
+ Category keys used to group commands in the menu and in `/help`.
1321
+
1322
+ ```typescript
1323
+ type CommandCategoryKey = 'context' | 'code' | 'collaborate' | 'model' | 'settings' | 'support'
1324
+ ```
1325
+
1326
+ #### `CommandId`
1327
+
1328
+ Union of all command ids (loosely `string`, since {@link CommandDef.id} is a string).
1329
+
1330
+ ```typescript
1331
+ type CommandId = CommandDef['id']
1332
+ ```
1333
+
1334
+ #### `DeviceOrientation`
1335
+
1336
+ Preview-only iframe orientation. Portrait is the natural orientation of a
1337
+ fixed-frame device; landscape swaps its width/height. This is a visual
1338
+ preview concern that lives in {@link PreviewPanel}'s local state — it is NOT
1339
+ part of the live-preview core state.
1340
+
1341
+ ```typescript
1342
+ type DeviceOrientation = 'portrait' | 'landscape'
1343
+ ```
1344
+
1345
+ #### `PreviewRenderState`
1346
+
1347
+ The preview's live render verdict, derived from the iframe bridges — the one preview
1348
+ fact the server can't observe itself (it has no browser). The host forwards it to the
1349
+ server so the post-loop verification won't pass a build while the app isn't actually
1350
+ rendering ("compiles + serves" ≠ "renders").
1351
+
1352
+ - `rendered` — the app drew content (`molecule:ready`).
1353
+ - `blank` — loaded but showed nothing (gave up / `#root` empty after settling).
1354
+ - `frozen` — rendered then locked up (heartbeats stopped).
1355
+ - `loading` — still loading / not yet determined.
1356
+
1357
+ ```typescript
1358
+ type PreviewRenderState = 'rendered' | 'blank' | 'frozen' | 'loading'
1359
+ ```
1360
+
1361
+ #### `PreviewStuckReason`
1362
+
1363
+ Why the preview could not show the running app — the failure CLASS the host hands
1364
+ to its AI agent so a fix can be targeted (and so the agent isn't told "it's broken"
1365
+ with no hint of how). Distinct from a JS error (`onPreviewError`): these are states
1366
+ the iframe itself can't report once it's in them.
1367
+
1368
+ ```typescript
1369
+ type PreviewStuckReason =
1370
+ // Heartbeats stopped after a render — the app's main thread is locked (an infinite
1371
+ // loop / runaway render). The iframe can post nothing else once frozen, so only the
1372
+ // host's heartbeat-silence watchdog can detect it.
1373
+ | 'frozen'
1374
+ // Repeated reload/remount cycles never produced a confirmed render — the document
1375
+ // loads but the app never mounts (e.g. a route that throws on every attempt).
1376
+ | 'load-failed'
1377
+ // The absolute readiness ceiling elapsed with no confirmed render and no active
1378
+ // build — a catch-all backstop so the preview can never spin forever.
1379
+ | 'load-timeout'
1380
+ ```
1381
+
1382
+ #### `SettingKey`
1383
+
1384
+ Stable ids for each user-controllable setting (also the i18n key suffix).
1385
+
1386
+ ```typescript
1387
+ type SettingKey =
1388
+ | 'model'
1389
+ | 'planModel'
1390
+ | 'executeModel'
1391
+ | 'commitModel'
1392
+ | 'compactModel'
1393
+ | 'mode'
1394
+ | 'effort'
1395
+ | 'maxLoops'
1396
+ | 'autoFix'
1397
+ | 'autoCommit'
1398
+ | 'hooks'
1399
+ | 'autoApproveCommands'
1400
+ | 'sounds'
1401
+ ```
1402
+
1403
+ #### `ShareCommand`
1404
+
1405
+ The parsed result of a `/share` command:
1406
+
1407
+ - `create` — POST a link at a valid role (`/share`, defaulting to `viewer`,
1408
+ or `/share <role>` with a recognized role).
1409
+ - `invalid` — an unrecognized role argument was given (the caller shows usage).
1410
+
1411
+ ```typescript
1412
+ type ShareCommand = { kind: 'create'; role: ShareRole } | { kind: 'invalid'; arg: string }
1413
+ ```
1414
+
1415
+ #### `ShareRole`
1416
+
1417
+ A role granted by a share link.
1418
+
1419
+ ```typescript
1420
+ type ShareRole = (typeof SHARE_ROLES)[number]
1421
+ ```
1422
+
1423
+ ### Functions
1424
+
1425
+ #### `ActivityCard(props)`
1426
+
1427
+ Compact, clickable inline card for a single captured activity.
1428
+
1429
+ ```typescript
1430
+ function ActivityCard({ activity, onActivityClick }: ActivityCardProps): JSX.Element
1431
+ ```
1432
+
1433
+ - `props` — Component props.
1434
+
1435
+ **Returns:** The rendered activity card element.
1436
+
1437
+ #### `activityFromEvent(raw)`
1438
+
1439
+ Maps a raw SSE `activity` event payload into a normalized {@link Activity}.
1440
+ Tolerates missing optional fields and supplies an id/timestamp if absent.
1441
+
1442
+ ```typescript
1443
+ function activityFromEvent(raw: {
1444
+ id?: string
1445
+ type?: string
1446
+ status?: string
1447
+ recipient?: string
1448
+ summary?: string
1449
+ timestamp?: string
1450
+ }): Activity
1451
+ ```
1452
+
1453
+ - `raw` — The `activity` field from the SSE event.
1454
+ - `raw.id` — Activity id; generated if absent.
1455
+ - `raw.type` — Channel type; defaults to `webhook` if absent.
1456
+ - `raw.status` — Lifecycle status; defaults to `captured` if absent.
1457
+ - `raw.recipient` — Optional recipient.
1458
+ - `raw.summary` — Optional short summary.
1459
+ - `raw.timestamp` — ISO timestamp; defaults to now if absent.
1460
+
1461
+ **Returns:** A normalized Activity object.
1462
+
1463
+ #### `activityIconName(type)`
1464
+
1465
+ Returns the bonded-icon-set glyph NAME for an activity type — pass it to
1466
+ `<Icon name={…} />` to render the themed SVG. Unknown/future types (which
1467
+ {@link activityFromEvent} normalizes to `webhook`) reuse the `link` glyph
1468
+ rather than risk a `getIcon` throw.
1469
+
1470
+ ```typescript
1471
+ function activityIconName(type: ActivityType): IconName
1472
+ ```
1473
+
1474
+ - `type` — The activity channel type.
1475
+
1476
+ **Returns:** The icon-set glyph name for the type.
1477
+
1478
+ #### `activityStatusColors(status)`
1479
+
1480
+ Resolves the status-pill colors for a given status. Uses RGBA literals (not
1481
+ ClassMap classes) because these semantic status hues are not part of the
1482
+ surface/text token set — the same approach `VerificationBadge` takes for its
1483
+ pass/fail coloring.
1484
+
1485
+ ```typescript
1486
+ function activityStatusColors(status: ActivityStatus): { fg: string; bg: string }
1487
+ ```
1488
+
1489
+ - `status` — The activity status.
1490
+
1491
+ **Returns:** An object with `fg` (text) and `bg` (background) CSS color strings.
1492
+
1493
+ #### `activityStatusLabel(status)`
1494
+
1495
+ Human-readable, translated label for a status (shown in the status pill).
1496
+
1497
+ ```typescript
1498
+ function activityStatusLabel(status: ActivityStatus): string
1499
+ ```
1500
+
1501
+ - `status` — The activity status.
1502
+
1503
+ **Returns:** The translated status label.
1504
+
1505
+ #### `activitySummaryLine(activity)`
1506
+
1507
+ Builds the one-line summary shown on the inline card: the activity's own
1508
+ summary, with the recipient appended after an arrow when present
1509
+ (e.g. `Welcome email → user@example.com`). Falls back to a translated,
1510
+ type-specific default when no summary was captured.
1511
+
1512
+ ```typescript
1513
+ function activitySummaryLine(activity: Pick<Activity, 'type' | 'recipient' | 'summary'>): string
1514
+ ```
1515
+
1516
+ - `activity` — The activity to summarize.
1517
+
1518
+ **Returns:** The single-line summary string.
1519
+
1520
+ #### `activityTypeLabel(type)`
1521
+
1522
+ Human-readable, translated label for a channel type (used as filter-tab labels).
1523
+
1524
+ ```typescript
1525
+ function activityTypeLabel(type: ActivityType): string
1526
+ ```
1527
+
1528
+ - `type` — The activity channel type.
1529
+
1530
+ **Returns:** The translated channel label.
1531
+
1532
+ #### `autoCommitReducer(state, action)`
1533
+
1534
+ Pure reducer for the auto-commit countdown. Deterministic and side-effect
1535
+ free: the component performs the actual commit when {@link isAutoCommitDue}
1536
+ becomes true, then dispatches `fired`.
1537
+
1538
+ ```typescript
1539
+ function autoCommitReducer(state: AutoCommitState, action: AutoCommitAction): AutoCommitState
1540
+ ```
1541
+
1542
+ - `state` — The current countdown state.
1543
+ - `action` — The action to apply.
1544
+
1545
+ **Returns:** The next countdown state.
1546
+
1547
+ #### `buildReportPayload(form, clientInfo)`
1548
+
1549
+ Builds the `POST /projects/:id/report` body from the form state, trimming all
1550
+ text fields and omitting `steps` entirely when it is blank. When `clientInfo`
1551
+ is supplied and non-empty, it is attached; an `undefined` or empty diagnostics
1552
+ object is omitted from the payload.
1553
+
1554
+ ```typescript
1555
+ function buildReportPayload(form: ReportFormState, clientInfo?: ClientInfo): ReportPayload
1556
+ ```
1557
+
1558
+ - `form` — The report form state.
1559
+ - `clientInfo` — Optional client diagnostics from {@link collectClientInfo}.
1560
+
1561
+ **Returns:** The normalized report payload.
1562
+
1563
+ #### `buildSharePayload(role)`
1564
+
1565
+ Builds the `POST /projects/:projectId/shares` body for a role.
1566
+
1567
+ ```typescript
1568
+ function buildSharePayload(role?: 'viewer' | 'commenter' | 'editor' | 'owner'): SharePayload
1569
+ ```
1570
+
1571
+ - `role` — The role to grant (defaults to {@link DEFAULT_SHARE_ROLE}).
1572
+
1573
+ **Returns:** The normalized share payload.
1574
+
1575
+ #### `buildShareUrl(result, origin)`
1576
+
1577
+ Resolves the copyable public URL for a created share link. Prefers the
1578
+ backend-supplied `url` (canonical origin / custom domain); otherwise builds
1579
+ `<origin>/share/<slug>` from the given origin, tolerating a trailing slash.
1580
+
1581
+ ```typescript
1582
+ function buildShareUrl(result: ShareLinkResult, origin: string): string
1583
+ ```
1584
+
1585
+ - `result` — The created link from the share endpoint.
1586
+ - `origin` — The current page origin (e.g. `window.location.origin`).
1587
+
1588
+ **Returns:** The absolute, copyable share URL.
1589
+
1590
+ #### `ChatPanel(props)`
1591
+
1592
+ AI chat panel with conversation history dropdown and Claude Code-style tool display.
1593
+
1594
+ ```typescript
1595
+ function ChatPanel({
1596
+ projectId,
1597
+ endpoint,
1598
+ initialMessage,
1599
+ onInitialMessageSent,
1600
+ activeFile,
1601
+ openTabs,
1602
+ onFileOpen,
1603
+ onFileDoubleClick,
1604
+ onFileDiff,
1605
+ onFileRevert,
1606
+ onFileChange,
1607
+ onFileDeleted,
1608
+ onCommit,
1609
+ onActivityClick,
1610
+ onProfileClick,
1611
+ onReadyToBuild,
1612
+ awaitingSandboxBoot,
1613
+ onClientAction,
1614
+ onTurnComplete,
1615
+ onLoadingChange,
1616
+ onNavigatePreview,
1617
+ onRegisterPushHandler,
1618
+ autoSubmitSignal,
1619
+ initialInputValue,
1620
+ hideConversationMenu,
1621
+ renderConversationHeader = true,
1622
+ conversationId: controlledConversationId,
1623
+ chatKey: controlledChatKey,
1624
+ onConversationId: controlledOnConversationId,
1625
+ openShareSignal: controlledShareSignal,
1626
+ openReportSignal: controlledReportSignal,
1627
+ openSettingsSignal: controlledSettingsSignal,
1628
+ onManageCustomModels,
1629
+ gitStatusTick,
1630
+ pendingMessage,
1631
+ pendingMessageKey,
1632
+ pendingMessageSuppressUser,
1633
+ pendingMessageUserInitiated,
1634
+ userEditedFile,
1635
+ userEditedFileKey,
1636
+ isPro,
1637
+ buildUpgradeCta,
1638
+ buildHelpUpgradeSection,
1639
+ userAvatar,
1640
+ agentName,
1641
+ productName,
1642
+ version,
1643
+ feedbackUrl,
1644
+ className,
1645
+ }: ChatPanelProps): JSX.Element
1646
+ ```
1647
+
1648
+ - `props` — Component props (see {@link MessageItemProps}).
1649
+
1650
+ **Returns:** The rendered chat panel element.
1651
+
1652
+ #### `clampPanelSize(currentSize, deltaPx, containerWidth, min, max)`
1653
+
1654
+ Clamp a panel's new size after a pixel drag delta.
1655
+
1656
+ ```typescript
1657
+ function clampPanelSize(
1658
+ currentSize: number,
1659
+ deltaPx: number,
1660
+ containerWidth: number,
1661
+ min?: number,
1662
+ max?: number,
1663
+ ): number
1664
+ ```
1665
+
1666
+ - `currentSize` — The panel's current size as a percentage.
1667
+ - `deltaPx` — The drag delta in pixels (positive = grow the left panel).
1668
+ - `containerWidth` — The layout container width in pixels.
1669
+ - `min` — Minimum allowed percentage. Defaults to {@link MIN_PANEL_PERCENT}.
1670
+ - `max` — Maximum allowed percentage. Defaults to {@link MAX_PANEL_PERCENT}.
1671
+
1672
+ **Returns:** The new size as a percentage, clamped to `[min, max]`.
1673
+
1674
+ #### `collectClientInfo(opts)`
1675
+
1676
+ Collects client-side diagnostics for a report. Reads `navigator`
1677
+ (userAgent/platform/language), `window` (inner viewport size, screen size,
1678
+ location href), plus the caller-supplied app version and theme. Every access
1679
+ is guarded (`typeof window/navigator !== 'undefined'` and per-property
1680
+ presence) so it is SSR-safe and never throws — it returns only the fields it
1681
+ could actually read, so the result may be partial or (in a headless
1682
+ environment) empty.
1683
+
1684
+ ```typescript
1685
+ function collectClientInfo(opts?: { appVersion?: string; theme?: string }): ClientInfo
1686
+ ```
1687
+
1688
+ - `opts` — Caller-supplied context.
1689
+ - `opts.appVersion` — The running build version, if known.
1690
+ - `opts.theme` — The active theme (`'light'` | `'dark'`), if known.
1691
+
1692
+ **Returns:** The populated subset of {@link ClientInfo}.
1693
+
1694
+ #### `CommandPalette(props)`
1695
+
1696
+ Command Palette overlay.
1697
+
1698
+ ```typescript
1699
+ function CommandPalette({ commands, onDismiss }: CommandPaletteProps): JSX.Element
1700
+ ```
1701
+
1702
+ - `props` — Component props.
1703
+
1704
+ **Returns:** The command palette element.
1705
+
1706
+ #### `DeviceFrameSelector(props)`
1707
+
1708
+ A dropdown that selects the preview device frame and hosts the Rotate +
1709
+ Open-in-new-tab actions.
1710
+
1711
+ ```typescript
1712
+ function DeviceFrameSelector({
1713
+ current,
1714
+ onChange,
1715
+ className,
1716
+ canRotate,
1717
+ rotated,
1718
+ onRotate,
1719
+ onOpenExternal,
1720
+ }: DeviceFrameSelectorWithActionsProps): JSX.Element
1721
+ ```
1722
+
1723
+ - `props` — Component props (see {@link DeviceFrameSelectorWithActionsProps}).
1724
+
1725
+ **Returns:** The rendered device-frame selector element.
1726
+
1727
+ #### `deviceIconName(device)`
1728
+
1729
+ Returns the icon-set glyph name for a device frame.
1730
+
1731
+ ```typescript
1732
+ function deviceIconName(device: DeviceFrame): string
1733
+ ```
1734
+
1735
+ - `device` — The device frame.
1736
+
1737
+ **Returns:** The icon name registered in the bonded icon set.
1738
+
1739
+ #### `EditorPanel(props)`
1740
+
1741
+ Code editor panel with tab bar and Monaco integration.
1742
+
1743
+ ```typescript
1744
+ function EditorPanel({
1745
+ className,
1746
+ onActiveFileChange,
1747
+ onEditorReady,
1748
+ onTabsChange,
1749
+ fileStatuses,
1750
+ formattingFile,
1751
+ countdownFile,
1752
+ countdownKey,
1753
+ formatEstimate = 2000,
1754
+ onFixWithAI,
1755
+ onTabDoubleClick,
1756
+ }: EditorPanelProps): JSX.Element
1757
+ ```
1758
+
1759
+ - `props` — Component props.
1760
+
1761
+ **Returns:** The rendered editor panel element.
1762
+
1763
+ #### `FileExplorer(props)`
1764
+
1765
+ Tree-view file explorer component with multi-select, keyboard navigation, and drag-and-drop.
1766
+
1767
+ ```typescript
1768
+ function FileExplorer({
1769
+ files,
1770
+ onFileSelect,
1771
+ onFileDoubleClick,
1772
+ onDirExpand,
1773
+ onRename,
1774
+ onDelete,
1775
+ onDeleteMultiple,
1776
+ onMoveFiles,
1777
+ onNewFile,
1778
+ onNewFolder,
1779
+ onCollapseAll,
1780
+ className,
1781
+ persistKey,
1782
+ activeFile,
1783
+ fileStatuses,
1784
+ }: FileExplorerProps): JSX.Element
1785
+ ```
1786
+
1787
+ - `props` — Component props (see {@link FileTreeItemProps}).
1788
+
1789
+ **Returns:** The rendered file explorer element.
1790
+
1791
+ #### `filterActivitiesByType(activities, type)`
1792
+
1793
+ Filters a list of activities by channel type. `null` (the "All" tab) returns
1794
+ every activity unchanged.
1795
+
1796
+ ```typescript
1797
+ function filterActivitiesByType(activities: Activity[], type: ActivityType | null): Activity[]
1798
+ ```
1799
+
1800
+ - `activities` — The activities to filter.
1801
+ - `type` — The channel type to keep, or `null` for all.
1802
+
1803
+ **Returns:** The filtered list (a new array unless `type` is null).
1804
+
1805
+ #### `formatAutoCommitBadge(state)`
1806
+
1807
+ Formats the countdown's remaining seconds for the compact badge (e.g. `"12s"`).
1808
+ Returns `''` when there is nothing to show (disabled or paused).
1809
+
1810
+ ```typescript
1811
+ function formatAutoCommitBadge(state: AutoCommitState): string
1812
+ ```
1813
+
1814
+ - `state` — The countdown state.
1815
+
1816
+ **Returns:** The badge label, or `''`.
1817
+
1818
+ #### `formatReportConfirmation(result)`
1819
+
1820
+ Builds the confirmation message shown after a report is submitted. Returns the
1821
+ English defaults; the component passes these through `t()` at render. The
1822
+ success `defaultValue` may contain the `{{productName}}` interpolation token,
1823
+ which the caller fills in from the host's product identity (neutral default:
1824
+ "the IDE"). When the result is not `ok`, returns the failure message.
1825
+
1826
+ ```typescript
1827
+ function formatReportConfirmation(result: ReportResult): { key: string; defaultValue: string }
1828
+ ```
1829
+
1830
+ - `result` — The `POST /projects/:id/report` response.
1831
+
1832
+ **Returns:** `{ key, defaultValue }` for the confirmation/failure message.
1833
+
1834
+ #### `getCustomEventCardFactory(name)`
1835
+
1836
+ Resolve the registered card factory for a custom event name, if any.
1837
+
1838
+ ```typescript
1839
+ function getCustomEventCardFactory(name: string): ChatEventCardFactory | undefined
1840
+ ```
1841
+
1842
+ - `name` — The custom event `name`.
1843
+
1844
+ **Returns:** The registered factory, or undefined if none is registered.
1845
+
1846
+ #### `groupCommandsByCategory(commands, categories)`
1847
+
1848
+ Groups commands under their categories, preserving category and command
1849
+ order and dropping empty categories. Used by the `/settings` command
1850
+ reference (and any other view that lists commands by section) so the
1851
+ grouping stays in sync with the registry automatically.
1852
+
1853
+ ```typescript
1854
+ function groupCommandsByCategory(
1855
+ commands?: readonly CommandDef[],
1856
+ categories?: readonly CommandCategory[],
1857
+ ): CommandGroup[]
1858
+ ```
1859
+
1860
+ - `commands` — Command registry to group (defaults to {@link COMMANDS}).
1861
+ - `categories` — Ordered categories (defaults to {@link COMMAND_CATEGORIES}).
1862
+
1863
+ **Returns:** One {@link CommandGroup} per non-empty category, in category order.
1864
+
1865
+ #### `hasRenderableAvatar(avatar)`
1866
+
1867
+ Whether a stored avatar value is renderable (vs. needing the icon fallback).
1868
+
1869
+ ```typescript
1870
+ function hasRenderableAvatar(avatar?: string | null): boolean
1871
+ ```
1872
+
1873
+ - `avatar` — The avatar value from user metadata.
1874
+
1875
+ **Returns:** `true` when {@link resolveUserAvatar} would return a src.
1876
+
1877
+ #### `Icon(props)`
1878
+
1879
+ Renders the named glyph from the bonded icon set.
1880
+
1881
+ ```typescript
1882
+ function Icon({ name, size = 16, className, ...rest }: IconProps): JSX.Element
1883
+ ```
1884
+
1885
+ - `props` — {@link IconProps}.
1886
+
1887
+ **Returns:** An `<svg>` element rendering the named glyph.
1888
+
1889
+ #### `isAutoCommitArmed(state)`
1890
+
1891
+ Whether the countdown is actively armed (counting down), i.e. a 1s tick
1892
+ interval should be running. False when disabled or paused after a commit.
1893
+
1894
+ ```typescript
1895
+ function isAutoCommitArmed(state: AutoCommitState): boolean
1896
+ ```
1897
+
1898
+ - `state` — The countdown state.
1899
+
1900
+ **Returns:** `true` when `remaining` is a number.
1901
+
1902
+ #### `isAutoCommitCountdownVisible(state)`
1903
+
1904
+ Whether the countdown is in its visible window — armed and within the final
1905
+ {@link AUTO_COMMIT_COUNTDOWN_VISIBLE_SECONDS} seconds — i.e. the commit
1906
+ button should render the live "Auto-commit in Ns" label instead of "Commit".
1907
+
1908
+ ```typescript
1909
+ function isAutoCommitCountdownVisible(state: AutoCommitState): boolean
1910
+ ```
1911
+
1912
+ - `state` — The countdown state.
1913
+
1914
+ **Returns:** `true` when armed with at most the visible-window seconds left.
1915
+
1916
+ #### `isAutoCommitDue(state)`
1917
+
1918
+ Whether a commit is due this instant (the countdown has reached zero). The
1919
+ component reacts to this by running `/commit` and dispatching `fired`.
1920
+
1921
+ ```typescript
1922
+ function isAutoCommitDue(state: AutoCommitState): boolean
1923
+ ```
1924
+
1925
+ - `state` — The countdown state.
1926
+
1927
+ **Returns:** `true` when `remaining === 0`.
1928
+
1929
+ #### `isAutoCommitEnabled(state)`
1930
+
1931
+ Whether auto-commit is enabled (a positive cadence is configured), regardless
1932
+ of whether it is currently counting down or paused.
1933
+
1934
+ ```typescript
1935
+ function isAutoCommitEnabled(state: AutoCommitState): boolean
1936
+ ```
1937
+
1938
+ - `state` — The countdown state.
1939
+
1940
+ **Returns:** `true` when `intervalSeconds > 0`.
1941
+
1942
+ #### `isDeviceRotatable(device)`
1943
+
1944
+ Whether a device frame can be rotated (true only for fixed-frame devices —
1945
+ tablet and mobile). Responsive and desktop are full-width with nothing to
1946
+ rotate, so the "Rotate" control is enabled only "where possible".
1947
+
1948
+ ```typescript
1949
+ function isDeviceRotatable(device: DeviceFrame): boolean
1950
+ ```
1951
+
1952
+ - `device` — The device frame.
1953
+
1954
+ **Returns:** `true` if the frame has a fixed size that can be rotated.
1955
+
1956
+ #### `isInputFocused(e)`
1957
+
1958
+ Returns true when the event target is an interactive input element
1959
+ where shortcuts should be suppressed by default.
1960
+
1961
+ ```typescript
1962
+ function isInputFocused(e: KeyboardEvent): boolean
1963
+ ```
1964
+
1965
+ - `e` — The keyboard event.
1966
+
1967
+ **Returns:** Whether an input-like element is focused.
1968
+
1969
+ #### `isMonacoFocused(e)`
1970
+
1971
+ Returns true when focus is inside a Monaco editor instance.
1972
+
1973
+ ```typescript
1974
+ function isMonacoFocused(e: KeyboardEvent): boolean
1975
+ ```
1976
+
1977
+ - `e` — The keyboard event.
1978
+
1979
+ **Returns:** Whether the target is inside `.monaco-editor`.
1980
+
1981
+ #### `isReportFormValid(form)`
1982
+
1983
+ Whether the report form has the minimum required fields: a non-empty title
1984
+ and a non-empty description.
1985
+
1986
+ ```typescript
1987
+ function isReportFormValid(form: ReportFormState): boolean
1988
+ ```
1989
+
1990
+ - `form` — The report form state.
1991
+
1992
+ **Returns:** `true` when the form can be submitted.
1993
+
1994
+ #### `isShareRole(value)`
1995
+
1996
+ Type guard for a valid {@link ShareRole} (case-insensitive callers should
1997
+ lower-case first).
1998
+
1999
+ ```typescript
2000
+ function isShareRole(value: string): boolean
2001
+ ```
2002
+
2003
+ - `value` — The candidate value.
2004
+
2005
+ **Returns:** `true` when `value` is one of the {@link SHARE_ROLES}.
2006
+
2007
+ #### `KeyboardShortcutsPanel(props)`
2008
+
2009
+ Keyboard shortcuts reference panel.
2010
+
2011
+ ```typescript
2012
+ function KeyboardShortcutsPanel({ shortcuts, onDismiss }: KeyboardShortcutsPanelProps): JSX.Element
2013
+ ```
2014
+
2015
+ - `props` — Component props.
2016
+
2017
+ **Returns:** The keyboard shortcuts panel element.
2018
+
2019
+ #### `livePanelSize(layout, panelConfigs, index)`
2020
+
2021
+ The live size (percentage) of the visible panel at `index`, read from
2022
+ `layout.sizes` keyed by the panel's position group (where `resizePanel`
2023
+ writes), falling back to the panel's `defaultSize`, then to an equal split.
2024
+
2025
+ ```typescript
2026
+ function livePanelSize(layout: WorkspaceLayout, panelConfigs: PanelConfig[], index: number): number
2027
+ ```
2028
+
2029
+ - `layout` — The current workspace layout.
2030
+ - `panelConfigs` — The visible panel configs, in render order.
2031
+ - `index` — The index of the panel within `panelConfigs`.
2032
+
2033
+ **Returns:** The panel size as a percentage.
2034
+
2035
+ #### `normalizeKeys(keys)`
2036
+
2037
+ Normalize a shortcut definition string into the same format produced by
2038
+ `serializeEvent`. Accepts `mod+shift+f` style strings (case-insensitive).
2039
+
2040
+ ```typescript
2041
+ function normalizeKeys(keys: string): string
2042
+ ```
2043
+
2044
+ - `keys` — Raw shortcut string.
2045
+
2046
+ **Returns:** Normalized lowercase combo string.
2047
+
2048
+ #### `parseAutoCommitCommand(input)`
2049
+
2050
+ Parses an `/autocommit [seconds]` command.
2051
+
2052
+ Returns `{ seconds: null }` when `/autocommit` is typed with no argument (the
2053
+ caller shows usage/current state), `{ seconds: n }` for a non-negative integer
2054
+ argument (`0` cancels), or `null` when the input is not the command.
2055
+
2056
+ ```typescript
2057
+ function parseAutoCommitCommand(input: string): { seconds: number | null } | null
2058
+ ```
2059
+
2060
+ - `input` — The raw chat input.
2061
+
2062
+ **Returns:** The parsed command, or `null` when it is not `/autocommit`.
2063
+
2064
+ #### `parseReportCommand(input)`
2065
+
2066
+ Parses a `/report [title]` or `/bug [title]` command. Returns the (possibly
2067
+ empty) trimmed title used to seed the modal when the input is one of those
2068
+ commands, else `null`.
2069
+
2070
+ ```typescript
2071
+ function parseReportCommand(input: string): { title: string } | null
2072
+ ```
2073
+
2074
+ - `input` — The raw chat input.
2075
+
2076
+ **Returns:** `{ title }` when it's a `/report` or `/bug` command, else `null`.
2077
+
2078
+ #### `parseShareCommand(input)`
2079
+
2080
+ Parses a `/share [role]` command. Bare `/share` creates a link at the default
2081
+ `viewer` role; `/share <role>` uses the named role when valid (any case); an
2082
+ unrecognized role is reported as invalid so the caller can show usage. Returns
2083
+ `null` when the input is not the `/share` command.
2084
+
2085
+ ```typescript
2086
+ function parseShareCommand(input: string): ShareCommand | null
2087
+ ```
2088
+
2089
+ - `input` — The raw chat input.
2090
+
2091
+ **Returns:** The parsed {@link ShareCommand}, or `null`.
2092
+
2093
+ #### `PreviewPanel(props)`
2094
+
2095
+ Live preview panel with iframe, device frame selector, and URL bar.
2096
+
2097
+ ```typescript
2098
+ function PreviewPanel({
2099
+ loadingIndicator,
2100
+ restartingIndicator,
2101
+ className,
2102
+ onPreviewError,
2103
+ onPreviewStuck,
2104
+ onRenderState,
2105
+ uiCommand,
2106
+ onUiResult,
2107
+ fileChangeTick,
2108
+ buildingHint,
2109
+ isBuilding,
2110
+ wakeAt,
2111
+ }: PreviewPanelProps): JSX.Element
2112
+ ```
2113
+
2114
+ - `props` — Component props.
2115
+
2116
+ **Returns:** The rendered preview panel element.
2117
+
2118
+ #### `QuickOpen(props)`
2119
+
2120
+ Quick Open file finder overlay.
2121
+
2122
+ ```typescript
2123
+ function QuickOpen({ projectId, onFileOpen, onDismiss }: QuickOpenProps): JSX.Element
2124
+ ```
2125
+
2126
+ - `props` — Component props.
2127
+
2128
+ **Returns:** The quick open element.
2129
+
2130
+ #### `QuickPicker(props)`
2131
+
2132
+ Quick picker overlay with keyboard navigation.
2133
+
2134
+ ```typescript
2135
+ function QuickPicker({
2136
+ items,
2137
+ placeholder,
2138
+ onSelect,
2139
+ onDismiss,
2140
+ loading,
2141
+ initialQuery,
2142
+ className,
2143
+ }: QuickPickerProps): JSX.Element
2144
+ ```
2145
+
2146
+ - `props` — Component props.
2147
+
2148
+ **Returns:** The quick picker element.
2149
+
2150
+ #### `registerCustomEventCard(name, factory)`
2151
+
2152
+ Register a renderer for a custom chat-stream event. Consuming apps call this at
2153
+ startup so their own `{ type: 'custom', name }` events surface as chat cards —
2154
+ keeping app-specific events out of the core ai-chat union and this package's
2155
+ ChatPanel. Re-registering the same name overwrites the previous factory.
2156
+
2157
+ ```typescript
2158
+ function registerCustomEventCard(name: string, factory: ChatEventCardFactory): void
2159
+ ```
2160
+
2161
+ - `name` — The custom event `name` to handle (matches the emitted event's `name`).
2162
+ - `factory` — Builds the card from the event's `data` (or returns null to skip).
2163
+
2164
+ #### `ReportModal(props)`
2165
+
2166
+ The bug-report / feedback modal opened by `/report`, `/bug`, and the header
2167
+ bug-report button.
2168
+
2169
+ ```typescript
2170
+ function ReportModal({
2171
+ projectId,
2172
+ conversationId,
2173
+ initialTitle,
2174
+ onClose,
2175
+ onSubmitted,
2176
+ productName = DEFAULT_PRODUCT_NAME,
2177
+ appVersion,
2178
+ }: {
2179
+ projectId: string
2180
+ conversationId?: string | null
2181
+ initialTitle?: string
2182
+ onClose: () => void
2183
+ onSubmitted: (result: ReportResult) => void
2184
+ productName?: string
2185
+ appVersion?: string
2186
+ }): JSX.Element
2187
+ ```
2188
+
2189
+ - `props` — Component props.
2190
+
2191
+ **Returns:** The rendered report modal.
2192
+
2193
+ #### `ResizeHandle(props)`
2194
+
2195
+ Draggable handle for resizing adjacent panels. Uses Pointer Events so it works
2196
+ with mouse, touch, and pen (an iPad drag resizes just like a desktop drag); a
2197
+ wide invisible grab zone wraps a thin visible line that brightens to the
2198
+ primary color on hover/drag for a clear affordance. Arrow keys nudge the split
2199
+ for keyboard users.
2200
+
2201
+ ```typescript
2202
+ function ResizeHandle({
2203
+ onResize,
2204
+ direction = 'horizontal',
2205
+ className,
2206
+ }: ResizeHandleProps): JSX.Element
2207
+ ```
2208
+
2209
+ - `props` — Component props.
2210
+
2211
+ **Returns:** The rendered resize handle element.
2212
+
2213
+ #### `resolveAutoCommitSeconds(saved)`
2214
+
2215
+ Resolves the persisted `project.settings.autoCommitSeconds` value to the
2216
+ effective cadence. A finite number is an explicit user choice: positive
2217
+ values floor to whole seconds, non-positive values mean "the user turned
2218
+ auto-commit off" (`0`). Anything else — missing, never configured, or
2219
+ invalid — falls back to {@link DEFAULT_AUTO_COMMIT_SECONDS}, because
2220
+ auto-commit is on by default.
2221
+
2222
+ ```typescript
2223
+ function resolveAutoCommitSeconds(saved: unknown): number
2224
+ ```
2225
+
2226
+ - `saved` — The raw persisted setting value.
2227
+
2228
+ **Returns:** The effective cadence in whole seconds (`0` = off).
2229
+
2230
+ #### `resolveDeviceSize(device, orientation)`
2231
+
2232
+ Resolves the concrete iframe `width`/`height` for a device frame at a given
2233
+ orientation. Fluid frames (responsive/desktop) ignore orientation and fill
2234
+ the preview area (`width` from dims, `height: '100%'`). Fixed frames render
2235
+ at their pixel size in portrait and at the swapped size in landscape.
2236
+
2237
+ ```typescript
2238
+ function resolveDeviceSize(
2239
+ device: DeviceFrame,
2240
+ orientation: DeviceOrientation,
2241
+ ): { width: string; height: string }
2242
+ ```
2243
+
2244
+ - `device` — The device frame.
2245
+ - `orientation` — The preview orientation.
2246
+
2247
+ **Returns:** The CSS `width` and `height` to apply to the preview iframe.
2248
+
2249
+ #### `resolveUserAvatar(avatar)`
2250
+
2251
+ Resolve a stored avatar value to a safe `<img>` src, or `null` to fall back to
2252
+ the generic icon.
2253
+
2254
+ ```typescript
2255
+ function resolveUserAvatar(avatar?: string | null): string | null
2256
+ ```
2257
+
2258
+ - `avatar` — The avatar value from user metadata (data-URI, URL, or absent).
2259
+
2260
+ **Returns:** A safe image src string, or `null` when there is no renderable avatar.
2261
+
2262
+ #### `SearchPanel(props)`
2263
+
2264
+ Search-in-files panel for the IDE sidebar with find-and-replace support.
2265
+
2266
+ ```typescript
2267
+ function SearchPanel({
2268
+ projectId,
2269
+ onResultClick,
2270
+ className,
2271
+ excludedDirs,
2272
+ onExcludedDirsChange,
2273
+ }: SearchPanelProps): JSX.Element
2274
+ ```
2275
+
2276
+ - `props` — Component props.
2277
+
2278
+ **Returns:** The search panel element.
2279
+
2280
+ #### `serializeEvent(e, isMac)`
2281
+
2282
+ Serialize a `KeyboardEvent` into a normalized combo string.
2283
+ Format: modifier keys in order `mod+ctrl+alt+shift+key` (lowercase).
2284
+ `mod` maps to Meta on Mac, Control elsewhere.
2285
+
2286
+ ```typescript
2287
+ function serializeEvent(e: KeyboardEvent, isMac?: boolean): string
2288
+ ```
2289
+
2290
+ - `e` — The keyboard event.
2291
+ - `isMac` — Override platform detection (for testing).
2292
+
2293
+ **Returns:** Serialized combo string.
2294
+
2295
+ #### `ShareModal(props)`
2296
+
2297
+ The share-link modal opened by `/share`, `/share <role>`, and the header
2298
+ share button.
2299
+
2300
+ ```typescript
2301
+ function ShareModal({
2302
+ projectId,
2303
+ initialRole = DEFAULT_SHARE_ROLE,
2304
+ onClose,
2305
+ onCreated,
2306
+ }: {
2307
+ projectId: string
2308
+ initialRole?: ShareRole
2309
+ onClose: () => void
2310
+ onCreated?: (result: ShareLinkResult) => void
2311
+ }): JSX.Element
2312
+ ```
2313
+
2314
+ - `props` — Component props.
2315
+
2316
+ **Returns:** The rendered share modal.
2317
+
2318
+ #### `SidebarTabs(props)`
2319
+
2320
+ Sidebar tab strip with file explorer and search icons.
2321
+
2322
+ ```typescript
2323
+ function SidebarTabs({ activeTab, onTabChange, children, className }: SidebarTabsProps): JSX.Element
2324
+ ```
2325
+
2326
+ - `props` — Component props.
2327
+
2328
+ **Returns:** The sidebar tabs element.
2329
+
2330
+ #### `TabBar(props)`
2331
+
2332
+ Horizontally scrollable tab bar for open editor files.
2333
+
2334
+ ```typescript
2335
+ function TabBar({
2336
+ tabs,
2337
+ activeFile,
2338
+ onSelect,
2339
+ onClose,
2340
+ onDoubleClick,
2341
+ fileStatuses,
2342
+ className,
2343
+ }: TabBarProps): JSX.Element | null
2344
+ ```
2345
+
2346
+ - `props` — Component props (see {@link TabItemProps}).
2347
+
2348
+ **Returns:** The rendered tab bar element, or null if no tabs are open.
2349
+
2350
+ #### `useCoarsePointer()`
2351
+
2352
+ Whether the primary pointer is coarse (touch). Use for interaction
2353
+ decisions: ≥44px touch targets, always-visible controls that desktop
2354
+ reveals on hover (hover does not exist on touch).
2355
+
2356
+ ```typescript
2357
+ function useCoarsePointer(): boolean
2358
+ ```
2359
+
2360
+ **Returns:** True on touch-first devices.
2361
+
2362
+ #### `useKeyboardShortcuts(shortcuts)`
2363
+
2364
+ Registers global keyboard shortcuts on the document.
2365
+
2366
+ ```typescript
2367
+ function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]): void
2368
+ ```
2369
+
2370
+ - `shortcuts` — Array of shortcut definitions. Callers should wrap the array in `useMemo` to avoid unnecessary re-subscriptions.
2371
+
2372
+ #### `useMediaQueryMatch(query)`
2373
+
2374
+ Subscribes to a CSS media query. SSR-safe: reports `false` until mounted in
2375
+ a browser.
2376
+
2377
+ ```typescript
2378
+ function useMediaQueryMatch(query: string): boolean
2379
+ ```
2380
+
2381
+ - `query` — A media query string, e.g. `'(max-width: 767px)'`.
2382
+
2383
+ **Returns:** Whether the query currently matches.
2384
+
2385
+ #### `useNarrowViewport()`
2386
+
2387
+ Whether the viewport is phone-width (below 768px). Use for layout-density
2388
+ decisions: bigger type, taller rows, `dvh`-capped popovers.
2389
+
2390
+ ```typescript
2391
+ function useNarrowViewport(): boolean
2392
+ ```
2393
+
2394
+ **Returns:** True on phone-width viewports.
2395
+
2396
+ #### `UserAvatar(props)`
2397
+
2398
+ Renders the user's avatar image, or the generic `user` icon when there is no
2399
+ safe, renderable avatar. When {@link UserAvatarProps.onClick} is provided the
2400
+ visual is wrapped in an accessible button so the avatar can open the user's
2401
+ profile.
2402
+
2403
+ ```typescript
2404
+ function UserAvatar({ userAvatar, size = 24, onClick }: UserAvatarProps): JSX.Element
2405
+ ```
2406
+
2407
+ - `props` — {@link UserAvatarProps}.
2408
+
2409
+ **Returns:** The avatar image or icon fallback, optionally wrapped in a button.
2410
+
2411
+ #### `WorkspaceLayout(props)`
2412
+
2413
+ Top-level workspace layout that arranges child panels in a row with draggable
2414
+ vertical dividers between them. Each divider resizes its left panel; the new
2415
+ size is written back through `resizePanel`, so it persists in workspace state.
2416
+
2417
+ ```typescript
2418
+ function WorkspaceLayout({ children, className }: WorkspaceLayoutProps): JSX.Element
2419
+ ```
2420
+
2421
+ - `props` — Component props.
2422
+
2423
+ **Returns:** The rendered workspace layout element.
2424
+
2425
+ ### Constants
2426
+
2427
+ #### `ACTIVITY_TYPES`
2428
+
2429
+ All channel types, in the order their filter tabs should appear.
2430
+
2431
+ ```typescript
2432
+ const ACTIVITY_TYPES: ActivityType[]
2433
+ ```
2434
+
2435
+ #### `AUTO_COMMIT_COUNTDOWN_VISIBLE_SECONDS`
2436
+
2437
+ How many final seconds of the countdown are DISPLAYED as a live countdown.
2438
+ Until the countdown drops to this threshold, the commit-bar button stays a
2439
+ plain green "Commit" (a bare "12s" pill told users nothing); the countdown
2440
+ label only takes over for these last seconds, right before the auto-commit
2441
+ fires. Cadences at or below the threshold show the countdown the whole time.
2442
+
2443
+ ```typescript
2444
+ const AUTO_COMMIT_COUNTDOWN_VISIBLE_SECONDS: 3
2445
+ ```
2446
+
2447
+ #### `AUTO_COMMIT_DISABLED`
2448
+
2449
+ The disabled (off) countdown state — the reducer's initial value.
2450
+
2451
+ ```typescript
2452
+ const AUTO_COMMIT_DISABLED: AutoCommitState
2453
+ ```
2454
+
2455
+ #### `COARSE_POINTER_QUERY`
2456
+
2457
+ The touch-first media query — matches when the PRIMARY pointer is coarse
2458
+ (finger), i.e. phones/tablets, regardless of viewport width.
2459
+
2460
+ ```typescript
2461
+ const COARSE_POINTER_QUERY: '(pointer: coarse)'
2462
+ ```
2463
+
2464
+ #### `COMMAND_CATEGORIES`
2465
+
2466
+ Ordered list of command categories used as section headings.
2467
+
2468
+ ```typescript
2469
+ const COMMAND_CATEGORIES: readonly CommandCategory[]
2470
+ ```
2471
+
2472
+ #### `COMMANDS`
2473
+
2474
+ Every available slash command, grouped by category order. This is the
2475
+ authoritative list — the menu, key handling, `/help`, AND the system prompt's
2476
+ "Available Commands" section all read from it.
2477
+
2478
+ ```typescript
2479
+ const COMMANDS: readonly CommandDef[]
2480
+ ```
2481
+
2482
+ #### `DEFAULT_AUTO_COMMIT_SECONDS`
2483
+
2484
+ The default auto-commit cadence in seconds — auto-commit is ON by default.
2485
+ Projects that have never persisted `autoCommitSeconds` hydrate to this
2486
+ cadence (paused, arming on the first file change); only an explicit `0`
2487
+ (the user cancelled) keeps auto-commit off.
2488
+
2489
+ The value is the TOTAL debounce after the last file change (once the agent's
2490
+ turn has finished): a short quiet period, then the final
2491
+ {@link AUTO_COMMIT_COUNTDOWN_VISIBLE_SECONDS} seconds render as a live
2492
+ countdown on the commit button. 5s = 2s quiet + 3s visible countdown.
2493
+
2494
+ ```typescript
2495
+ const DEFAULT_AUTO_COMMIT_SECONDS: 5
2496
+ ```
2497
+
2498
+ #### `DEFAULT_SEARCH_EXCLUDED_DIRS`
2499
+
2500
+ Default search-excluded directory names shown when the host supplies none —
2501
+ VS Code's `search.exclude`/`files.exclude` defaults plus the platform's
2502
+ vendored/build dirs. MUST stay in sync with the API-side list in
2503
+ `@molecule/api-ai-tools` (utilities.ts) — the cross-stack import boundary
2504
+ forces the duplication.
2505
+
2506
+ ```typescript
2507
+ const DEFAULT_SEARCH_EXCLUDED_DIRS: readonly [
2508
+ 'node_modules',
2509
+ 'bower_components',
2510
+ '.git',
2511
+ '.svn',
2512
+ '.hg',
2513
+ 'CVS',
2514
+ 'dist',
2515
+ '.next',
2516
+ '.vite',
2517
+ 'molecule',
2518
+ ]
2519
+ ```
2520
+
2521
+ #### `DEFAULT_SHARE_ROLE`
2522
+
2523
+ The default (and safest) role when `/share` is run with no argument.
2524
+
2525
+ ```typescript
2526
+ const DEFAULT_SHARE_ROLE: 'viewer' | 'commenter' | 'editor' | 'owner'
2527
+ ```
2528
+
2529
+ #### `DEVICE_DIMENSIONS`
2530
+
2531
+ The fixed pixel frame each device renders the preview iframe at. `none`
2532
+ (responsive) and `desktop` are fluid — full width, full height, nothing to
2533
+ rotate. `tablet` (768×1024) and `mobile` (375×667) are fixed frames that can
2534
+ be rotated to landscape (swapping width/height).
2535
+
2536
+ ```typescript
2537
+ const DEVICE_DIMENSIONS: Record<DeviceFrame, DeviceDimensions>
2538
+ ```
2539
+
2540
+ #### `DEVICE_FRAMES`
2541
+
2542
+ The device frames the selector dropdown lists, in display order.
2543
+ `none` = responsive (no fixed frame, full width).
2544
+
2545
+ ```typescript
2546
+ const DEVICE_FRAMES: readonly DeviceFrame[]
2547
+ ```
2548
+
2549
+ #### `DEVICE_META`
2550
+
2551
+ Per-frame display metadata: the icon-set glyph name and the i18n label
2552
+ (key + English default) shown in the dropdown trigger + menu items.
2553
+
2554
+ ```typescript
2555
+ const DEVICE_META: Record<
2556
+ DeviceFrame,
2557
+ { readonly icon: IconName; readonly labelKey: string; readonly label: string }
2558
+ >
2559
+ ```
2560
+
2561
+ #### `EMPTY_REPORT_FORM`
2562
+
2563
+ An empty report form (the modal's initial state).
2564
+
2565
+ ```typescript
2566
+ const EMPTY_REPORT_FORM: ReportFormState
2567
+ ```
2568
+
2569
+ #### `IS_MAC`
2570
+
2571
+ Detect macOS / iOS for Cmd vs Ctrl.
2572
+
2573
+ ```typescript
2574
+ const IS_MAC: boolean
2575
+ ```
2576
+
2577
+ #### `MAX_AVATAR_SRC_LENGTH`
2578
+
2579
+ Maximum avatar source length we will render inline (~256 KB data-URI).
2580
+
2581
+ ```typescript
2582
+ const MAX_AVATAR_SRC_LENGTH: 262144
2583
+ ```
2584
+
2585
+ #### `MAX_PANEL_PERCENT`
2586
+
2587
+ Largest a panel may grow to, as a percentage of the container.
2588
+
2589
+ ```typescript
2590
+ const MAX_PANEL_PERCENT: 80
2591
+ ```
2592
+
2593
+ #### `MIN_PANEL_PERCENT`
2594
+
2595
+ Smallest a panel may shrink to, as a percentage of the container.
2596
+
2597
+ ```typescript
2598
+ const MIN_PANEL_PERCENT: 10
2599
+ ```
2600
+
2601
+ #### `NARROW_VIEWPORT_QUERY`
2602
+
2603
+ The phone-width media query — matches viewports below 768px, mirroring the
2604
+ host workspace's single-pane mobile breakpoint.
2605
+
2606
+ ```typescript
2607
+ const NARROW_VIEWPORT_QUERY: '(max-width: 767px)'
2608
+ ```
2609
+
2610
+ #### `SETTINGS`
2611
+
2612
+ Every user-controllable setting, in display order. The authoritative list —
2613
+ the `/settings` view (via `buildSettingsList`) and the system prompt both read
2614
+ from it, so they cannot drift. The default `model` and the per-mode
2615
+ `planModel` / `executeModel` rows are kept distinct (rather than collapsed
2616
+ into one "Model" row); `effort`, `autoCommit`, and `hooks` are each listed
2617
+ alongside (or in `hooks`' case, instead of) the command the panel references —
2618
+ so a user's actual configuration is never understated and the panel never
2619
+ shows a command for a setting it hides.
2620
+
2621
+ ```typescript
2622
+ const SETTINGS: readonly SettingMeta[]
2623
+ ```
2624
+
2625
+ #### `SHARE_ROLE_LABELS`
2626
+
2627
+ Short English labels per role (the component wraps these in `t()` at render).
2628
+
2629
+ ```typescript
2630
+ const SHARE_ROLE_LABELS: Record<'viewer' | 'commenter' | 'editor' | 'owner', string>
2631
+ ```
2632
+
2633
+ #### `SHARE_ROLES`
2634
+
2635
+ Share roles, ordered least- to most-privileged. Mirrors the
2636
+ `@molecule/api-resource-share` `SHARE_ROLES` contract; duplicated here (rather
2637
+ than imported) because an `app-*` package may not import an `api-*` package
2638
+ across the stack boundary.
2639
+
2640
+ ```typescript
2641
+ const SHARE_ROLES: readonly ['viewer', 'commenter', 'editor', 'owner']
2642
+ ```
2643
+
2644
+ #### `ToolCallCard`
2645
+
2646
+ Compact tool-call row with status dot, label, summary, and expandable detail pane.
2647
+
2648
+ ```typescript
2649
+ const ToolCallCard: MemoExoticComponent<
2650
+ ({
2651
+ id,
2652
+ name,
2653
+ input,
2654
+ output,
2655
+ status,
2656
+ fileDiff,
2657
+ isUndone: isUndoneProp,
2658
+ onUndoToggle,
2659
+ onFileOpen,
2660
+ onFileDoubleClick,
2661
+ onFileDiff,
2662
+ onFileRevert,
2663
+ onAskUserResponse,
2664
+ className,
2665
+ }: ToolCallCardProps) => JSX.Element | null
2666
+ >
2667
+ ```
2668
+
2669
+ ## Injection Notes
2670
+
2671
+ ### Requirements
2672
+
2673
+ Peer dependencies:
2674
+
2675
+ - `@molecule/app-ai-chat` ^1.0.1
2676
+ - `@molecule/app-ai-models` ^1.0.1
2677
+ - `@molecule/app-ai-voice` ^1.0.1
2678
+ - `@molecule/app-code-editor` ^1.0.1
2679
+ - `@molecule/app-country-flags` ^1.0.1
2680
+ - `@molecule/app-i18n` ^1.0.1
2681
+ - `@molecule/app-icons` ^1.0.1
2682
+ - `@molecule/app-ide` ^1.0.1
2683
+ - `@molecule/app-live-preview` ^1.0.1
2684
+ - `@molecule/app-logger` ^1.0.1
2685
+ - `@molecule/app-react` ^1.0.1
2686
+ - `@molecule/app-storage` ^1.0.1
2687
+ - `@molecule/app-ui` ^1.0.1
2688
+ - `@molecule/app-ui-react` ^1.0.1
2689
+ - `react` ^18.0.0 || ^19.0.0
2690
+ - `react-dom` ^18.0.0 || ^19.0.0
2691
+
2692
+ ### Runtime Dependencies
2693
+
2694
+ - `@molecule/app-ai-chat`
2695
+ - `@molecule/app-ai-models`
2696
+ - `@molecule/app-ai-voice`
2697
+ - `@molecule/app-code-editor`
2698
+ - `@molecule/app-country-flags`
2699
+ - `@molecule/app-i18n`
2700
+ - `@molecule/app-icons`
2701
+ - `@molecule/app-ide`
2702
+ - `@molecule/app-live-preview`
2703
+ - `@molecule/app-logger`
2704
+ - `@molecule/app-react`
2705
+ - `@molecule/app-storage`
2706
+ - `@molecule/app-ui`
2707
+ - `@molecule/app-ui-react`
2708
+ - `material-file-icons`
2709
+ - `react`
2710
+ - `react-dom`
2711
+
2712
+ - The panels are hook-driven and THROW without provider wiring: wrap the
2713
+ tree in `@molecule/app-react`'s `WorkspaceProvider`, `EditorProvider`,
2714
+ `ChatProvider`, and `PreviewProvider` (or the umbrella
2715
+ `MoleculeProvider`), each wired to a bond — typically
2716
+ `@molecule/app-ide-default` (workspace/layout state + persistence),
2717
+ `@molecule/app-code-editor-monaco`, `@molecule/app-ai-chat-http`
2718
+ (streaming chat endpoint), and `@molecule/app-live-preview-iframe`.
2719
+ `ChatPanel` additionally needs the bonded HTTP client
2720
+ (`useHttpClient`) and `I18nProvider`; `getClassMap()` needs a ClassMap
2721
+ bond.
2722
+ - `WorkspaceLayout` takes panels as CHILDREN and matches them in order to
2723
+ the workspace provider's panel configs (extra configs are ignored, so
2724
+ rendering 2 children against the default 3-panel layout works). Panel
2725
+ sizes persist through the workspace provider's `resizePanel`.
2726
+ - App-specific chat/stream events do NOT belong in this package — emit
2727
+ `{ type: 'custom', name, data }` events and register a card with
2728
+ `registerCustomEventCard(name, factory)` in the consuming app.
2729
+ - `ReportModal` and `ShareModal` are exported so a HOST can mount them
2730
+ itself. They need only `projectId` + the bonded HTTP client — no
2731
+ workspace/editor/preview provider, no chat, no running sandbox — because
2732
+ they POST to `/projects/:id/report` and `/projects/:id/shares` on the
2733
+ platform API. Do NOT route a host's "report a bug" / "share" button
2734
+ through `ChatPanel`'s `openReportSignal` / `openShareSignal` alone: those
2735
+ props are observed inside the panel, so in any phase where the host does
2736
+ not mount a `ChatPanel` (a hibernating sandbox, an error screen) the
2737
+ button silently does nothing. Mount the modal directly in those phases.
2738
+ `ChatPanel` renders its own copies for `/report` and `/share`; a host that
2739
+ mounts them too must gate on whether the panel is mounted so the modal
2740
+ never opens twice.
2741
+ - `ReportModal` does NOT show its own success state — it calls `onSubmitted`
2742
+ and closes, so a host that mounts it MUST surface the confirmation itself
2743
+ (`formatReportConfirmation(result)` returns the i18n key + default copy,
2744
+ and `result.url` is the filed issue). `ShareModal` does show the created
2745
+ link inline, but only until it is closed.
2746
+ - Text routes through `t('ide.*')` — `@molecule/app-locales-ide` supplies
2747
+ translations.
2748
+
2749
+ ## Translations
2750
+
2751
+ Translation strings are provided by `@molecule/app-locales-ide`.