portable-agent-layer 0.71.0 → 0.73.0

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 (104) hide show
  1. package/package.json +10 -7
  2. package/src/cli/migrate.ts +1 -1
  3. package/src/cli/server.ts +7 -0
  4. package/src/cli/skill.ts +1 -1
  5. package/src/hooks/CompactRecover.ts +28 -86
  6. package/src/hooks/LedgerUnapplied.ts +3 -28
  7. package/src/hooks/LoadContext.ts +33 -60
  8. package/src/hooks/SecurityValidator.ts +16 -109
  9. package/src/hooks/handlers/agenda.ts +195 -7
  10. package/src/hooks/handlers/failure-principle.ts +19 -44
  11. package/src/hooks/handlers/session-intelligence.ts +13 -70
  12. package/src/hooks/lib/agenda-store.ts +2 -0
  13. package/src/hooks/lib/capture-store.ts +103 -0
  14. package/src/hooks/lib/compact-recall.ts +89 -0
  15. package/src/hooks/lib/failure-principle.ts +98 -0
  16. package/src/hooks/lib/goal-links.ts +83 -0
  17. package/src/hooks/lib/ledger-hook.ts +35 -0
  18. package/src/hooks/lib/ledger.ts +48 -1
  19. package/src/hooks/lib/models.ts +1 -1
  20. package/src/hooks/lib/projects.ts +5 -0
  21. package/src/hooks/lib/security-gate.ts +159 -0
  22. package/src/hooks/lib/session-context.ts +74 -0
  23. package/src/hooks/lib/settings.ts +2 -0
  24. package/src/hooks/lib/telos-goals.ts +8 -2
  25. package/src/hooks/lib/token-usage.ts +2 -0
  26. package/src/tools/agent/algorithm-reflect.ts +28 -97
  27. package/src/tools/agent/analyze.ts +19 -120
  28. package/src/tools/agent/handoff-note.ts +29 -77
  29. package/src/tools/agent/project.ts +34 -153
  30. package/src/tools/agent/relationship-note.ts +27 -46
  31. package/src/tools/agent/synthesize.ts +1 -1
  32. package/src/tools/agent/thread.ts +43 -123
  33. package/src/tools/control-room/attention.ts +146 -0
  34. package/src/tools/control-room/data.ts +78 -7
  35. package/src/tools/control-room/detail.ts +158 -0
  36. package/src/tools/control-room/matrix.ts +92 -20
  37. package/src/tools/control-room/prefs.ts +96 -0
  38. package/src/tools/control-room/server-config.ts +8 -0
  39. package/src/tools/control-room/server.ts +196 -11
  40. package/src/tools/control-room/snooze.ts +65 -0
  41. package/src/tools/control-room/static.ts +68 -0
  42. package/src/tools/control-room/ui/app.tsx +196 -50
  43. package/src/tools/control-room/ui/attention-bell.tsx +118 -0
  44. package/src/tools/control-room/ui/components/README.md +18 -0
  45. package/src/tools/control-room/ui/components/badge.tsx +38 -0
  46. package/src/tools/control-room/ui/components/button.tsx +43 -0
  47. package/src/tools/control-room/ui/components/card.tsx +45 -0
  48. package/src/tools/control-room/ui/components/input.tsx +24 -0
  49. package/src/tools/control-room/ui/components/label.tsx +18 -0
  50. package/src/tools/control-room/ui/components/popover.tsx +37 -0
  51. package/src/tools/control-room/ui/components/separator.tsx +25 -0
  52. package/src/tools/control-room/ui/components/skeleton.tsx +14 -0
  53. package/src/tools/control-room/ui/components/switch.tsx +27 -0
  54. package/src/tools/control-room/ui/components/table.tsx +60 -0
  55. package/src/tools/control-room/ui/components/toggle-group.tsx +38 -0
  56. package/src/tools/control-room/ui/dist/assets/index-BoQjFpzV.js +49 -0
  57. package/src/tools/control-room/ui/dist/assets/index-Dncp2bYg.css +2 -0
  58. package/src/tools/control-room/ui/dist/index.html +19 -0
  59. package/src/tools/control-room/ui/format.ts +0 -12
  60. package/src/tools/control-room/ui/frame.tsx +125 -0
  61. package/src/tools/control-room/ui/index.html +2 -2
  62. package/src/tools/control-room/ui/lib/api.ts +27 -0
  63. package/src/tools/control-room/ui/lib/cn.ts +6 -0
  64. package/src/tools/control-room/ui/lib/write.ts +43 -0
  65. package/src/tools/control-room/ui/package.json +28 -0
  66. package/src/tools/control-room/ui/parts.tsx +235 -0
  67. package/src/tools/control-room/ui/screens/log.tsx +274 -0
  68. package/src/tools/control-room/ui/screens/project-detail.tsx +302 -0
  69. package/src/tools/control-room/ui/screens/projects.tsx +128 -0
  70. package/src/tools/control-room/ui/screens/settings.tsx +205 -0
  71. package/src/tools/control-room/ui/screens/today.tsx +391 -0
  72. package/src/tools/control-room/ui/theme.css +127 -0
  73. package/src/tools/control-room/ui/vite.config.ts +36 -0
  74. package/src/tools/control-room/writes.ts +106 -0
  75. package/src/tools/ledger/outcomes.ts +9 -0
  76. package/src/tools/ledger/query.ts +2 -0
  77. package/src/tools/ledger/view.ts +34 -10
  78. package/src/tools/lib/algorithm-reflect.ts +84 -0
  79. package/src/tools/lib/analyze-report.ts +120 -0
  80. package/src/tools/lib/handoff-note.ts +102 -0
  81. package/src/tools/lib/note-flags.ts +59 -0
  82. package/src/tools/lib/project-isc.ts +212 -0
  83. package/src/tools/lib/relationship-reflect.ts +402 -0
  84. package/src/tools/lib/self-model.ts +499 -0
  85. package/src/tools/lib/session-usage.ts +216 -0
  86. package/src/tools/lib/skill-doctor.ts +457 -0
  87. package/src/tools/lib/thread.ts +119 -0
  88. package/src/tools/lib/token-report.ts +173 -0
  89. package/src/tools/lib/transcript-usage.ts +42 -0
  90. package/src/tools/lib/usage-buckets.ts +329 -0
  91. package/src/tools/relationship-reflect.ts +48 -412
  92. package/src/tools/self-model.ts +76 -558
  93. package/src/tools/session-summary.ts +8 -215
  94. package/src/tools/skill-doctor.ts +9 -444
  95. package/src/tools/token-cost.ts +18 -428
  96. package/src/tools/control-room/ui/agenda.tsx +0 -43
  97. package/src/tools/control-room/ui/agents.tsx +0 -67
  98. package/src/tools/control-room/ui/app.css +0 -857
  99. package/src/tools/control-room/ui/board.tsx +0 -82
  100. package/src/tools/control-room/ui/handoffs.tsx +0 -37
  101. package/src/tools/control-room/ui/ledger.tsx +0 -136
  102. package/src/tools/control-room/ui/matrix.tsx +0 -117
  103. package/src/tools/control-room/ui/panel.tsx +0 -60
  104. package/src/tools/control-room/ui/signal.tsx +0 -161
