@molecule/app-ide-react
v1.4.0
Published
React IDE components for molecule.dev workspace
Readme
@molecule/app-ide-react
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
@molecule/app-ide-react — React components for an AI-powered IDE
workspace: WorkspaceLayout (resizable panel row), ChatPanel (streaming
AI chat with tool-call cards, @ file mentions, / commands), EditorPanel
(tabbed Monaco editor), PreviewPanel (live-preview iframe with device
frames + crash/blank recovery), FileExplorer, CommandPalette,
QuickOpen, TabBar, plus registerCustomEventCard() for app-specific
chat cards and useKeyboardShortcuts().
Quick Start
import { ChatPanel, EditorPanel, PreviewPanel, WorkspaceLayout } from '@molecule/app-ide-react'
;<WorkspaceLayout>
<ChatPanel
projectId="proj_abc123"
onFileOpen={(path) => console.log('open', path)}
onFileChange={(path, content) => console.log('changed', path, content.length)}
onReadyToBuild={() => console.log('boot sandbox')}
/>
<EditorPanel
onActiveFileChange={(path) => console.log('active', path)}
onFixWithAI={(req) => console.log('fix', req)}
/>
<PreviewPanel onPreviewError={(errs) => console.error(errs)} />
</WorkspaceLayout>Type
feature
Installation
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
npm install -D @types/reactAPI
Interfaces
Activity
A single captured activity. Mirrors the SSE activity.activity payload; the
REST list endpoint additionally returns payload and result for the
expanded detail view.
interface Activity {
id: string
type: ActivityType
status: ActivityStatus
recipient?: string
summary?: string
/** ISO 8601 timestamp. */
timestamp: string
/** Full captured payload — only present on the REST detail response (dev only). */
payload?: unknown
/** Provider result / synthetic success record — only present on the REST detail response. */
result?: unknown
}ActivityCardProps
Props for the inline {@link ActivityCard}.
interface ActivityCardProps {
/** The captured activity to render. */
activity: Activity
/** Called when the card is clicked — should open the Activity panel filtered to this activity. */
onActivityClick?: (activity: Activity) => void
}AutoCommitState
The countdown's state.
intervalSeconds is the configured cadence (0 = disabled). remaining is
the live count: a positive number while counting down, 0 at the instant a
commit is due, and null while disabled or paused (after a commit, awaiting
the next file change to re-arm).
interface AutoCommitState {
/** Configured countdown length in seconds; `0` when auto-commit is off. */
intervalSeconds: number
/** Seconds left until the next auto-commit; `null` when disabled or paused. */
remaining: number | null
}ChatEventCard
A chat system card: a short message with an optional action (or actions). Mirrors the system-card shape ChatPanel renders for upgrade prompts, guest reminders, etc.
interface ChatEventCard {
/** The card's text. */
text: string
/** An optional action button (or buttons): a link (`href`) and/or a click handler. */
action?: ChatEventCardAction | ChatEventCardAction[]
/**
* Composable inline body for a `tone` (tip) card: an ordered list of segments rendered
* in sequence — plain strings as text, {@link ChatEventCardAction}s as inline underlined
* links — so prose and links interleave freely (e.g. text → an inline link → a trailing
* period). When set, the renderer uses this INSTEAD of `text` + appended `action`s, so a
* link can sit mid-sentence rather than only at the end. Segments carry their own spacing
* (no auto-space is inserted between them). Keep `text` populated with a plain-text
* equivalent for accessibility / non-toned consumers. Only honored when `tone` is set.
*/
content?: ChatEventCardSegment[]
/**
* When true, ChatPanel renders the card as a stand-out tip box rather than muted inline
* text. Prefer setting {@link ChatEventCard.tone} (which implies emphasis AND picks the
* accent colour + icon); `emphasized` without a `tone` falls back to the neutral `info`
* tone. The app opts in; the shared package never infers emphasis from a card's copy.
*/
emphasized?: boolean
/**
* The card's tip TONE — picks its accent colour + default icon so every notice card
* shares ONE consistent box (icon + tinted body + a uniform 1px border + actions),
* differing only by colour/icon per kind:
* - `info` — blue, info glyph (neutral notice)
* - `gold` — amber, lightbulb (an honest tip / onboarding note)
* - `upgrade` — amber, clock (a plan/limit/budget nudge)
* - `success` — green, check (a completed action, e.g. a saved script)
* - `signup` — primary, sign-in (an auth nudge)
*
* Setting `tone` implies emphasis. Cards that supply composable {@link ChatEventCard.content}
* render their inline links in the box; cards that supply `action`(s) render them as a
* consistent row of accent buttons. Omit `tone` (and `emphasized`) for a plain muted line.
*/
tone?: 'info' | 'gold' | 'upgrade' | 'success' | 'signup'
/**
* Optional icon-name override (a `@molecule/app-icons` glyph) — defaults to the tone's
* icon. Use only a name that exists in the bonded set (`getIcon` throws otherwise);
* sets with extra glyphs register them via `CustomIconNames` augmentation.
*/
icon?: IconName
/**
* The limit this card already explains, named by the same `limitType` the backend puts
* on its limit errors (e.g. `'ai_cost'`). A limit is hit ONCE but can surface twice —
* as this persisted card (recorded when the turn was interrupted) and again as the live
* limit banner when the NEXT send is refused — which reads as two cards saying the same
* thing. When a live error carries the same `limitType`, ChatPanel hides this card for
* as long as that banner is up, so exactly one surface states the limit; the card
* returns as soon as the error clears. The banner is never the one suppressed: it is
* the only feedback the refused send gets, and its message can be more specific than
* the card's (a platform-capacity refusal shares `limitType` with a personal-budget
* one). The app owns the identifier; the shared package only matches it.
*/
coversLimitType?: string
}ChatEventCardAction
A single call-to-action on a chat card: a labelled link (href) and/or click
handler. The app supplies any route/copy — the shared package never hardcodes one.
interface ChatEventCardAction {
/** Button label (already localized by the app). */
label: string
/** Link target. App-owned — e.g. the host's own pricing/auth route. */
href?: string
/** Click handler (alternative to, or alongside, `href`). */
onClick?: () => void
/**
* Render the action's label as inline monospace code — a command/identifier like
* `/report` or a skill name — so it stands out from prose while staying clickable.
*/
code?: boolean
/**
* Semantic design-system button color. When set, the card renders this action
* as a real `cm.button` (the ClassMap's standard tinted button, same as every
* other button in the app) in this color, so a CTA looks identical wherever it
* appears — e.g. an app can keep "Sign up" `primary` and "Log in" `success`
* across its auth page, banners, and chat cards. When omitted, the card's
* legacy accent-outline treatment applies. The app owns the semantics; the
* shared package just passes the color through to the ClassMap.
*/
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'
}ChatEventCardCode
A non-interactive inline monospace code span in a card body — a command or identifier
the prose refers to (/report, a skill name) that should read as code but isn't
clickable. For a clickable command, use {@link ChatEventCardAction} with code: true.
interface ChatEventCardCode {
/** The code text, rendered monospaced/tinted. */
code: string
}ChatMessageItemProps
Properties for the chat message item component.
interface ChatMessageItemProps {
message: ChatMessage
className?: string
}ChatPanelProps
Props for the {@link ChatPanel} component — the IDE chat surface plus the callbacks the host app uses to react to AI activity (file changes, boot, client actions, etc.).
interface ChatPanelProps {
projectId: string
endpoint?: string
/** If provided, auto-send this message once on mount (e.g., prompt from landing page). */
initialMessage?: string
/** Called after the initial message has been sent — used to clear router state. */
onInitialMessageSent?: () => void
/** 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. */
onFileOpen?: (path: string, opts?: { focus?: boolean }) => void
/** Called when a filename in a tool call is double-clicked — should pin the tab. */
onFileDoubleClick?: (path: string) => void
/** Called when a file in the uncommitted list is clicked for diff view. */
onFileDiff?: (path: string, diff?: { original: string; modified: string }) => void
/** Called to undo/redo a file change — writes the given content to the file path. */
onFileRevert?: (path: string, content: string) => Promise<void>
/** Called when the AI creates or modifies a file — should refresh the editor if the file is open. */
onFileChange?: (path: string, content: string) => void
/** Called when a file is removed from disk (e.g. reverting an untracked file). */
onFileDeleted?: (path: string) => void
/** Called after a successful commit — should refresh file explorer git status. */
onCommit?: () => void
/** Called when an inline activity card is clicked — should open the Activity panel filtered to this activity. */
onActivityClick?: (activity: ActivityFromCard) => void
/**
* Reports a chat timeline item that threw during render, caught by that item's
* error boundary. The item degrades to an inline notice either way; this is how the
* host gets the crash into its telemetry instead of it being visible only to the
* one user who hit it.
*/
onRenderError?: (error: Error, info: ErrorInfo) => void
/**
* Called when a user avatar in the chat timeline is clicked — the host opens
* that user's profile (e.g. molecule.dev's profile modal). Receives the clicked
* user's {@link ChatUserIdentity}. Omit it (the default) to render the avatars
* non-interactive (static image/icon, exactly as before). Only real user
* avatars are clickable — the molecule glyph on auto-sent messages is not.
*/
onProfileClick?: (user: ChatUserIdentity) => void
/** Called when the server signals (via the `ready_to_build` stream event) that discovery is complete and the sandbox should boot. */
onReadyToBuild?: () => void
/**
* True while the plan has finished streaming but the sandbox is still booting
* (after `ready_to_build`, before the post-boot build kickoff). When set and no
* message is actively streaming, the chat shows a "waiting for the development
* environment" indicator so the conversation doesn't appear to silently stall.
*/
awaitingSandboxBoot?: boolean
/** Called when the agent requests a UI action via the `client_action` stream event (reload/navigate the preview, open a file). */
onClientAction?: (action: IdeClientAction) => void
/** Called on each stream `done` — host uses it to keep the boot view up until the parallel during-boot plan stream finishes. */
onTurnComplete?: () => void
/**
* Called whenever the chat's loading state changes — true when a turn (plan or build) is in
* progress, false when idle. The host uses this as the authoritative "the agent is actively
* building" signal to drive the preview's "Building your app…" overlay, so a half-built /
* blank preview during a long build always shows progress instead of a bare white screen.
*/
onLoadingChange?: (loading: boolean) => void
/**
* Navigates the live preview to a route path. Wired so a `[label](/route)` markdown link in
* an assistant message (e.g. the agent's "your app is ready" handoff) jumps the preview to
* that page on click. User-initiated, so the host should navigate unconditionally (it is not
* the rate-limited agent `navigate_preview` action).
*/
onNavigatePreview?: (path: string) => void
/**
* Called on mount with a handler the parent invokes to deliver a broadcast chat event
* from another project member (the push channel); called with null on unmount.
*/
onRegisterPushHandler?: (
handler: ((conversationId: string, event: ChatStreamEvent) => void) | null,
) => void
/** Changing this value submits the current input draft — used to send a prefilled prompt after the prompt→chat morph docks. */
autoSubmitSignal?: number
/** Seeds the input with this text on mount (prompt→chat morph), so the chat input shows the prompt before it is sent. */
initialInputValue?: string
/** Hide the conversation-selector header (e.g. during discovery, before any history is worth showing). */
hideConversationMenu?: boolean
/**
* Whether to render the built-in conversation header (the picker + searchable
* history dropdown, the share / bug-report / settings buttons, and the
* new-chat "+"). Defaults to `true` (the package owns that chrome). Pass
* `false` to operate **headless** — the host renders those controls itself
* (e.g. molecule.dev's Workspace top bar) and drives the chat through the
* controlled props below: {@link ChatPanelProps.conversationId} /
* {@link ChatPanelProps.chatKey} / {@link ChatPanelProps.onConversationId} for
* the conversation, and {@link ChatPanelProps.openShareSignal} /
* {@link ChatPanelProps.openReportSignal} to open the in-chat modals.
*/
renderConversationHeader?: boolean
/**
* Host-controlled active conversation id (headless mode). Drives the chat
* endpoint's `?conversationId=`. When `undefined` (the default) the panel owns
* the active conversation internally (localStorage-backed). `null` is a valid
* controlled value meaning "no conversation yet".
*/
conversationId?: string | null
/**
* Host-controlled remount key for the inner chat (headless mode). Changing it
* remounts the conversation timeline (a new chat or a switch); the backend
* assigning an id mid-stream must NOT change it (that would drop in-flight
* messages). Falls back to the internal key when omitted.
*/
chatKey?: string
/**
* Called whenever the active conversation id changes — the backend assigns one
* mid-stream and the host needs it to keep its own picker in sync WITHOUT
* remounting (do not change {@link ChatPanelProps.chatKey} in response).
*/
onConversationId?: (id: string | null) => void
/** 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. */
openShareSignal?: number
/** Changing this opens the in-chat `/report` modal (host-driven). Overrides the built-in header's bug-report button signal. */
openReportSignal?: number
/** Changing this opens the in-chat `/settings` view (host-driven). Overrides the built-in header's settings button signal. */
openSettingsSignal?: number
/**
* When provided, the `/model` picker shows an "Add or manage your own
* models…" row at the bottom of the list; choosing it closes the picker and
* invokes this callback (the host opens its own custom-provider management
* surface). Omit to hide the row — the shared package stays host-agnostic.
*/
onManageCustomModels?: () => void
/** Spinner/busy indicator node to show for in-chat loading states (e.g. the "designing" indicator). Falls back to a built-in dots animation. */
spinner?: ReactNode
/** Path of the currently focused file in the editor (shown first in @ picker). */
activeFile?: string | null
/** Paths of all open editor tabs (shown after active file in @ picker). */
openTabs?: string[]
/** Incremented to trigger a git status refresh (e.g. after file create/rename/delete). */
gitStatusTick?: number
/** Message to auto-send (e.g. from "Fix with AI"). Sent when pendingMessageKey changes. */
pendingMessage?: string
/** Incremented to trigger sending pendingMessage. */
pendingMessageKey?: number
/** 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. */
pendingMessageSuppressUser?: boolean
/**
* When true, the pending message was directly requested by the user (e.g. the
* editor's or broken-preview overlay's "Fix with AI" button) rather than
* dispatched autonomously (preview-health / preview-error auto-fix). A user
* Stop suppresses autonomous automatic sends until the user re-engages; a
* user-initiated pending message IS that re-engagement, so it always sends.
*/
pendingMessageUserInitiated?: boolean
/** File path the user just edited in the editor — triggers auto-deletion of queued autofix messages. */
userEditedFile?: string
/** Incremented to trigger the user-edit check (same path may be edited multiple times). */
userEditedFileKey?: number
/**
* Whether the current user is anonymous. The shared IDE no longer renders any
* built-in sign-up/guest card itself — guest reminders now arrive as a `custom`
* stream event the host registers via {@link registerCustomEventCard}, and upgrade
* call-to-actions come from {@link ChatPanelProps.buildUpgradeCta}, and the host's own
* `buildUpgradeCta` closure decides whether an anonymous user should sign up vs. upgrade.
*
* It IS read for one thing: a limit error the backend raised for an anonymous caller
* (`requiresSignup`) is dropped once this is explicitly `false` — the viewer signed in
* mid-session (the in-IDE auth modal never navigates, so the panel keeps running), and a
* "create a free account for more" banner with dead-end Sign up / Log in buttons is stale
* the moment they have an account. Leave it `undefined` and nothing is suppressed.
*/
isAnonymous?: boolean
/** When true, user has a paid plan and can use all models (drives locked-model display). */
isPro?: boolean
/**
* Retained for call-site compatibility. The periodic "sign up to keep your work"
* reminder is no longer generated client-side — the host's backend decides when to
* emit it as a `guest_reminder` `custom` stream event (so it can be suppressed during
* discovery server-side). This prop no longer drives any built-in behavior.
* @deprecated Guest reminders moved to the host-emitted `custom` event + registry.
*/
suppressGuestReminder?: boolean
/**
* Builds the call-to-action button(s) shown when the chat surfaces an upgrade /
* sign-in nudge — a locked model the user can't select, or a usage/resource limit
* the backend reported. The shared IDE owns NO pricing or auth routes, so the host
* supplies the button(s) here (e.g. its own `/pricing` or `/signup`). Return
* `null`/`undefined` (the default) to render the nudge text with no button.
* `requiresSignup`, when set, is the backend's flag that the user must sign up
* rather than upgrade an existing plan; when unset the host's own auth state decides.
*/
buildUpgradeCta?: (context: {
requiresSignup?: boolean
}) => ChatEventCardAction | ChatEventCardAction[] | null | undefined
/**
* Optional app-specific section appended to the `/help` output — e.g. a plan /
* upgrade blurb. The shared IDE has no pricing or plan copy, so the host supplies
* the (already-localized) lines plus any call-to-action. Return `null` (the default)
* to append nothing.
*/
buildHelpUpgradeSection?: () =>
{ lines: string[]; action?: ChatEventCardAction | ChatEventCardAction[] } | null | undefined
/**
* The signed-in user's profile avatar (SOC1) — an inline `data:image/*` URI or
* an `http(s)` URL — rendered beside their own messages in the chat timeline.
* The host passes whatever value its user metadata holds; the shared IDE gates
* it (`resolveUserAvatar`) so only a safe, renderable source reaches the DOM and
* falls back to a generic icon otherwise. Omit it (the default) to always show
* the icon.
*/
userAvatar?: string | null
/**
* Display name of the AI coding agent, interpolated into all shared chat copy
* that refers to it (the stalled-stream notice, sound-event descriptions, the
* `/help` body, tips, `/settings` and command descriptions, the `/scripts`
* empty state). The shared IDE owns NO product branding, so the host passes its
* own agent brand name. Defaults to the neutral `'the assistant'`
* (`DEFAULT_AGENT_NAME` from `@molecule/app-react`) so the package alone never
* names a specific product.
*/
agentName?: string
/**
* Display name of the host product / IDE, interpolated into shared chat copy
* that refers to the product (the `/help` intro, the report-confirmation and
* report-modal subheading, the command-menu version line). The host passes its
* own product brand name; defaults to the neutral `'the IDE'`
* (`DEFAULT_PRODUCT_NAME` from `@molecule/app-react`).
*/
productName?: string
/**
* The host's current app/build version (e.g. `'0.1.0'`), shown in the `/version`
* command's menu description and its output. The shared IDE has no build version
* of its own, so when omitted it falls back to the package default constant.
*/
version?: string
/**
* Host-specific slash commands to MERGE into the command menu, `/help`, and the
* keyboard dispatcher, on top of the shared {@link COMMANDS} registry. For
* commands the host handles itself (server-side intercepts or the agent), so
* they show up in the menu instead of being invisible. Selecting one fills the
* input with `/<id> ` (so the user can add arguments) and sending it routes to
* the host's own handler — the shared package never dispatches these.
*
* The host owns keeping this list in sync with its handlers; molecule.dev, for
* example, fetches it from `GET /ai/commands`. Ids must not collide with a
* shared command id. Optional — omit for the plain shared command set.
*/
extraCommands?: readonly CommandDef[]
/**
* URL the command-menu "Report a problem" link points at (the host's own issue
* tracker / feedback page). The shared IDE owns no product URLs, so when this
* is omitted (the default) the link is not rendered. The in-chat `/report`
* modal — which POSTs to the project's own backend — is unaffected.
*/
feedbackUrl?: string
className?: string
}ChatUserIdentity
Identity of the user whose avatar was clicked in the chat timeline, passed to {@link ChatPanelProps.onProfileClick} so the host can open that user's profile.
Today the chat is solo — the only avatar shown is the signed-in user's own — so the only known field is the avatar value. The interface is intentionally forward-compatible: collaborator fields (id, name) can be added here when multi-user chat lands, without changing the callback signature.
interface ChatUserIdentity {
/** The clicked user's avatar value (data-URI / URL), if any. */
avatar?: string | null
}ClientInfo
Client-side diagnostics attached to a report so triage can see the running environment without asking the user. Every field is optional — only what could be read in the current environment is present (see {@link collectClientInfo}).
interface ClientInfo {
/** The running build version. */
appVersion?: string
/** `navigator.userAgent` (browser + OS). */
userAgent?: string
/** `navigator.platform`. */
platform?: string
/** `navigator.language`. */
language?: string
/** Inner viewport size, `${innerWidth}×${innerHeight}`. */
viewport?: string
/** Physical screen size, `${screen.width}×${screen.height}`. */
screen?: string
/** Active theme — `'light'` or `'dark'`. */
theme?: string
/** The current page URL (`window.location.href`). */
url?: string
}Command
A command available in the command palette.
interface Command {
/** Unique identifier. */
id: string
/** Display label. */
label: string
/** Keyboard shortcut hint (e.g. "Cmd+P"). */
shortcut?: string
/** Handler invoked when the command is executed. */
execute: () => void
/** Category prefix (e.g. "View", "File"). */
category?: string
}CommandCategory
A command category with its display label.
interface CommandCategory {
/** Stable category key referenced by {@link CommandDef.category}. */
key: CommandCategoryKey
/** Human-readable category heading (English default; wrapped in `t()` at render). */
label: string
}CommandDef
Metadata describing a single slash command.
interface CommandDef {
/** Command id (the part after the slash, e.g. `'help'`). */
id: string
/** Display label including the leading slash (e.g. `'/help'`). */
label: string
/**
* Short description shown in the menu and in `/help` (English default). May
* contain the `{{agentName}}` interpolation token, filled in by the render
* sites (command menu, `/help`, `/settings` card) from the host's agent
* identity (neutral default: "the assistant").
*/
description: string
/** Category this command is grouped under. */
category: CommandCategoryKey
/**
* Argument syntax for commands that take options, shown in the `/settings`
* command reference (English default). `[…]` = optional, `<…>` = required.
* Omit for commands that take no arguments.
*/
usage?: string
}CommandGroup
A category paired with the commands that belong to it.
interface CommandGroup {
/** The category metadata (key + label). */
category: CommandCategory
/** Commands in this category, in registry order. */
commands: CommandDef[]
}CommandPaletteProps
Properties for the command palette.
interface CommandPaletteProps {
/** Available commands. */
commands: Command[]
/** Called when the palette is dismissed. */
onDismiss: () => void
}DeviceDimensions
Per-frame iframe sizing. width/height are the PORTRAIT CSS sizes; a
fixed-frame (rotatable) device swaps them in landscape. '100%' width with
a null height means "fluid" — fill the available preview area (responsive /
desktop have no fixed frame to rotate).
interface DeviceDimensions {
/** Portrait CSS width (e.g. `'768px'`, or `'100%'` for a fluid frame). */
readonly width: string
/** Portrait CSS height in px (e.g. `'1024px'`), or `null` to fill the area. */
readonly height: string | null
/** Whether the frame has a fixed size that can be rotated portrait ⇄ landscape. */
readonly rotatable: boolean
}DeviceFrameSelectorProps
Properties for device frame selector.
interface DeviceFrameSelectorProps {
current: DeviceFrame
onChange: (device: DeviceFrame) => void
className?: string
}EditorPanelProps
Properties for the editor panel component.
interface EditorPanelProps {
className?: string
/** Called whenever the active file changes (tab switch, file open, file close). */
onActiveFileChange?: (path: string | null) => void
/** Called once after the editor is fully mounted and ready to accept files. */
onEditorReady?: () => void
/** Called whenever the open tab list changes (file opened or closed). */
onTabsChange?: (paths: string[]) => void
/** Maps file path to git status for coloring tab filenames. */
fileStatuses?: Record<string, string>
/** Path of the file currently being formatted, for visual indicator. */
formattingFile?: string | null
/** Path of the file with an active save debounce countdown. */
countdownFile?: string | null
/** Incremented each keystroke to restart the countdown animation. */
countdownKey?: number
/** Estimated format duration in ms (rolling average, default 2000). */
formatEstimate?: number
/** Called when the user triggers "Fix with AI" from the editor's lightbulb or context menu. */
onFixWithAI?: (request: FixWithAIRequest) => void
/** Override double-click on a tab. Return `true` to skip the default pin behavior. */
onTabDoubleClick?: (path: string) => boolean
}FileExplorerProps
Properties for file explorer.
interface FileExplorerProps {
files: FileNode[]
onFileSelect: (path: string) => void
onFileDoubleClick?: (path: string) => void
onDirExpand?: (path: string) => void
/** Called when the user chooses "Rename" from the context menu. */
onRename?: (path: string) => void
/** Called when the user chooses "Delete" from the context menu. */
onDelete?: (path: string) => void
/** Called when the user deletes multiple selected files/folders via context menu or keyboard. */
onDeleteMultiple?: (paths: string[]) => void
/** Called when the user moves files via drag-and-drop or cut+paste. */
onMoveFiles?: (moves: Array<{ oldPath: string; newPath: string }>) => void
/** Called when the user chooses "New File" from the context menu. */
onNewFile?: (dirPath: string) => void
/** Called when the user chooses "New Folder" from the context menu. */
onNewFolder?: (dirPath: string) => void
/** Called when the user chooses "Collapse All" from the context menu. */
onCollapseAll?: () => void
className?: string
/** localStorage key for persisting expand/collapse state across reloads. */
persistKey?: string
/** Path of the currently active file — highlighted in the tree. */
activeFile?: string | null
/** Maps file path to git status — used to color directory names by highest-priority child status. */
fileStatuses?: Record<string, string>
}FileNode
File Node interface.
interface FileNode {
name: string
path: string
type: 'file' | 'directory'
children?: FileNode[]
isDimmed?: boolean
gitStatus?: 'modified' | 'added' | 'deleted' | 'untracked'
/** If this entry is a symlink, the target it points to. */
symlinkTarget?: string
}IconProps
Props for {@link Icon}. Extends SVGProps so callers can forward any SVG/HTML
attribute (data-mol-id, aria-*, role, event handlers, style) to the
root <svg> without the component enumerating them.
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'width' | 'height' | 'viewBox' | 'fill'> {
/** Name of the glyph to look up in the bonded icon set (e.g. `'sync'`). */
name: IconName
/** Width and height of the rendered SVG in pixels. Defaults to 16. */
size?: number
/** Class name forwarded to the root `<svg>`. */
className?: string
}IdeClientAction
A non-mutating UI action the AI agent asks the IDE to perform — reload or
navigate the live preview, open a file in the editor, or drive the preview's
interaction bridge (preview_ui). Delivered via the client_action
chat-stream event (and, for preview_ui, also via the host's collab socket
so a mid-build tab reload can't orphan it).
interface IdeClientAction {
action: 'reload_preview' | 'navigate_preview' | 'open_file' | 'preview_ui'
/** navigate_preview: a URL path (e.g. "/dashboard"). open_file: a file path. */
path?: string
/** preview_ui: correlates the command with its ui-result round-trip. */
requestId?: string
/** preview_ui: the interaction the preview bridge should perform. */
command?: 'snapshot' | 'click' | 'fill' | 'select' | 'waitFor'
/** preview_ui: the `data-mol-id` of the target element (preferred). */
molId?: string
/** preview_ui: CSS-selector fallback when no molId is available. */
selector?: string
/** preview_ui: visible-label match for apps whose elements carry no molId. */
text?: string
/** preview_ui: value to set for fill/select. */
value?: string
}KeyboardShortcut
A keyboard shortcut definition.
interface KeyboardShortcut {
/** Key combo string, e.g. `"mod+p"`, `"mod+shift+f"`. `mod` = Cmd (Mac) / Ctrl (others). */
keys: string
/** Handler invoked when the shortcut fires. */
handler: () => void
/** If true, fires even when an `<input>` / `<textarea>` is focused. */
allowInInput?: boolean
/** If true, fires even when the Monaco editor is focused. */
allowInEditor?: boolean
/** Human-readable label for display in the command palette. */
label?: string
}KeyboardShortcutsPanelProps
Properties for the keyboard shortcuts reference panel.
interface KeyboardShortcutsPanelProps {
/** List of shortcuts to display. */
shortcuts: ShortcutEntry[]
/** Called when the panel is dismissed. */
onDismiss: () => void
}PreviewPanelProps
Props for the {@link PreviewPanel} — the live app preview (iframe + device frame + URL bar).
interface PreviewPanelProps {
/** Custom loading indicator shown while the dev server is starting. */
loadingIndicator?: ReactNode
/**
* The current UI command the host wants performed in the preview iframe (AI-driven
* end-to-end verification). The panel posts it to the iframe's interaction bridge when it
* CHANGES (keyed on `id`, so each new command fires exactly once). The panel only relays it;
* the host owns what to send and what to do with the result.
*/
uiCommand?: PreviewUiCommand | null
/** Called when the iframe replies to a {@link PreviewUiCommand}, keyed by the command `id`. */
onUiResult?: (id: string, result: PreviewUiResult) => void
/** Custom loading indicator shown when the dev server restarts mid-session. Falls back to loadingIndicator if not provided. */
restartingIndicator?: ReactNode
/** Called when the preview iframe reports runtime JS errors. */
onPreviewError?: (
errors: Array<{ message: string; source?: string; line?: number; column?: number }>,
) => void
/** Incremented when AI edits files. Triggers an iframe reload only when the preview is broken. */
fileChangeTick?: number
/**
* Active-build hint (e.g. a basename like `GuestMenu.tsx`) the host sets while the
* AI is editing files. When non-null the overlay is forced on — covering the
* blank-white iframe reload a build triggers — and shows "Updating `<hint>`…" so
* the user sees what's being worked on. Null when no build edit is in flight.
*/
buildingHint?: string | null
/**
* Whether the AI agent is actively building right now (a chat turn is in progress).
* The host derives this from the chat's loading state. While true, the preview keeps a
* "Building your app…" status overlay up whenever the app has NOT confirmed it rendered
* content (no `molecule:ready`) — so a half-built / blank / white iframe during a long
* build always shows progress instead of a bare white screen. A confirmed render still
* reveals the live app (HMR updates stay visible), so this never hides a working preview.
*/
isBuilding?: boolean
/**
* Timestamp (ms since epoch) of when the preview's backing server/sandbox was last
* woken from sleep or restarted, or 0/undefined when it never was. While this is
* recent, the panel treats the preview like a fresh cold boot: the dev server behind
* it is restarting and recompiling, so a document that reloads to blank (or a
* transient error page that never runs the bridge) is EXPECTED for a while and must
* NOT trip the fast "preview is blank" accusation — the honest starting/loading
* status stays up, and only the generous never-rendered ceiling can accuse. A real
* render (`molecule:ready`) clears the patience immediately, so a healthy wake
* reveals as fast as ever.
*/
wakeAt?: number
/**
* Called when the preview gives up showing the running app — after exhausting reload
* recovery, at the absolute readiness ceiling, OR when the heartbeat watchdog detects a
* frozen (locked-thread) app. Receives a {@link PreviewStuckReport} (failure class +
* route) so the host can drive recovery UI AND hand the agent an actionable, targeted
* fix request. The argument is optional for backward compatibility with no-arg callers.
*/
onPreviewStuck?: (report?: PreviewStuckReport) => void
/**
* Called when the preview's render verdict changes ({@link PreviewRenderState}) — and
* with the current location so the host can report WHERE. The host forwards this to the
* server so Synthase's post-loop verification can confirm the app actually rendered (not
* just that it compiled + served) before calling a build done.
*/
onRenderState?: (state: PreviewRenderState, url?: string) => void
className?: string
}PreviewStuckReport
Structured report passed to {@link PreviewPanelProps.onPreviewStuck} when the preview gives up. Carries the failure class + the route it happened on so the host can compose an actionable, agent-fixable message instead of a bare "preview is stuck".
interface PreviewStuckReport {
/** The failure class — what left the preview unable to show the running app. */
reason: PreviewStuckReason
/** The preview's current location (route) when the failure was detected, if known. */
url?: string
}PreviewUiCommand
A live-preview interaction the host asks the panel to perform inside the iframe, so an AI agent can verify a feature end-to-end by DRIVING the app the user is watching (no headless browser). The panel just relays it to the iframe's interaction bridge — generic, so it carries no host/API specifics.
interface PreviewUiCommand {
/** Correlates this command with its result; the host round-trips on it. */
id: string
/** `snapshot` the interactive UI, or act on an element. */
action: 'snapshot' | 'click' | 'fill' | 'select' | 'waitFor'
/** `data-mol-id` of the target element (preferred over selector). */
molId?: string
/** CSS-selector fallback when no molId is available. */
selector?: string
/** Visible-label match — targets apps whose elements carry no `data-mol-id`. */
text?: string
/** Value to set for `fill` / `select`. */
value?: string
/**
* `snapshot` only — the moment the host pointed the preview at a new URL (`Date.now()`).
* Only a document that loaded at or after it may answer, so a navigation snapshot can never
* come from the OUTGOING page still sitting in the iframe. Set it ONLY when a new document
* is genuinely loading (the URL actually changed) — otherwise nothing can satisfy it and the
* command goes unanswered. Omit for a plain read.
*/
minLoadedAt?: number
}PreviewUiResult
The preview interaction bridge's reply to a {@link PreviewUiCommand}.
interface PreviewUiResult {
ok: boolean
/** Interactive-element list + url/title from the preview (present on a snapshot / success). */
snapshot?: unknown
found?: boolean
error?: string
/**
* Failed network requests from the last ~10s (method, url, status, bounded response body),
* captured in-page — so a click that 4xx'd explains itself in the same result.
*/
recentNetworkErrors?: string[]
/**
* Present when the bridge gave up on a settle budget instead of observing the page settle —
* it names what was still pending (document parsing, an empty root, an unreached route,
* in-flight requests). The snapshot may be incomplete, so a race stays distinguishable from
* a genuinely broken page.
*/
stillSettling?: string
}QuickOpenProps
Properties for the quick-open file finder.
interface QuickOpenProps {
/** Project ID used for API calls. */
projectId: string
/** Called when the user selects a file. */
onFileOpen: (path: string) => void
/** Called when the picker is dismissed. */
onDismiss: () => void
}QuickPickerItem
An item in the quick picker list.
interface QuickPickerItem {
/** Unique identifier. */
id: string
/** Primary label. */
label: string
/** Secondary text shown beside the label. */
detail?: string
/** Optional icon element. */
icon?: ReactNode
}QuickPickerProps
Properties for the reusable quick picker overlay.
interface QuickPickerProps {
/** Items to display and filter. */
items: QuickPickerItem[]
/** Placeholder text for the search input. */
placeholder?: string
/** Called when the user selects an item. */
onSelect: (item: QuickPickerItem) => void
/** Called when the user dismisses the picker (Escape or backdrop click). */
onDismiss: () => void
/** Show a loading indicator. */
loading?: boolean
/** Pre-fill the search input. */
initialQuery?: string
className?: string
}ReportFormState
The report modal's form state.
interface ReportFormState {
/** Short summary / issue title. */
title: string
/** Detailed description of the problem or request. */
description: string
/** Optional reproduction steps (free text). */
steps: string
/** Whether to attach the recent conversation to the report. */
includeChat: boolean
}ReportPayload
The POST /projects/:id/report request body.
interface ReportPayload {
/** Short summary / issue title. */
title: string
/** Detailed description. */
description: string
/** Reproduction steps — omitted entirely when blank. */
steps?: string
/** Whether the backend should attach the recent conversation. */
includeChat: boolean
/** Client diagnostics — omitted entirely when none could be collected. */
clientInfo?: ClientInfo
}ReportResult
The POST /projects/:id/report response.
interface ReportResult {
/** Whether the report was recorded. */
ok: boolean
/** Link to the created issue, when one was filed. */
url?: string
/** The persisted DB row id. */
id?: string
}ResizeHandleProps
Properties for resize handle.
interface ResizeHandleProps {
onResize: (delta: number) => void
direction?: 'horizontal' | 'vertical'
className?: string
}SearchPanelProps
Properties for the search-in-files panel.
interface SearchPanelProps {
/** Project ID used for API calls. */
projectId: string
/** Called when the user clicks a search result. */
onResultClick?: (path: string, line: number) => void
className?: string
/**
* The project's excluded directory names (VS Code `search.exclude`
* semantics). Displayed and editable in the panel; the backend applies the
* SAME set server-side to every search surface (panel + AI tools), so this
* prop is display/edit state — searches don't send it per query. When
* omitted, the panel shows {@link DEFAULT_SEARCH_EXCLUDED_DIRS}.
*/
excludedDirs?: string[]
/** Persist an edited excluded-dir set (the host owns storage). */
onExcludedDirsChange?: (dirs: string[]) => void
}SearchResponse
Response from the search API endpoint.
interface SearchResponse {
/** The search pattern used. */
pattern: string
/** Grouped results by file. */
results: SearchResult[]
/** Total number of matches across all files. */
totalCount: number
/** Whether results were truncated. */
truncated: boolean
}SearchResult
A single file's search results.
interface SearchResult {
/** Relative file path. */
file: string
/** Matching lines within the file. */
matches: Array<{ line: number; content: string }>
}SettingMeta
Canonical, value-free metadata for a single user-controllable setting.
interface SettingMeta {
/** Stable id (also the i18n key suffix, e.g. `'effort'`). */
id: SettingKey
/** Human-readable label (English default; wrapped in `t()` at render). */
label: string
/**
* One-line explanation of what the setting does (English default). May
* contain the `{{agentName}}` interpolation token, filled in at render from
* the host's agent identity (neutral default: "the assistant").
*/
description: string
/**
* The slash command that edits this setting client-side. Drives the inline
* "Edit" affordance and cross-links the setting to its command. Omitted only
* for read-only settings.
*/
editCommand?: CommandId
/**
* The exact slash-command input to prefill when editing, for settings whose
* bare {@link SettingMeta.editCommand} is not specific enough — e.g. the
* per-mode model rows both run the `model` command but must scope it to a
* mode (`/model --plan`, `/model --execute`). Omit when running the bare
* command suffices.
*/
editInput?: string
}ShareLinkResult
The POST /projects/:projectId/shares response — the created public link.
Mirrors the relevant fields of the @molecule/api-resource-share ShareLink.
interface ShareLinkResult {
/** The link's unique id (used by the revoke route). */
id?: string
/** Opaque slug embedded in the public URL. */
slug: string
/** The role this link grants. */
role: ShareRole
/**
* A fully-qualified share URL, when the backend supplies one. Preferred over
* client-side construction so the canonical origin (e.g. a custom domain)
* wins over the current page origin.
*/
url?: string
}SharePayload
The POST /projects/:projectId/shares request body.
interface SharePayload {
/** Role granted to anyone who opens the link. */
role: ShareRole
}ShortcutEntry
A shortcut entry for display in the keyboard shortcuts panel.
interface ShortcutEntry {
/** Human-readable label describing the action. */
label: string
/** Display string for the key combo (e.g. "⌘P", "⌘⇧F"). */
keys: string
/** Optional grouping category. */
category?: string
/** Handler invoked when the row is clicked. */
execute?: () => void
}SidebarTabsProps
Properties for the sidebar tab switcher.
interface SidebarTabsProps {
/** Currently active sidebar tab. */
activeTab: 'files' | 'search'
/** Called when the user switches tabs. */
onTabChange: (tab: 'files' | 'search') => void
/** Tab content rendered below the tab buttons. */
children: ReactNode
className?: string
}TabBarProps
Properties for tab bar.
interface TabBarProps {
tabs: EditorTab[]
activeFile: string | null
onSelect: (path: string) => void
onClose: (path: string) => void
onDoubleClick?: (path: string) => void
/** Maps file path to git status for coloring tab filenames. */
fileStatuses?: Record<string, string>
className?: string
}ToolCallCardProps
Properties for tool call card.
interface ToolCallCardProps {
id: string
name: string
input?: unknown
output?: unknown
status: 'pending' | 'running' | 'done' | 'error'
/** Snapshot of original/modified file content captured at tool-call time. */
fileDiff?: { original: string; modified: string }
/** Externally controlled undo state — when true, the card displays as undone. */
isUndone?: boolean
/** Called when the undo/redo button is toggled on this tool call. */
onUndoToggle?: (id: string, undone: boolean) => void
/** Called when a filename in the card is clicked — should open the file as a preview tab. */
onFileOpen?: (path: string) => void
/** Called when a filename in the card is double-clicked — should pin the tab. */
onFileDoubleClick?: (path: string) => void
/** Called when a file-changing card is clicked — should open the file diff in the editor. */
onFileDiff?: (path: string, diff?: { original: string; modified: string }) => void
/** Called to undo/redo a file change — writes the given content to the file path. */
onFileRevert?: (path: string, content: string) => Promise<void>
/** Called when the user responds to an `ask_user` tool call (clicks an option or submits free text). */
onAskUserResponse?: (response: string) => void
className?: string
}UserAvatarProps
Props for {@link UserAvatar}.
interface UserAvatarProps {
/**
* The signed-in user's avatar — an inline `data:image/*` URI or an `http(s)`
* URL from their profile metadata. Unsafe / unset / oversized values are
* ignored (see {@link resolveUserAvatar}) and the generic icon is shown.
*/
userAvatar?: string | null
/** Diameter of the avatar in pixels. Defaults to 24. */
size?: number
/**
* Optional click handler. When supplied, the avatar becomes an interactive
* button (pointer cursor, hover/focus ring, keyboard- and screen-reader
* accessible) that opens the user's profile — the host decides what to show.
* When omitted (the default) the avatar renders exactly as before: a static,
* non-interactive image/icon, so existing call sites are unaffected.
*/
onClick?: () => void
}WorkspaceLayoutProps
Properties for workspace layout.
interface WorkspaceLayoutProps {
children: ReactNode
className?: string
}Types
ActivityStatus
Lifecycle status of a captured activity.
type ActivityStatus = 'captured' | 'sent' | 'delivered' | 'failed'ActivityType
Channel categories a captured activity can belong to.
type ActivityType = 'email' | 'sms' | 'push' | 'webhook' | 'channel'AutoCommitAction
Actions the countdown reducer accepts.
set— apply a/autocommit <seconds>command (seconds <= 0disables); arms AND starts a fresh countdown (an explicit, just-now user choice).hydrate— restore a cadence persisted on the project (e.g. on reload), enabled but PAUSED (seconds <= 0disables). Unlikeset, it does NOT start counting down — the countdown only re-arms on the next file change — so reopening a project never auto-commits a tree the user hasn't touched.reset— a file changed; restart the full countdown (no-op when disabled).tick— one second elapsed; decrement toward zero (no-op when paused).fired— a commit was just dispatched; pause until the next file change.
type AutoCommitAction =
| { type: 'set'; seconds: number }
| { type: 'hydrate'; seconds: number }
| { type: 'reset' }
| { type: 'tick' }
| { type: 'fired' }ChatEventCardFactory
Turns a custom event's data payload into a chat card, or returns null to render
nothing for that event.
type ChatEventCardFactory = (data: Record<string, unknown> | undefined) => ChatEventCard | nullChatEventCardSegment
One inline segment of a card's composable body: literal text, an inline monospace {@link ChatEventCardCode} span, or a labelled link/action ({@link ChatEventCardAction}). See {@link ChatEventCard.content}.
type ChatEventCardSegment = string | ChatEventCardCode | ChatEventCardActionCommandCategoryKey
Category keys used to group commands in the menu and in /help.
type CommandCategoryKey = 'context' | 'code' | 'collaborate' | 'model' | 'settings' | 'support'CommandId
Union of all command ids (loosely string, since {@link CommandDef.id} is a string).
type CommandId = CommandDef['id']DeviceOrientation
Preview-only iframe orientation. Portrait is the natural orientation of a fixed-frame device; landscape swaps its width/height. This is a visual preview concern that lives in {@link PreviewPanel}'s local state — it is NOT part of the live-preview core state.
type DeviceOrientation = 'portrait' | 'landscape'PreviewRenderState
The preview's live render verdict, derived from the iframe bridges — the one preview fact the server can't observe itself (it has no browser). The host forwards it to the server so the post-loop verification won't pass a build while the app isn't actually rendering ("compiles + serves" ≠ "renders").
rendered— the app drew content (molecule:ready).blank— loaded but showed nothing (gave up /#rootempty after settling).frozen— rendered then locked up (heartbeats stopped).loading— still loading / not yet determined.
type PreviewRenderState = 'rendered' | 'blank' | 'frozen' | 'loading'PreviewStuckReason
Why the preview could not show the running app — the failure CLASS the host hands
to its AI agent so a fix can be targeted (and so the agent isn't told "it's broken"
with no hint of how). Distinct from a JS error (onPreviewError): these are states
the iframe itself can't report once it's in them.
type PreviewStuckReason =
// Heartbeats stopped after a render — the app's main thread is locked (an infinite
// loop / runaway render). The iframe can post nothing else once frozen, so only the
// host's heartbeat-silence watchdog can detect it.
| 'frozen'
// Repeated reload/remount cycles never produced a confirmed render — the document
// loads but the app never mounts (e.g. a route that throws on every attempt).
| 'load-failed'
// The absolute readiness ceiling elapsed with no confirmed render and no active
// build — a catch-all backstop so the preview can never spin forever.
| 'load-timeout'SettingKey
Stable ids for each user-controllable setting (also the i18n key suffix).
type SettingKey =
| 'model'
| 'planModel'
| 'executeModel'
| 'commitModel'
| 'compactModel'
| 'mode'
| 'effort'
| 'maxLoops'
| 'autoFix'
| 'autoCommit'
| 'hooks'
| 'autoApproveCommands'
| 'sounds'ShareCommand
The parsed result of a /share command:
create— POST a link at a valid role (/share, defaulting toviewer, or/share <role>with a recognized role).invalid— an unrecognized role argument was given (the caller shows usage).
type ShareCommand = { kind: 'create'; role: ShareRole } | { kind: 'invalid'; arg: string }ShareRole
A role granted by a share link.
type ShareRole = (typeof SHARE_ROLES)[number]Functions
ActivityCard(props)
Compact, clickable inline card for a single captured activity.
function ActivityCard({ activity, onActivityClick }: ActivityCardProps): JSX.Elementprops— Component props.
Returns: The rendered activity card element.
activityFromEvent(raw)
Maps a raw SSE activity event payload into a normalized {@link Activity}.
Tolerates missing optional fields and supplies an id/timestamp if absent.
function activityFromEvent(raw: {
id?: string
type?: string
status?: string
recipient?: string
summary?: string
timestamp?: string
}): Activityraw— Theactivityfield from the SSE event.raw.id— Activity id; generated if absent.raw.type— Channel type; defaults towebhookif absent.raw.status— Lifecycle status; defaults tocapturedif absent.raw.recipient— Optional recipient.raw.summary— Optional short summary.raw.timestamp— ISO timestamp; defaults to now if absent.
Returns: A normalized Activity object.
activityIconName(type)
Returns the bonded-icon-set glyph NAME for an activity type — pass it to
<Icon name={…} /> to render the themed SVG. Unknown/future types (which
{@link activityFromEvent} normalizes to webhook) reuse the link glyph
rather than risk a getIcon throw.
function activityIconName(type: ActivityType): IconNametype— The activity channel type.
Returns: The icon-set glyph name for the type.
activityStatusColors(status)
Resolves the status-pill colors for a given status. Uses RGBA literals (not
ClassMap classes) because these semantic status hues are not part of the
surface/text token set — the same approach VerificationBadge takes for its
pass/fail coloring.
function activityStatusColors(status: ActivityStatus): { fg: string; bg: string }status— The activity status.
Returns: An object with fg (text) and bg (background) CSS color strings.
activityStatusLabel(status)
Human-readable, translated label for a status (shown in the status pill).
function activityStatusLabel(status: ActivityStatus): stringstatus— The activity status.
Returns: The translated status label.
activitySummaryLine(activity)
Builds the one-line summary shown on the inline card: the activity's own
summary, with the recipient appended after an arrow when present
(e.g. Welcome email → [email protected]). Falls back to a translated,
type-specific default when no summary was captured.
function activitySummaryLine(activity: Pick<Activity, 'type' | 'recipient' | 'summary'>): stringactivity— The activity to summarize.
Returns: The single-line summary string.
activityTypeLabel(type)
Human-readable, translated label for a channel type (used as filter-tab labels).
function activityTypeLabel(type: ActivityType): stringtype— The activity channel type.
Returns: The translated channel label.
autoCommitReducer(state, action)
Pure reducer for the auto-commit countdown. Deterministic and side-effect
free: the component performs the actual commit when {@link isAutoCommitDue}
becomes true, then dispatches fired.
function autoCommitReducer(state: AutoCommitState, action: AutoCommitAction): AutoCommitStatestate— The current countdown state.action— The action to apply.
Returns: The next countdown state.
buildReportPayload(form, clientInfo)
Builds the POST /projects/:id/report body from the form state, trimming all
text fields and omitting steps entirely when it is blank. When clientInfo
is supplied and non-empty, it is attached; an undefined or empty diagnostics
object is omitted from the payload.
function buildReportPayload(form: ReportFormState, clientInfo?: ClientInfo): ReportPayloadform— The report form state.clientInfo— Optional client diagnostics from {@link collectClientInfo}.
Returns: The normalized report payload.
buildSharePayload(role)
Builds the POST /projects/:projectId/shares body for a role.
function buildSharePayload(role?: 'viewer' | 'commenter' | 'editor' | 'owner'): SharePayloadrole— The role to grant (defaults to {@link DEFAULT_SHARE_ROLE}).
Returns: The normalized share payload.
buildShareUrl(result, origin)
Resolves the copyable public URL for a created share link. Prefers the
backend-supplied url (canonical origin / custom domain); otherwise builds
<origin>/share/<slug> from the given origin, tolerating a trailing slash.
function buildShareUrl(result: ShareLinkResult, origin: string): stringresult— The created link from the share endpoint.origin— The current page origin (e.g.window.location.origin).
Returns: The absolute, copyable share URL.
ChatPanel(props)
AI chat panel with conversation history dropdown and Claude Code-style tool display.
function ChatPanel({
projectId,
endpoint,
initialMessage,
onInitialMessageSent,
activeFile,
openTabs,
onFileOpen,
onFileDoubleClick,
onFileDiff,
onFileRevert,
onFileChange,
onFileDeleted,
onCommit,
onActivityClick,
onRenderError,
onProfileClick,
onReadyToBuild,
awaitingSandboxBoot,
onClientAction,
onTurnComplete,
onLoadingChange,
onNavigatePreview,
onRegisterPushHandler,
autoSubmitSignal,
initialInputValue,
hideConversationMenu,
renderConversationHeader = true,
conversationId: controlledConversationId,
chatKey: controlledChatKey,
onConversationId: controlledOnConversationId,
openShareSignal: controlledShareSignal,
openReportSignal: controlledReportSignal,
openSettingsSignal: controlledSettingsSignal,
onManageCustomModels,
gitStatusTick,
pendingMessage,
pendingMessageKey,
pendingMessageSuppressUser,
pendingMessageUserInitiated,
userEditedFile,
userEditedFileKey,
isPro,
isAnonymous,
buildUpgradeCta,
buildHelpUpgradeSection,
userAvatar,
agentName,
productName,
version,
extraCommands,
feedbackUrl,
className,
}: ChatPanelProps): JSX.Elementprops— Component props (see {@link MessageItemProps}).
Returns: The rendered chat panel element.
clampPanelSize(currentSize, deltaPx, containerWidth, min, max)
Clamp a panel's new size after a pixel drag delta.
function clampPanelSize(
currentSize: number,
deltaPx: number,
containerWidth: number,
min?: number,
max?: number,
): numbercurrentSize— The panel's current size as a percentage.deltaPx— The drag delta in pixels (positive = grow the left panel).containerWidth— The layout container width in pixels.min— Minimum allowed percentage. Defaults to {@link MIN_PANEL_PERCENT}.max— Maximum allowed percentage. Defaults to {@link MAX_PANEL_PERCENT}.
Returns: The new size as a percentage, clamped to [min, max].
collectClientInfo(opts)
Collects client-side diagnostics for a report. Reads navigator
(userAgent/platform/language), window (inner viewport size, screen size,
location href), plus the caller-supplied app version and theme. Every access
is guarded (typeof window/navigator !== 'undefined' and per-property
presence) so it is SSR-safe and never throws — it returns only the fields it
could actually read, so the result may be partial or (in a headless
environment) empty.
function collectClientInfo(opts?: { appVersion?: string; theme?: string }): ClientInfoopts— Caller-supplied context.opts.appVersion— The running build version, if known.opts.theme— The active theme ('light'|'dark'), if known.
Returns: The populated subset of {@link ClientInfo}.
CommandPalette(props)
Command Palette overlay.
function CommandPalette({ commands, onDismiss }: CommandPaletteProps): JSX.Elementprops— Component props.
Returns: The command palette element.
DeviceFrameSelector(props)
A dropdown that selects the preview device frame and hosts the Rotate + Open-in-new-tab actions.
function DeviceFrameSelector({
current,
onChange,
className,
canRotate,
rotated,
onRotate,
onOpenExternal,
}: DeviceFrameSelectorWithActionsProps): JSX.Elementprops— Component props (see {@link DeviceFrameSelectorWithActionsProps}).
Returns: The rendered device-frame selector element.
deviceIconName(device)
Returns the icon-set glyph name for a device frame.
function deviceIconName(device: DeviceFrame): stringdevice— The device frame.
Returns: The icon name registered in the bonded icon set.
EditorPanel(props)
Code editor panel with tab bar and Monaco integration.
function EditorPanel({
className,
onActiveFileChange,
onEditorReady,
onTabsChange,
fileStatuses,
formattingFile,
countdownFile,
countdownKey,
formatEstimate = 2000,
onFixWithAI,
onTabDoubleClick,
}: EditorPanelProps): JSX.Elementprops— Component props.
Returns: The rendered editor panel element.
FileExplorer(props)
Tree-view file explorer component with multi-select, keyboard navigation, and drag-and-drop.
function FileExplorer({
files,
onFileSelect,
onFileDoubleClick,
onDirExpand,
onRename,
onDelete,
onDeleteMultiple,
onMoveFiles,
onNewFile,
onNewFolder,
onCollapseAll,
className,
persistKey,
activeFile,
fileStatuses,
}: FileExplorerProps): JSX.Elementprops— Component props (see {@link FileTreeItemProps}).
Returns: The rendered file explorer element.
filterActivitiesByType(activities, type)
Filters a list of activities by channel type. null (the "All" tab) returns
every activity unchanged.
function filterActivitiesByType(activities: Activity[], type: ActivityType | null): Activity[]activities— The activities to filter.type— The channel typ
