# Personal taste core — serafim

> Transferable invariants confirmed by edit requests in ≥2 projects (21st-private-1, eval-hub-app). Read together with the project taste.md; on conflict the project wins, except antipatterns — those are never overridden.

Taste axis: classical (order, density, restraint) — 14/15 core rules on the classical axis.

## Antipatterns (permanent)
- Hide actions/buttons when the action is impossible in the current context
- Remove decorative or unnecessary icons
- Don't show empty, null or placeholder content — hide the element when there is no data _(confidence: medium)_
- Remove redundant labels/headers when meaning is obvious from context _(confidence: medium)_

## Craft norms (violation = bug)
- Match the rest of the app's established style and design system for any component
- Keep elements properly aligned; crooked/misaligned layout reads as a bug
- Prevent layout shifts; skeleton loaders must match the final rendered state
- Match the design mock exactly _(confidence: medium)_
- Keep text and element sizing consistent across the interface _(confidence: medium)_
- Group and structure content for fast readability instead of a wall of text _(confidence: low)_

## Personal preferences
- Default to a meaningful view/content rather than an empty or 'all' state _(confidence: medium)_
- Use muted text for body content and highlight only meaningful elements like links _(confidence: medium)_
- Place confirm buttons and limit values inside the input field on the right _(confidence: medium)_

## Meta-preferences
- Reuse existing components and patterns instead of creating new ones
- Animate state changes and transitions with smooth morphing, consistent with the rest of the app


---

# taste.md — eval-hub-app (v7)

> Project style profile. Read TOGETHER with the personal core `~/.taste/core.md`: the core holds the person's invariants, this file holds this repo's style. Conflicts resolve in favor of the project, except core antipatterns.

<!-- taste:auto:start -->
## Style identity
Build a quiet, Notion-like surface: near-neutral backgrounds with `rgba(55,53,47,…)` hairline borders and soft `shadow-sm`; reserve deep shadows (`shadow-cmd`) for floating command/popover layers only. Keep text small and dense — default to 11–13px, lead with muted foreground (`--fg-muted`) and let only links or active states carry full contrast. Favor `rounded-md` (6px) corners and compact `h-7` controls with `px-3`/`py-2` rhythm; tokens drive light/dark, so never hardcode color. Animate with restraint — `transition-colors`/`transition-opacity` at `duration-200 ease-out`, no flashy motion. Overall character: tight, orderly, text-forward, and unobtrusive.

## Directives — owner's words, source of truth
These override code statistics. Code adoption may lag; stats below only fact-check.
- **Ensure all inputs have a focus ring, except the cmd+k input** — owner, 2026-05-28: «у всех инпутов проверь что есть ринги кроме кмд к…» _(scope: global)_
- **In charts/graphs, use the same settings (customization) icon as the one used in the table — keep it identical across both** — owner, 2026-05-29: «иконку настроек сделатй одинаковую вот такюу и там и там - чтобы в чартах была как в таблице…» _(scope: zone; restated 3×)_
- **Use tooltips in our style with the morph animation, matching the tooltips on the evals page / tables and lists** — owner, 2026-05-29: «добавь всем тултипы с морфом как в таблице/списках…» _(scope: zone; restated 2×; pins evals page / tables tooltips)_
- **Style cmd+k / search blocks to match the search block in the header** — owner, 2026-05-31: «чтобы блок про cmd k выглядел как в шапке в блоке поиска…» _(scope: zone; restated 2×)_

## Tokens (from code)
- src/app/globals.css: `--fg-muted: rgb(125, 124, 120)`, `--border: rgba(55, 53, 47, 0.09)`, `--border-strong: rgba(55, 53, 47, 0.16)`, `--shadow-sm: 0 1px 2px rgba(15, 15, 15, 0.04)`, `--shadow-cmd: 0 24px 80px rgba(15, 15, 15, 0.20)`, `--radius-sm: 3px`, `--radius-md: 6px`, `--radius-lg: 10px`, `--background: var(--bg)`, `--foreground: var(--fg)`, `--card-foreground: var(--fg)`, `--popover-foreground: var(--fg)`, `--primary: var(--fg)`, `--primary-foreground: var(--fg-on-fill)`, `--secondary-foreground: var(--fg)`, `--muted: var(--bg-soft)`, `--muted-foreground: var(--fg-muted)`, `--accent: var(--bg-hover)`

