# Task designs

Change how tasks look while keeping the same storage and operations.

A task design is a set of Remix components. Replace one part of the board or detail page and keep the rest of Bud's built-in interface. Storage stays independent: a design renders the fields, statuses and actions declared by your work adapter.

## Start with one card

Create a browser module inside your project:

```tsx title="designs/compact-card.tsx"
import { defineTaskDesign, TaskCardDefault } from '@bud.sh/app/design'
import type { TaskCardDesignProps } from '@bud.sh/app/design'
import type { Handle, RemixNode } from 'remix/component'

function CompactCard(handle: Handle<TaskCardDesignProps>): () => RemixNode {
  return () => <TaskCardDefault {...handle.props} compact />
}

export default defineTaskDesign({ card: CompactCard })
```

Add it with `taskDesign()`, separately from your storage:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { taskDesign } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  plugins: [taskDesign('./designs/compact-card.tsx')],
})
```

The card keeps Bud's links, keyboard focus and drag behavior. Columns, dialogs and the task page retain their defaults. Leave `taskDesign()` out to use the built-in design throughout.

## Bring back more fields

Bud's built-in tasks screen is deliberately small: a title, a description, who created the task, its epic, linked tasks, the agent's run, the discussion and activity. Beads records much more, such as priority and assignee, and a design can show it. A `properties` slot puts a card beside the task's page:

```tsx title="designs/properties.tsx"
import { defineTaskDesign } from '@bud.sh/app/design'

export default defineTaskDesign({
  properties: (handle) => () => (
    <dl class="grid grid-cols-[6rem_1fr] gap-2">
      <dt>Priority</dt>
      <dd>{handle.props.renderField('priority')}</dd>
      <dt>Assignee</dt>
      <dd>{handle.props.renderField('assignee')}</dd>
    </dl>
  ),
})
```

`priority` and `assignee` are the Beads adapter's field IDs; another adapter may use other IDs, which `fieldByRole` finds by role. An `actions` slot adds controls under the properties, such as a button that runs one of the schema's `actions` through `handle.props.operations.act`.

The marketing consumer includes this example at `apps/web-marketing/designs/compact-card.tsx`, disabled by default.

## Available slots

Every slot is a Remix component. Its setup runs once; read changing data from `handle.props` inside the returned render function.

| Slot         | What it replaces                                                  | Built-in component |
| ------------ | ----------------------------------------------------------------- | ------------------ |
| `card`       | A board card or list row                                          | `TaskCardDefault`  |
| `page`       | The complete task detail page, including loading and error states | `TaskPageDefault`  |
| `properties` | A card beside the page, empty by default                          | None               |
| `actions`    | Controls under the properties, empty by default                   | None               |
| `sections`   | The sections below the discussion, by default Activity            | `SectionsDefault`  |
| `column`     | A status column's heading and cards                               | `ColumnDefault`    |

All defaults are exported from `@bud.sh/app/design`. Render one inside your own component to compose it. A custom page can render `<TaskPageDefault {...handle.props} />` and retain any separately configured properties, actions or section slots.

The workspace keeps the column's drop target. A column receives its `status`, `items` and rendered card `children`; rendering those children preserves your selected card design.

## Data and field rendering

Slots receive `schema`, optional `operations`, `renderField`, `itemHref`, `onSelect` and a `project` display identity containing only `id` and `name`. Item slots receive the current `item`. A page's item is `null` while loading or after a failed read; columns receive `items` instead.

Cards also receive selection, completion, compact layout, drag and link props. Pages receive the loaded detail, snapshot, discussion slot, loading state, errors, refresh callback and editor drafts. The exported `TaskCardDesignProps`, `TaskPageDesignProps` and other slot types describe the complete interface.

Use schema roles when placing content; an adapter chooses its own field IDs. For example:

```tsx
import { fieldByRole } from '@bud.sh/app/design'

let title = fieldByRole(handle.props.schema, 'title')
let content = title ? handle.props.renderField(title) : null
```

`renderField(fieldOrId, value?)` uses the shared renderers for text, dates, people, labels, markdown, links and JSON. It reads the item's current value unless you supply one. Plain strings and metadata remain escaped.

## Keep operations explicit

Designs receive consumer-owned callbacks rather than storage services. Keep controls conditional on the schema's capabilities and the available operations. Use the revision from the item when saving; preserve a draft after a conflict or uncertain result and let the user review it.

The default editor, menu and sections already handle these cases. Reusing them also retains lazy section loading, cancellation, focus restoration and discussion behavior.

## Module loading

Design modules run only in Bud's browser interface. They must remain inside your project, outside `node_modules`, and cannot be server modules. Bud shares its Remix runtime through the same import map used by custom screens.

Use Bud's components from `@bud.sh/app/screen`, the design exports, theme tokens or inline styles. Custom modules cannot import CSS. Bud rebuilds installed modules when a local input changes; reload the task screen to load that build. Workspace source development uses Vite's module pipeline.

A missing or unsafe module path prevents startup. A build failure or invalid export appears in Tasks with a retry action. Bud never silently substitutes the default design for a broken configured module.
