@nurix/etna 0.19.2 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/data/harness-index.json +1 -1
  2. package/data/harnesses/code/CLAUDE.md +2 -2
  3. package/data/harnesses/code/harness.json +2 -2
  4. package/data/harnesses/code/rules/frontend.md +3 -3
  5. package/data/harnesses/code/skills/design/SKILL.md +20 -8
  6. package/data/harnesses/code/skills/design/data/catalog/rules.csv +10 -5
  7. package/data/harnesses/code/skills/design/data/combinations/Readme.md +8 -4
  8. package/data/harnesses/code/skills/design/data/combinations/accent-presets.md +113 -0
  9. package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +11 -0
  10. package/data/harnesses/code/skills/design/data/combinations/manifest.json +57 -10
  11. package/data/harnesses/code/skills/design/data/combinations/motion.csv +10 -0
  12. package/data/harnesses/code/skills/design/data/combinations/palettes.csv +92 -49
  13. package/data/harnesses/code/skills/design/data/combinations/styles.csv +67 -54
  14. package/data/harnesses/code/skills/design/data/combinations/typography.csv +64 -61
  15. package/data/harnesses/code/skills/design/data/design-md/library/expo.md +4 -4
  16. package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +5 -5
  17. package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +6 -6
  18. package/data/harnesses/code/skills/design/data/design-md/library/notion.md +4 -4
  19. package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +4 -4
  20. package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +2 -2
  21. package/data/harnesses/code/skills/design/data/design-md/library/slack.md +6 -6
  22. package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +5 -5
  23. package/data/harnesses/code/skills/design/data/design-md/nurix.md +11 -7
  24. package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +256 -141
  25. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +309 -1
  26. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +309 -1
  27. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +309 -1
  28. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +309 -1
  29. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +309 -1
  30. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +309 -1
  31. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +313 -1
  32. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +309 -1
  33. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +309 -1
  34. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +309 -1
  35. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +309 -1
  36. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +309 -1
  37. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +309 -1
  38. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +313 -1
  39. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +309 -1
  40. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +313 -1
  41. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +309 -1
  42. package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +123 -123
  43. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +309 -1
  44. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +309 -1
  45. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +309 -1
  46. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +313 -1
  47. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +1 -1
  48. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +10 -0
  49. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +309 -1
  50. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +313 -1
  51. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +313 -1
  52. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +309 -1
  53. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +309 -1
  54. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +313 -1
  55. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +309 -1
  56. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +313 -1
  57. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +309 -1
  58. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +307 -2
  59. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +309 -1
  60. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +309 -1
  61. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +309 -1
  62. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +309 -1
  63. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +309 -1
  64. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +309 -1
  65. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +1 -1
  66. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +10 -0
  67. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +309 -1
  68. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +309 -1
  69. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +310 -2
  70. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +313 -1
  71. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +309 -1
  72. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +309 -1
  73. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +309 -1
  74. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +309 -1
  75. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +313 -1
  76. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +313 -1
  77. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +309 -1
  78. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +313 -1
  79. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +309 -1
  80. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +309 -1
  81. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +313 -1
  82. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +309 -1
  83. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +309 -1
  84. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +1 -1
  85. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +10 -0
  86. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +309 -1
  87. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +309 -1
  88. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +309 -1
  89. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +309 -1
  90. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +309 -1
  91. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +309 -1
  92. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +309 -1
  93. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/command-center.tsx +25 -25
  94. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +18 -18
  95. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +82 -105
  96. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +97 -93
  97. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +16 -15
  98. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +34 -34
  99. package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +5 -5
  100. package/data/harnesses/code/skills/design/references/create.md +49 -7
  101. package/data/harnesses/code/skills/design/references/diagnosis.md +3 -3
  102. package/data/harnesses/code/skills/design/references/hard-rules.md +25 -3
  103. package/data/harnesses/code/skills/design/references/improve.md +32 -3
  104. package/data/harnesses/code/skills/design/references/interaction.md +3 -3
  105. package/data/harnesses/code/skills/design/references/introspect.md +1 -1
  106. package/data/harnesses/code/skills/design/references/lifecycle.md +2 -2
  107. package/data/harnesses/code/skills/design/references/personality.md +280 -133
  108. package/data/harnesses/code/skills/design/references/structure.md +22 -1
  109. package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +306 -26
  110. package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +596 -104
  111. package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +200 -11
  112. package/data/harnesses/code/skills/design/scripts/lib/vocabulary.mjs +65 -0
  113. package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +95 -11
  114. package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +50 -5
  115. package/data/harnesses/code/skills/design/scripts/render-rung.mjs +214 -14
  116. package/data/harnesses/code/skills/design/scripts/scaffold.mjs +242 -25
  117. package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +81 -6
  118. package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +101 -5
  119. package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +142 -17
  120. package/data/harnesses/etna/CLAUDE.md +1 -1
  121. package/data/harnesses/etna/harness.json +2 -2
  122. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- // skills/design/data/operator-pack/exemplars/entity-detail.tsx
2
- // personality-invariant: composition anchor, not a look source.
1
+ // skills/design/data/operator-pack/exemplars/entity-detail.tsx — reference render — imitate the ambition and the composition; the project's tokens supply the look.
2
+ // Surface: brand-forward (delight-allowing)
3
3
  import { useEffect, useState } from "react";
4
4
  import { Button } from "@nurix/components/button";
5
5
  import { Badge } from "@nurix/components/badge";
@@ -13,28 +13,24 @@ import { Input } from "@nurix/components/input";
13
13
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "@nurix/components/tabs";
14
14
  import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, SheetClose } from "@nurix/components/sheet";
15
15
  import { Table, defineColumn, type ColumnDefinition } from "@nurix/components/table";
16
- import { Boxes, CircleCheck, CircleX, Pencil, RefreshCw, Rocket, ScrollText, TriangleAlert } from "lucide-react";
16
+ import { ArrowRight, Boxes, CircleCheck, CircleX, Pencil, RefreshCw, Rocket, ScrollText, TriangleAlert } from "lucide-react";
17
17
  // A deployment-console service: a long-lived entity with many attributes, related instances, and deployment history (entity-detail).
18
18
  type ServiceHealth = "healthy" | "degraded" | "down";
19
19
  type SaveState = "idle" | "saving" | "saved" | "conflict";
20
20
  type LoadStatus = "ready" | "loading" | "error";
21
21
  export interface Service {
22
- id: string; name: string; slug: string; environment: string; health: ServiceHealth;
23
- owner: string; runtime: string; region: string; replicas: number; lastDeployedAt: string;
22
+ id: string; name: string; slug: string; environment: string; health: ServiceHealth; owner: string; runtime: string; region: string; replicas: number; lastDeployedAt: string;
24
23
  }
25
24
  export interface Instance { id: string; region: string; version: string; status: string; cpuPercent: number; startedAt: string }
26
25
  export interface Deployment { id: string; version: string; actor: string; status: "succeeded" | "failed" | "rolled_back"; at: string }
