Design System
Neon Pilot's design system lives in packages/ui and is published as @neon-pilot/ui.
Extension authors should normally import through the public SDK surface:
import { Field, TextInput, ToolbarButton } from '@neon-pilot/extensions/ui';
This applies to sibling first-party extension repositories too, such as ../neon-pilot-extensions. Those extensions
should not copy local Field, TextInput, Select, Pill, notice, progress, or
panel classes when the SDK component exists. Build the extension after replacement with its repo script, for example
pnpm build system-video-probe from ../neon-pilot-extensions.
The desktop app may import from local compatibility paths such as packages/desktop/ui/src/components/ui, but reusable
components should be implemented in packages/ui first and re-exported through @neon-pilot/extensions/ui.
Agent Workflow
- Search
packages/ui/README.mdand Storybook before writing local UI. - Use shared primitives for buttons, fields, switches, notices, page shells, empty states, and status indicators.
-
If a needed reusable component is missing, add it to
packages/ui, add a story, add tests when behavior is non-trivial, and re-export it through the extension SDK. - Replace local copies in app and extension code when the new component is compatible.
- Keep extension-specific workflow logic inside the extension; keep generic chrome and controls in the design system.
For dynamic or generated settings UIs, avoid local input/select/button class constants. Compose the shared primitives directly.
Use Field for simple controls, and use FieldLabel plus FieldHint in a neutral wrapper for
composite controls that contain buttons.
Always use the most user-friendly input method available. Prefer constrained controls over free-form text whenever the value space
is known: Select or InlineSelect instead of a typed string, Switch or
SettingToggleRow instead of a boolean text field, SegmentedControl or tabs instead of magic mode names,
swatches instead of hex-only color fields, and resource pickers instead of path text boxes. Prefer explicit key/value or
structured row editors for individual settings and records over a Textarea containing JSON. Use raw JSON editing only
for genuinely large, deeply nested, import/export, or expert-only payloads, and pair it with validation and clear errors.
User-reachable UI actions should be command-backed when they may need a shortcut, command-palette entry, hardware trigger, or automation hook. Add or reuse an extension/app command for meaningful page actions, toolbar actions, navigation, and workflow operations instead of leaving behavior trapped inside an anonymous button handler.
When auditing an extension or app surface, search first for repeated local recipes such as
rounded-md border border-border-subtle, bg-elevated p-, ui-toolbar-button, local
BUTTON_CLASS constants, details/summary, and hand-written empty/error/loading text. Most of
those should collapse into an existing primitive.
Commands
Run the component package build:
pnpm --dir packages/ui run build
Run component tests:
pnpm --dir packages/ui run test
Start Storybook:
pnpm --dir packages/ui run storybook
Build Storybook:
CI=true pnpm --dir packages/ui run build:storybook
Current Foundation
The shared package includes:
-
Actions:
Button,ButtonLink,ToolbarButton,TextButton,MessageActionButton,IconButton,IconLink,BrowsePathButton,CheckButton,TaskListItem,ChoiceRow,ActionTile - Attachment controls:
AttachmentChip,AttachmentChipButton -
Status:
Pill,StatusDot,RingStatusDot,Spinner,Keycap,Tooltip,Notice -
Surfaces:
SurfacePanel,PanelHeader,PanelMessage,CompactCard,WorkbenchShell,WorkbenchHeader,RailSection,RailSubsection,ShelfSection,ShelfHeader,ShelfBody -
Overlays:
Dialog,DialogHeader,DialogBody,DialogFooter,ConfirmDialog,TextPromptDialog -
Feedback:
CenteredState,CenteredLoadingState,CenteredMessage,LoadingState,ErrorState,EmptyState,AppPageEmptyState -
Forms:
Field,FieldLabel,FieldHint,FieldError,TextInput,SearchInput,Textarea,Select,InlineTextInput,InlineSelect,Checkbox,KeyboardShortcutCaptureInput,Switch,SettingsPanel,SettingsRow,SettingToggleRow,SettingsSection -
Menus:
MenuShell,PositionedMenu,MenuGroupLabel,MenuItem,MenuSeparator -
Selection and filtering:
SegmentedControl,TabList,TabButton,TabPanel,FilterToolbar -
Data display:
SectionLabel,MetaLabel,CardTitle,CardBody,CardMeta,SupportingText,InlineMeta,MessageCard,MessageMeta,ToolResultCard,ResourcePickerDialog,ResourcePickerToolbar,ResourcePickerList,ResourceList,ResourceListRow,ResourceListItem,ResourceListLink,RowButton,InlineCode,InlineCodeButton,CodeBlock,Disclosure,ProgressBar,ProgressRow,Stat,StatGrid,MetricTile,DashboardGrid,DashboardGridCell,KeyValueList,KeyValueItem,KeyValueTable,DataTable,DataTableHead,DataTableBody,DataTableRow,DataTableHeaderCell,DataTableCell,DataTableEmptyRow,DataTableActionGroup,TerminalBlock -
Pages and sections:
PageHeader,AppPageLayout,AppPageIntro,AppPageSection,AppPageToc,AppPageEmptyState,RuntimePage,RuntimeHeader,RuntimeHeaderControls,RuntimeStrip,RuntimeSection,RuntimeFooter - Utility:
cx
Host-backed extension components are also exposed through public SDK subpaths when they need app-owned data or behavior:
-
@neon-pilot/extensions/ui: app page shells, shared primitives,ActivityTreeView,ChatView,ChatRailComposer,ExtensionChatRail,CheckpointInlineDiff,DiffActionButton,ContextMenuWrapper, and file-tree helpers such asuseFileTreeModel -
@neon-pilot/extensions/settings: settings-oriented exports plus shared form, notice, progress, and subsection primitives such asSettingsSection,Field,SettingToggleRow, andRailSubsection -
@neon-pilot/extensions/workbench-files:WorkspaceExplorerandWorkspaceFileDocument
Prefer package primitives for generic chrome. Use host-backed components only when the component depends on desktop/app state, workspace files, transcript rendering, activity-tree behavior, or native context menus.
Extraction Priorities
Extract in small tranches and migrate real usage each time:
- Forms and feedback: fields, inputs, switches, notices.
- Overlays: confirmation dialogs and richer positioned menu behavior.
- Layout: settings sections, cards, page headers, richer search/filter bars.
- Data display: sortable columns, richer table states, and nested table actions.
- Host-owned app patterns: file trees, activity trees, chat/transcript surfaces, diff/artifact views.
Each tranche should include documentation, Storybook coverage, and at least one app or extension replacement so the component is proven against production usage.
Migration Map
- Raw action buttons ->
Button,ToolbarButton, orIconButton - Raw file/folder picker icon buttons ->
BrowsePathButton - Raw todo/checklist rows ->
TaskListItemwithCheckButton - Raw selectable radio/checkbox option rows ->
ChoiceRow - Raw text/search/number inputs ->
TextInputorSearchInput - Raw selects ->
Select -
Raw local
Field,TextInput,Select, orPillhelpers in external extensions -> SDK imports from@neon-pilot/extensions/ui - Raw textareas ->
Textarea - Boolean settings ->
SwitchorSettingToggleRow - Local nested settings cards or provider subsections ->
SettingsPanel - Local segmented filters ->
SegmentedControl - Local tab rows ->
TabList,TabButton, andTabPanel - Local search/filter header rows ->
FilterToolbar - Local route section headers with title/description/count/actions ->
AppPageSection - Local fixed/absolute menu shells ->
PositionedMenuwithMenuItem -
Local loading/error/empty messages ->
LoadingState,ErrorState,EmptyState,PanelMessage,CenteredMessage, orNotice - Local bordered section cards with title/meta rows ->
SurfacePanelwithPanelHeader - Local workbench editor/detail/preview panes ->
WorkbenchShellwithWorkbenchHeader -
Local left/right rail groups ->
RailSectionplusResourceListItem,ResourceListRow, orRowButton -
Local compact transcript/workbench shelves ->
ShelfSectionwithShelfHeaderand optionalShelfBody -
Local extension runtime/model-server/setup pages ->
RuntimePage,RuntimeHeader,RuntimeStrip,RuntimeSection,RuntimeFooter, andTerminalBlock -
Local bordered metric tiles or status grids ->
DashboardGrid,DashboardGridCell, andMetricTile -
Local bordered data cards ->
SurfacePanel,ResourceList,ResourceListRow,ResourceListItem,KeyValueList,KeyValueTable,DataTable, orDisclosure -
Local modal resource/file/folder/workspace pickers ->
ResourcePickerDialog,ResourcePickerToolbar,ResourcePickerList, andResourceListItem - Local non-collapsible tool result cards ->
ToolResultCard - Local table no-results
tr/tdplaceholders ->DataTableEmptyRow - Local table row action wrappers ->
DataTableActionGroup - Local compact uppercase labels ->
SectionLabelorMetaLabel - Local code, command, log, or JSON blocks ->
CodeBlockorTerminalBlock -
Local file trees or workspace file panels ->
useFileTreeModelfor app-integrated trees, orWorkspaceExplorer/WorkspaceFileDocumentwhen the extension needs the existing workspace file UX -
Local transcript/chat surfaces ->
ChatView,ChatRailComposer,ExtensionChatRail,MessageCard,MessageMeta,MessageActionButton, and transcript-specific primitives before rebuilding message chrome
Recent Production Examples
Use these as reference implementations when migrating similar surfaces:
-
extensions/system-settings/src/SettingsPage.tsx: settings provider advanced sections useDisclosure. -
extensions/system-telemetry/src/traces/TracesToolHealth.tsx: telemetry tool cards useSurfacePanel,StatusDot,Pill,MetricTile, andProgressBar. -
extensions/system-extension-manager/src/panels.tsx: manifest inspection usesDisclosureandCodeBlock. -
packages/desktop/ui/src/components/ConversationArtifactWorkbench.tsxandConversationCheckpointWorkbench.tsx: full-height workbench panes useWorkbenchShell. -
packages/desktop/ui/src/components/conversation/ConversationActivityShelf.tsxandConversationQueueShelf.tsx: compact conversation shelves useShelfSection. -
packages/desktop/ui/src/components/chat/MessageBlocks.tsx: transcript messages useMessageCard,MessageMeta, andMessageActionButton. -
../neon-pilot-extensions/system-ds4/src/frontend.tsx: runtime settings useSurfacePanel,Disclosure,ProgressBar,Pill,DashboardGrid,MetricTile, andCodeBlock. -
../neon-pilot-extensions/system-local-models/src/page.tsxandsystem-video-probe/src/frontend.tsx: selected-model/runtime summaries useDashboardGridandMetricTile. -
../neon-pilot-extensions/system-browser/src/panels.tsx: browser tab rail usesRailSectionandResourceListItem.
Agent Checklist
Before introducing local UI markup in an app page or extension:
-
Check
packages/ui/README.mdfor a matching primitive and inspectpackages/ui/src/stories/Primitives.stories.tsxfor composition examples. -
Prefer SDK imports from
@neon-pilot/extensions/uifor extension code and@neon-pilot/extensions/settingsfor Settings extension code. - Choose the friendliest control before defaulting to text: dropdowns, toggles, segmented controls, pickers, key/value editors, and structured rows should replace raw inputs or JSON textareas whenever practical.
- Back meaningful user actions with commands so they can be discovered, automated, and hot-keyed.
- Keep domain behavior local, but move generic chrome, spacing, state color, empty/error/loading presentation, and modal/list/table shells to shared primitives.
-
When adding a new primitive, update
packages/ui/src/index.ts,packages/desktop/ui/src/extensions/ui.tsor settings exports if extensions need it,packages/ui/README.md, Storybook, and tests where behavior is non-trivial. -
Validate the actual touched surface: build the package or extension, run
pnpm run check:extensions:staticfor extension boundary work, and perform browser/app QA when user-visible UI changes.