Skip to content
Development
Agent

ui-kit

The desktop app has exactly one component kit: **`@emdash/ui`** (`packages/ui`). The legacy Tailwind/cva kit that lived at `apps/emdash-desktop/src/core/primitives/ui/browser/` was deleted by the UI-kit unification migration — do not reintroduce it or shim to its old APIs.

From plugin
emdash
5.7k32 skills32 agents

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

The desktop app has exactly one component kit: **`@emdash/ui`** (`packages/ui`). The legacy Tailwind/cva kit that lived at `apps/emdash-desktop/src/core/primitives/ui/browser/` was deleted by the UI-kit unification migration — do not reintroduce it or shim to its old APIs.

Agent definition

ui-kit.md

Which UI Kit

The desktop app has exactly one component kit: **`@emdash/ui`** (`packages/ui`). The legacy Tailwind/cva kit that lived at `apps/emdash-desktop/src/core/primitives/ui/browser/` was deleted by the UI-kit unification migration — do not reintroduce it or shim to its old APIs.

Rules

  • **Components come from `@emdash/ui`.** Buttons, dialogs, fields, inputs, selects,

menus, tooltips, toasts, tabs, markdown, time display — import them from `@emdash/ui`. Do not hand-build a styled equivalent in feature code.

  • **Feature-local Tailwind is for layout one-offs only.** Flex/grid/gap/padding/typography

utilities in feature `browser/` code are fine until the separate future Tailwind-removal effort. Do not use Tailwind to build new reusable styled components.

  • **New generic primitives go in `packages/ui`,** styled with vanilla-extract recipes and

base-ui patterns per [UI styling conventions](ui-styling.md). Storybook stories covering variants and states are mandatory for every new or extended component.

  • **App-specific components live in their owning feature slice** under

`src/core/features/<feature>/browser/` (or the owning service/primitive), composed from `@emdash/ui` primitives. Only promote to `packages/ui` when a component is genuinely app-agnostic.

  • **`cn()` lives at `src/core/primitives/styling/browser/cn.ts`**

(`@core/primitives/styling/browser/cn`). It is the only sanctioned class-merging helper app-side; `packages/ui` does not use it.

Choosing a popup control

  • **`Select`** is for a short, bounded set of mutually exclusive values. Its popup is detached

from the trigger and sizes to at least the trigger by default. Use `width="trigger"` when a form control should keep an exact column width. Avoid `alignItemWithTrigger`; it makes the selected row overlap the trigger and is reserved for a deliberately native-select-like interaction.

  • **`Combobox` / `ComboboxPopover`** is for searchable or potentially large collections of

entities such as agents, branches, remotes, machines, models, and working copies. Prefer the composed `ComboboxPopover` when its trigger/list/footer API is sufficient. Use `width="content-at-least-trigger"` when rows contain useful secondary metadata that needs more room than the trigger.

  • **`DropdownMenu`** is for commands and actions, not choosing a persisted value. Radio and

checkbox items are appropriate when an action menu also exposes a current preference.

  • **`ContextMenu`** is the right-click variant of an action menu.
  • **`Popover`** is for arbitrary interactive content that is not fundamentally a list of choices

or commands.

  • **`SplitButton`** is for a primary action with a menu of alternative actions.
  • **`ComboboxPopup`** is for inline text completion anchored to an editor or input caret, such as

mentions and slash commands.

Popup width is an explicit content decision rather than a positioning side effect. `Select`, `Combobox`, and `DropdownMenu` content support `trigger`, `content`, and `content-at-least-trigger` widths.

Page-level lists

A page-level list of records is a **`CollectionView`** (`packages/ui/src/react/patterns/collection-view/`), full stop. Card grids, trees (`TreeView`), sidebars, popup pickers, and changed-files lists are different patterns. The reference renderings are the stories in `packages/ui/src/react/patterns/collection-view/collection-view.stories.tsx`; the full locked spec lives with the unify-list-views effort.

  • **One shell, two row styles**: every list is the same rounded card surface with soft

dividers, hover/selected states, and always-on virtualization. Row content is either tabular `columns` (`CollectionViewColumn[]`, with `CollectionViewCell` for two-line text cells) or freeform `renderRow` — exactly one of the two. There is no header row.

  • **State is opt-in**: pass `view` (a `createListView` instance, rendered inside its

`Root`) for search/filter/sort/sections/selection/pagination, or plain `items` + `getItemKey` when no state layer is needed. Selection, sections, and loading/error status auto-wire in view mode; `renderSectionHeader` overrides the default label+count section header (for select-all headers and the like).

  • **Density** is a two-value axis: `default` (60px estimate) or `compact` (36px).

Taller measured content overrides via `estimateSize`; per-surface pixel tweaking is not a thing.

  • **Toolbar and footer are slots**: compose `CollectionToolbar.Root` with

`CollectionToolbar.Search`, `Spacer`, `Group`, and `Separator` as needed, then put it in `toolbar`; floating bulk bars and banners built on `ListPopoverCard` go in `footer`.

  • **Sorting UI** is the shared `SortSelect` bound to `view.useSort()` — sort keys and

labels live in the sort spec, never re-declared in the UI.

  • **Multi-select mechanics are framework-given** (modifier-click toggle, shift-range);

the default presentation is a hover-revealed leading checkbox column plus a floating `ListPopoverCard` bulk bar. Row click opens the item and never mutates.

  • **Row actions are tiered**: context menu for the full set, trailing ellipsis menu for

discoverability on management tables, hover buttons for at most 1–2 high-frequency actions. Destructive actions go last, separated, destructive-styled, with a confirm modal when irreversible.

  • **An empty state is mandatory** (`EmptyState` is the default content; rich

interactive empty states are allowed); `Spinner` is the loading default. Custom `EmptyState` slot content must pass `bare` — the card paints its own surface, so the component's panel background would patch over it.

  • **Query-backed lists use `useQueryListSource`**: when the data comes from React

Query, bridge the query result into the view with `useQueryListSource(query, buildItems)` (an `external` list source) instead of `observable.box` bridges or hand-rolled loading/error branches. Routing is then framework-owned: `loading` + no rows

Read more
Ships withemdash

Emdash is the Open-Source Agentic Development Environment (🧡 YC W26). Run multiple coding agents in parallel. Use any provider.

Get the whole plugin
Stats
5,748
Stars
589
Forks
Active
Maintenance
TypeScript
Language
Apache-2.0
License
8m ago
Last commit
1y ago
Created

Repo: generalaction/emdash

Other agents on emdash.