Skip to content
Back to skills

Rembric Dashboard Ui

ASecurity

Rembric dashboard UI work (dark monochrome zinc surfaces, lime accent, Geist type, Spectrum data-tables) — building / editing / extending pages, styles, components, modals, tables, filters, pagination, mobile responsive, destructive confirmations. Use when the user asks for changes under `apps/web/src/app/dashboard/` or `apps/web/src/components/dashboard/`, mentions Rembric design tokens (lime, zinc, command bar, StatusPill, hl-lime, data-table), or requests a new dashboard page / form / table.

  • 13 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
ai-agentsgoshellbashreactnextjsnodedockertestingdatabasebackend

Works with

  • cli
  • mcp

Security analysis

A100/100

Scanned September 24, 2026

npx -y skills add susomejias/rembric --skill rembric-dashboard-ui --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rembric Dashboard Ui?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rembric Dashboard Ui
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/susomejias-rembric-dashboard-ui/badge)](https://www.skillsdirectory.com/skills/susomejias-rembric-dashboard-ui)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rembric-dashboard-ui
description: |
  Rembric dashboard UI work (dark monochrome zinc surfaces, lime accent, Geist
  type, Spectrum data-tables) — building / editing / extending pages, styles,
  components, modals, tables, filters, pagination, mobile responsive,
  destructive confirmations. Use when the user asks for changes under
  `apps/web/src/app/dashboard/` or `apps/web/src/components/dashboard/`, mentions
  Rembric design tokens (lime, zinc, command bar, StatusPill, hl-lime, data-table),
  or requests a new dashboard page / form / table.
license: MIT
metadata:
  author: Rembric
  version: '1.1'
---

# Rembric Dashboard UI

Reach for this skill whenever the user wants UI work on the Rembric dashboard
(`/dashboard/*`). The dashboard is a **Next.js App Router + React 19** surface
styled with **Tailwind v4** and **shadcn/Radix** primitives. It is no longer the
Hono/HTMX/SSR-helper stack the old version of this skill described — there is no
`src/dashboard/`, `dashboard-router.ts`, `components.ts`, or `templates.ts`.

## Trigger

Auto-trigger when the user asks for:

- A new dashboard page, route, or view
- Edits to an existing dashboard page (memories, sessions, entities, judgments,
  consolidation, prompts, projects, tokens, maintenance, settings, activity,
  overview, login, OAuth consent)
- New visual components (cards, pills, buttons, banners, modals)
- Mobile / responsive tweaks
- New destructive actions that need confirmation
- New tables, filters, paginators
- Anything that mentions: `lime`, `zinc`, `command bar`, `StatusPill`,
  `data-table`, `hl-lime`, `ConfirmSubmit`, `ActionForm`, `Time`

Do NOT trigger for:

- Server / service / DB / MCP / consolidation backend work
- OpenSpec change scaffolding (use the `openspec-*` skills for that)
- Plugin / hook / CLI / `apps/plugin/` work

## The surface, verified

```text
apps/web/src/app/dashboard/layout.tsx          CommandFrame wrapper (command bar, counters, version)
apps/web/src/app/dashboard/**/page.tsx         one async server component per route
apps/web/src/components/dashboard/command-bar.tsx  CommandFrame + floating CommandBar (no sidebar)
apps/web/src/components/dashboard/sessions-table.tsx  canonical Spectrum data-table usage
apps/web/src/components/dashboard/ui.tsx       StatusPill (canonical) + LEGACY primitives
apps/web/src/components/spectrumui/data-table.tsx   the shared list-view table engine
apps/web/src/components/dashboard/filters.tsx   LEGACY FilterForm/Field/Input/Select/Actions + Pager
apps/web/src/components/dashboard/action-form.tsx   ActionForm + useActionFormId
apps/web/src/components/dashboard/csrf-field.tsx    CsrfField
apps/web/src/components/dashboard/confirm-submit.tsx ConfirmSubmit (AlertDialog ↔ form)
apps/web/src/components/dashboard/app-sidebar.tsx   LEGACY Sidebar rail (only updaterReadState is live)
apps/web/src/components/dashboard/time.tsx      Time (client, UTC fallback → viewer TZ)
apps/web/src/components/dashboard/support.ts    PAGE_SIZE, queryWithPage, relativeTime, …
apps/web/src/components/dashboard/{markdown-panel,nav-user,site-header}.tsx
apps/web/src/components/ui/*                    shadcn primitives (alert-dialog, button, table, …)
apps/web/src/app/layout.tsx                     Geist / Geist Mono localFont wiring (--font-geist*)
apps/web/src/app/fonts/                         self-hosted Geist + Geist Mono woff2
apps/web/src/app/globals.css                    Tailwind v4 theme + design tokens
apps/web/src/lib/nav.ts                         NAV / NAV_GROUPS / navEntryForPath / CHROME_FREE_PATHS
apps/web/src/lib/utils.ts                       cn()
apps/web/src/test/dashboard/*.test.tsx          per-page tests
```

Pages are **async server components**. `searchParams` is a `Promise` in this
Next.js version — `const params = await searchParams` before reading. Data comes
from `getServices()` (`@/lib/services`) — `const { repos, ... } = getServices()`.

## Identity

The dashboard was **redesigned** to a dark monochrome zinc system with the Rembric
lime as the single accent. `apps/web/src/app/globals.css` is the live token
surface, mapped into Tailwind via `@theme inline` so utilities like `bg-background`,
`bg-card`, `bg-popover`, `text-foreground`, `text-muted-foreground`, `border-border`,
`bg-primary`, `text-warn`, `text-destructive` resolve to the tokens.

- **Surfaces (dark monochrome zinc).** Background `#09090b`; cards `#101012`;
  raised / popover / muted / secondary `#18181b`; accent `#1c1c1f`; borders
  `#1f1f23` (1px); input `#27272a`; ring `#3f3f46`. Text ladder: `#fafafa`
  (`--foreground`) → `#a1a1aa` → `#71717a` (`--muted-foreground`).
- **Lime is the ONLY accent.** `--primary: #c6f24e`, `--primary-foreground:
#09090b`. Primary buttons are lime fill + near-black text. On light surfaces
  lime is **chrome only** (fills, borders, dots, bars) — **never lime text on a
  light background**. Amber `--warn: #ff8c00` and `--destructive` stay for warn /
  danger tones; charts use the zinc ladder with lime as `--chart-1`.
- **Fonts.** UI and display output use **Geist** (`--font-geist`, wired to
  `--font-sans` / `--font-display`). **Geist Mono** (`--font-geist-mono`,
  `--font-mono`) owns labels, meta, counters and tabular data. Both are
  self-hosted woff2 in `apps/web/src/app/fonts/` and loaded in
  `apps/web/src/app/layout.tsx`. No CDN fonts, no second display face.
- **Radius & depth.** Radius `--radius: 1rem`: cards `rounded-2xl` (16), inputs
  `rounded-xl` (12), buttons `rounded-lg` (10). Soft shadows only
  (`shadow-lg shadow-black/20`–`shadow-black/40`). Depth comes from **radial lime
  glows** — e.g. `bg-[radial-gradient(640px_260px_at_50%_-60px,rgba(198,242,78,0.09),transparent_70%)]`
  — instead of heavy borders.
- **`StatusPill`** (`ui.tsx`) is the shared app-wide status pill: a
  `rounded-full` container with a dot plus a tinted background/border per tone
  (`TONE_PILL`), driven by the `STATUS_TONE` map (active/judged → lime,
  abandoned/superseded → amber, archived/ended/pending → dim, orphaned/deleted →
  danger). Use it for status, never a hand-rolled badge.
- **List views: Spectrum UI data-table.** Every list/table page goes through
  `DataTable` from `@/components/spectrumui/data-table` (variant `"panel"`):
  quick-filter pills with live counts, client search, selectable rows with a
  rising bulk-action bar, contextual `⋯` row menu, and a detail disclosure.
  **`apps/web/src/components/dashboard/sessions-table.tsx` is the canonical
  implementation** to copy from — see also `judgments-table.tsx`,
  `prompts-table.tsx`, `memories-table.tsx`, `entities-table.tsx`,
  `tokens-table.tsx`.
- **Charts.** `activity-chart.tsx` is the reference for lime/zinc bar visuals:
  lime for saves, amber for superseded/archived ops, rounded caps, hover tooltips
  on `bg-popover` with soft `shadow-black/40`.
- **Shell: floating command bar, no sidebar.** `command-bar.tsx` renders a
  floating centered header (`rounded-2xl border bg-card/90 backdrop-blur`) with
  primary nav, a "More" dropdown, counters and a mobile `Sheet`. There is **no
  sidebar rail**. `/dashboard/login` and `/dashboard/oauth-consent` are
  **chrome-free standalone screens** — `CHROME_FREE_PATHS` in
  `apps/web/src/lib/nav.ts` plus `isChromeFreePath()` short-circuit the frame.

### Pending OpenSpec change

The formal OpenSpec `dashboard` spec update for this identity is **pending**
(branch `feat/dashboard-identity-redesign`). Until it is written and merged, the
**LIVE reference is the branch code** — `sessions-table.tsx`,
`command-bar.tsx`, `activity-chart.tsx`, `ui.tsx` and `globals.css` — **not** the
old spec text or `DESIGN.md`, whose prose still describes the previous brutalist
identity. Do not touch `openspec/specs/dashboard/spec.md` or archived change
`design.md` files from UI work; that is a separate task.

### Legacy primitives (do NOT use for new work)

The old brutalist primitives are still exported from `ui.tsx` and remain in
unrestyled pages (consolidation, maintenance, and several detail pages) pending
migration. Marked **LEGACY**; do not document or copy them as the standard:
`ViewHead`, `SectionBar`, `StatCard`/`StatGrid`, `Kv`/`KvGrid`,
`Panel`/`PanelHead`, `DataTable`/`DataHead`/`DataTh`/`DataBody`/`DataTr`/`DataTd`,
`TableEmpty`, `Pill`/`ReviewPill`/`Chip`/`Tag`, `Bar`, `Notice`, `Flash`. The
hard-bordered `Pill` and the `ViewHead` lime-block title belong to the old
identity. `filters.tsx` (`FilterForm`/`Pager`) and the `app-sidebar.tsx` rail are
legacy too — list pages now use the Spectrum data-table's own toolbar and
pagination. When you restyle a legacy page, replace these with the new identity,
not with another legacy primitive.

## Hard rules

- **New list views use the Spectrum `DataTable`.** Import
  `DataTable`/`DataTableColumn` from `@/components/spectrumui/data-table`, pass
  `variant="panel"`, and mirror `sessions-table.tsx`: `searchable`,
  `quickFilter`, `selectable` + `bulkActions`, `rowActions` (the `⋯` menu),
  `renderDetail`, `pageSize`. Do not hand-roll a table and do not reach for the
  legacy `DataTable` from `ui.tsx`.
- **Use `StatusPill` for status.** One pill, tone-driven, app-wide. Don't invent
  a second status badge.
- **Every mutation is a server action through `ActionForm`.** Import
  `ActionForm`/`ActionState` from `@/components/dashboard/action-form`, put a
  `<CsrfField form={FORM_NAME} />` in it, and start the action by calling
  `guardAction(formData, FORM_NAME)`/`guardFailure(...)` from `@/lib/actions/guard`
  (session + admin + CSRF gate). The action returns `{ error: string | null }`.
- **Destructive actions MUST use `ConfirmSubmit`.** It wires a Radix `AlertDialog`
  to the enclosing `ActionForm` via `useActionFormId`, so it only works **inside**
  an `ActionForm`, wrapping the trigger button (`<Button type="button">`). Pass
  `tone="danger"` for irreversible actions, `"warn"` for reversible ones. Mirror
  the call sites in `sessions-table.tsx` (row `⋯` menu + bulk bar) and under
  `apps/web/src/app/dashboard/`.
- **Timestamps go through `<Time>`.** It emits `<time dateTime data-rembric-ts>`
  with a UTC fallback and re-renders in the viewer's timezone after mount. Never
  hand-write `toISOString` / `toLocaleString` in views.
- **Nav lives in `apps/web/src/lib/nav.ts`.** Add entries to the `NAV` array
  (group, `num`, label, `href`, lucide `icon`, optional `badgeKey`); the command
  bar and its mobile sheet both read it. Chrome-free screens are listed in
  `CHROME_FREE_PATHS` in the same file.
- **`export const dynamic = 'force-dynamic'`** on dashboard routes/layouts — the
  dashboard reads per-request session + DB state.
- **No new CSS files and no inline `<style>`.** Style with Tailwind utilities
  bound to the tokens in `globals.css`.
- **`PAGE_SIZE`** (from `support.ts`) stays the single page size for paginated
  data queries — import it, never hard-code a second one. The Spectrum table's
  `pageSize` prop controls rows per page in the client.

## Recipes

- **Add a new page** — create `apps/web/src/app/dashboard/<name>/page.tsx` (an
  async server component with `export const dynamic = 'force-dynamic'`), compose
  it from the new identity (Spectrum `DataTable` for lists, `StatusPill` for
  status), add the nav entry in `apps/web/src/lib/nav.ts`, and add a co-located
  test in `apps/web/src/test/dashboard/`.
- **Filterable listing** — a `<DataTable>` with `searchable`, `quickFilter`
  (pills keyed on the status column), `selectable` + `bulkActions`, `rowActions`
  for the `⋯` menu, and `pageSize`. Copy `sessions-table.tsx` verbatim, then swap
  the columns.
- **Destructive action** — server action (`guardAction` + `verifyCsrf`) inside an
  `<ActionForm>`, `<CsrfField>`, and a `<ConfirmSubmit tone="warn|danger"
title=… description=… confirmLabel=…>` wrapping a `type="button"` trigger.
- **Row navigation** — the `⋯` menu carries "View details" as a `next/link`; there
  is no `data-href`/`ROW_LINK` script anymore.
- **Chrome-free screen** — if a route must render without the command bar, add it
  to `CHROME_FREE_PATHS` in `lib/nav.ts`; `CommandFrame` short-circuits on
  `isChromeFreePath()`.

## Quick reference: helper signatures

```ts
// spectrumui/data-table.tsx (canonical for list views)
DataTable<T>({
  data, columns, rowId, rowLabel,
  variant?: 'default'|'bordered'|'striped'|'minimal'|'panel', density,
  searchable?, searchPlaceholder?, searchText?,
  quickFilter?: { columnId, label?, getValue?, options?, allLabel? },
  selectable?, bulkActions?: (ctx) => ReactNode,
  renderDetail?: (row) => ReactNode, rowActions?: (row, actions) => ReactNode,
  onDelete?, pageSize?, emptyState?, totals?, stickyHeader?, …
})
DataTableColumn<T>({ id, header, cell?, value?, sortable?, numeric?, hideBelow?, … })

// ui.tsx — LIVE
StatusPill({ status })   Time({ value, className? })   LABEL

// ui.tsx — LEGACY (unrestyled pages only), see Legacy primitives above
ViewHead({ num?, title, hl?, meta?, titleVisible? })   BackLink({ href, label })
SectionBar({ name, meta?, more? })   StatCard / StatGrid   Kv / KvGrid
Panel / PanelHead   DataTable / DataHead / DataTh / DataBody / DataTr / DataTd
TableEmpty   Pill / ReviewPill / Chip / Tag / Bar / Notice / Flash

// filters.tsx — LEGACY
FilterForm({ action, children, className? })   FilterField / FilterInput / FilterSelect
FilterActions({ clearHref })   Pager({ page, hasMore, total, totalLabel, path, query })

// action-form.tsx
ActionForm({ action, children, className? })   // action: FormAction
useActionFormId()   ActionState = { error: string | null }
```

## Files you will most likely touch

```text
apps/web/src/app/dashboard/<view>/page.tsx        server component route
apps/web/src/app/dashboard/layout.tsx             CommandFrame wrapper
apps/web/src/components/dashboard/<view>-table.tsx  Spectrum data-table (copy sessions-table.tsx)
apps/web/src/components/dashboard/command-bar.tsx shared shell / nav (floating, no sidebar)
apps/web/src/components/dashboard/ui.tsx          StatusPill (live) + legacy primitives
apps/web/src/components/dashboard/confirm-submit.tsx / action-form.tsx / csrf-field.tsx
apps/web/src/lib/nav.ts                           NAV array + CHROME_FREE_PATHS
apps/web/src/app/globals.css                      design tokens (Tailwind v4 theme)
apps/web/src/app/layout.tsx                       Geist / Geist Mono localFont wiring
apps/web/src/test/dashboard/<view>.test.tsx       co-located page test
```

## Build, test, validate

After any change:

```bash
pnpm run typecheck   # tsc --noEmit, must be clean
pnpm run lint        # ESLint, must be clean
pnpm test            # turbo test across workspaces, must stay green
pnpm run build       # next build
```

Dashboard component tests are Vitest + Testing Library under
`apps/web/src/test/dashboard/*.test.tsx`; run one file with
`pnpm --filter @rembric/web exec vitest run src/test/dashboard/<name>.test.tsx`.

For visual verification, bring up the host dev server (see `docs/docker.md` →
"Local dev (host)"):

```bash
REMBRIC_DATA_DIR=./data-dev REMBRIC_ADMIN_TOKEN=<16+-char-token> pnpm run dev
# → http://127.0.0.1:3000/dashboard
```

`next dev` hot-reloads, so there is no build-and-restart step. Point a fresh
`REMBRIC_DATA_DIR` at a scratch directory; never aim a dev server at a real
deployment's database.

## When to ask vs proceed

Proceed without asking when the change is mechanical and matches an existing
pattern (e.g. new column on a data-table, new `StatusPill` status mapping, new
destructive row action, new responsive tweak).

Ask the user first when:

- The change introduces a new colour, font face, or radius (a token change). The
  new identity is not yet locked by an OpenSpec spec — the formal `dashboard`
  spec update is pending on `feat/dashboard-identity-redesign` — so token edits
  need an agreed OpenSpec change rather than a drive-by.
- The change adds a new top-level nav item (affects the `NAV` array / command-bar
  order and the mobile sheet).
- The change touches behaviour: pagination model, modal flow, mobile sheet
  behaviour.
- The change would add a new runtime dependency or client-side framework.
- The user's request is ambiguous about destructive vs. reversible tone (`warn` vs
  `danger` is a semantic decision).

## Validation checklist before "done"

1. `pnpm run typecheck` → clean
2. `pnpm run lint` → clean
3. `pnpm test` → all green
4. `pnpm run build` → succeeds
5. Visual smoke at the affected page in the host dev server, at desktop +
   narrow viewports
6. If a destructive action was added: the dialog opens with the correct tone,
   Cancel doesn't submit, Confirm does

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…