Custom instructions Written once, in force forever, forgotten by March. Each rule carries a date, the project layer overrides the global one, and the block spends part of every turn whether or not it still applies. npx shadcn@latest add button progress Tokens this needs: --card, --card-foreground, --muted, --muted-foreground, --border, --status-warn, --chart-1, --scale-seq-3 The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // CustomInstructions.tsx ──────────────────────────────────────────────────────────────────────── import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; /** A rule the viewer wrote, and when. The date is what turns a standing order * into something a person can audit six months later. */ export type Rule = { text: string; written: Date }; /** Two scopes, no more. Project sits above global and wins on conflict; the * order is printed rather than left to be discovered. */ export type Scope = "project" | "global"; export type Layer = { scope: Scope; name: string; rules: Rule[] }; const SCOPE_ORDER: Scope[] = ["project", "global"]; const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const day = (d: Date) => `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`; type Props = { layers: Layer[]; /** Characters the block may spend on each turn. Every rule is prepended to * every conversation, so the cost is per turn and it never stops. */ limit: number; /** The chip on the conversation opens the text. One click, no settings dive. */ onOpen: () => void; }; export function CustomInstructions({ layers, limit, onOpen }: Props) { const ordered = [...layers].sort((a, b) => SCOPE_ORDER.indexOf(a.scope) - SCOPE_ORDER.indexOf(b.scope)); const rules = ordered.flatMap((l) => l.rules); const used = rules.reduce((n, r) => n + r.text.length, 0); const pct = Math.min(100, Math.round((used / limit) * 100)); return (
{ordered.map((layer, i) => (
0 ? "border-t" : undefined}>

{layer.name}

    {layer.rules.map((r) => (
  • {r.text}
  • ))}
))}

project overrides global

{used} of {limit} characters, spent on every turn, forever

{/* The box is not a settings panel and should not look like one. */}

Standing guidance: advisory rather than binding, and a long block dilutes itself

); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { CustomInstructions } from "./CustomInstructions"; /** * One project rule over two global ones, each dated. 99 characters against a * 400 budget puts the bar at a quarter. The chip is what a conversation shows; * clicking it would open this panel. */ export default function Demo() { return ( {}} /> ); }