## Grammar (utility frequencies from code)
- Typography: `text-[11px]`×78, `text-[12px]`×62, `text-[13px]`×44, `text-[10px]`×22
- Radii: `rounded-md`×110, `rounded-full`×18, `rounded-lg`×14, `rounded-[4px]`×10
- Control heights: `h-7`×34, `h-4`×15, `h-5`×9, `h-3`×9
- Gaps/paddings: `px-3`×72, `py-2`×64, `gap-1`×49, `px-2`×47, `gap-2`×47
- Animation: `transition-colors`×66, `duration-200`×16, `ease-out`×15, `transition-opacity`×13

## Key entities

### page-header (`src/components/page-header.tsx`, imports: 27)
**PageHeader** — Notion-style header for list/workspace pages; no border-bottom. **Layout**: Root `<header>` containing a `flex flex-wrap items-start justify-between gap-3` row. Left side `min-w-0 flex-1` holds the title block; right side renders `actions` (when present) in a `flex shrink-0 gap-2` c
- user rule: Use the app's brand colors, logo and typography
- full spec: `.taste/facts/page-header.md`

### pill (`src/components/pill.tsx`, imports: 34)
**Appearance**: Notion-style pill/chip primitive — 20px height, square-ish corners (3px radius), pastel background with same-hue ink text. Base class `chip`; small variant adds `chip-sm`. **Colors**: 9 named colors mapped via `COLOR_CLASS` to CSS classes (`chip-gray`, `chip-brown`, `chip-orange`, `c
- full spec: `.taste/facts/pill.md`

### section (`src/components/section.tsx`, imports: 34)
**Section** (Notion-style content section): Renders a `<section className="space-y-3">` containing a header block and `children`. Header uses `flex items-baseline justify-between gap-3`. Left side (`flex min-w-0 items-baseline gap-2`) holds the title `<h2>` styled `shrink-0 whitespace-nowrap text-[1
- full spec: `.taste/facts/section.md`

### tooltip (`src/components/ui/tooltip.tsx`, imports: 17)
**Sub-components**: Built on Base UI Tooltip primitives — exports `TooltipProvider` (wraps `Provider`, default `delay={300}`), `Tooltip` (`Root`), `TooltipTrigger` (`Trigger`), `TooltipContent` (`Portal` > `Positioner` > `Popup`), and `IconTooltip` convenience wrapper. Each carries `data-slot` attrs
- user rule: Add a morphing animation to the tooltips
- owner pattern (18 msgs): When the owner mentions tooltips, he usually wants consistent, on-brand tooltips added to every icon-only control, using the established morphing style (like the evals page / tables / lists), and old/redundant native hints removed or replaced.
- full ask-pattern: `.taste/topics/tooltips.md`
- full spec: `.taste/facts/tooltip.md`

### sort-control (`src/components/sort-control.tsx`, imports: 15)
**SortControl** — A Notion-style multi-rule sort popover (table-agnostic). Renders an `ArrowUpDown` (size-4) icon trigger button (`TRIGGER_CLS`: 7×7 relative inline-flex, rounded-md, muted fg, hover bg `--bg-hover`, `data-[popup-open]` active state); when `isCustom` the trigger fg becomes `--fg`. Tr
- user rule: Remove decorative icons from the sort menu
- user rule: Use readable visual indicators of sort direction (ascending/descending) instead of abstract icons
- full spec: `.taste/facts/sort-control.md`

### filter-bar (`src/components/filter-bar.tsx`, imports: 9)
**FilterBar** — Linear/Notion-style client-side toolbar (`"use client"`); filters/sorts in-memory via History API (`shallowPush` using `window.history.pushState`, no server round-trip). Wraps content in `FilterMenuContext.Provider` with a single-open menu coordinator (`openMenuId`); root `<div class
- user rule: Add a sort icon on the left when the filter is open
- owner pattern (40 msgs): When the owner talks about filters he wants a unified, reusable filter UI matching the Notion/eval-table style: filter buttons live in the right header row next to search/sort, selected filters render as chips/blocks below, with proper add-filter logic, saved…
- full ask-pattern: `.taste/topics/filters.md`
- full spec: `.taste/facts/filter-bar.md`

### entity-card (`src/components/entity-card.tsx`, imports: 14)
**EntityCard** — Notion-style gallery card; sub-components: `Pill`, `WatchStar`, `CompareToggle`. **Container**: `div.group.relative.flex.h-full.min-w-0.flex-col.rounded-md` with thin border `border-[color:var(--border)]`, background `bg-[color:var(--bg)]`, no shadow; on hover border strengthens to 
- owner pattern (15 msgs): When the owner talks about cards, he usually wants them tailored per entity with only meaningful data shown, compact/clean layout matching the feed style, and proper visual structure (no overflow, no clutter, hide empty cards).
- full ask-pattern: `.taste/topics/cards.md`
- full spec: `.taste/facts/entity-card.md`

### tabs (`src/components/ui/tabs.tsx`, imports: 6)
**Tabs** — Composable tabs built on Base UI (`@base-ui/react/tabs`). Exports: `Tabs`, `TabsList`, `TabsIndicator`, `TabsTrigger`, `TabsContent`, plus `tabsListVariants`. Supports `horizontal` (default) and `vertical` orientation via `data-orientation` and `group-data-horizontal/vertical` selectors. 
- variants: variant(default|line), defaultVariants(variant)
- user rule: Place the tabs in a single visual block with the rest of the content
- owner pattern (60 msgs): When the owner talks about tabs, he almost always wants ONE shared tab/table component reused everywhere with consistent styling, an animated sliding underlay on the selected tab, and counts shown in the tab label. He also keeps separating page content into…
- full ask-pattern: `.taste/topics/tabs.md`
- full spec: `.taste/facts/tabs.md`

### tab-strip (`src/components/tab-strip.tsx`, imports: 5)
**TabStrip** — shared tab strip used for panel-switching (detail pages) and URL-driven nav (list filter bars). Must be rendered inside a `<Tabs>` (TabsPrimitive.Root) owned by the host, which controls `value`/`defaultValue` and surrounding scroll/border chrome. Takes `items: TabStripItem[]`. **Conta
- user rule: Display the mention count in the tab in the same style as numbers in other tabs
- owner pattern (60 msgs): When the owner talks about tabs, he almost always wants ONE shared tab/table component reused everywhere with consistent styling, an animated sliding underlay on the selected tab, and counts shown in the tab label. He also keeps separating page content into…
- full ask-pattern: `.taste/topics/tabs.md`
- full spec: `.taste/facts/tab-strip.md`

### list-search-input (`src/components/list-search-input.tsx`, imports: 3)
**ListSearchInput** — a debounced (300ms) URL-synced search box that writes its trimmed value to a query param (`paramKey`, default `q`) via `window.history.replaceState` (shallow update, no navigation/scroll reset), syncing `value` state back when the URL param changes externally. **Expanded input 
- user rule: Make text and element sizes in the articles list identical between the two states for the first 10 entries in a row
- owner pattern (42 msgs): When the owner talks about search he usually wants a single unified cmd+k powered search (plus a dedicated mobile dialog) rather than scattered per-list search boxes, with precise layout placement (search icon on the right near sort/filter, expanding left…
- full ask-pattern: `.taste/topics/search.md`
- full spec: `.taste/facts/list-search-input.md`

### morph-tooltip (`src/components/ui/morph-tooltip.tsx`, imports: 11)
**MorphTooltipGroup**: wraps a control row (renders a `<div>` with the passed `className`, e.g. flex layout) and portals ONE shared tooltip pill (`.morph-tip`) to `document.body` that morphs/slides to whichever control the pointer or keyboard focus is on. Provides `MorphGroupContext` (boolean, defau
- full spec: `.taste/facts/morph-tooltip.md`

### load-more (`src/components/load-more.tsx`, imports: 10)
**Purpose**: Infinite-scroll sentinel element; parent attaches an `IntersectionObserver` to the forwarded `ref` and flips `hasMore` off when all items are shown. **Props**: `ref: Ref<HTMLDivElement>` (observer target), `hasMore: boolean` (visibility flag).
- full spec: `.taste/facts/load-more.md`

### property-table (`src/components/property-table.tsx`, imports: 10)
**PropertyTable**: A Notion-style key/value block rendered as a `<dl>` with classes `grid grid-cols-[140px_1fr] gap-x-4 gap-y-1.5 text-[13px]`, plus optional `className` merged via `cn`. Accepts `rows: PropertyRow[]` and `className?`. **Empty-state behavior**: Filters out rows whose `value` is `null
- full spec: `.taste/facts/property-table.md`

### entity-glyph (`src/components/entity-glyph.tsx`, imports: 9)
**Purpose & exports**: Renders an icon (`<Icon>`) representing one of 8 `EntityKind` values: `eval`, `tool`, `model`, `organization`, `person`, `paper`, `leaderboard`, `capability`. Also exports `entityKindLabel(kind)` helper returning capitalized labels (e.g. `"eval"` → `"Eval"`, `"organization"` →
- full spec: `.taste/facts/entity-glyph.md`

### entity-row (`src/components/entity-row.tsx`, imports: 9)
**Layout**: A `group relative` flex row (`flex items-center gap-2`) with horizontal padding `px-2`, a bottom hairline divider (`border-b border-[color:var(--divider)]`), and `hover:bg-[color:var(--bg-hover)]` with `transition-colors`. Effective height ~36px (link padded `py-2`). **Main link**: A Nex
- full spec: `.taste/facts/entity-row.md`

### open-external (`src/components/icons/open-external.tsx`, imports: 9)
**OpenExternalIcon**: Renders an inline SVG depicting an "open in new window/external link" glyph — a box (rounded-corner rectangle outline) with an arrow pointing out to the upper-right corner. **SVG attributes**: `xmlns="http://www.w3.org/2000/svg"`, `viewBox="0 0 24 24"`, `fill="none"`, `stroke="
- full spec: `.taste/facts/open-external.md`

### rail-url (`src/components/rail-url.tsx`, imports: 8)
**Renders** an external anchor (`<a>`) with `href` set to the passed URL, `target="_blank"`, `rel="noopener noreferrer"`, and a `title` showing the prettified URL. **Layout/classes**: `inline-flex min-w-0 max-w-full items-center gap-1 text-[rgb(35,131,226)] underline-offset-2 hover:underline` — a bl
- full spec: `.taste/facts/rail-url.md`

### simple-markdown (`src/components/simple-markdown.tsx`, imports: 8)
**SimpleMarkdown** — Renders Markdown for trusted vault bodies and untrusted hosted content (READMEs, model cards). Pipeline: `remark-gfm` (tables, task lists, strikethrough, autolinks) → `rehype-raw` (parses inline HTML) → `rehype-sanitize` (must run after raw; strips scripts/handlers/unsafe attrs)
- full spec: `.taste/facts/simple-markdown.md`

### icons (`src/components/icons.tsx`, imports: 6)
**Module purpose**: Exports custom SVG icon components for `eval-hub-app`, filling gaps left by lucide-react (brand glyphs + custom content-type glyphs), all using `fill="currentColor"` or `stroke="currentColor"` to inherit text color, with `aria-hidden` set. **Chevron glyphs** (`DoubleChevronRight`
- owner pattern (60 msgs): When the owner talks about icons he usually wants a specific icon swapped or standardized across the whole app, and wants icon-only buttons (no labels) placed in a precise position with consistent behavior. He often dictates the exact SVG to use.
- full ask-pattern: `.taste/topics/icons.md`
- full spec: `.taste/facts/icons.md`

### score-chart-panel (`src/components/score-chart-panel.tsx`, imports: 6)
**ScoreChartPanel** — a chart/table toggle panel previewing a ranked leaderboard (bars sorted desc by value), measuring its own column width to auto-fit bars without horizontal scroll. **Layout & chart geometry:** Renders an `EvalChart` of provider bars whose width/gap are computed from the measured
- owner pattern (21 msgs): When the owner talks about charts he wants model-comparison/eval data shown as charts by default (in his own style), reusable across models/leaderboards/evals pages, with table↔chart toggles and interactive bars.
- full ask-pattern: `.taste/topics/charts.md`
- full spec: `.taste/facts/score-chart-panel.md`

### dropdown-menu (`src/components/ui/dropdown-menu.tsx`, imports: 5)
**Structure & sub-components**: Composed of `DropdownMenu` (root), `DropdownMenuTrigger`, `DropdownMenuPortal`, `DropdownMenuContent`, `DropdownMenuGroup`, `DropdownMenuLabel`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuSeparator`,
- owner pattern (22 msgs): When the owner mentions menus he usually wants polished, keyboard-accessible dropdowns/command menus (cmd+k) that behave like real native menus — with search+autofocus inputs, proper morph/spring animations, and correct click/positioning behavior. He…
- full ask-pattern: `.taste/topics/menus.md`
- full spec: `.taste/facts/dropdown-menu.md`

## How the owner usually asks (cross-cutting)
- **tables** (48 msgs): When the owner talks about tables, he wants them unified into one consistent Notion-style component — same fullscreen toggle, settings/sort icons, search and filters behavior everywhere. He repeatedly asks for fullscreen → `.taste/topics/tables.md`
- **sidebar** (24 msgs): When the owner talks about the sidebar he usually wants a collapsible left sidebar that opens on hover, doesn't shift or jitter the layout, behaves smartly per context (mobile, landing, page list state), and has the righ → `.taste/topics/sidebar.md`
- **buttons** (60 msgs): When the owner talks about buttons he's almost always dictating exact placement, icon style, and behavior — he wants icon-only buttons consistent with existing table/list patterns, placed precisely (left/right/corner), w → `.taste/topics/buttons.md`
- **loading** (11 msgs): When the owner talks about loading, he wants skeleton/shimmer placeholders that exactly match the final rendered layout — same dimensions, aspect ratio, sidebar, and reserved container space — so nothing jumps or reflows → `.taste/topics/loading.md`
- **layout** (42 msgs): When the owner talks about layout/header (шапка), he usually wants a consistent, fixed (non-scrolling) header that looks identical across all pages, with correct symmetric paddings and properly placed action icons. He ca → `.taste/topics/layout.md`
- **animation** (52 msgs): When the owner mentions animation, he almost always wants smooth motion/morph transitions that match the existing patterns already used elsewhere (table, tabs, tooltips). He cares heavily about consistency, speed, and el → `.taste/topics/animation.md`
- **typography** (6 msgs): When the owner mentions typography, he usually wants visual consistency (same font/size/height across related elements) and clean, legible text. He flags mismatched fonts and wants emphasis (bold) applied meaningfully or → `.taste/topics/typography.md`

## Local antipatterns
- Unify number format in tabs — remove brackets and dots, keep only muted text on the right
- Don't use uppercase for regular text, write in normal case
- Remove the text heading if its meaning is obvious from context

## Exemplar pages
- /organizations/anthropic (5×)
- /capabilities (2×)
- /leaderboards/artificial-analysis-intelligence (2×)
- /evals/prime-kishanpb-physgym-arena-drhard-public (2×)
- /collections (2×)
- /models/grok-4 (2×)
- /evals (2×)
<!-- taste:auto:end -->
