# Files changed · PR \#136 · flambo/atlantis

[View on GitCafe](https://git.cafe/flambo/atlantis/pull/136/diffs)

Repository: [flambo/atlantis](https://git.cafe/flambo/atlantis)

Visibility: public

[feat\(web\): console-style redesign on shadcn-svelte components](https://git.cafe/flambo/atlantis/pull/136.md)

Version: 0

Head commit: 3962184f3f88f51fd5654a3c1519709b1d6e8672

Base commit: 1fe1fe79d88f28c0bc5b82c1a6c57f59beb2283d

Comparison base: 1fe1fe79d88f28c0bc5b82c1a6c57f59beb2283d

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/package.json

Status: modified; +2; −2

```diff
@@ -34,7 +34,7 @@
 		"@types/better-sqlite3": "^7.6.13",
 		"@types/d3-scale": "4.0.9",
 		"@types/node": "^22.15.17",
-		"bits-ui": "^2.16.3",
+		"bits-ui": "^2.18.0",
 		"clsx": "^2.1.1",
 		"eslint": "^9.18.0",
 		"eslint-config-prettier": "^10.0.1",
@@ -56,7 +56,7 @@
 		"vitest": "^5.0.1"
 	},
 	"dependencies": {
-		"@lucide/svelte": "^1.33.0",
+		"@lucide/svelte": "^1.54.0",
 		"@tanstack/charts": "1.0.0",
 		"better-sqlite3": "^12.6.2",
 		"d3-scale": "4.0.2",
```

## apps/web/src/app.css

Status: modified; +80; −483

```diff
@@ -5,8 +5,13 @@
 @custom-variant dark (&:where(.dark, .dark *));
 
 @theme {
+	--font-sans:
+		Inter, 'Inter Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
+	--font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, monospace;
+	--color-selection: var(--selection);
+	--color-selection-foreground: var(--selection-foreground);
-	--font-body: ui-sans-serif, system-ui, sans-serif;
-	--font-sans: ui-sans-serif, system-ui, sans-serif;
+	--color-success: var(--success);
+	--color-warning: var(--warning);
 	--color-sidebar-ring: var(--sidebar-ring);
 	--color-sidebar-border: var(--sidebar-border);
 	--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -48,44 +53,35 @@
 }
 
 :root {
-	--neutral-0: #ffffff;
-	--neutral-50: #f5f7fa;
-	--neutral-100: #edf1f6;
-	--neutral-200: #dce3ec;
-	--neutral-300: #bac6d5;
-	--neutral-500: #627086;
-	--neutral-700: #334155;
-	--neutral-900: #182233;
-	--background: var(--neutral-50);
+	--background: #fafafa;
+	--foreground: #0a0a0a;
+	--card: #ffffff;
-	--foreground: var(--neutral-900);
-	--card: var(--neutral-0);
 	--card-foreground: var(--foreground);
-	--popover: var(--card);
+	--popover: #ffffff;
 	--popover-foreground: var(--foreground);
 	--primary: #2563eb;
 	--primary-foreground: #ffffff;
+	--secondary: #f5f5f5;
+	--secondary-foreground: #171717;
+	--muted: #f5f5f5;
+	--muted-foreground: #666666;
+	--accent: #f2f2f2;
+	--accent-foreground: #0a0a0a;
+	--selection: #eff6ff;
+	--selection-foreground: #1d4ed8;
+	--destructive: #dc2626;
+	--success: #15803d;
+	--warning: #a16207;
-	--secondary: var(--neutral-100);
-	--secondary-foreground: var(--neutral-700);
-	--muted: var(--neutral-100);
-	--muted-foreground: #56657a;
-	--accent: #eaf0ff;
-	--accent-foreground: #1d4ed8;
-	--destructive: #bd303d;
-	--success: #087f6a;
-	--warning: #956500;
 	--info: #2563eb;
+	--border: #e5e5e5;
+	--input: #dedede;
+	--ring: #2563eb;
+	--radius: 0.5rem;
+	--shadow-card: 0 1px 2px #0000000a;
+	--elevation-popover: 0 8px 30px -8px #00000029;
+	--chart-text-color: #737373;
+	--chart-grid-color: #ebebeb;
+	--chart-grid-highlight-color: #f5f5f5;
-	--border: var(--neutral-200);
-	--input: var(--neutral-300);
-	--ring: var(--primary);
-	--radius: 0.375rem;
-	--space-panel: 1rem;
-	--type-label: 0.75rem;
-	--type-body: 0.875rem;
-	--type-title: 1.5rem;
-	--elevation-popover: 0 6px 24px #18223318;
-	--chart-text-color: #56657a;
-	--chart-grid-color: #dce3ec;
-	--chart-grid-highlight-color: #edf1f6;
 	--chart-tooltip-bg: var(--popover);
 	--chart-tooltip-text-color: var(--foreground);
 	--chart-tooltip-border-color: var(--border);
@@ -117,24 +113,33 @@
 	--sidebar-ring: var(--ring);
 }
 .dark {
+	--background: #0a0a0a;
+	--foreground: #ededed;
+	--card: #111111;
+	--popover: #171717;
+	--primary: #2563eb;
+	--primary-foreground: #ffffff;
+	--secondary: #1f1f1f;
+	--secondary-foreground: #ededed;
+	--muted: #1c1c1c;
+	--muted-foreground: #a1a1a1;
+	--accent: #232323;
+	--accent-foreground: #ededed;
+	--selection: #172554;
+	--selection-foreground: #93c5fd;
+	--destructive: #f87171;
+	--success: #4ade80;
+	--warning: #facc15;
+	--info: #60a5fa;
+	--border: #262626;
+	--input: #333333;
+	--ring: #3b82f6;
+	--shadow-card: 0 1px 2px #00000066;
+	--elevation-popover: 0 8px 30px -8px #000000b3;
-	--neutral-0: #171f2c;
-	--neutral-50: #101722;
-	--neutral-100: #202b3b;
-	--neutral-200: #303d50;
-	--neutral-300: #45556c;
-	--neutral-500: #a3b2c7;
-	--neutral-700: #cfdae9;
-	--neutral-900: #eaf0f8;
-	--muted-foreground: #a3b2c7;
-	--accent: #203359;
-	--accent-foreground: #a8c5ff;
-	--destructive: #f18d96;
-	--success: #62c6b1;
-	--warning: #e8b951;
 	--chart-spectrum-lightness: 60;
-	--chart-text-color: #a3b2c7;
-	--chart-grid-color: #303d50;
-	--chart-grid-highlight-color: #202b3b;
+	--chart-text-color: #8f8f8f;
+	--chart-grid-color: #262626;
+	--chart-grid-highlight-color: #1c1c1c;
 	--chart-series-1: #689bff;
 	--chart-series-2: #e5b44c;
 	--chart-series-3: #56c8bb;
@@ -144,7 +149,6 @@
 	--chart-series-7: #b3a0e3;
 	--chart-series-8: #b4bdc9;
 	--chart-series-other: #8a9aaf;
-	--elevation-popover: 0 6px 24px #00000040;
 	color-scheme: dark;
 }
 
@@ -174,53 +178,30 @@
 	}
 }
 
+.shell {
-.page-layout,
-.shell-width {
 	width: 100%;
 	max-width: 1920px;
 	margin-inline: auto;
-	padding-inline: clamp(1rem, 2.5vw, 3rem);
+	padding-inline: clamp(1rem, 2.5vw, 2rem);
 }
+.page {
+	padding-block: 1.25rem 2rem;
-.page-layout {
-	padding-block: 1.5rem;
 }
-.page-heading {
-	font-size: var(--type-title);
-	font-weight: 600;
-	letter-spacing: -0.035em;
-	line-height: 1.3;
-}
-.page-eyebrow {
-	color: var(--muted-foreground);
-	font-size: var(--type-label);
-	margin-bottom: 0.375rem;
-}
 body {
-	font-size: var(--type-body);
+	font-size: 0.875rem;
 	font-variant-numeric: tabular-nums;
+	-webkit-font-smoothing: antialiased;
 }
-:focus-visible {
+:focus-visible:not([data-slot]) {
 	outline: 2px solid var(--ring);
-	outline-offset: 3px;
+	outline-offset: 2px;
 }
 ::selection {
-	background: var(--accent);
-	color: var(--accent-foreground);
+	background: var(--selection);
+	color: var(--selection-foreground);
 }
 [data-slot='card'] {
-	box-shadow: none;
-}
-[data-slot='card-title'] {
-	line-height: 1.4;
-}
-[data-drag-handle] {
-	padding-block: 0.875rem;
-}
-[data-drag-handle] h2 {
-	font-size: 0.875rem;
+	box-shadow: var(--shadow-card);
-	font-weight: 600;
-	letter-spacing: -0.01em;
-	padding-right: 5rem;
 }
 .chart-frame {
 	background: var(--card);
@@ -239,14 +220,16 @@
 .ts-chart-host {
 	font-variant-numeric: tabular-nums;
 }
+.ts-chart__interactive-legend {
+	pointer-events: none !important;
+}
 .ts-chart__interactive-legend button {
+	pointer-events: auto !important;
 	border: 0 !important;
-	border-radius: 3px !important;
+	border-radius: 4px !important;
 	background: transparent !important;
 	color: var(--chart-text-color) !important;
+	font: 500 12px/1.25 var(--font-sans) !important;
-	font:
-		500 12px/1.25 system-ui,
-		sans-serif !important;
 }
 .ts-chart__interactive-legend button:hover {
 	background: var(--muted) !important;
@@ -258,24 +241,6 @@
 table {
 	font-variant-numeric: tabular-nums;
 }
-th {
-	color: var(--muted-foreground);
-	font-size: var(--type-label);
-	font-weight: 500;
-}
-@media (max-width: 639px) {
-	.page-layout {
-		padding-block: 1rem;
-	}
-	.chart-frame.traffic-frame {
-		--chart-height: 380px;
-	}
-	.file-filters > div {
-		width: 100%;
-		flex-direction: column;
-		align-items: stretch;
-	}
-}
 @media (prefers-reduced-motion: reduce) {
 	*,
 	*::before,
@@ -284,380 +249,12 @@
 		transition-duration: 0.01ms !important;
 	}
 }
-
-.ts-chart__interactive-legend {
-	pointer-events: none !important;
+@media (pointer: coarse) {
+	.toolbar-trigger,
+	[data-card-menu-trigger] {
-}
+		min-height: 2.75rem;
-.ts-chart__interactive-legend button {
-	pointer-events: auto !important;
-}
-
-.app-topbar .shell-width {
-	padding-inline: clamp(0.75rem, 1.5vw, 1.5rem);
-}
-.console-page {
-	width: 100%;
-	max-width: 1920px;
-	margin-inline: auto;
-	padding: 1rem clamp(0.75rem, 1.5vw, 1.5rem) 1.5rem;
-}
-.console-heading {
-	display: flex;
-	align-items: end;
-	justify-content: space-between;
-	gap: 1.5rem;
-	margin-bottom: 1rem;
-}
-.console-heading .page-heading {
-	font-size: 1.25rem;
-}
-.console-heading .dataset-tabs {
-	border-bottom: 0;
-}
-.console-body {
-	display: grid;
-	grid-template-columns: 12.5rem minmax(0, 1fr);
-	gap: 1rem;
-	align-items: start;
-}
-.console-body.rail-collapsed {
-	grid-template-columns: minmax(0, 1fr);
-}
-.console-rail {
-	position: sticky;
-	top: 1rem;
-	min-width: 0;
-	max-height: calc(100dvh - 7rem);
-	overflow-y: auto;
-	padding: 0.75rem;
-	background: var(--card);
-	border: 1px solid var(--border);
-	border-top: 2px solid var(--primary);
-}
-.console-toolbar {
-	position: sticky;
-	top: 0;
-	z-index: 20;
-	display: flex;
-	flex-wrap: wrap;
-	align-items: center;
-	gap: 0.5rem;
-	margin-bottom: 1rem;
-	padding: 0.75rem;
-	background: var(--card);
-	border: 1px solid var(--border);
-}
-.console-toolbar .date-range-filter {
-	flex: 1 1 28rem;
-}
-.console-toolbar .rail-toggle,
-.console-toolbar .reset-view,
-.console-toolbar input[type='date'] {
-	height: 2.25rem;
-}
-.console-toolbar .date-range-filter {
-	align-items: center;
-	grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
-	gap: 0.5rem;
-}
-.console-toolbar .date-range-filter > div {
-	display: flex;
-	align-items: center;
-	gap: 0.5rem;
-}
-.console-toolbar .date-range-filter label {
-	margin: 0;
-	flex-shrink: 0;
-}
-.console-toolbar .date-range-filter > span {
-	padding: 0;
-}
-.filter-summary {
-	display: flex;
-	flex-wrap: wrap;
-	gap: 0.375rem;
-	flex-basis: 100%;
-	order: 4;
-	padding-top: 0.5rem;
-	border-top: 1px solid var(--border);
-	font-size: 0.6875rem;
-	color: var(--muted-foreground);
-}
-.filter-summary span {
-	white-space: nowrap;
-	padding: 0.125rem 0.5rem;
-	border: 1px solid var(--border);
-	border-radius: 0.25rem;
-	background: var(--muted);
-}
-.rail-heading {
-	font-size: 0.75rem;
-	font-weight: 600;
-	margin-bottom: 0.75rem;
-}
-.rail-section {
-	padding-block: 1rem;
-	border-bottom: 1px solid var(--border);
-}
-.rail-section:first-child {
-	padding-top: 0;
-}
-.primary-filters .router-filter {
-	display: block;
-}
-.primary-filters .router-filter > span {
-	display: block;
-	font-size: 0.75rem;
-	color: var(--muted-foreground);
-	margin-bottom: 0.5rem;
-}
-.primary-filters .router-filter > div {
-	flex-direction: column;
-	align-items: stretch;
-	gap: 0.25rem;
-}
-.primary-filters .router-filter label span {
-	overflow-wrap: anywhere;
-	min-width: 0;
-	font-size: 0.8125rem;
-}
-.primary-filters [role='group']:not([aria-label='MAAD options']) {
-	width: 100%;
-}
-.primary-filters [aria-label='Traffic direction'],
-.primary-filters [aria-label='Granularity'],
-.primary-filters [aria-label='MAAD measure'] {
-	grid-template-columns: repeat(2, minmax(0, 1fr));
-}
-.primary-filters [aria-label='Granularity'] > :last-child,
-.primary-filters [aria-label='Traffic direction'] > :first-child,
-.primary-filters [aria-label='MAAD measure'] > :first-child {
-	grid-column: 1 / -1;
-}
-.primary-filters [role='group'] button,
-.primary-filters .router-filter label {
-	min-height: 2.75rem;
-}
-.primary-filters .router-filter label {
-	display: flex;
-	align-items: center;
-}
-
-@media (pointer: fine) {
-	.console-rail .primary-filters [role='group'] button {
-		height: 2rem;
-		min-height: 2rem;
 	}
-	.console-rail .primary-filters .router-filter label {
+	[data-card-menu-trigger] {
+		min-width: 2.75rem;
-		min-height: 2rem;
 	}
-	.console-rail .rail-section {
-		padding-block: 0.25rem;
-	}
-	.console-rail .rail-heading {
-		margin-bottom: 0.5rem;
-	}
-	.console-rail .rail-section:first-child > div {
-		margin-bottom: 0.25rem;
-	}
-	.console-rail .rail-section:first-child .rail-heading {
-		margin-bottom: 0;
-	}
-	.console-rail .rail-section:first-child {
-		padding-top: 0;
-	}
 }
-
-.card-actions {
-	position: absolute;
-	top: 0.25rem;
-	right: 0.5rem;
-	z-index: 10;
-	display: flex;
-	align-items: center;
-	gap: 0.125rem;
-}
-.card-actions button {
-	height: 2.25rem;
-	width: 2.25rem;
-}
-.chart-panels [data-drag-handle] {
-	min-height: 2.75rem;
-	padding-right: 10.5rem;
-}
-.chart-panels [data-drag-handle] h2 {
-	padding-right: 0;
-}
-.chart-panels > section > [data-slot='card'] {
-	border-radius: 0;
-}
-.chart-panels [data-drag-handle] {
-	background: var(--card);
-	padding-block: 0.75rem;
-}
-.chart-panels [data-slot='card-content'] {
-	padding: 0;
-}
-.chart-panels .chart-frame {
-	padding: 0.75rem;
-}
-.chart-panels .metric-selector {
-	padding: 0.5rem 0.75rem;
-	border-bottom: 1px solid var(--border);
-	background: var(--muted);
-}
-.metric-toolbar > [role='group'] {
-	width: auto;
-	margin-left: auto;
-}
-.metric-quickselect [role='group'] {
-	width: auto;
-}
-.metric-toolbar [role='group'] {
-	background: var(--card);
-}
-.chart-panels .traffic-frame {
-	--chart-height: 520px;
-}
-.chart-panels > [data-chart-id='dashboard'][data-chart-activated='false'] {
-	min-height: 740px !important;
-}
-.chart-panels [data-slot='card-content'] > [role='group'],
-.chart-panels [data-slot='card-content'] > .flex,
-.chart-panels [data-slot='card-content'] > .grid {
-	padding: 0.75rem;
-	margin: 0;
-	border-bottom: 1px solid var(--border);
-}
-.control-popover {
-	max-height: calc(100dvh - 2rem);
-	overflow-y: auto;
-}
-.file-summary {
-	padding: 1rem;
-}
-.file-router-card {
-	display: grid;
-	grid-template-columns: minmax(16rem, 0.8fr) minmax(0, 2fr);
-}
-.file-router-card > .file-summary {
-	border-right: 1px solid var(--border);
-	border-bottom: 0;
-}
-.file-router-card .file-metrics {
-	grid-template-columns: minmax(0, 1fr);
-}
-@media (min-width: 1600px) {
-	.file-router-card .file-metrics {
-		grid-template-columns: repeat(2, minmax(0, 1fr));
-	}
-}
-.catalog {
-	border: 1px solid var(--border);
-	background: var(--card);
-}
-.catalog-row {
-	display: grid;
-	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
-	align-items: center;
-	gap: 1rem;
-	padding: 1rem;
-	border-bottom: 1px solid var(--border);
-}
-.catalog-row:last-child {
-	border-bottom: 0;
-}
-.catalog-row:hover {
-	background: var(--accent);
-}
-.catalog-header {
-	color: var(--muted-foreground);
-	font-size: 0.75rem;
-	padding-block: 0.625rem;
-	background: var(--muted);
-}
-.file-context p {
-	margin-bottom: 0.5rem;
-	overflow-wrap: anywhere;
-}
-@media (max-width: 1279px) {
-	.file-router-card {
-		grid-template-columns: minmax(0, 1fr);
-	}
-	.file-router-card > .file-summary {
-		border-right: 0;
-		border-bottom: 1px solid var(--border);
-	}
-}
-@media (max-width: 1023px) {
-	.console-body {
-		grid-template-columns: minmax(0, 1fr);
-	}
-	.console-heading {
-		align-items: start;
-	}
-	.console-toolbar .date-range-filter {
-		flex: 1 1 28rem;
-	}
-	.filter-sheet .primary-filters {
-		width: 100%;
-	}
-}
-@media (max-width: 639px) {
-	.console-heading {
-		flex-direction: column;
-		gap: 0.25rem;
-	}
-	.console-heading .dataset-tabs {
-		width: 100%;
-	}
-	.console-toolbar {
-		gap: 0.5rem;
-		padding: 0.625rem;
-	}
-	.console-toolbar .date-range-filter {
-		order: 3;
-		flex-basis: 100%;
-	}
-	.console-toolbar .date-range-filter > div {
-		display: block;
-	}
-	.console-toolbar .date-range-filter label {
-		margin-bottom: 0.25rem;
-	}
-	.console-toolbar .date-range-filter {
-		align-items: end;
-	}
-	.console-toolbar .date-range-filter > span {
-		padding-bottom: 0.5rem;
-	}
-	.filter-summary {
-		flex-basis: 100%;
-		order: 4;
-	}
-	.metric-toolbar > [role='group'] {
-		margin-left: 0;
-	}
-	.metric-quickselect {
-		width: 100%;
-	}
-	.metric-quickselect [role='group'] {
-		width: 100%;
-	}
-	.chart-panels .traffic-frame {
-		--chart-height: 400px;
-	}
-	.chart-panels > [data-chart-id='dashboard'][data-chart-activated='false'] {
-		min-height: 560px !important;
-	}
-	.catalog-row {
-		grid-template-columns: minmax(0, 1fr) auto;
-	}
-	.catalog-row .catalog-mode {
-		grid-column: 1;
-		grid-row: 2;
-	}
-	.catalog-header {
-		display: none;
-	}
-}
```

## apps/web/src/lib/components/charts/BreakdownChart.svelte

Status: modified; +48; −55

```diff
@@ -19,9 +19,10 @@
 	import { navigateToNetflowFile } from '#lib/utils/netflow-file-navigation.ts';
 	import { dateStringToEpochPST, formatDateAsPSTDateString } from '#lib/utils/timezone.ts';
 	import { ensureCachedWindow, readCachedWindow, type TimeRange } from '#lib/utils/window-cache.ts';
-	import { Checkbox } from '#lib/components/ui/checkbox/index.ts';
 	import { Skeleton } from '#lib/components/ui/skeleton/index.ts';
+	import ChecklistPopover from '#lib/components/common/ChecklistPopover.svelte';
+	import SegmentedToggle from '#lib/components/common/SegmentedToggle.svelte';
+	import ToolbarSelect from '#lib/components/common/ToolbarSelect.svelte';
-	import SegmentedControl from '#lib/components/common/SegmentedControl.svelte';
 	import ChartCard from './ChartCard.svelte';
 	import { getSourceLineDash } from './flow-characteristics';
 	import { sourceSeriesColor, categoricalSeriesColor } from './chart-colors';
@@ -210,6 +211,10 @@
 		activeMetrics = next;
 		props.onMetricsChange?.({ metrics: next as MetricsForKind<Kind> });
 	}
+	function handleSetAllMetrics(checked: boolean) {
+		activeMetrics = checked ? config.metrics.map((metric) => metric.key) : [];
+		props.onMetricsChange?.({ metrics: activeMetrics as MetricsForKind<Kind> });
+	}
 	function handleRouterChange(router: string) {
 		props.onRouterChange?.({ router });
 	}
@@ -440,65 +445,53 @@
 >
 	{#snippet controls()}
 		{#if props.kind === 'spectrum'}
-			<div class="flex flex-wrap items-center gap-3">
-				{#if (props.availableRouters ?? []).length === 0}
-					<Skeleton class="h-9 w-48" aria-hidden="true" />
-				{:else}
-					<SegmentedControl
-						options={(props.availableRouters ?? []).map((routerName: string) => ({
-							value: routerName,
-							label: routerName
-						}))}
-						value={props.router ?? null}
-						onValueChange={handleRouterChange}
-						ariaLabel="Spectrum source"
-						class="flex flex-wrap"
-						buttonClass="w-auto sm:min-w-20"
-					/>
-				{/if}
-				<SegmentedControl
+			{#if (props.availableRouters ?? []).length === 0}
+				<Skeleton class="h-8 w-32" aria-hidden="true" />
+			{:else}
+				<ToolbarSelect
+					label="Source"
+					variant="ghost"
+					value={props.router ?? ''}
-					options={DIMENSION_SIDE_OPTIONS}
-					value={addressType}
-					onValueChange={handleAddressTypeChange}
-					ariaLabel="Spectrum address side"
-					class="grid-cols-2"
-					buttonClass="sm:min-w-20"
+					options={(props.availableRouters ?? []).map((routerName: string) => ({
+						value: routerName,
+						label: routerName
+					}))}
+					onValueChange={handleRouterChange}
 				/>
+			{/if}
+			<SegmentedToggle
+				options={DIMENSION_SIDE_OPTIONS}
+				value={addressType}
-			</div>
+				onValueChange={handleAddressTypeChange}
+				ariaLabel="Spectrum address side"
+			/>
 		{:else if config.seriesByRouter}
 			{@const selected = splitDimensionMetricKey(
 				(activeMetrics[0] ?? config.defaultMetrics[0]) as DimensionMetricKey
 			)}
-			<div class="flex flex-wrap items-center gap-3">
-				<SegmentedControl
-					options={DIMENSION_SIDE_OPTIONS}
-					value={selected.side}
-					onValueChange={(side) => handleDimensionChange(side, selected.order)}
-					ariaLabel="MAAD address side"
-					class="grid-cols-2"
-					buttonClass="sm:min-w-20"
-				/>
-				<SegmentedControl
-					options={DIMENSION_ORDER_OPTIONS}
-					value={selected.order}
-					onValueChange={(order) => handleDimensionChange(selected.side, order)}
+			<SegmentedToggle
+				options={DIMENSION_SIDE_OPTIONS}
+				value={selected.side}
+				onValueChange={(side) => handleDimensionChange(side, selected.order)}
+				ariaLabel="MAAD address side"
+			/>
+			<SegmentedToggle
+				options={DIMENSION_ORDER_OPTIONS}
+				value={selected.order}
-					ariaLabel="MAAD dimension"
+				onValueChange={(order) => handleDimensionChange(selected.side, order)}
+				ariaLabel="MAAD dimension"
+			/>
-					class="grid-cols-3"
-					buttonClass="sm:min-w-12"
-				/>
-			</div>
 		{:else}
+			<ChecklistPopover
+				dialogLabel={`${config.title} metrics`}
+				items={config.metrics.map((metric) => ({
+					key: metric.key,
+					label: metric.label,
+					checked: activeMetrics.includes(metric.key)
+				}))}
+				onToggle={handleMetricToggle}
+				onSetAll={handleSetAllMetrics}
-			<div class="flex flex-wrap items-center gap-4">
-				{#each config.metrics as metric (metric.key)}
-					<label class="text-foreground flex cursor-pointer items-center gap-2 text-sm">
-						<Checkbox
-							checked={activeMetrics.includes(metric.key)}
-							onCheckedChange={() => handleMetricToggle(metric.key)}
-						/>
-						<span>{metric.label}</span>
-					</label>
-				{/each}
+			/>
-			</div>
 		{/if}
 	{/snippet}
 
```

## apps/web/src/lib/components/charts/CardMenu.svelte

Status: added; +45; −0

```diff
@@ -1,0 +1,45 @@
+<script lang="ts">
+	import { ArrowDown, ArrowUp, Ellipsis, Minus, Plus } from '@lucide/svelte';
+	import { Button } from '#lib/components/ui/button/index.ts';
+	import * as DropdownMenu from '#lib/components/ui/dropdown-menu/index.ts';
+	import type { CardFrame } from './card-frame.ts';
+
+	let { frame }: { frame: CardFrame } = $props();
+</script>
+
+<DropdownMenu.Root>
+	<DropdownMenu.Trigger>
+		{#snippet child({ props })}
+			<Button
+				{...props}
+				variant="ghost"
+				size="icon-sm"
+				class="text-muted-foreground hover:text-foreground"
+				aria-label={`${frame.title} card options`}
+				data-card-menu-trigger
+			>
+				<Ellipsis class="size-4" />
+			</Button>
+		{/snippet}
+	</DropdownMenu.Trigger>
+	<DropdownMenu.Content align="end" class="w-44">
+		<DropdownMenu.Group>
+			<DropdownMenu.Label class="text-muted-foreground truncate text-xs font-normal"
+				>{frame.title}</DropdownMenu.Label
+			>
+			<DropdownMenu.Item disabled={frame.first} onSelect={() => frame.move(-1)}
+				><ArrowUp />Move up</DropdownMenu.Item
+			>
+			<DropdownMenu.Item disabled={frame.last} onSelect={() => frame.move(1)}
+				><ArrowDown />Move down</DropdownMenu.Item
+			>
+		</DropdownMenu.Group>
+		{#if frame.resizable}
+			<DropdownMenu.Separator />
+			<DropdownMenu.Group>
+				<DropdownMenu.Item onSelect={() => frame.resize(80)}><Plus />Taller</DropdownMenu.Item>
+				<DropdownMenu.Item onSelect={() => frame.resize(-80)}><Minus />Shorter</DropdownMenu.Item>
+			</DropdownMenu.Group>
+		{/if}
+	</DropdownMenu.Content>
+</DropdownMenu.Root>
```

## apps/web/src/lib/components/charts/ChartCard.svelte

Status: modified; +13; −25

```diff
@@ -1,6 +1,6 @@
 <script lang="ts">
 	import ChartLoading from '#lib/components/charts/ChartLoading.svelte';
-	import DragGrip from '#lib/components/common/DragGrip.svelte';
+	import ChartCardHeader from './ChartCardHeader.svelte';
 	import * as Card from '#lib/components/ui/card/index.ts';
 	import type { Snippet } from 'svelte';
 
@@ -53,42 +53,28 @@
 						? 'empty'
 						: 'ready'}
 >
-	<Card.Header
-		class="border-border relative cursor-grab border-b py-4 select-none active:cursor-grabbing"
-		draggable="true"
-		data-drag-handle
+	<ChartCardHeader {title} {subtitle} controls={unavailableCopy ? undefined : controls} />
-	>
-		<Card.Title class="flex items-baseline gap-3 text-lg font-semibold">
-			<h2>{title}</h2>
-			{#if subtitle}
-				<span class="text-muted-foreground text-sm font-normal">{subtitle}</span>
-			{/if}
-		</Card.Title>
-		<DragGrip />
-	</Card.Header>
 
-	<Card.Content class="space-y-4 py-4">
+	<Card.Content class="p-0">
 		{#if unavailableCopy}
 			<div
-				class="border-border bg-background/60 text-muted-foreground flex min-h-32 items-center justify-center rounded-md border px-6 py-8 text-center"
+				class="text-muted-foreground flex min-h-40 items-center justify-center px-6 py-8 text-center text-sm"
 				data-testid="chart-unavailable"
 			>
 				{unavailableCopy}
 			</div>
 		{:else}
-			{@render controls?.()}
-
 			<div
 				class={size === 'spectrum'
-					? 'chart-frame relative h-[400px] min-h-[300px] resize-y overflow-hidden rounded-md border [--chart-height:400px]'
+					? 'chart-frame relative h-[400px] min-h-[300px] resize-y overflow-hidden p-3 [--chart-height:400px]'
 					: size === 'split'
+						? 'chart-frame relative h-[640px] min-h-[520px] resize-y overflow-hidden p-3 [--chart-height:640px] xl:h-[320px] xl:min-h-[240px] xl:[--chart-height:320px]'
+						: 'chart-frame relative h-[320px] min-h-[240px] resize-y overflow-hidden p-3'}
-						? 'chart-frame relative h-[640px] min-h-[520px] resize-y overflow-hidden rounded-md border [--chart-height:640px] xl:h-[320px] xl:min-h-[240px] xl:[--chart-height:320px]'
-						: 'chart-frame relative h-[320px] min-h-[240px] resize-y overflow-hidden rounded-md border'}
 				role="presentation"
 			>
 				{#if selectionUnavailableCopy}
 					<div
+						class="text-muted-foreground flex h-full items-center justify-center px-6 text-center text-sm"
-						class="text-muted-foreground flex h-full items-center justify-center px-6 text-center"
 						data-testid="chart-selection-unavailable"
 					>
 						{selectionUnavailableCopy}
@@ -96,13 +82,15 @@
 				{:else if loading}
 					<ChartLoading label={loadingCopy} />
 				{:else if error}
+					<div class="text-destructive flex h-full items-center justify-center text-sm">
+						{error}
-					<div class="text-destructive flex h-full items-center justify-center">{error}</div>
+					</div>
 				{:else if noMetrics}
-					<div class="text-muted-foreground flex h-full items-center justify-center">
+					<div class="text-muted-foreground flex h-full items-center justify-center text-sm">
 						{noMetricsCopy}
 					</div>
 				{:else if empty}
-					<div class="text-muted-foreground flex h-full items-center justify-center">
+					<div class="text-muted-foreground flex h-full items-center justify-center text-sm">
 						{emptyCopy}
 					</div>
 				{:else}
```

## apps/web/src/lib/components/charts/ChartCardHeader.svelte

Status: added; +46; −0

```diff
@@ -1,0 +1,46 @@
+<script lang="ts">
+	import { GripVertical } from '@lucide/svelte';
+	import type { Snippet } from 'svelte';
+	import CardMenu from './CardMenu.svelte';
+	import { optionalCardFrame } from './card-frame.ts';
+
+	let {
+		title,
+		subtitle = null,
+		controls
+	}: { title: string; subtitle?: string | null; controls?: Snippet } = $props();
+
+	const frame = optionalCardFrame();
+</script>
+
+<div class="chart-card-header border-border @container border-b">
+	<div class="flex min-h-12 flex-wrap items-center gap-x-3 gap-y-2 px-3 py-2">
+		<div
+			class={[
+				'flex min-w-0 flex-1 items-center gap-1 self-stretch',
+				frame && 'cursor-grab select-none active:cursor-grabbing'
+			]}
+			draggable={frame ? 'true' : undefined}
+			data-drag-handle={frame ? '' : undefined}
+		>
+			{#if frame}<GripVertical
+					class="text-muted-foreground/50 -ml-1 size-4 shrink-0"
+					aria-hidden="true"
+				/>{/if}
+			<h2 class="text-foreground truncate text-sm font-semibold tracking-tight">{title}</h2>
+			{#if subtitle}<span class="text-muted-foreground ml-1.5 truncate text-xs">{subtitle}</span
+				>{/if}
+		</div>
+		{#if controls || frame}
+			<div
+				class={[
+					'flex flex-wrap items-center gap-1.5',
+					controls && 'basis-full @2xl:ml-auto @2xl:basis-auto @2xl:justify-end'
+				]}
+			>
+				{@render controls?.()}
+				{#if frame}<div class="ml-auto"><CardMenu {frame} /></div>{/if}
+			</div>
+		{/if}
+	</div>
+</div>
```

## apps/web/src/lib/components/charts/CoverageStrip.svelte

Status: modified; +4; −11

```diff
@@ -33,7 +33,7 @@
 </script>
 
 <script lang="ts">
-	import DragGrip from '#lib/components/common/DragGrip.svelte';
+	import ChartCardHeader from './ChartCardHeader.svelte';
 	import type { BucketCoverage, CoverageState } from '#lib/types/types.ts';
 	import type { GroupByOption, RouterConfig } from '#lib/components/netflow/types.ts';
 	import { dateStringToEpochPST } from '#lib/utils/timezone.ts';
@@ -262,7 +262,7 @@
 </script>
 
 <div
-	class="bg-card rounded-lg border shadow-sm"
+	class="bg-card text-card-foreground rounded-lg border shadow-(--shadow-card)"
 	data-testid="coverage-strip-card"
 	data-state={loading
 		? 'loading'
@@ -272,20 +272,13 @@
 				? 'empty'
 				: 'ready'}
 >
-	<div
-		class="relative cursor-grab border-b p-3 select-none active:cursor-grabbing"
-		draggable="true"
+	<ChartCardHeader title="Coverage" />
-		data-drag-handle
-	>
-		<h2 class="text-foreground text-sm font-semibold">Coverage</h2>
-		<DragGrip />
-	</div>
 
 	<div class="px-3 py-2">
 		{#if loading}
 			<div class="text-muted-foreground py-1 text-xs">Loading coverage...</div>
 		{:else if error}
+			<div class="text-destructive py-1 text-xs">{error}</div>
-			<div class="py-1 text-xs text-red-500">{error}</div>
 		{:else if visibleTimelines.length === 0}
 			<div class="text-muted-foreground py-1 text-xs">No coverage available</div>
 		{:else}
```

## apps/web/src/lib/components/charts/FlowCharacteristicsChart.svelte

Status: modified; +7; −14

```diff
@@ -1,5 +1,6 @@
 <script lang="ts">
 	import ChartCard from './ChartCard.svelte';
+	import SegmentedToggle from '#lib/components/common/SegmentedToggle.svelte';
 	import MetricLinePanel, { type MetricLineSeries } from './MetricLinePanel.svelte';
 	import { indexObservationBuckets, type IndexedObservationBucket } from './flow-characteristics';
 	import type { GroupByOption } from '#lib/components/netflow/types.ts';
@@ -108,20 +109,12 @@
 	emptyCopy="No flow characteristics for the selected filters"
 >
 	{#snippet controls()}
+		<SegmentedToggle
+			options={IP_FAMILY_OPTIONS}
+			value={observationFamily}
+			onValueChange={(family) => (observationFamily = family)}
+			ariaLabel="IP family"
-		<div class="flex flex-wrap items-center gap-4" role="group" aria-label="IP family">
+		/>
-			{#each IP_FAMILY_OPTIONS as option (option.value)}
-				<label class="text-foreground flex cursor-pointer items-center gap-2 text-sm">
-					<input
-						type="radio"
-						name="flow-characteristics-ip-family"
-						checked={observationFamily === option.value}
-						onchange={() => (observationFamily = option.value)}
-						class="border-input accent-primary focus-visible:ring-ring size-4 focus-visible:ring-2"
-					/>
-					<span>{option.label}</span>
-				</label>
-			{/each}
-		</div>
 	{/snippet}
 
 	<div class="grid h-full gap-4 xl:grid-cols-2">
```

## apps/web/src/lib/components/charts/PortCardinalityChart.svelte

Status: modified; +30; −32

```diff
@@ -1,6 +1,7 @@
 <script lang="ts">
 	import { SvelteSet } from 'svelte/reactivity';
-	import { Checkbox } from '#lib/components/ui/checkbox/index.ts';
+	import ChecklistPopover from '#lib/components/common/ChecklistPopover.svelte';
+	import SegmentedToggle from '#lib/components/common/SegmentedToggle.svelte';
 	import { sourceSeriesColor } from './chart-colors';
 	import ChartCard from './ChartCard.svelte';
 	import MetricLinePanel, { type MetricLineSeries } from './MetricLinePanel.svelte';
@@ -49,6 +50,10 @@
 		{ side: 'destination', range: 'high', label: 'Destination ports >1023' }
 	];
 
+	const PORT_FAMILY_OPTIONS = [
+		{ value: 'ipv4', label: 'IPv4' },
+		{ value: 'ipv6', label: 'IPv6' }
+	] as const;
 	let portFamily = $state<Exclude<NetflowIpFamily, 'all'>>('ipv4');
 	const activePortSeries = new SvelteSet(PORT_OPTIONS.map(({ side, range }) => `${side}-${range}`));
 	const granularity = $derived(GROUP_BY_TO_GRANULARITY[props.groupBy]);
@@ -114,37 +119,30 @@
 	emptyCopy="No port data for the selected filters"
 >
 	{#snippet controls()}
-		<div class="flex flex-wrap items-center gap-x-6 gap-y-3">
-			<div class="flex flex-wrap items-center gap-4" role="group" aria-label="Port IP family">
-				{#each ['ipv4', 'ipv6'] as const as family (family)}
-					<label class="text-foreground flex cursor-pointer items-center gap-2 text-sm">
-						<input
-							type="radio"
-							name="port-cardinality-ip-family"
-							checked={portFamily === family}
-							onchange={() => (portFamily = family)}
-							class="border-input accent-primary focus-visible:ring-ring size-4 focus-visible:ring-2"
-						/>
-						<span>{family.toUpperCase()}</span>
-					</label>
-				{/each}
-			</div>
-			<div
-				class="flex flex-wrap items-center gap-x-4 gap-y-2"
-				role="group"
-				aria-label="Port ranges"
-			>
-				{#each PORT_OPTIONS as option (`${option.side}-${option.range}`)}
-					<label class="text-foreground flex cursor-pointer items-center gap-2 text-sm">
-						<Checkbox
-							checked={activePortSeries.has(`${option.side}-${option.range}`)}
+		<SegmentedToggle
+			options={PORT_FAMILY_OPTIONS}
+			value={portFamily}
+			onValueChange={(family) => (portFamily = family)}
+			ariaLabel="Port IP family"
+		/>
+		<ChecklistPopover
+			label="Ranges"
+			dialogLabel="Port ranges"
+			items={PORT_OPTIONS.map((option) => ({
+				key: `${option.side}-${option.range}` as const,
+				label: option.label,
+				checked: activePortSeries.has(`${option.side}-${option.range}`)
+			}))}
+			onToggle={(key) => {
+				const option = PORT_OPTIONS.find(({ side, range }) => `${side}-${range}` === key);
+				if (option) togglePortSeries(option.side, option.range);
+			}}
+			onSetAll={(checked) => {
+				activePortSeries.clear();
+				if (checked)
+					for (const { side, range } of PORT_OPTIONS) activePortSeries.add(`${side}-${range}`);
+			}}
+		/>
-							onCheckedChange={() => togglePortSeries(option.side, option.range)}
-						/>
-						<span>{option.label}</span>
-					</label>
-				{/each}
-			</div>
-		</div>
 	{/snippet}
 
 	<MetricLinePanel
```

## apps/web/src/lib/components/charts/card-frame.ts

Status: added; +17; −0

```diff
@@ -1,0 +1,17 @@
+import { createContext } from 'svelte';
+
+export type CardFrame = {
+	readonly id: string;
+	readonly title: string;
+	readonly first: boolean;
+	readonly last: boolean;
+	readonly resizable: boolean;
+	move(offset: number): void;
+	resize(offset: number): void;
+};
+
+export const [getCardFrame, setCardFrame, hasCardFrame] = createContext<CardFrame>();
+
+export function optionalCardFrame(): CardFrame | null {
+	return hasCardFrame() ? getCardFrame() : null;
+}
```

## apps/web/src/lib/components/common/AnalysisLayout.svelte

Status: deleted; +0; −102

```diff
@@ -1,102 +1,0 @@
-<script lang="ts">
-	import { MediaQuery } from 'svelte/reactivity';
-	import { PanelLeftClose, PanelLeftOpen, SlidersHorizontal } from '@lucide/svelte';
-	import { Button } from '#lib/components/ui/button/index.ts';
-	import type { Snippet } from 'svelte';
-
-	let {
-		title,
-		eyebrow,
-		railLabel = 'Filters',
-		rail,
-		navigation,
-		toolbar,
-		children
-	}: {
-		title: string;
-		eyebrow: string;
-		railLabel?: string;
-		rail: Snippet;
-		navigation?: Snippet;
-		toolbar?: Snippet;
-		children: Snippet;
-	} = $props();
-	const id = $props.id();
-	const desktop = new MediaQuery('(min-width: 1024px)', true);
-	let collapsed = $state(false);
-	let sheetOpen = $state(false);
-	let sheetModule = $state<Promise<typeof import('./FilterSheet.svelte')> | null>(null);
-
-	function openSheet() {
-		sheetModule ??= import('./FilterSheet.svelte');
-		sheetOpen = true;
-	}
-</script>
-
-<svelte:window
-	onkeydown={(event) => {
-		if (sheetOpen && event.key === 'Escape' && !event.defaultPrevented) sheetOpen = false;
-	}}
-	onresize={() => {
-		if (desktop.current) sheetOpen = false;
-	}}
-/>
-
-<div class="console-page">
-	<div class="console-heading">
-		<div class="min-w-0">
-			<p class="page-eyebrow">{eyebrow}</p>
-			<h1 class="page-heading break-words">{title}</h1>
-		</div>
-		{@render navigation?.()}
-	</div>
-	<div class="console-body" class:rail-collapsed={collapsed}>
-		{#if desktop.current && !collapsed}
-			<aside class="console-rail" aria-label={railLabel}>
-				{@render rail()}
-			</aside>
-		{/if}
-		<div class="console-workarea min-w-0">
-			<div class="console-toolbar">
-				{#if desktop.current}
-					<Button
-						variant="outline"
-						size="icon"
-						class="rail-toggle"
-						aria-label={collapsed
-							? `Show ${railLabel.toLowerCase()}`
-							: `Hide ${railLabel.toLowerCase()}`}
-						aria-expanded={!collapsed}
-						onclick={() => (collapsed = !collapsed)}
-					>
-						{#if collapsed}<PanelLeftOpen size={17} />{:else}<PanelLeftClose size={17} />{/if}
-					</Button>
-				{:else}
-					<Button
-						variant="outline"
-						size="sm"
-						class="rail-toggle"
-						aria-label={`Open ${railLabel.toLowerCase()}`}
-						aria-haspopup="dialog"
-						data-filter-trigger={id}
-						aria-expanded={sheetOpen}
-						onclick={openSheet}><SlidersHorizontal size={15} />{railLabel}</Button
-					>
-				{/if}
-				{@render toolbar?.()}
-			</div>
-			<div class="console-results">{@render children()}</div>
-		</div>
-	</div>
-</div>
-
-{#if !desktop.current && sheetModule}
-	{#await sheetModule then { default: FilterSheet }}
-		<FilterSheet
-			bind:open={sheetOpen}
-			title={railLabel}
-			onClose={() => document.querySelector<HTMLElement>(`[data-filter-trigger="${id}"]`)?.focus()}
-			>{@render rail()}</FilterSheet
-		>
-	{/await}
-{/if}
```

## apps/web/src/lib/components/common/CardLayoutControls.svelte

Status: deleted; +0; −66

```diff
@@ -1,66 +1,0 @@
-<script lang="ts">
-	import { tick } from 'svelte';
-	import { ArrowUp, ArrowDown, Minus, Plus } from '@lucide/svelte';
-	import { Button } from '#lib/components/ui/button/index.ts';
-	let {
-		title,
-		first,
-		last,
-		onMove,
-		onResize,
-		resizable = true
-	}: {
-		title: string;
-		first: boolean;
-		last: boolean;
-		resizable?: boolean;
-		onMove: (offset: number) => void;
-		onResize: (offset: number) => void;
-	} = $props();
-
-	async function move(button: HTMLElement, offset: number) {
-		if (!(button instanceof HTMLButtonElement)) return;
-		const controls = button.parentElement;
-		onMove(offset);
-		await tick();
-		if (!button.isConnected) return;
-		if (button.disabled)
-			controls?.querySelector<HTMLButtonElement>('button:not(:disabled)')?.focus();
-		else button.focus();
-	}
-</script>
-
-<div class="card-actions" role="group" aria-label={`Arrange ${title}`}>
-	<Button
-		variant="ghost"
-		size="icon-sm"
-		disabled={first}
-		onclick={(event) => void move(event.currentTarget, -1)}
-		aria-label={`Move ${title} up`}
-		title="Move up"><ArrowUp size={15} /></Button
-	>
-	<Button
-		variant="ghost"
-		size="icon-sm"
-		disabled={last}
-		onclick={(event) => void move(event.currentTarget, 1)}
-		aria-label={`Move ${title} down`}
-		title="Move down"><ArrowDown size={15} /></Button
-	>
-	{#if resizable}
-		<Button
-			variant="ghost"
-			size="icon-sm"
-			onclick={() => onResize(-80)}
-			aria-label={`Make ${title} shorter`}
-			title="Shorter plot"><Minus size={15} /></Button
-		>
-		<Button
-			variant="ghost"
-			size="icon-sm"
-			onclick={() => onResize(80)}
-			aria-label={`Make ${title} taller`}
-			title="Taller plot"><Plus size={15} /></Button
-		>
-	{/if}
-</div>
```

## apps/web/src/lib/components/common/ChecklistPopover.svelte

Status: added; +50; −0

```diff
@@ -1,0 +1,50 @@
+<script lang="ts" generics="K extends string">
+	import { Button } from '#lib/components/ui/button/index.ts';
+	import { Checkbox } from '#lib/components/ui/checkbox/index.ts';
+	import ToolbarPopover from './ToolbarPopover.svelte';
+
+	let {
+		label = 'Metrics',
+		dialogLabel,
+		items,
+		onToggle,
+		onSetAll
+	}: {
+		label?: string;
+		dialogLabel: string;
+		items: readonly { key: K; label: string; checked: boolean }[];
+		onToggle: (key: K) => void;
+		onSetAll?: (checked: boolean) => void;
+	} = $props();
+
+	const checkedCount = $derived(items.filter((item) => item.checked).length);
+</script>
+
+<ToolbarPopover
+	{label}
+	value={`${checkedCount}/${items.length}`}
+	{dialogLabel}
+	variant="ghost"
+	align="end"
+	contentClass="w-60"
+>
+	<div class="flex items-center justify-between gap-2 border-b px-3 py-2">
+		<p class="text-xs font-medium">{dialogLabel}</p>
+		{#if onSetAll}
+			<div class="flex gap-0.5">
+				<Button variant="ghost" size="xs" onclick={() => onSetAll(true)}>All</Button>
+				<Button variant="ghost" size="xs" onclick={() => onSetAll(false)}>None</Button>
+			</div>
+		{/if}
+	</div>
+	<div class="flex flex-col p-1" role="group" aria-label={dialogLabel}>
+		{#each items as item (item.key)}
+			<label
+				class="hover:bg-muted flex cursor-pointer items-center gap-2.5 rounded-sm px-2 py-1.5 text-sm"
+			>
+				<Checkbox checked={item.checked} onCheckedChange={() => onToggle(item.key)} />
+				<span>{item.label}</span>
+			</label>
+		{/each}
+	</div>
+</ToolbarPopover>
```

## apps/web/src/lib/components/common/ControlPopover.svelte

Status: deleted; +0; −42

```diff
@@ -1,42 +1,0 @@
-<script lang="ts">
-	import { Popover } from 'bits-ui';
-	import { ChevronDown, X } from '@lucide/svelte';
-	import { Button } from '#lib/components/ui/button/index.ts';
-	import type { Snippet } from 'svelte';
-
-	let { label, summary, children }: { label: string; summary: string; children: Snippet } =
-		$props();
-</script>
-
-<Popover.Root>
-	<Popover.Trigger>
-		{#snippet child({ props })}
-			<Button {...props} variant="outline" size="sm" aria-label={label}
-				>{summary}<ChevronDown size={13} /></Button
-			>
-		{/snippet}
-	</Popover.Trigger>
-	<Popover.Content
-		side="bottom"
-		align="start"
-		sideOffset={6}
-		class="control-popover bg-popover text-popover-foreground z-40 w-[min(24rem,calc(100vw-2rem))] rounded-md border p-3 shadow-lg"
-		role="dialog"
-		aria-label={label}
-	>
-		<div class="mb-3 flex items-center justify-between gap-2">
-			<p class="text-sm font-semibold">{label}</p>
-			<Popover.Close>
-				{#snippet child({ props })}
-					<Button
-						{...props}
-						variant="ghost"
-						size="icon-xs"
-						aria-label={`Close ${label.toLowerCase()}`}><X size={14} /></Button
-					>
-				{/snippet}
-			</Popover.Close>
-		</div>
-		{@render children()}
-	</Popover.Content>
-</Popover.Root>
```

## apps/web/src/lib/components/common/DragGrip.svelte

Status: deleted; +0; −13

```diff
@@ -1,13 +1,0 @@
-<span
-	class="pointer-events-none absolute inset-0 flex items-start justify-center pt-1 text-gray-400"
-	aria-hidden="true"
->
-	<span class="grid grid-cols-3 grid-rows-2 gap-[2px]">
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-		<span class="h-[2px] w-[2px] rounded-full bg-current"></span>
-	</span>
-</span>
```

## apps/web/src/lib/components/common/FilterSheet.svelte

Status: deleted; +0; −46

```diff
@@ -1,46 +1,0 @@
-<script lang="ts">
-	import { Dialog } from 'bits-ui';
-	import { X } from '@lucide/svelte';
-	import { Button } from '#lib/components/ui/button/index.ts';
-	import type { Snippet } from 'svelte';
-
-	let {
-		open = $bindable(false),
-		title,
-		children,
-		onClose
-	}: { open?: boolean; title: string; children: Snippet; onClose?: () => void } = $props();
-</script>
-
-<Dialog.Root bind:open>
-	<Dialog.Portal>
-		<Dialog.Overlay class="fixed inset-0 z-50 bg-black/40" />
-		<Dialog.Content
-			onCloseAutoFocus={(event) => {
-				if (onClose) {
-					event.preventDefault();
-					onClose();
-				}
-			}}
-			class="filter-sheet bg-card text-card-foreground fixed inset-y-0 left-0 z-50 flex w-[min(22rem,calc(100vw-1rem))] flex-col border-r shadow-xl"
-		>
-			<div class="border-border flex shrink-0 items-center justify-between border-b px-4 py-3">
-				<Dialog.Title class="text-sm font-semibold">{title}</Dialog.Title>
-				<Dialog.Close>
-					{#snippet child({ props })}
-						<Button
-							{...props}
-							variant="ghost"
-							size="icon-sm"
-							aria-label={`Close ${title.toLowerCase()}`}><X size={18} /></Button
-						>
-					{/snippet}
-				</Dialog.Close>
-			</div>
-			<Dialog.Description class="sr-only"
-				>These options apply to the current view. Close this panel to return to the results.</Dialog.Description
-			>
-			<div class="min-h-0 flex-1 overflow-y-auto p-4">{@render children()}</div>
-		</Dialog.Content>
-	</Dialog.Portal>
-</Dialog.Root>
```

## apps/web/src/lib/components/common/SegmentedControl.svelte

Status: deleted; +0; −95

```diff
@@ -1,95 +1,0 @@
-<script lang="ts" module>
-	export type SegmentedControlOption<T extends string> = {
-		value: T;
-		label: string;
-		disabled?: boolean;
-		disabledReason?: string;
-		title?: string;
-	};
-
-	export const segmentedControlGroupClass =
-		'grid w-full gap-0.5 rounded-md border border-border bg-muted/50 p-0.5 sm:w-fit';
-	export const segmentedControlItemClass =
-		'flex h-auto min-h-8 w-full items-center justify-center rounded px-2.5 py-1 text-center text-xs font-medium leading-tight transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none';
-	export const segmentedControlActiveClass =
-		'bg-accent text-accent-foreground ring-1 ring-inset ring-primary/25 hover:bg-accent hover:text-accent-foreground';
-	export const segmentedControlInactiveClass =
-		'text-muted-foreground hover:bg-background/60 hover:text-foreground';
-</script>
-
-<script lang="ts" generics="T extends string">
-	import { Button } from '#lib/components/ui/button/index.ts';
-	import * as Tooltip from '#lib/components/ui/tooltip/index.ts';
-	import { cn } from '#lib/utils.ts';
-
-	interface Props {
-		options: readonly SegmentedControlOption<T>[];
-		value: T | null;
-		onValueChange?: (value: T) => void;
-		class?: string;
-		style?: string;
-		buttonClass?: string;
-		ariaLabel?: string;
-	}
-
-	let {
-		options,
-		value,
-		onValueChange,
-		class: className,
-		style,
-		buttonClass,
-		ariaLabel
-	}: Props = $props();
-
-	function selectOption(option: SegmentedControlOption<T>) {
-		if (!option.disabled && !option.disabledReason && option.value !== value) {
-			onValueChange?.(option.value);
-		}
-	}
-
-	function optionClass(option: SegmentedControlOption<T>) {
-		return cn(
-			segmentedControlItemClass,
-			option.value === value ? segmentedControlActiveClass : segmentedControlInactiveClass,
-			(option.disabled || option.disabledReason) && 'cursor-not-allowed opacity-50',
-			buttonClass
-		);
-	}
-</script>
-
-<div class={cn(segmentedControlGroupClass, className)} {style} role="group" aria-label={ariaLabel}>
-	{#each options as option (option.value)}
-		{#if option.disabledReason}
-			<Tooltip.Root>
-				<Tooltip.Trigger>
-					{#snippet child({ props })}
-						<Button
-							{...props}
-							variant="ghost"
-							class={optionClass(option)}
-							onclick={() => selectOption(option)}
-							aria-pressed={option.value === value}
-							aria-disabled="true"
-							title={option.title}
-						>
-							{option.label}
-						</Button>
-					{/snippet}
-				</Tooltip.Trigger>
-				<Tooltip.Content sideOffset={4}>{option.disabledReason}</Tooltip.Content>
-			</Tooltip.Root>
-		{:else}
-			<Button
-				variant="ghost"
-				class={optionClass(option)}
-				onclick={() => selectOption(option)}
-				aria-pressed={option.value === value}
-				disabled={option.disabled}
-				title={option.title}
-			>
-				{option.label}
-			</Button>
-		{/if}
-	{/each}
-</div>
```

## apps/web/src/lib/components/common/SegmentedToggle.svelte

Status: added; +54; −0

```diff
@@ -1,0 +1,54 @@
+<script lang="ts" module>
+	export type SegmentedOption<T extends string> = {
+		value: T;
+		label: string;
+		title?: string;
+		disabled?: boolean;
+	};
+</script>
+
+<script lang="ts" generics="T extends string">
+	import * as ToggleGroup from '#lib/components/ui/toggle-group/index.ts';
+	import { cn } from '#lib/utils.ts';
+
+	let {
+		options,
+		value,
+		onValueChange,
+		ariaLabel,
+		class: className,
+		itemClass
+	}: {
+		options: readonly SegmentedOption<T>[];
+		value: T | null;
+		onValueChange?: (value: T) => void;
+		ariaLabel: string;
+		class?: string;
+		itemClass?: string;
+	} = $props();
+</script>
+
+<ToggleGroup.Root
+	type="single"
+	bind:value={
+		() => value ?? '',
+		(next: string) => {
+			if (next && next !== value) onValueChange?.(next as T);
+		}
+	}
+	spacing={1}
+	aria-label={ariaLabel}
+	class={cn('segmented bg-muted gap-0.5 rounded-md p-0.5', className)}
+>
+	{#each options as option (option.value)}
+		<ToggleGroup.Item
+			value={option.value}
+			disabled={option.disabled}
+			title={option.title}
+			class={cn(
+				'text-muted-foreground hover:text-foreground data-[state=on]:bg-card data-[state=on]:text-foreground h-7 min-w-0 rounded-[calc(var(--radius)-2px)] px-2.5 text-xs font-medium hover:bg-transparent data-[state=on]:shadow-xs dark:data-[state=on]:bg-[#2a2a2a]',
+				itemClass
+			)}>{option.label}</ToggleGroup.Item
+		>
+	{/each}
+</ToggleGroup.Root>
```

## apps/web/src/lib/components/common/ToolbarPopover.svelte

Status: added; +75; −0

```diff
@@ -1,0 +1,75 @@
+<script lang="ts">
+	import { ChevronDown } from '@lucide/svelte';
+	import type { Snippet } from 'svelte';
+	import { Button } from '#lib/components/ui/button/index.ts';
+	import * as Popover from '#lib/components/ui/popover/index.ts';
+	import { cn } from '#lib/utils.ts';
+
+	let {
+		label,
+		value,
+		ariaLabel,
+		dialogLabel = label,
+		variant = 'outline',
+		align = 'start',
+		open = $bindable(false),
+		onOpenChange,
+		focusContentOnOpen = false,
+		icon,
+		class: className,
+		contentClass,
+		children
+	}: {
+		label: string;
+		value?: string;
+		ariaLabel?: string;
+		dialogLabel?: string;
+		variant?: 'outline' | 'ghost';
+		align?: 'start' | 'center' | 'end';
+		open?: boolean;
+		onOpenChange?: (open: boolean) => void;
+		focusContentOnOpen?: boolean;
+		icon?: Snippet;
+		class?: string;
+		contentClass?: string;
+		children: Snippet;
+	} = $props();
+	let content = $state<HTMLElement | null>(null);
+</script>
+
+<Popover.Root bind:open {onOpenChange}>
+	<Popover.Trigger>
+		{#snippet child({ props })}
+			<Button
+				{...props}
+				{variant}
+				size="sm"
+				aria-label={ariaLabel}
+				class={cn('toolbar-trigger gap-1.5 px-2.5 font-medium', className)}
+			>
+				{@render icon?.()}
+				<span>{label}</span>
+				{#if value}<span class="text-muted-foreground truncate font-normal">{value}</span>{/if}
+				<ChevronDown class="text-muted-foreground size-3.5" />
+			</Button>
+		{/snippet}
+	</Popover.Trigger>
+	<Popover.Content
+		bind:ref={content}
+		{align}
+		sideOffset={6}
+		role="dialog"
+		aria-label={dialogLabel}
+		onOpenAutoFocus={(event) => {
+			if (!focusContentOnOpen) return;
+			event.preventDefault();
+			content?.focus();
+		}}
+		class={cn(
+			'max-h-[calc(100dvh-6rem)] w-auto max-w-[calc(100vw-1.5rem)] gap-0 overflow-y-auto p-0',
+			contentClass
+		)}
+	>
+		{@render children()}
+	</Popover.Content>
+</Popover.Root>
```

## apps/web/src/lib/components/common/ToolbarSelect.svelte

Status: added; +71; −0

```diff
@@ -1,0 +1,71 @@
+<script lang="ts" module>
+	export type ToolbarSelectOption<T extends string> = {
+		value: T;
+		label: string;
+		description?: string;
+		disabled?: boolean;
+	};
+</script>
+
+<script lang="ts" generics="T extends string">
+	import * as Select from '#lib/components/ui/select/index.ts';
+	import { cn } from '#lib/utils.ts';
+
+	let {
+		label,
+		value,
+		options,
+		onValueChange,
+		variant = 'outline',
+		class: className
+	}: {
+		label: string;
+		value: T;
+		options: readonly ToolbarSelectOption<T>[];
+		onValueChange: (value: T) => void;
+		variant?: 'outline' | 'ghost';
+		class?: string;
+	} = $props();
+
+	const selected = $derived(options.find((option) => option.value === value));
+</script>
+
+<Select.Root
+	type="single"
+	{value}
+	onValueChange={(next) => {
+		if (next && next !== value) onValueChange(next as T);
+	}}
+	items={options.map((option) => ({
+		value: option.value,
+		label: option.label,
+		disabled: option.disabled
+	}))}
+>
+	<Select.Trigger
+		size="sm"
+		aria-label={`${label}: ${selected?.label ?? value}`}
+		class={cn(
+			'toolbar-trigger h-8 gap-1.5 px-2.5 font-medium shadow-xs',
+			variant === 'outline'
+				? 'border-border bg-background hover:bg-muted dark:bg-input/30 dark:hover:bg-input/50'
+				: 'hover:bg-muted border-transparent bg-transparent shadow-none',
+			className
+		)}
+	>
+		<span>{label}</span>
+		<span class="text-muted-foreground font-normal">{selected?.label ?? value}</span>
+	</Select.Trigger>
+	<Select.Content align="start" class="min-w-44">
+		{#each options as option (option.value)}
+			<Select.Item value={option.value} label={option.label} disabled={option.disabled}>
+				<span class="flex flex-col gap-0.5 whitespace-normal">
+					<span>{option.label}</span>
+					{#if option.description}<span class="text-muted-foreground text-xs"
+							>{option.description}</span
+						>{/if}
+				</span>
+			</Select.Item>
+		{/each}
+	</Select.Content>
+</Select.Root>
```

[Next page](https://git.cafe/flambo/atlantis/pull/136/diffs?after=ImFwcHMvd2ViL3NyYy9saWIvY29tcG9uZW50cy9jb21tb24vVG9vbGJhclNlbGVjdC5zdmVsdGUi&expectedVersion=0&format=markdown)