26
+ export interface ActivityEvent { id: string; at: string; actor: string; summary: string; ref: string }
27
+ export interface ServiceMetric { label: string; value: string; unit: string; note: string; tone: string }
27
28
  export interface ConfigPatch { replicas: number }
28
29
  export interface EntityDetailProps {
29
- service: Service | null; instances: Instance[]; deployments: Deployment[];
30
- status?: LoadStatus; metricsStale?: boolean; errorDetail?: string; saveState?: SaveState;
31
- onRetry?: () => void; onDeploy?: () => void; onSaveConfig?: (patch: ConfigPatch) => void;
30
+ service: Service | null; instances: Instance[]; deployments: Deployment[]; activity?: ActivityEvent[]; metrics?: ServiceMetric[];
31
+ status?: LoadStatus; metricsStale?: boolean; errorDetail?: string; saveState?: SaveState; onRetry?: () => void; onDeploy?: () => void; onSaveConfig?: (patch: ConfigPatch) => void;
32
32
  }
33
- const HEALTH_META: Record<ServiceHealth, { label: string; cls: string; Icon: typeof CircleCheck }> = {
34
- healthy: { label: "Healthy", cls: "text-success", Icon: CircleCheck },
35
- degraded: { label: "Degraded", cls: "text-warning", Icon: TriangleAlert },
36
- down: { label: "Down", cls: "text-destructive", Icon: CircleX },
37
- };
33
+ const HEALTH_META: Record<ServiceHealth, { label: string; cls: string; Icon: typeof CircleCheck }> = { healthy: { label: "Healthy", cls: "text-success", Icon: CircleCheck }, degraded: { label: "Degraded", cls: "text-warning", Icon: TriangleAlert }, down: { label: "Down", cls: "text-destructive", Icon: CircleX } };
38
34
  const DEPLOY_LABEL: Record<Deployment["status"], string> = { succeeded: "Deployed", failed: "Deploy failed", rolled_back: "Rolled back" };
39
35
  const INSTANCE_STATUS = [{ value: "running", label: "Running" }, { value: "starting", label: "Starting" }, { value: "unknown", label: "Unknown" }];