@@ -0,0 +1,118 @@
1
+ import { useState } from "react";
2
+ import { useNavigate } from "react-router";
3
+ import type { AttentionItem, AttentionView } from "../attention";
4
+ import { Button } from "./components/button";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "./components/popover";
6
+ import { Empty } from "./frame";
7
+ import { useLoaded } from "./lib/api";
8
+ import { cn } from "./lib/cn";
9
+ import { post } from "./lib/write";
10
+
11
+ const DOT: Record<AttentionItem["severity"], string> = {
12
+ alarm: "bg-alarm",
13
+ waiting: "bg-accent",
14
+ note: "bg-neutral-400",
15
+ };
16
+
17
+ function BellIcon() {
18
+ return (
19
+ <svg
20
+ width="16"
21
+ height="16"
22
+ viewBox="0 0 24 24"
23
+ fill="none"
24
+ stroke="currentColor"
25
+ strokeWidth="1.5"
26
+ strokeLinecap="round"
27
+ strokeLinejoin="round"
28
+ aria-hidden="true"
29
+ >
30
+ <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
31
+ <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
32
+ </svg>
33
+ );
34
+ }
35
+
36
+ function Item({ item, onOpen }: { item: AttentionItem; onOpen: () => void }) {
37
+ return (
38
+ <button
39
+ type="button"
40
+ onClick={onOpen}
41
+ className={cn(
42
+ "grid w-full cursor-pointer grid-cols-[8px_1fr_auto] items-start gap-3 border-b border-divider px-4 py-3 text-left last:border-b-0 hover:bg-neutral-200",
43
+ item.read && "opacity-55"
44
+ )}
45
+ >
46
+ <span className={cn("mt-1.5 size-1.5", DOT[item.severity])} />
47
+ <span>
48
+ <span className="block font-medium">{item.title}</span>
49
+ <span className="block text-[12px] text-neutral-700">{item.detail}</span>
50
+ </span>
51
+ <span className="text-[11px] whitespace-nowrap text-neutral-600">
52
+ {item.at.slice(5, 10)}
53
+ </span>
54
+ </button>
55
+ );
56
+ }
57
+
58
+ export function AttentionBell() {
59
+ const [nonce, setNonce] = useState(0);
60
+ const [open, setOpen] = useState(false);
61
+ const view = useLoaded<AttentionView>(`/api/attention?v=${nonce}`);
62
+ const navigate = useNavigate();
63
+ const reload = () => setNonce((n) => n + 1);
64
+ const unread = view.state === "ready" ? view.data.unread : 0;
65
+
66
+ const openItem = (item: AttentionItem) => {
67
+ void post("/api/attention/read", { ids: [item.id] }).then(() => {
68
+ setOpen(false);
69
+ reload();
70
+ void navigate(item.href);
71
+ });
72
+ };
73
+
74
+ return (
75
+ <Popover open={open} onOpenChange={setOpen}>
76
+ <PopoverTrigger asChild>
77
+ <button
78
+ type="button"
79
+ aria-label={`${unread} things need you`}
80
+ className="relative cursor-pointer border border-transparent p-1.5 text-ink hover:border-divider"
81
+ >
82
+ <BellIcon />
83
+ {unread > 0 && (
84
+ <span className="absolute -top-1 -right-1 grid min-w-4 place-items-center bg-accent-900 px-1 text-[10px] font-semibold text-bg tabular-nums">
85
+ {unread}
86
+ </span>
87
+ )}
88
+ </button>
89
+ </PopoverTrigger>
90
+ <PopoverContent className="max-h-[70vh] overflow-auto">
91
+ <div className="flex items-baseline justify-between border-b border-divider px-4 py-2.5">
92
+ <h2 className="font-heading m-0 text-[16px] font-semibold">
93
+ Needs your attention
94
+ </h2>
95
+ <Button
96
+ variant="ghost"
97
+ size="sm"
98
+ onClick={() => {
99
+ void post("/api/attention/read", { all: true }).then(reload);
100
+ }}
101
+ >
102
+ mark all read
103
+ </Button>
104
+ </div>
105
+ {view.state === "ready" && view.data.items.length === 0 ? (
106
+ <div className="px-4">
107
+ <Empty>Nothing is waiting on you.</Empty>
108
+ </div>
109
+ ) : (
110
+ view.state === "ready" &&
111
+ view.data.items.map((item) => (
112
+ <Item key={item.id} item={item} onOpen={() => openItem(item)} />
113
+ ))
114
+ )}
115
+ </PopoverContent>
116
+ </Popover>
117
+ );
118
+ }
@@ -0,0 +1,18 @@
1
+ # ui/components
2
+
3
+ shadcn/ui components, vendored and re-themed for Industry.
4
+
5
+ Two adaptations were made to every file, and both apply to anything added later:
6
+
7
+ - **No RSC.** The `"use client"` directive is dropped — nothing here renders on a
8
+ server. It matches `components.json`'s `"rsc": false`.
9
+ - **Industry, not the shadcn default palette.** The upstream components are
10
+ rounded, filled and shadowed and speak in semantic tokens (`bg-primary`,
11
+ `text-muted-foreground`, `border-input`). Industry is square, hairline and
12
+ unfilled, and its tokens are the ones in `../theme.css` (`accent-*`,
13
+ `neutral-*`, `divider`, `ink`, `bg`). Class strings are rewritten accordingly;
14
+ no parallel semantic token layer exists, so there is one vocabulary to learn.
15
+
16
+ `bunx shadcn@latest add <name>` will fetch a component, but its output needs both
17
+ passes above before it belongs here. Copying the shape from
18
+ `~/Development/git/catalyst/src/components/ui` is usually faster.
@@ -0,0 +1,38 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { Slot } from "radix-ui";
3
+ import type * as React from "react";
4
+ import { cn } from "../lib/cn";
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1 rounded-none border border-transparent px-2 py-0.5 text-[10px] whitespace-nowrap [&>svg]:size-3",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ accent: "bg-accent-100 text-accent-900",
12
+ neutral: "bg-neutral-200 text-neutral-800",
13
+ outline: "border-divider text-neutral-700",
14
+ alarm: "border-alarm/40 bg-alarm/10 text-alarm",
15
+ },
16
+ },
17
+ defaultVariants: { variant: "neutral" },
18
+ }
19
+ );
20
+
21
+ function Badge({
22
+ className,
23
+ variant,
24
+ asChild = false,
25
+ ...props
26
+ }: React.ComponentProps<"span"> &
27
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
28
+ const Comp = asChild ? Slot.Root : "span";
29
+ return (
30
+ <Comp
31
+ data-slot="badge"
32
+ className={cn(badgeVariants({ variant }), className)}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ export { Badge };
@@ -0,0 +1,43 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { Slot } from "radix-ui";
3
+ import type * as React from "react";
4
+ import { cn } from "../lib/cn";
5
+
6
+ const buttonVariants = cva(
7
+ "inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-none border font-medium whitespace-nowrap transition-colors disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ primary: "border-accent bg-accent text-bg hover:bg-accent-600",
12
+ secondary: "border-divider bg-transparent text-ink hover:bg-neutral-200",
13
+ ghost: "border-transparent bg-transparent text-ink hover:bg-neutral-200",
14
+ },
15
+ size: {
16
+ default: "h-8 px-4 text-[12px]",
17
+ sm: "h-6 px-2 text-[11px]",
18
+ icon: "size-8",
19
+ },
20
+ },
21
+ defaultVariants: { variant: "secondary", size: "default" },
22
+ }
23
+ );
24
+
25
+ function Button({
26
+ className,
27
+ variant,
28
+ size,
29
+ asChild = false,
30
+ ...props
31
+ }: React.ComponentProps<"button"> &
32
+ VariantProps<typeof buttonVariants> & { asChild?: boolean }) {
33
+ const Comp = asChild ? Slot.Root : "button";
34
+ return (
35
+ <Comp
36
+ data-slot="button"
37
+ className={cn(buttonVariants({ variant, size, className }))}
38
+ {...props}
39
+ />
40
+ );
41
+ }
42
+
43
+ export { Button };
@@ -0,0 +1,45 @@
1
+ import type * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ /** Industry's card is the blueprint object: square, hairline, unfilled, marked. */
5
+ function Card({ className, ...props }: React.ComponentProps<"section">) {
6
+ return (
7
+ <section
8
+ data-slot="card"
9
+ className={cn("blueprint flex flex-col bg-bg p-4", className)}
10
+ {...props}
11
+ />
12
+ );
13
+ }
14
+
15
+ function CardHeader({ className, ...props }: React.ComponentProps<"header">) {
16
+ return (
17
+ <header
18
+ data-slot="card-header"
19
+ className={cn("mb-3 flex items-baseline justify-between gap-3", className)}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ function CardTitle({ className, ...props }: React.ComponentProps<"h2">) {
26
+ return (
27
+ <h2 data-slot="card-title" className={cn("eyebrow m-0", className)} {...props} />
28
+ );
29
+ }
30
+
31
+ function CardAction({ className, ...props }: React.ComponentProps<"span">) {
32
+ return (
33
+ <span
34
+ data-slot="card-action"
35
+ className={cn("text-[11px] text-neutral-600", className)}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
42
+ return <div data-slot="card-content" className={cn(className)} {...props} />;
43
+ }
44
+
45
+ export { Card, CardAction, CardContent, CardHeader, CardTitle };
@@ -0,0 +1,24 @@
1
+ import type * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ const FIELD =
5
+ "h-8 rounded-none border border-divider bg-bg px-2 text-[12px] text-ink outline-none transition-colors focus-visible:border-accent disabled:pointer-events-none disabled:opacity-45";
6
+
7
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
8
+ return (
9
+ <input type={type} data-slot="input" className={cn(FIELD, className)} {...props} />
10
+ );
11
+ }
12
+
13
+ /** Industry's forms are native controls; catalyst does the same rather than wrap Radix Select. */
14
+ function NativeSelect({ className, ...props }: React.ComponentProps<"select">) {
15
+ return (
16
+ <select
17
+ data-slot="native-select"
18
+ className={cn(FIELD, "cursor-pointer", className)}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ export { Input, NativeSelect };
@@ -0,0 +1,18 @@
1
+ import { Label as LabelPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ function Label({
6
+ className,
7
+ ...props
8
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
9
+ return (
10
+ <LabelPrimitive.Root
11
+ data-slot="label"
12
+ className={cn("eyebrow flex items-center gap-2 select-none", className)}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ export { Label };
@@ -0,0 +1,37 @@
1
+ import { Popover as PopoverPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
6
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
7
+ }
8
+
9
+ function PopoverTrigger({
10
+ ...props
11
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
12
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
13
+ }
14
+
15
+ function PopoverContent({
16
+ className,
17
+ align = "end",
18
+ sideOffset = 8,
19
+ ...props
20
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
21
+ return (
22
+ <PopoverPrimitive.Portal>
23
+ <PopoverPrimitive.Content
24
+ data-slot="popover-content"
25
+ align={align}
26
+ sideOffset={sideOffset}
27
+ className={cn(
28
+ "z-50 w-[380px] rounded-none border border-divider bg-bg text-ink shadow-lg outline-none",
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ </PopoverPrimitive.Portal>
34
+ );
35
+ }
36
+
37
+ export { Popover, PopoverContent, PopoverTrigger };
@@ -0,0 +1,25 @@
1
+ import { Separator as SeparatorPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ function Separator({
6
+ className,
7
+ orientation = "horizontal",
8
+ decorative = true,
9
+ ...props
10
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
11
+ return (
12
+ <SeparatorPrimitive.Root
13
+ data-slot="separator"
14
+ decorative={decorative}
15
+ orientation={orientation}
16
+ className={cn(
17
+ "shrink-0 bg-divider data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ export { Separator };
@@ -0,0 +1,14 @@
1
+ import type * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
5
+ return (
6
+ <div
7
+ data-slot="skeleton"
8
+ className={cn("animate-pulse rounded-none bg-neutral-200", className)}
9
+ {...props}
10
+ />
11
+ );
12
+ }
13
+
14
+ export { Skeleton };
@@ -0,0 +1,27 @@
1
+ import { Switch as SwitchPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ /** Square, like everything else Industry draws — the knob slides, nothing rounds. */
6
+ function Switch({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
10
+ return (
11
+ <SwitchPrimitive.Root
12
+ data-slot="switch"
13
+ className={cn(
14
+ "inline-flex h-4 w-7 shrink-0 cursor-pointer items-center border border-divider transition-colors data-[state=checked]:bg-accent data-[state=unchecked]:bg-transparent",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <SwitchPrimitive.Thumb
20
+ data-slot="switch-thumb"
21
+ className="block size-2.5 transition-transform data-[state=checked]:translate-x-3.5 data-[state=checked]:bg-bg data-[state=unchecked]:translate-x-0.5 data-[state=unchecked]:bg-neutral-500"
22
+ />
23
+ </SwitchPrimitive.Root>
24
+ );
25
+ }
26
+
27
+ export { Switch };
@@ -0,0 +1,60 @@
1
+ import type * as React from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ /**
5
+ * Rows scroll under a header that stays. That needs `border-separate`, because a
6
+ * collapsed border belongs to the table rather than the cell and does not travel
7
+ * with a sticky one — so the header rule is drawn as a shadow instead.
8
+ */
9
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
10
+ return (
11
+ <table
12
+ data-slot="table"
13
+ className={cn("w-full border-separate border-spacing-0 text-left", className)}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
20
+ return <thead data-slot="table-header" className={cn(className)} {...props} />;
21
+ }
22
+
23
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
24
+ return <tbody data-slot="table-body" className={cn(className)} {...props} />;
25
+ }
26
+
27
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
28
+ return (
29
+ <tr
30
+ data-slot="table-row"
31
+ className={cn("transition-colors hover:bg-neutral-200/50", className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
38
+ return (
39
+ <th
40
+ data-slot="table-head"
41
+ className={cn(
42
+ "eyebrow sticky top-0 z-10 bg-bg px-3 py-2 shadow-[0_1px_0_var(--color-divider)]",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
51
+ return (
52
+ <td
53
+ data-slot="table-cell"
54
+ className={cn("border-b border-divider/60 px-3 py-2 align-top", className)}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow };
@@ -0,0 +1,38 @@
1
+ import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ /** Industry's segmented control: one hairline box, hairline rules between options. */
6
+ function ToggleGroup({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Root>) {
10
+ return (
11
+ <ToggleGroupPrimitive.Root
12
+ data-slot="toggle-group"
13
+ className={cn(
14
+ "flex items-stretch border border-divider text-[11px] leading-none",
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ function ToggleGroupItem({
23
+ className,
24
+ ...props
25
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Item>) {
26
+ return (
27
+ <ToggleGroupPrimitive.Item
28
+ data-slot="toggle-group-item"
29
+ className={cn(
30
+ "cursor-pointer border-l border-divider bg-transparent px-3 py-2 text-ink transition-colors first:border-l-0 hover:bg-neutral-200 data-[state=on]:bg-accent data-[state=on]:text-bg",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ export { ToggleGroup, ToggleGroupItem };