* refactor(ui): adopt Badge component consistently Replaces hand-rolled `<span class="rounded ...">` pill markup with the shared `<Badge>` component in billing, team, source detail, and tools pages. Also documents the preference in AGENTS.md so new code reaches for `<Badge>` and `<CardStack>` instead of reinventing them. No behaviour change — the visual output is equivalent to the previous inline styles, just routed through the shared component so the styling stays consistent across the app. * Update AGENTS.md ---------
Unified · Split
| @@ -1,6 +1,7 @@ | |||
| 1 | import { createFileRoute, Link } from "@tanstack/react-router"; | 1 | import { createFileRoute, Link } from "@tanstack/react-router"; |
| 2 | import { useCustomer, useListPlans } from "autumn-js/react"; | 2 | import { useCustomer, useListPlans } from "autumn-js/react"; |
| 3 | import { Button } from "@executor/react/components/button"; | 3 | import { Button } from "@executor/react/components/button"; |
| 4 | import { Badge } from "@executor/react/components/badge"; | ||
| 4 | 5 | ||
| 5 | type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number]; | 6 | type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number]; |
| 6 | 7 | ||
| @@ -68,14 +69,14 @@ function BillingPage() { · 61 unmodified lines | |||
| 68 | {planName} | 69 | {planName} |
| 69 | </p> | 70 | </p> |
| 70 | {isSwitching && ( | 71 | {isSwitching && ( |
| 71 | <span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-amber-600 dark:text-amber-400 leading-none"> | 72 | <Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400"> |
| 72 | Switching | 73 | Switching |
| 73 | </span> | 74 | </Badge> |
| 74 | )} | 75 | )} |
| 75 | {isCanceling && !isSwitching && ( | 76 | {isCanceling && !isSwitching && ( |
| 76 | <span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-amber-600 dark:text-amber-400 leading-none"> | 77 | <Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400"> |
| 77 | Canceling | 78 | Canceling |
| 78 | </span> | 79 | </Badge> |
| 79 | )} | 80 | )} |
| 80 | </div> | 81 | </div> |
| 81 | <p className="mt-1 text-[0.75rem] text-muted-foreground/70 leading-none"> | 82 | <p className="mt-1 text-[0.75rem] text-muted-foreground/70 leading-none"> |
| @@ -2,6 +2,7 @@ import { useState } from "react"; · 1 unmodified line | |||
| 2 | import { createFileRoute, Link } from "@tanstack/react-router"; | 2 | import { createFileRoute, Link } from "@tanstack/react-router"; |
| 3 | import { useCustomer, useListPlans } from "autumn-js/react"; | 3 | import { useCustomer, useListPlans } from "autumn-js/react"; |
| 4 | import { Button } from "@executor/react/components/button"; | 4 | import { Button } from "@executor/react/components/button"; |
| 5 | import { Badge } from "@executor/react/components/badge"; | ||
| 5 | 6 | ||
| 6 | type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number]; | 7 | type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number]; |
| 7 | 8 | ||
| @@ -120,19 +121,19 @@ function PlansPage() { · 112 unmodified lines | |||
| 120 | {plan.name} | 121 | {plan.name} |
| 121 | </p> | 122 | </p> |
| 122 | {isCurrent && ( | 123 | {isCurrent && ( |
| 123 | <span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-amber-600 dark:text-amber-400 leading-none"> | 124 | <Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400"> |
| 124 | Your plan | 125 | Your plan |
| 125 | </span> | 126 | </Badge> |
| 126 | )} | 127 | )} |
| 127 | {isCanceling && ( | 128 | {isCanceling && ( |
| 128 | <span className="rounded-full bg-amber-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-amber-600 dark:text-amber-400 leading-none"> | 129 | <Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400"> |
| 129 | Canceling | 130 | Canceling |
| 130 | </span> | 131 | </Badge> |
| 131 | )} | 132 | )} |
| 132 | {isScheduled && ( | 133 | {isScheduled && ( |
| 133 | <span className="rounded-full bg-emerald-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-emerald-600 dark:text-emerald-400 leading-none"> | 134 | <Badge className="bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"> |
| 134 | Scheduled | 135 | Scheduled |
| 135 | </span> | 136 | </Badge> |
| 136 | )} | 137 | )} |
| 137 | </div> | 138 | </div> |
| 138 | <p className="mt-1 text-[0.75rem] text-muted-foreground/70">{meta.tagline}</p> | 139 | <p className="mt-1 text-[0.75rem] text-muted-foreground/70">{meta.tagline}</p> |
| @@ -13,6 +13,7 @@ import { · 12 unmodified lines | |||
| 13 | DialogClose, | 13 | DialogClose, |
| 14 | } from "@executor/react/components/dialog"; | 14 | } from "@executor/react/components/dialog"; |
| 15 | import { Button } from "@executor/react/components/button"; | 15 | import { Button } from "@executor/react/components/button"; |
| 16 | import { Badge } from "@executor/react/components/badge"; | ||
| 16 | import { Input } from "@executor/react/components/input"; | 17 | import { Input } from "@executor/react/components/input"; |
| 17 | import { Label } from "@executor/react/components/label"; | 18 | import { Label } from "@executor/react/components/label"; |
| 18 | import { | 19 | import { |
| @@ -214,14 +215,12 @@ function TeamPage() { · 195 unmodified lines | |||
| 214 | {member.name ?? member.email} | 215 | {member.name ?? member.email} |
| 215 | </p> | 216 | </p> |
| 216 | {member.isCurrentUser && ( | 217 | {member.isCurrentUser && ( |
| 217 | <span className="rounded bg-muted px-1.5 py-0.5 text-[0.625rem] font-medium text-muted-foreground leading-none"> | 218 | <Badge className="bg-muted text-muted-foreground">You</Badge> |
| 218 | You | ||
| 219 | </span> | ||
| 220 | )} | 219 | )} |
| 221 | {member.status === "pending" && ( | 220 | {member.status === "pending" && ( |
| 222 | <span className="rounded bg-amber-500/10 px-1.5 py-0.5 text-[0.625rem] font-medium text-amber-600 dark:text-amber-400 leading-none"> | 221 | <Badge className="bg-amber-500/10 text-amber-600 dark:text-amber-400"> |
| 223 | Invited | 222 | Invited |
| 224 | </span> | 223 | </Badge> |
| 225 | )} | 224 | )} |
| 226 | </div> | 225 | </div> |
| 227 | {member.name && ( | 226 | {member.name && ( |
| @@ -14,6 +14,7 @@ import type { ToolSummary } from "../components/tool-tree"; · 13 unmodified lines | |||
| 14 | import { useScope } from "../hooks/use-scope"; | 14 | import { useScope } from "../hooks/use-scope"; |
| 15 | import type { SourcePlugin } from "../plugins/source-plugin"; | 15 | import type { SourcePlugin } from "../plugins/source-plugin"; |
| 16 | import { Button } from "../components/button"; | 16 | import { Button } from "../components/button"; |
| 17 | import { Badge } from "../components/badge"; | ||
| 17 | 18 | ||
| 18 | export function SourceDetailPage(props: { | 19 | export function SourceDetailPage(props: { |
| 19 | namespace: string; | 20 | namespace: string; |
| @@ -116,13 +117,9 @@ export function SourceDetailPage(props: { · 96 unmodified lines | |||
| 116 | {sourceData?.name ?? namespace} | 117 | {sourceData?.name ?? namespace} |
| 117 | </h2> | 118 | </h2> |
| 118 | {sourceData?.runtime && ( | 119 | {sourceData?.runtime && ( |
| 119 | <span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"> | 120 | <Badge className="bg-muted text-muted-foreground">built-in</Badge> |
| 120 | built-in | ||
| 121 | </span> | ||
| 122 | )} | 121 | )} |
| 123 | <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"> | 122 | <Badge variant="secondary">{sourceData?.kind ?? "source"}</Badge> |
| 124 | {sourceData?.kind ?? "source"} | ||
| 125 | </span> | ||
| 126 | {Result.isSuccess(tools) && !editing && ( | 123 | {Result.isSuccess(tools) && !editing && ( |
| 127 | <span className="hidden text-[11px] tabular-nums text-muted-foreground/50 sm:block"> | 124 | <span className="hidden text-[11px] tabular-nums text-muted-foreground/50 sm:block"> |
| 128 | {sourceTools.length} {sourceTools.length === 1 ? "tool" : "tools"} | 125 | {sourceTools.length} {sourceTools.length === 1 ? "tool" : "tools"} |
| @@ -208,7 +205,7 @@ export function SourceDetailPage(props: { · 79 unmodified lines | |||
| 208 | onSuccess: () => ( | 205 | onSuccess: () => ( |
| 209 | <div className="flex min-h-0 flex-1 overflow-hidden"> | 206 | <div className="flex min-h-0 flex-1 overflow-hidden"> |
| 210 | {/* Left: tool tree */} | 207 | {/* Left: tool tree */} |
| 211 | <div className="flex w-72 shrink-0 flex-col border-r border-border bg-card/30 lg:w-80 xl:w-[22rem]"> | 208 | <div className="flex w-72 shrink-0 flex-col border-r border-border/60 lg:w-80 xl:w-[22rem]"> |
| 212 | <ToolTree | 209 | <ToolTree |
| 213 | tools={sourceTools} | 210 | tools={sourceTools} |
| 214 | selectedToolId={selectedToolId} | 211 | selectedToolId={selectedToolId} |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { useAtomValue, Result } from "@effect-atom/atom-react"; | 1 | import { useAtomValue, Result } from "@effect-atom/atom-react"; |
| 2 | import { toolsAtom } from "../api/atoms"; | 2 | import { toolsAtom } from "../api/atoms"; |
| 3 | import { useScope } from "../hooks/use-scope"; | 3 | import { useScope } from "../hooks/use-scope"; |
| 4 | import { Badge } from "../components/badge"; | ||
| 4 | 5 | ||
| 5 | export function ToolsPage() { | 6 | export function ToolsPage() { |
| 6 | const scopeId = useScope(); | 7 | const scopeId = useScope(); |
| @@ -61,9 +62,7 @@ export function ToolsPage() { · 54 unmodified lines | |||
| 61 | </p> | 62 | </p> |
| 62 | )} | 63 | )} |
| 63 | </div> | 64 | </div> |
| 64 | <span className="shrink-0 rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-secondary-foreground"> | 65 | <Badge variant="secondary">{t.sourceId}</Badge> |
| 65 | {t.sourceId} | ||
| 66 | </span> | ||
| 67 | </div> | 66 | </div> |
| 68 | ))} | 67 | ))} |
| 69 | </div> | 68 | </div> |