` cannot opt back out. If a flow genuinely needs a heading or
full-contrast body, **extend `useConfirm`** (an optional prop, applied to all
call sites) rather than forking a one-off modal.
**In component tests**, mock it — `ConfirmProvider` pulls in `next/router`,
which is not available in jsdom:
```tsx
jest.mock('@/useConfirm', () => ({ useConfirm: jest.fn() }));
```
Assert on the arguments (and render the message `ReactNode` if you need to check
the copy). Exercise the real dialog in E2E instead.
### EmptyState Component (REQUIRED)
**Use `EmptyState` (`@/components/EmptyState`) for all empty/no-data states.** Do not create ad-hoc inline empty states.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `icon` | `ReactNode` | — | Icon in the theme circle (hidden if not provided) |
| `title` | `string` | — | Heading text (headline style — no trailing period) |
| `description` | `ReactNode` | — | Subtext below the title |
| `children` | `ReactNode` | — | Actions (buttons, links) below description |
| `variant` | `"default" \| "card"` | `"default"` | `"card"` wraps in a bordered Paper |
```tsx
// ❌ BAD - ad-hoc inline empty states
No data
Nothing here
// ✅ GOOD - use the EmptyState component
}
title="No alerts created yet"
description="Create alerts from dashboard charts or saved searches."
variant="card"
/>
```
**Title copy**: Treat `title` as a short headline (like `Title` in the UI). Do **not** end it with a period. Use `description` for full sentences, which should use normal punctuation including a trailing period when appropriate. Match listing pages (e.g. dashboards and saved searches use parallel phrasing such as “No matching … yet” / “No … yet” without dots).
### Object names: use `InlineNameInput` (REQUIRED)
**Name or rename a dashboard, saved search, tile, or any other user-created
object with `InlineNameInput` (`@/components/InlineNameInput/InlineNameInput`).**
Do not build a pencil-button edit mode, a bordered name `TextInput` in a
header, or a "Name" field inside a save modal. See
[naming_objects.md](naming_objects.md) for which variant to use.
### Code snippets (REQUIRED)
**Do not render raw ``, ad-hoc `Paper` + monospace text, or unstyled ``.** Use the existing Mantine/product components so snippets match Terraform export, onboarding, and Storybook guidelines.
| Kind | Component | When |
|------|-----------|------|
| **Inline code** | Mantine `` | Short tokens in prose (`Session`, a column name, a flag). |
| **Fenced / multi-line / copyable** | `CopySnippet` (`@/components/ClickStackOnboarding/CopySnippet`) | Install commands, HCL, JSX examples, any block the user might copy. Wraps `` plus a Copy button. |
```tsx
// ✅ inline — Mantine Code
Create a source with Session type.
// ✅ fenced / copyable — CopySnippet (Code + Copy)
// Label is optional when the surrounding heading already names the snippet
// ❌ BAD — raw pre / Paper chrome
{snippet}
{snippet}
```
**SQL query previews** (rendered ClickHouse SQL with highlighting) still use `SQLPreview` / `ChartSQLPreview` — those are editors, not snippet chrome.
Storybook: `Components/Code` (live `InlineCode`, `FencedBlocks`, and `Usage`). Guidelines markdown maps fenced blocks → `CopySnippet` and inline `` `code` `` → `` automatically. Follow the same split in app UI.
### Chart cards (ChartCard)
**Use `ChartCard` (`@/components/charts/ChartCard`) to wrap a chart in a card.**
It is a bordered surface with the same header treatment as a custom dashboard
tile (a full-bleed divider under the title). It replaces the older `ChartBox`;
don't hand-roll a bordered ``/`
` around a chart.
`ChartCard` renders the card **chrome only**. The header divider is drawn only
when a descendant renders a `ChartContainer` with a `title` (or `toolbarItems`) —
`ChartCard` supplies the `ChartContainerCardHeaderProvider` that switches that
header into card mode — so put a chart that renders a `ChartContainer` inside it
(`DBTimeChart`, `DBTableChart`, `DBHeatmapChart`, `DBListBarChart`, …). Content
with its own heading (e.g. a bespoke table card) should still route that heading
through a titled `ChartContainer` rather than a bare `Text`, so it gets the same
card header — divider and top padding included — instead of sitting flush against
the top border. The tile-level controls (fullscreen, line/bar display switcher,
kebab menu) belong to dashboard tiles and are intentionally **not** part of
`ChartCard`.
| Prop | Type | Description |
|------|------|-------------|
| `children` | `ReactNode` | The chart, usually a `DB*Chart` (or a titled `ChartContainer`) |
| `style` | `CSSProperties` | Sizing/overflow override — pass a fixed `height`, or `flex: 1; height: 100%` to fill a flex row (`paddingInline` is pinned to keep the divider aligned) |
| `data-testid` | `string` | Test hook |
```tsx
// ✅ GOOD — shared card chrome, consistent with dashboard tiles
// ❌ BAD — hand-rolled card that drifts from the dashboard look
```
**Give it a height.** `ChartCard` is `width: 100%` and fills its parent, so the
parent (or a `style={{ height }}`) must define the height. For equal-width
side-by-side charts (e.g. the RED row) use
`style={{ flex: 1, minWidth: 0, minHeight: 0, height: '100%' }}` inside a
`Flex`. See the `Charts/ChartCard` Storybook stories for the variants.
## UI text: use sentence case
All user-facing text uses **sentence case** — capitalize only the **first word**
and any proper nouns/acronyms. Do **not** use Title Case (capitalizing every
significant word).
This applies to every string a user reads: field labels, buttons, tab/menu
items, headings, section titles, modal titles, placeholders, tooltips, table
column headers, empty states, and toast/notification copy.
| Title Case (avoid) | Sentence case (use) |
|--------------------|---------------------|
| `Data Source` | `Data source` |
| `Chart Name` | `Chart name` |
| `Add Series` | `Add series` |
| `Count of Events` | `Count of events` |
| `Save Changes` | `Save changes` |
| `Delete Dashboard` | `Delete dashboard` |
**Keep the original casing of proper nouns, product names, and acronyms**
anywhere in the string — sentence case only changes the words around them:
HyperDX, ClickHouse, ClickStack, OpenTelemetry/OTel, Lucene, SQL, PromQL,
MongoDB, Kubernetes, JSON, CSV, URL, ID, API, MCP, CPU, P95. For example:
`Search your events w/ Lucene`, `Edit SQL`, `Copy as cURL`, `View in ClickHouse`.
Only sentence-case **static UI chrome**. Never rewrite dynamic/user data (column
names, tag values, log/trace content, source or dashboard names a user typed) —
render those verbatim.
## Semantic design tokens (prefer over raw Mantine colors)
The UI is built with **Mantine components**, but **colors and surfaces** should follow the **semantic CSS custom properties** in our themes (`--color-*`, etc.), not ad-hoc Mantine palette values. Those tokens are defined in `packages/app/src/theme/_tokens.scss`, align with a **Click UI**–style system, and are shared by every deployment. They are the path toward a shared design system even while Mantine remains the component layer.
- **Do**: Use Mantine for layout, components, and spacing; use **semantic tokens** for themed backgrounds, text colors, borders, and states (e.g. `style={{ color: 'var(--color-text-muted)' }}` or `style={{ border: '1px solid var(--color-border)' }}`).
- **Do not**: Rely on raw Mantine color props for app chrome and content when a semantic token exists — e.g. `c="gray.5"`, `bg="dark.7"`, or arbitrary `color="blue.4"` for surfaces that should match the rest of the product.
- **Reference**: `packages/app/src/theme/semanticColorsGrouped.ts` (token names), `packages/app/src/theme/_tokens.scss`, and Storybook (`SemanticColors` and related theme stories) for a visual map.
Mantine theme overrides in `packages/app/src/theme/**` may map Mantine’s scale to our palette; that does not replace using **`var(--color-...)`** in new styling where you need explicit color control.
## Click UI (incremental adoption)
`@clickhouse/click-ui` is a dependency of `packages/app`. `ThemeWrapper` mounts
`ClickUIProvider` inside `MantineProvider` with `persistTheme={false}`, so it
follows the app color mode and never writes its own theme to localStorage.
Storybook and `renderWithMantine` in tests get the provider for free.
- **Do**: Prefer a click-ui component over a new Mantine one when click-ui has
an equivalent for a simple primitive (`Link`, `Text`, `Title`, `Container`,
`Badge`, `Separator`, `Spacer`, `Icon`). Import from `@clickhouse/click-ui`.
- **Do not**: Mix both libraries inside one small component, and do not
replace working Mantine code outside the change you were asked to make.
- **`Container` is not a `Box`**: click-ui has no plain-wrapper equivalent of
Mantine `Box`. `Container` is a flex layout, closer to `Group` or `Stack`. It
defaults to `display="flex"`, `orientation="horizontal"`, and
`alignItems="center"`, so a direct swap puts the children in one row. It also
has no `m`/`p`/`w`-style props, only `gap` and `padding` tokens. To replace a
`Box` that only carries attributes such as `id` or `data-testid`, use a plain
HTML element. To replace a `Box` that uses Mantine style props, keep the
`Box`, or set `Container` props to match the layout and check it visually.
- **Styling**: click-ui styles live in the `clickui` CSS cascade layer, so any
unlayered app or Mantine rule wins over them. Override `--click-*` variables
outside the layer if a token must change.
- **First use**: `packages/app/src/components/ContactSupportText.tsx` and the
`Click UI/Link` story in `packages/app/src/stories/ClickUI.stories.tsx`.
**Chart and visualization colors are a separate, more specific contract** — they have their own categorical, semantic, and heatmap palettes wired through `packages/app/src/utils.ts` helpers. Don't hard-code series colors or reuse `--color-text-*` for charts. See [`data_viz_colors.md`](./data_viz_colors.md) before touching anything that renders data.
## Refactoring
- Edit files directly - don't create `component-v2.tsx` copies
- Look for duplicate code across the affected area
- Verify all callers and integrations after changes
- Refactor to improve clarity or reduce complexity, not just to change
## File Naming and Organization
- Clear, descriptive names following package conventions
- Avoid "temp", "refactored", "improved" in permanent filenames
- Put related components in a single directory