40
36
  const instanceColumns: ColumnDefinition<Instance>[] = [
@@ -45,80 +41,69 @@ const instanceColumns: ColumnDefinition<Instance>[] = [
45
41
  defineColumn<Instance>({ slug: "cpuPercent", label: "CPU", type: "number", metadata: { suffix: "%" } }),
46
42
  defineColumn<Instance>({ slug: "startedAt", label: "Started", type: "date", metadata: { relativeFormat: true } }),
47
43
  ];
48
- // Loading shell mirrors the header identity and tab bar, never a centered spinner.
44
+ // Loading shell mirrors the header identity, tab bar, and metrics strip, never a centered spinner.
49
45
  function DetailSkeleton() {
50
46
  return (
51
- <div className="min-h-[100dvh] bg-background p-6">
52
- <div className="flex items-center gap-3">
53
- <Skeleton className="h-10 w-10 rounded-md" />
54
- <div className="flex flex-col gap-2"><Skeleton className="h-5 w-48" /><Skeleton className="h-4 w-32" /></div>
55
- </div>
47
+ <div className="min-h-[100dvh] bg-background p-6 font-sans">
48
+ <div className="flex items-center gap-3"><Skeleton className="h-10 w-10 rounded-md" /><div className="flex flex-col gap-2"><Skeleton className="h-5 w-48" /><Skeleton className="h-4 w-32" /></div></div>
56
49
  <div className="mt-6 flex gap-2"><Skeleton className="h-8 w-24" /><Skeleton className="h-8 w-24" /><Skeleton className="h-8 w-24" /></div>
57
- <div className="mt-6"><Skeleton className="h-40 w-full" /></div>
50
+ <div className="mt-6 grid grid-cols-2 gap-4 xl:grid-cols-4">{[0, 1, 2, 3].map((i) => <Skeleton key={i} className="h-20 w-full" />)}</div><div className="mt-6"><Skeleton className="h-40 w-full" /></div>
58
51
  </div>
59
52
  );
60
53
  }
61
54
  // Entity load failure states what happened, why, and the recovery path. Never a dead end.
62
55
  function DetailError({ detail, onRetry }: { detail?: string; onRetry?: () => void }) {
63
56
  return (
64
- <div className="flex min-h-[100dvh] items-center justify-center bg-background p-6">
57
+ <div className="flex min-h-[100dvh] items-center justify-center bg-background p-6 font-sans">
65
58
  <Alert variant="destructive" className="max-w-md">
66
- <CircleX className="h-4 w-4" />
67
- <AlertTitle>Could not load this service</AlertTitle>
59
+ <CircleX className="h-4 w-4" /><AlertTitle>Could not load this service</AlertTitle>
68
60
  <AlertDescription>{detail ?? "The deployment API did not respond."} Retry the request, or open the platform status page if it keeps failing.</AlertDescription>
69
61
  <div className="mt-3"><Button size="sm" onClick={onRetry}><RefreshCw className="h-4 w-4" /> Retry</Button></div>
70
62
  </Alert>
71
63
  </div>
72
64
  );
73
65
  }
74
- // Config edit opens beside the work; the save path narrates saving, saved, and the concurrent-edit conflict.
75
- function EditConfigSheet({ service, open, onOpenChange, saveState, onSave }: {
76
- service: Service; open: boolean; onOpenChange: (v: boolean) => void; saveState: SaveState; onSave?: (patch: ConfigPatch) => void;
77
- }) {
66
+ // Config edit opens beside the work on the overlay ladder; the save path narrates saving, saved, and the concurrent-edit conflict.
67
+ function EditConfigSheet({ service, open, onOpenChange, saveState, onSave }: { service: Service; open: boolean; onOpenChange: (v: boolean) => void; saveState: SaveState; onSave?: (patch: ConfigPatch) => void }) {
78
68
  const [replicas, setReplicas] = useState(String(service.replicas));
79
69
  const [replicaError, setReplicaError] = useState<string | null>(null);
80
70
  const saving = saveState === "saving";
81
71
  return (
82
72
  <Sheet open={open} onOpenChange={onOpenChange}>
83
- <SheetContent side="right">
73
+ <SheetContent side="right" className="bg-surface-overlay shadow-overlay">
84
74
  <SheetHeader>
85
- <SheetTitle>Edit configuration</SheetTitle>
86
- <SheetDescription>Changes apply on the next deploy of {service.slug}.</SheetDescription>
75
+ <SheetTitle className="text-h3">Edit configuration</SheetTitle><SheetDescription className="text-small">Changes apply on the next deploy of {service.slug}.</SheetDescription>
87
76
  </SheetHeader>
88
77
  <SheetBody className="flex flex-col gap-4">
89
78
  {saveState === "conflict" && (
90
79
  <Alert variant="destructive">
91
- <TriangleAlert className="h-4 w-4" />
92
- <AlertTitle>Configuration changed elsewhere</AlertTitle>
93
- <AlertDescription>Another operator saved this service while you were editing. Reload to see their values before saving yours.</AlertDescription>
80
+ <TriangleAlert className="h-4 w-4" /><AlertTitle>Configuration changed elsewhere</AlertTitle><AlertDescription>Another operator saved this service while you were editing. Reload to see their values before saving yours.</AlertDescription>
94
81
  </Alert>
95
82
  )}
96
83
  <div className="flex flex-col gap-2">
97
84
  <Label htmlFor="replicas">Desired replicas</Label>
98
85
  <Input id="replicas" inputMode="numeric" value={replicas} aria-invalid={!!replicaError} onChange={(e) => setReplicas(e.target.value)}
99
86
  onBlur={() => { const n = Number(replicas); setReplicaError(!Number.isInteger(n) || n < 1 ? "Enter a whole number of one or more." : null); }} />
100
- {replicaError && <p className="text-sm text-destructive">{replicaError}</p>}
87
+ {replicaError && <p className="text-small text-destructive">{replicaError}</p>}
101
88
  </div>
102
- <p role="status" aria-live="polite" className="min-h-4 text-sm text-muted-foreground">
103
- {saveState === "saving" && "Saving configuration…"}
104
- {saveState === "saved" && "Configuration saved."}
105
- </p>
89
+ <p role="status" aria-live="polite" className="min-h-4 text-small text-muted-foreground">{saveState === "saving" && "Saving configuration…"}{saveState === "saved" && "Configuration saved."}</p>
106
90
  </SheetBody>
107
91
  <SheetFooter>
108
- <SheetClose asChild><Button variant="secondary" size="sm">Cancel</Button></SheetClose>
109
- <Button size="sm" disabled={saving || !!replicaError} onClick={() => onSave?.({ replicas: Number(replicas) })}>
110
- {saving && <RefreshCw className="h-4 w-4" />} Save changes
111
- </Button>
92
+ <SheetClose asChild><Button variant="secondary" size="sm">Cancel</Button></SheetClose><Button size="sm" disabled={saving || !!replicaError} onClick={() => onSave?.({ replicas: Number(replicas) })}>{saving && <RefreshCw className="h-4 w-4" />} Save changes</Button>
112
93
  </SheetFooter>
113
94
  </SheetContent>
114
95
  </Sheet>
115
96
  );
116
97
  }
117
- // Service entity-detail: identity header, tabbed overview / instances / activity, edit in a side panel.
98
+ // Service entity-detail: a raised identity header (its brand-forward region), a furnished overview (metrics strip, config, activity preview, instance summary), tabbed related views, edit in a side panel.
118
99
  export function EntityDetail(props: EntityDetailProps) {
119
100
  const { service, instances, deployments, status = "ready", metricsStale = false, errorDetail, saveState = "idle", onRetry, onDeploy, onSaveConfig } = props;
101
+ const metrics = props.metrics ?? SERVICE_METRICS;
102
+ const activity = props.activity ?? RECENT_ACTIVITY;
120
103
  const [tab, setTab] = useState("overview");
121
104
  const [editOpen, setEditOpen] = useState(false);
105
+ const byStatus = instances.reduce((acc, i) => { acc[i.status] = (acc[i.status] ?? 0) + 1; return acc; }, {} as Record<string, number>);
106
+ const regions = [...new Set(instances.map((i) => i.region))];
122
107
  // Keyboard parity for the Edit affordance: press E to open the config panel.
123
108
  useEffect(() => {
124
109
  function onKey(e: KeyboardEvent) {
@@ -126,89 +111,95 @@ export function EntityDetail(props: EntityDetailProps) {
126
111
  if (el && (el.tagName === "INPUT" || el.tagName === "TEXTAREA")) return;
127
112
  if (e.key === "e" && !editOpen) { e.preventDefault(); setEditOpen(true); }
128
113
  }
129
- window.addEventListener("keydown", onKey);
130
- return () => window.removeEventListener("keydown", onKey);
114
+ window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey);
131
115
  }, [editOpen]);
132
116
  if (status === "loading") return <DetailSkeleton />;
133
117
  if (status === "error" || !service) return <DetailError detail={errorDetail} onRetry={onRetry} />;
134
118
  const health = HEALTH_META[service.health];
135
119
  const attributes: { label: string; value: string; mono?: boolean; num?: boolean }[] = [
136
- { label: "Environment", value: service.environment }, { label: "Runtime", value: service.runtime, mono: true },
137
- { label: "Region", value: service.region, mono: true }, { label: "Owner", value: service.owner },
120
+ { label: "Environment", value: service.environment }, { label: "Runtime", value: service.runtime, mono: true }, { label: "Region", value: service.region, mono: true }, { label: "Owner", value: service.owner },
138
121
  { label: "Desired replicas", value: String(service.replicas), num: true }, { label: "Last deployed", value: new Date(service.lastDeployedAt).toLocaleString(), num: true },
139
122
  ];
140
123
  return (
141
- <div className="min-h-[100dvh] bg-background text-foreground">
142
- <header className="flex flex-wrap items-start justify-between gap-4 border-b border-border p-6">
124
+ <div className="min-h-[100dvh] bg-background font-sans text-foreground">
125
+ <header className="flex flex-wrap items-start justify-between gap-4 border-b border-border bg-surface-raised p-6 shadow-sm">
143
126
  <div className="flex items-start gap-3">
144
127
  <span className="grid h-10 w-10 place-items-center rounded-md bg-muted text-muted-foreground"><Boxes className="h-5 w-5" /></span>
145
128
  <div>
146
- <div className="flex items-center gap-2">
147
- <h1 className="text-xl font-medium">{service.name}</h1>
148
- <Badge variant="outline" className={`gap-1 ${health.cls}`}><health.Icon className="h-3.5 w-3.5" /> {health.label}</Badge>
149
- </div>
150
- <p className="mt-1 text-sm text-muted-foreground">{service.slug} · {service.environment}</p>
129
+ <div className="flex items-center gap-2"><h1 className="text-h1">{service.name}</h1><Badge variant="outline" className={`gap-1 ${health.cls}`}><health.Icon className="h-3.5 w-3.5" /> {health.label}</Badge></div>
130
+ <p className="mt-1 text-small text-muted-foreground">{service.slug} · {service.environment}</p>
151
131
  </div>
152
132
  </div>
153
133
  <div className="flex items-center gap-2">
154
- <Button variant="secondary" size="sm" onClick={() => setEditOpen(true)}><Pencil className="h-4 w-4" /> Edit config <Kbd className="ml-1">E</Kbd></Button>
155
- <Button size="sm" onClick={onDeploy}><Rocket className="h-4 w-4" /> Deploy</Button>
134
+ <Button variant="secondary" size="sm" onClick={() => setEditOpen(true)}><Pencil className="h-4 w-4" /> Edit config <Kbd className="ml-1">E</Kbd></Button><Button size="sm" onClick={onDeploy}><Rocket className="h-4 w-4" /> Deploy</Button>
156
135
  </div>
157
136
  </header>
158
137
  <div className="p-6">
159
138
  {metricsStale && (
160
139
  <Alert className="mb-4">
161
- <TriangleAlert className="h-4 w-4 text-warning" />
162
- <AlertTitle>Live health signals unavailable</AlertTitle>
163
- <AlertDescription>The health collector is unreachable, so instance status and the figures below reflect the last cached snapshot and may be stale.</AlertDescription>
140
+ <TriangleAlert className="h-4 w-4 text-warning" /><AlertTitle>Live health signals unavailable</AlertTitle><AlertDescription>The health collector is unreachable, so instance status and the figures below reflect the last cached snapshot and may be stale.</AlertDescription>
164
141
  </Alert>
165
142
  )}
166
143
  <Tabs value={tab} onValueChange={setTab}>
167
- <TabsList>
168
- <TabsTrigger value="overview">Overview</TabsTrigger>
169
- <TabsTrigger value="instances">Instances</TabsTrigger>
170
- <TabsTrigger value="activity">Activity</TabsTrigger>
171
- </TabsList>
144
+ <TabsList><TabsTrigger value="overview">Overview</TabsTrigger><TabsTrigger value="instances">Instances</TabsTrigger><TabsTrigger value="activity">Activity</TabsTrigger></TabsList>
172
145
  <TabsContent value="overview">
173
- <section className="mt-4">
174
- <h2 className="mb-3 text-sm font-medium">Configuration <span className="text-xs font-normal text-muted-foreground">sample data</span></h2>
175
- <Card><CardContent className="p-0">
176
- {/* mock: fixture-supplied values, not authoritative production readings */}
177
- <dl className="divide-y divide-border">
178
- {attributes.map((a) => (
179
- <div key={a.label} className="flex items-center justify-between gap-4 px-4 py-3 text-sm">
180
- <dt className="text-muted-foreground">{a.label}</dt>
181
- <dd className={`${a.mono ? "font-mono" : ""} ${a.num ? "tabular-nums" : ""}`}>{a.value}</dd>
182
- </div>
146
+ <div className="mt-4 flex flex-col gap-6">
147
+ <section aria-labelledby="ov-health">
148
+ <h2 id="ov-health" className="mb-3 text-h3">Service health <span className="text-caption font-normal text-muted-foreground">sample data</span></h2>
149
+ <div className="grid grid-cols-2 gap-4 xl:grid-cols-4">{/* mock: fixture-supplied readings, not authoritative production statistics */}
150
+ {metrics.map((m) => (
151
+ <Card key={m.label} className="flex flex-col gap-1 p-4"><span className="text-caption text-muted-foreground">{m.label}</span>
152
+ <span className="flex items-baseline gap-1"><span className="text-h2 tabular-nums">{m.value}</span><span className="text-caption text-muted-foreground">{m.unit}</span></span><span className={`text-caption ${m.tone}`}>{m.note}</span>
153
+ </Card>
183
154
  ))}
184
- </dl>
185
- </CardContent></Card>
186
- </section>
155
+ </div>
156
+ </section>
157
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
158
+ <section aria-labelledby="ov-config">
159
+ <h2 id="ov-config" className="mb-3 text-h3">Configuration</h2>
160
+ <Card><CardContent className="p-0"><dl className="divide-y divide-border">
161
+ {attributes.map((a) => (
162
+ <div key={a.label} className="flex items-center justify-between gap-4 px-4 py-3 text-small"><dt className="text-muted-foreground">{a.label}</dt><dd className={`${a.mono ? "font-mono" : ""} ${a.num ? "tabular-nums" : ""}`}>{a.value}</dd></div>
163
+ ))}
164
+ </dl></CardContent></Card>
165
+ </section>
166
+ <div className="flex flex-col gap-6">
167
+ <section aria-labelledby="ov-activity">
168
+ <div className="mb-3 flex items-center justify-between"><h2 id="ov-activity" className="text-h3">Recent activity</h2><Button variant="ghost" size="sm" onClick={() => setTab("activity")}>All activity <ArrowRight className="h-4 w-4" /></Button></div>
169
+ <Card><CardContent className="p-0"><ol className="divide-y divide-border">
170
+ {activity.map((ev) => (
171
+ <li key={ev.id} className="flex items-baseline gap-3 px-4 py-2 text-small">
172
+ <time className="shrink-0 font-mono tabular-nums text-muted-foreground">{ev.at}</time><span className="min-w-0 flex-1 truncate">{ev.summary} <span className="text-muted-foreground">by {ev.actor}</span></span><span className="shrink-0 font-mono text-muted-foreground">{ev.ref}</span>
173
+ </li>
174
+ ))}
175
+ </ol></CardContent></Card>
176
+ </section>
177
+ <section aria-labelledby="ov-instances">
178
+ <div className="mb-3 flex items-center justify-between"><h2 id="ov-instances" className="text-h3">Instances</h2><Button variant="ghost" size="sm" onClick={() => setTab("instances")}>All instances <ArrowRight className="h-4 w-4" /></Button></div>
179
+ <Card><CardContent className="flex flex-wrap items-center gap-x-6 gap-y-2 p-4 text-small">
180
+ <span className="flex items-center gap-1.5 text-success"><CircleCheck className="h-4 w-4" /><span className="tabular-nums">{byStatus.running ?? 0}</span> running</span><span className="flex items-center gap-1.5 text-warning"><RefreshCw className="h-4 w-4" /><span className="tabular-nums">{byStatus.starting ?? 0}</span> starting</span><span className="flex items-center gap-1.5 text-destructive"><CircleX className="h-4 w-4" /><span className="tabular-nums">{byStatus.unknown ?? 0}</span> unknown</span><span className="text-muted-foreground">across {regions.length} regions (<span className="font-mono">{regions.join(", ")}</span>)</span>
181
+ </CardContent></Card>
182
+ </section>
183
+ </div>
184
+ </div>
185
+ </div>
187
186
  </TabsContent>
188
187
  <TabsContent value="instances">
189
188
  <div className="mt-4">
190
- {instances.length === 0 ? (
191
- <EmptyState icon={<Boxes className="h-10 w-10 text-muted-foreground" />} title="No running instances"
192
- description="This service has no instances in this environment yet. Deploy a build to bring the first one online."
193
- action={<Button size="sm" onClick={onDeploy}><Rocket className="h-4 w-4" /> Deploy</Button>} />
194
- ) : (
195
- <Table data={instances} columns={instanceColumns} tableConfig={{ header: true, density: "compact" }} />
196
- )}
189
+ {instances.length === 0
190
+ ? <EmptyState icon={<Boxes className="h-10 w-10 text-muted-foreground" />} title="No running instances" description="This service has no instances in this environment yet. Deploy a build to bring the first one online." action={<Button size="sm" onClick={onDeploy}><Rocket className="h-4 w-4" /> Deploy</Button>} />
191
+ : <Table data={instances} columns={instanceColumns} tableConfig={{ header: true, density: "compact" }} />}
197
192
  </div>
198
193
  </TabsContent>
199
194
  <TabsContent value="activity">
200
195
  <div className="mt-4">
201
196
  {deployments.length === 0 ? (
202
- <EmptyState icon={<ScrollText className="h-10 w-10 text-muted-foreground" />} title="No deployments recorded"
203
- description="Deployment history for this service appears here once the first build ships." />
197
+ <EmptyState icon={<ScrollText className="h-10 w-10 text-muted-foreground" />} title="No deployments recorded" description="Deployment history for this service appears here once the first build ships." />
204
198
  ) : (
205
199
  <ol className="flex flex-col">
206
200
  {deployments.map((d) => (
207
- <li key={d.id} className="flex items-center gap-3 border-b border-border py-3 text-sm last:border-0">
208
- {d.status === "succeeded" ? <CircleCheck className="h-4 w-4 text-success" /> : <CircleX className="h-4 w-4 text-destructive" />}
209
- <span className="font-mono">{d.version}</span>
210
- <span className="text-muted-foreground">{DEPLOY_LABEL[d.status]} by {d.actor}</span>
211
- <time className="ml-auto font-mono tabular-nums text-muted-foreground">{new Date(d.at).toLocaleString()}</time>
201
+ <li key={d.id} className="flex items-center gap-3 border-b border-border py-3 text-small last:border-0">
202
+ {d.status === "succeeded" ? <CircleCheck className="h-4 w-4 text-success" /> : <CircleX className="h-4 w-4 text-destructive" />}<span className="font-mono">{d.version}</span><span className="text-muted-foreground">{DEPLOY_LABEL[d.status]} by {d.actor}</span><time className="ml-auto font-mono tabular-nums text-muted-foreground">{new Date(d.at).toLocaleString()}</time>
212
203
  </li>
213
204
  ))}
214
205
  </ol>
@@ -224,8 +215,7 @@ export function EntityDetail(props: EntityDetailProps) {
224
215
  // exemplar fixture data — seeded, domain-shaped
225
216
  export const FIXTURE_SEED = 7314;
226
217
  export const service: Service = {
227
- id: "svc_0e9c1a7b", name: "checkout-api", slug: "checkout-api", environment: "production", health: "degraded",
228
- owner: "payments-platform", runtime: "node20", region: "iad", replicas: 6, lastDeployedAt: "2026-07-03T14:22:00Z",
218
+ id: "svc_0e9c1a7b", name: "checkout-api", slug: "checkout-api", environment: "production", health: "degraded", owner: "payments-platform", runtime: "node20", region: "iad", replicas: 6, lastDeployedAt: "2026-07-03T14:22:00Z",
229
219
  };
230
220
  export const instances: Instance[] = [
231
221
  { id: "checkout-api-7f4b21", region: "iad", version: "2026.7.3-a19f", status: "running", cpuPercent: 34, startedAt: "2026-07-03T14:23:00Z" },
@@ -241,5 +231,19 @@ export const deployments: Deployment[] = [
241
231
  { id: "dep_4e2", version: "2026.6.28-c4e2", actor: "s.delacroix", status: "failed", at: "2026-06-28T22:05:00Z" },
242
232
  { id: "dep_4b0", version: "2026.6.20-11ad", actor: "m.okonkwo", status: "succeeded", at: "2026-06-20T10:33:00Z" },
243
233
  ];
234
+ export const RECENT_ACTIVITY: ActivityEvent[] = [
235
+ { id: "act-91", at: "07-03 14:22", actor: "m.okonkwo", summary: "Deployed 2026.7.3-a19f to production", ref: "dep_5a1" },
236
+ { id: "act-90", at: "07-03 11:05", actor: "autoscaler", summary: "Scaled replicas 4 to 6 on sustained queue depth", ref: "evt_88c2" },
237
+ { id: "act-89", at: "07-02 16:48", actor: "s.delacroix", summary: "Config change: LOG_LEVEL info to warn", ref: "cfg_7719" },
238
+ { id: "act-88", at: "07-02 09:14", actor: "monitor", summary: "Alert fired: p95 latency above 250 ms for 12 minutes", ref: "alr_5520" },
239
+ { id: "act-87", at: "07-01 18:40", actor: "priya.nair", summary: "Rolled back 2026.7.1-77b0 after elevated 5xx", ref: "dep_4f7" },
240
+ { id: "act-86", at: "07-01 07:03", actor: "health-collector", summary: "Instance checkout-api-0000aa marked unknown after 3 failed probes", ref: "evt_4411" },
241
+ ];
242
+ export const SERVICE_METRICS: ServiceMetric[] = [
243
+ { label: "Uptime, 30 days", value: "99.92", unit: "%", note: "target 99.90", tone: "text-success" },
244
+ { label: "Error rate", value: "1.84", unit: "%", note: "elevated last hour", tone: "text-warning" },
245
+ { label: "Throughput", value: "412", unit: "req/s", note: "peak 590 today", tone: "text-muted-foreground" },
246
+ { label: "p95 latency", value: "268", unit: "ms", note: "SLO 250 ms", tone: "text-warning" },
247
+ ];
244
248
  export const emptyInstances: Instance[] = [];
245
249
  export const emptyDeployments: Deployment[] = [];
@@ -1,5 +1,5 @@
1
- // skills/design/data/operator-pack/exemplars/list-detail.tsx
2
- // personality-invariant: composition anchor, not a look source.
1
+ // skills/design/data/operator-pack/exemplars/list-detail.tsx — reference render — imitate the ambition and the composition; the project's tokens supply the look.
2
+ // Surface: baseline
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { Button } from "@nurix/components/button";
5
5
  import { Badge } from "@nurix/components/badge";
@@ -36,7 +36,7 @@ const PRIORITY_META: Record<Priority, { label: string; cls: string }> = {
36
36
  // Loading shell mirrors the toolbar and table rows, never a centered spinner.
37
37
  function QueueSkeleton() {
38
38
  return (
39
- <div className="min-h-[100dvh] bg-background p-6">
39
+ <div className="min-h-[100dvh] bg-background p-6 font-sans">
40
40
  <div className="flex items-center gap-3"><Skeleton className="h-8 w-64" /><Skeleton className="h-8 w-32" /></div>
41
41
  <div className="mt-6 flex flex-col gap-2">{Array.from({ length: 8 }).map((_, i) => <Skeleton key={i} className="h-11 w-full" />)}</div>
42
42
  </div>
@@ -45,7 +45,7 @@ function QueueSkeleton() {
45
45
  // Queue load failure states what happened, why, and the recovery path. Never a dead end.
46
46
  function QueueError({ detail, onRetry }: { detail?: string; onRetry?: () => void }) {
47
47
  return (
48
- <div className="flex min-h-[100dvh] items-center justify-center bg-background p-6">
48
+ <div className="flex min-h-[100dvh] items-center justify-center bg-background p-6 font-sans">
49
49
  <Alert variant="destructive" className="max-w-md">
50
50
  <CircleX className="h-4 w-4" />
51
51
  <AlertTitle>Could not load the ticket queue</AlertTitle>
@@ -55,7 +55,8 @@ function QueueError({ detail, onRetry }: { detail?: string; onRetry?: () => void
55
55
  </div>
56
56
  );
57
57
  }
58
- // The selected row projects into a side panel beside the table, never a modal (floor P3).
58
+ // The selected row projects into a side panel beside the table, never a modal (f03); the panel
59
+ // rides the elevation.overlay + surface-overlay ladder so it reads as the topmost layer.
59
60
  function TicketDetail({ ticket, open, onOpenChange, onResolve }: { ticket: Ticket | null; open: boolean; onOpenChange: (v: boolean) => void; onResolve?: (id: string) => void }) {
60
61
  if (!ticket) return null;
61
62
  const st = STATUS_META[ticket.status];
@@ -66,15 +67,15 @@ function TicketDetail({ ticket, open, onOpenChange, onResolve }: { ticket: Ticke
66
67
  ];
67
68
  return (
68
69
  <Sheet open={open} onOpenChange={onOpenChange}>
69
- <SheetContent side="right">
70
+ <SheetContent side="right" className="bg-surface-overlay shadow-overlay">
70
71
  <SheetHeader>
71
- <SheetTitle>{ticket.subject}</SheetTitle>
72
- <SheetDescription className={st.cls}>{st.label} · {ticket.id}</SheetDescription>
72
+ <SheetTitle className="text-h3">{ticket.subject}</SheetTitle>
73
+ <SheetDescription className={`text-small ${st.cls}`}>{st.label} · {ticket.id}</SheetDescription>
73
74
  </SheetHeader>
74
75
  <SheetBody>
75
76
  <dl className="divide-y divide-border">
76
77
  {rows.map((r) => (
77
- <div key={r.label} className="flex items-center justify-between gap-4 py-3 text-sm">
78
+ <div key={r.label} className="flex items-center justify-between gap-4 py-3 text-small">
78
79
  <dt className="text-muted-foreground">{r.label}</dt>
79
80
  <dd className={`${r.mono ? "font-mono" : ""} ${r.num ? "tabular-nums" : ""}`}>{r.value}</dd>
80
81
  </div>
@@ -119,9 +120,9 @@ export function ListDetail(props: ListDetailProps) {
119
120
  const start = filtered.length === 0 ? 0 : page * PAGE_SIZE + 1;
120
121
  const end = Math.min(filtered.length, (page + 1) * PAGE_SIZE);
121
122
  return (
122
- <div className="min-h-[100dvh] bg-background text-foreground">
123
+ <div className="min-h-[100dvh] bg-background font-sans text-foreground">
123
124
  <header className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-6">
124
- <div><h1 className="text-xl font-medium">Ticket queue</h1><p className="mt-1 text-sm text-muted-foreground">Scan, triage, and resolve support tickets.</p></div>
125
+ <div><h1 className="text-h1">Ticket queue</h1><p className="mt-1 text-small text-muted-foreground">Scan, triage, and resolve support tickets.</p></div>
125
126
  <Button size="sm" onClick={onNew}><Plus className="h-4 w-4" /> New ticket <Kbd className="ml-1">N</Kbd></Button>
126
127
  </header>
127
128
  <div className="flex flex-wrap items-center gap-2 border-b border-border px-6 py-3">
@@ -153,9 +154,9 @@ export function ListDetail(props: ListDetailProps) {
153
154
  ) : (
154
155
  <Card><CardContent className="p-0">
155
156
  <div className="overflow-auto" onKeyDown={onRowsKeyDown}>
156
- <table className="w-full border-collapse text-sm">
157
+ <table className="w-full border-collapse text-small">
157
158
  <thead className="sticky top-0 bg-card">
158
- <tr className="border-b border-border text-left text-muted-foreground">
159
+ <tr className="border-b border-border text-left text-caption text-muted-foreground">
159
160
  <th scope="col" className="w-10 px-4 py-2"><Checkbox checked={allOnPage} aria-label="Select all on this page" onCheckedChange={() => setSelected((s) => { const n = new Set(s); allOnPage ? pageRows.forEach((t) => n.delete(t.id)) : pageRows.forEach((t) => n.add(t.id)); return n; })} /></th>
160
161
  <th scope="col" className="px-4 py-2 font-medium">Ticket</th>
161
162
  <th scope="col" className="px-4 py-2 font-medium">Subject</th>
@@ -174,7 +175,7 @@ export function ListDetail(props: ListDetailProps) {
174
175
  return (
175
176
  <tr key={t.id} ref={(el) => { rowRefs.current[i] = el; }} tabIndex={i === activeIndex ? 0 : -1} aria-selected={selected.has(t.id)}
176
177
  onClick={() => { setActiveIndex(i); setOpenId(t.id); }}
177
- className="cursor-pointer border-b border-border last:border-0 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
178
+ className="motion-lift cursor-pointer border-b border-border last:border-0 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
178
179
  <td className="px-4 py-2" onClick={(e) => e.stopPropagation()}><Checkbox checked={selected.has(t.id)} aria-label={`Select ${t.id}`} onCheckedChange={() => toggle(t.id)} /></td>
179
180
  <td className="px-4 py-2 font-mono tabular-nums">{t.id}</td>
180
181
  <td className="max-w-xs truncate px-4 py-2">{t.subject}</td>
@@ -191,7 +192,7 @@ export function ListDetail(props: ListDetailProps) {
191
192
  </tbody>
192
193
  </table>
193
194
  </div>
194
- <div className="flex items-center justify-between gap-3 border-t border-border px-4 py-3 text-sm text-muted-foreground">
195
+ <div className="flex items-center justify-between gap-3 border-t border-border px-4 py-3 text-caption text-muted-foreground">
195
196
  <span className="tabular-nums">{start}-{end} of {filtered.length}</span>
196
197
  <div className="flex items-center gap-2">
197
198
  <Button size="sm" variant="secondary" disabled={page === 0} onClick={() => setPage((p) => Math.max(0, p - 1))}>Previous</Button>
@@ -1,5 +1,5 @@
1
- // skills/design/data/operator-pack/exemplars/log-stream.tsx
2
- // personality-invariant: composition anchor, not a look source.
1
+ // skills/design/data/operator-pack/exemplars/log-stream.tsx — reference render — imitate the ambition and the composition; the project's tokens supply the look.
2
+ // Surface: baseline
3
3
  import * as React from "react";
4
4
  import { ScrollText, Search, ChevronRight, ChevronDown, Circle, TriangleAlert, WifiOff, RotateCw, ListFilter, Inbox } from "lucide-react";
5
5
  import { Button } from "@nurix/components/button";
@@ -15,26 +15,11 @@ import { cn } from "@nurix/components/utils";
15
15
  export type LogLevel = "debug" | "info" | "warn" | "error";
16
16
 
17
17
  export interface LogLine {
18
- id: string;
19
- timestamp: string;
20
- level: LogLevel;
21
- source: string;
22
- message: string;
23
- status: number | null;
24
- latencyMs: number | null;
25
- detail: Record<string, string | number>;
26
- }
27
- export interface DegradedInfo {
28
- droppedLines: number;
29
- reason: string;
18
+ id: string; timestamp: string; level: LogLevel; source: string; message: string; status: number | null; latencyMs: number | null; detail: Record<string, string | number>;
30
19
  }
20
+ export interface DegradedInfo { droppedLines: number; reason: string }
31
21
  export interface LogStreamProps {
32
- lines?: LogLine[];
33
- isLoading?: boolean;
34
- error?: string | null;
35
- degraded?: DegradedInfo | null;
36
- initiallyFollowing?: boolean;
37
- onRetry?: () => void;
22
+ lines?: LogLine[]; isLoading?: boolean; error?: string | null; degraded?: DegradedInfo | null; initiallyFollowing?: boolean; onRetry?: () => void;
38
23
  }
39
24
 
40
25
  const RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
@@ -114,17 +99,17 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
114
99
  }
115
100
 
116
101
  return (
117
- <section onKeyDown={onRootKeyDown} data-density-step="tighter" className="flex min-h-[100dvh] flex-col bg-background text-foreground">
102
+ <section onKeyDown={onRootKeyDown} data-density-step="tighter" className="flex min-h-[100dvh] flex-col bg-background font-sans text-foreground">
118
103
  <header className="flex flex-col gap-2 border-b border-border px-4 py-3">
119
104
  <div className="flex items-center justify-between gap-3">
120
105
  <div className="flex items-center gap-2">
121
106
  <ScrollText aria-hidden className="size-4 text-muted-foreground" />
122
- <h1 className="text-base font-semibold">Edge ingest log</h1>
107
+ <h1 className="text-h2">Edge ingest log</h1>
123
108
  <Badge variant="outline" className="gap-1.5">
124
109
  <Circle aria-hidden className={cn("size-2 fill-current", isFollowing ? "text-success animate-pulse motion-reduce:animate-none" : "text-muted-foreground")} />{isFollowing ? "Live" : "Paused"}
125
110
  </Badge>
126
111
  </div>
127
- <label className="flex items-center gap-2 text-xs text-muted-foreground">Follow tail
112
+ <label className="flex items-center gap-2 text-caption text-muted-foreground">Follow tail
128
113
  <Switch checked={isFollowing} onChange={setIsFollowing} aria-label="Follow the live tail" />
129
114
  </label>
130
115
  </div>
@@ -137,13 +122,13 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
137
122
  <SelectTrigger aria-label="Minimum severity" className="w-44"><SelectValue /></SelectTrigger>
138
123
  <SelectContent>{LEVEL_OPTIONS.map((o) => (<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>))}</SelectContent>
139
124
  </Select>
140
- <span className="hidden items-center gap-1 text-xs text-muted-foreground sm:flex">
125
+ <span className="hidden items-center gap-1 text-caption text-muted-foreground sm:flex">
141
126
  <Kbd size="sm">↑</Kbd><Kbd size="sm">↓</Kbd> move<Kbd size="sm">Enter</Kbd> expand
142
127
  </span>
143
128
  </div>
144
129
  </header>
145
130
  {degraded ? (
146
- <div role="status" className="flex items-center gap-2 border-b border-border bg-muted px-4 py-1.5 text-xs">
131
+ <div role="status" className="flex items-center gap-2 border-b border-border bg-muted px-4 py-1.5 text-caption">
147
132
  <TriangleAlert aria-hidden className="size-4 shrink-0 text-warning" />
148
133
  <span className="font-medium">Buffer truncated.</span>
149
134
  <span className="tabular-nums text-muted-foreground">{degraded.droppedLines.toLocaleString()} lines dropped. {degraded.reason}</span>
@@ -153,10 +138,10 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
153
138
  <div role="alert" className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-4 py-8 text-center">
154
139
  <WifiOff aria-hidden className="size-8 text-destructive" />
155
140
  <div className="space-y-1">
156
- <p className="text-sm font-semibold">Log stream disconnected</p>
157
- <p className="max-w-md text-sm text-muted-foreground">{error}</p>
141
+ <p className="text-h3">Log stream disconnected</p>
142
+ <p className="max-w-md text-small text-muted-foreground">{error}</p>
158
143
  </div>
159
- <div role="status" className="flex items-center gap-2 text-xs text-muted-foreground">
144
+ <div role="status" className="flex items-center gap-2 text-caption text-muted-foreground">
160
145
  <RotateCw aria-hidden className="size-4 animate-spin motion-reduce:animate-none" />Reconnecting to the collector…
161
146
  </div>
162
147
  <Button size="sm" onClick={onRetry}>Retry connection</Button>
@@ -171,13 +156,13 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
171
156
  ))}
172
157
  </div>
173
158
  ) : (
174
- <div className="flex items-center gap-3 border-b border-border px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
175
- <span className="w-24 shrink-0 pl-6">Time</span><span className="w-12 shrink-0">Level</span><span className="w-36 shrink-0">Source</span>
159
+ <div className="flex items-center gap-3 border-b border-border px-4 py-1.5 text-caption font-medium uppercase tracking-wide text-muted-foreground">
160
+ <span className="w-[13ch] shrink-0 pl-6">Time</span><span className="w-12 shrink-0">Level</span><span className="w-36 shrink-0">Source</span>
176
161
  <span className="min-w-0 flex-1">Message</span><span className="w-14 shrink-0 text-right">Status</span><span className="w-24 shrink-0 text-right">Latency</span>
177
162
  </div>
178
163
  )}
179
164
  {!error && !isLoading ? (
180
- <div role="log" aria-live="polite" aria-relevant="additions" aria-label="Edge ingest log stream, newest last" onKeyDown={onListKeyDown} className="min-h-0 flex-1 overflow-auto font-mono">
165
+ <div role="log" aria-live="polite" aria-relevant="additions" aria-label="Edge ingest log stream, newest last" onKeyDown={onListKeyDown} className="min-h-0 flex-1 overflow-auto font-mono text-small">
181
166
  {visible.length === 0 ? (
182
167
  <div className="flex h-full items-center justify-center px-4 py-8">
183
168
  {hasFilter ? (
@@ -193,9 +178,9 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
193
178
  const isOpen = expandedId === line.id;
194
179
  return (
195
180
  <div key={line.id} className="border-b border-border">
196
- <button ref={(el) => { rowRefs.current[i] = el; }} tabIndex={i === activeIndex ? 0 : -1} aria-expanded={isOpen} onClick={() => { setActiveIndex(i); setExpandedId((cur) => (cur === line.id ? null : line.id)); }} className="flex w-full items-center gap-3 px-4 py-1 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
181
+ <button ref={(el) => { rowRefs.current[i] = el; }} tabIndex={i === activeIndex ? 0 : -1} aria-expanded={isOpen} onClick={() => { setActiveIndex(i); setExpandedId((cur) => (cur === line.id ? null : line.id)); }} className="motion-lift flex w-full items-center gap-3 px-4 py-1 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
197
182
  {isOpen ? <ChevronDown aria-hidden className="size-3.5 shrink-0 text-muted-foreground" /> : <ChevronRight aria-hidden className="size-3.5 shrink-0 text-muted-foreground" />}
198
- <span className="w-24 shrink-0 tabular-nums text-muted-foreground">{formatClock(line.timestamp)}</span>
183
+ <span className="w-[13ch] shrink-0 tabular-nums text-muted-foreground">{formatClock(line.timestamp)}</span>
199
184
  <span className={cn("w-12 shrink-0 font-medium", meta.className)}>{meta.label}</span>
200
185
  <span className="w-36 shrink-0 truncate text-muted-foreground">{line.source}</span>
201
186
  <span className="min-w-0 flex-1 truncate">{line.message}</span>
@@ -217,7 +202,7 @@ export function LogStream({ lines, isLoading = false, error = null, degraded = n
217
202
  )}
218
203
  </div>
219
204
  ) : null}
220
- <footer className="flex items-center justify-between gap-3 border-t border-border px-4 py-1.5 text-xs text-muted-foreground">
205
+ <footer className="flex items-center justify-between gap-3 border-t border-border px-4 py-1.5 text-caption text-muted-foreground">
221
206
  <span className="tabular-nums">Showing {visible.length} of {allLines.length} lines</span>
222
207
  <span className="hidden items-center gap-1 sm:flex"><Kbd size="sm">/</Kbd> to filter</span>
223
208
  </footer>
@@ -236,6 +221,21 @@ const SEED_LINES: LogLine[] = [
236
221
  { id: "l-07", timestamp: "2026-07-04T09:12:07.418Z", level: "info", source: "edge-ingest.writer", message: "Retry succeeded on queue.secondary", status: 202, latencyMs: 61, detail: { upstream: "queue.secondary", trace_id: "trc_d9a2f0", attempt: 3, bytes_in: 2048 } },
237
222
  { id: "l-08", timestamp: "2026-07-04T09:12:08.902Z", level: "warn", source: "edge-ingest.schema", message: "Event field ts coerced from string to int64", status: 202, latencyMs: 39, detail: { trace_id: "trc_e5f712", field: "ts", records: 3 } },
238
223
  { id: "l-09", timestamp: "2026-07-04T09:12:09.377Z", level: "info", source: "edge-ingest.router", message: "POST /v1/events accepted, 12 records queued", status: 202, latencyMs: 44, detail: { upstream: "queue.primary", partition: 1, trace_id: "trc_f00312", bytes_in: 8192 } },
224
+ { id: "l-10", timestamp: "2026-07-04T09:12:09.481Z", level: "debug", source: "edge-ingest.dns", message: "Resolved queue.primary to 10.8.0.14 from cache", status: null, latencyMs: 0, detail: { host: "queue.primary", ttl_s: 42, cached: "true" } },
225
+ { id: "l-11", timestamp: "2026-07-04T09:12:09.502Z", level: "warn", source: "edge-ingest.router", message: "POST /v1/events rejected: payload 12.4 MB exceeds the 10 MB per-batch ceiling for tenant_2210", status: 413, latencyMs: 18, detail: { tenant: "tenant_2210", bytes_in: 13002744, limit_bytes: 10485760, trace_id: "trc_04c1aa" } },
226
+ { id: "l-12", timestamp: "2026-07-04T09:12:09.594Z", level: "info", source: "edge-ingest.compress", message: "Batch recompressed zstd level 3, ratio 6.2:1", status: null, latencyMs: 11, detail: { codec: "zstd", ratio: "6.2:1", bytes_out: 1322 } },
227
+ { id: "l-13", timestamp: "2026-07-04T09:12:09.688Z", level: "error", source: "edge-ingest.router", message: "POST /v1/events malformed JSON at offset 1024, batch dropped", status: 400, latencyMs: 6, detail: { tenant: "tenant_8804", offset: 1024, trace_id: "trc_15d2bb", error: "unexpected end of input" } },
228
+ { id: "l-14", timestamp: "2026-07-04T09:12:09.771Z", level: "info", source: "edge-ingest.writer", message: "Batch flushed to queue.primary, 47 records", status: 202, latencyMs: 52, detail: { upstream: "queue.primary", flushed: 47, trace_id: "trc_26e3cc" } },
229
+ { id: "l-15", timestamp: "2026-07-04T09:12:09.845Z", level: "debug", source: "edge-ingest.cache", message: "Hot-set hit ratio 0.94 over the last 10s window", status: null, latencyMs: null, detail: { hits: 8812, misses: 561, window_s: 10 } },
230
+ { id: "l-16", timestamp: "2026-07-04T09:12:09.912Z", level: "info", source: "edge-ingest.router", message: "GET /v1/streams/live upgraded to websocket", status: 101, latencyMs: 9, detail: { upstream: "gateway.ws", conn_id: "ws_7742", trace_id: "trc_37f4dd" } },
231
+ { id: "l-17", timestamp: "2026-07-04T09:12:10.033Z", level: "warn", source: "edge-ingest.schema", message: "Field amount_cents received as float for 9 records, coerced to int64 with rounding", status: 202, latencyMs: 41, detail: { field: "amount_cents", records: 9, trace_id: "trc_48a5ee" } },
232
+ { id: "l-18", timestamp: "2026-07-04T09:12:10.127Z", level: "info", source: "edge-ingest.router", message: "POST /v1/events accepted, 3 records queued", status: 202, latencyMs: 28, detail: { upstream: "queue.primary", partition: 2, trace_id: "trc_59b6ff", bytes_in: 1536 } },
233
+ { id: "l-19", timestamp: "2026-07-04T09:12:10.204Z", level: "error", source: "edge-ingest.writer", message: "queue.secondary returned 503, circuit breaker half-open after 2 consecutive failures", status: 503, latencyMs: 2891, detail: { upstream: "queue.secondary", attempt: 2, breaker: "half-open", trace_id: "trc_6ac700" } },
234
+ { id: "l-20", timestamp: "2026-07-04T09:12:10.311Z", level: "debug", source: "edge-ingest.tls", message: "TLS handshake completed for client 10.2.7.44", status: null, latencyMs: 5, detail: { cipher: "TLS_AES_256_GCM", client_ip: "10.2.7.44", alpn: "h2" } },
235
+ { id: "l-21", timestamp: "2026-07-04T09:12:10.398Z", level: "info", source: "edge-ingest.auth", message: "Token refreshed for tenant_4821, scope events:write", status: 200, latencyMs: 14, detail: { tenant: "tenant_4821", scope: "events:write", expires_in_s: 3600 } },
236
+ { id: "l-22", timestamp: "2026-07-04T09:12:10.476Z", level: "warn", source: "edge-ingest.ratelimit", message: "Client tenant_9917 throttled, 429 returned", status: 429, latencyMs: 7, detail: { tenant: "tenant_9917", limit: "300/min", retry_after_s: 60, trace_id: "trc_7bd811" } },
237
+ { id: "l-23", timestamp: "2026-07-04T09:12:10.581Z", level: "info", source: "edge-ingest.router", message: "GET /v1/health checked by readiness probe", status: 200, latencyMs: 2, detail: { probe: "readiness", trace_id: "trc_8ce922" } },
238
+ { id: "l-24", timestamp: "2026-07-04T09:12:10.672Z", level: "info", source: "edge-ingest.writer", message: "Batch flushed to queue.primary, 21 records", status: 202, latencyMs: 49, detail: { upstream: "queue.primary", flushed: 21, trace_id: "trc_9df033" } },
239
239
  ];
240
240
 
241
241
  const STREAM_LINES: LogLine[] = [