@spunto/design-system 0.5.0 → 0.6.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.
package/README.md CHANGED
@@ -51,6 +51,10 @@ const nextConfig = { transpilePackages: ["@spunto/design-system"] }
51
51
  (a confirmation variant, non-dismissible), `Tooltip`. Their portals render into
52
52
  the provider's overlay container; `Tooltip`'s shared delay group is mounted by the
53
53
  provider too.
54
+ - _Terminal_ — `Terminal` (+`TerminalHandle`, `TerminalOptions`), a transport-agnostic
55
+ xterm.js surface: mount, shared dark ANSI theme, fit-on-resize. No opinion on where
56
+ bytes come from (WebSocket, SSE, a static string) — feed it via the `write`/`writeln`
57
+ ref handle. Opt-in addons through `options` (currently `webLinks`, clickable URLs).
54
58
  - **`SpuntoProvider` + `toast()`** — the imperative toast system (see below). The
55
59
  provider is also the umbrella that mounts the tooltip delay group and the overlay
56
60
  portal container the dialogs/selects render into.
@@ -104,6 +108,26 @@ toast.dismiss(id)
104
108
  `toastPosition` (default `"top-right"`), `toastDuration` (default `5000`, `0` =
105
109
  persistent) and `toastLimit` (default `3`).
106
110
 
111
+ ## LLM / agent docs (`llms.txt`)
112
+
113
+ The [showcase](showcase/) also exposes its documentation as **plain markdown**,
114
+ so coding agents can consume the component APIs directly. Everything is generated
115
+ from the same content that drives the visual pages
116
+ (`showcase/src/content/components.ts`) — there is no second source to keep in sync.
117
+
118
+ Served both in dev (Vite middleware) and in the built site (static files) at
119
+ `https://design.spunto.net`:
120
+
121
+ - **`/llms.txt`** — index (llmstxt.org convention) linking every page.
122
+ - **`/<component>.md`** — one page per component with import, example and the full
123
+ props API (e.g. `/button.md`, `/select.md`).
124
+ - **`/colors.md`**, `/typography.md`, `/radius.md` — the foundations/tokens.
125
+ - **`/llms-full.txt`** — every page concatenated into a single file.
126
+
127
+ Adding a component = add its entry to `content/components.ts` + a demo file in
128
+ `showcase/src/demos/`; the visual page and its `.md` are then produced
129
+ automatically (see `showcase/vite-plugin-llms.ts`).
130
+
107
131
  ## Peer requirements
108
132
 
109
133
  - React 19+, and a **Tailwind CSS v4** pipeline in the consuming app.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spunto/design-system",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Spunto's shared design system — warm/flame tokens, color constants, and UI primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,6 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@xterm/addon-fit": "^0.11.0",
48
+ "@xterm/addon-web-links": "^0.12.0",
48
49
  "@xterm/xterm": "^6.0.0",
49
50
  "class-variance-authority": "^0.7.1",
50
51
  "clsx": "^2.1.1",
@@ -42,6 +42,11 @@ export interface TerminalHandle {
42
42
  focus: () => void
43
43
  }
44
44
 
45
+ export interface TerminalOptions {
46
+ /** Turns URLs in terminal output into clickable links (`@xterm/addon-web-links`). */
47
+ webLinks?: boolean
48
+ }
49
+
45
50
  export interface TerminalProps {
46
51
  className?: string
47
52
  fontSize?: number
@@ -52,6 +57,12 @@ export interface TerminalProps {
52
57
  * common case this primitive was built for.
53
58
  */
54
59
  onData?: (data: string) => void
60
+ /**
61
+ * Opt-in xterm addons — kept out of the base bundle since most consumers
62
+ * (plain log output) don't need them. Read once at mount, like the other
63
+ * props here.
64
+ */
65
+ options?: TerminalOptions
55
66
  }
56
67
 
57
68
  /**
@@ -61,18 +72,29 @@ export interface TerminalProps {
61
72
  * plug in on top.
62
73
  */
63
74
  const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Terminal(
64
- { className, fontSize = 13, scrollback = 5000, onData },
75
+ { className, fontSize = 13, scrollback = 5000, onData, options },
65
76
  ref
66
77
  ) {
67
78
  const outerRef = useRef<HTMLDivElement>(null)
68
79
  const mountRef = useRef<HTMLDivElement>(null)
69
80
  const termRef = useRef<XTerm | null>(null)
81
+ // xterm mounts asynchronously (dynamic import); a consumer wiring up a
82
+ // transport (WebSocket, SSE...) in its own effect can easily win that race
83
+ // and call write() before termRef exists. Queue those calls instead of
84
+ // dropping them, and flush in order once xterm is ready.
85
+ const pendingRef = useRef<Array<{ method: "write" | "writeln"; data: string | Uint8Array }>>([])
70
86
 
71
87
  useImperativeHandle(
72
88
  ref,
73
89
  () => ({
74
- write: (data) => termRef.current?.write(data),
75
- writeln: (data) => termRef.current?.writeln(data),
90
+ write: (data) => {
91
+ if (termRef.current) termRef.current.write(data)
92
+ else pendingRef.current.push({ method: "write", data })
93
+ },
94
+ writeln: (data) => {
95
+ if (termRef.current) termRef.current.writeln(data)
96
+ else pendingRef.current.push({ method: "writeln", data })
97
+ },
76
98
  clear: () => termRef.current?.clear(),
77
99
  focus: () => termRef.current?.focus(),
78
100
  }),
@@ -103,6 +125,10 @@ const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Terminal(
103
125
 
104
126
  const fitAddon = new FitAddon()
105
127
  term.loadAddon(fitAddon)
128
+ if (options?.webLinks) {
129
+ const { WebLinksAddon } = await import("@xterm/addon-web-links")
130
+ term.loadAddon(new WebLinksAddon())
131
+ }
106
132
  // FitAddon computes available space from *this* element's parent minus
107
133
  // this element's own padding — it has no idea about an outer wrapper's
108
134
  // padding/border. Mounting on a padding-less, border-less inner div (see
@@ -113,6 +139,12 @@ const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Terminal(
113
139
  fitAddon.fit()
114
140
  termRef.current = term
115
141
 
142
+ for (const { method, data } of pendingRef.current) {
143
+ if (method === "writeln") term.writeln(data as string)
144
+ else term.write(data)
145
+ }
146
+ pendingRef.current = []
147
+
116
148
  if (onData) term.onData(onData)
117
149
 
118
150
  resizeObserver = new ResizeObserver(() => fitAddon.fit())
package/src/index.ts CHANGED
@@ -24,7 +24,7 @@ export { Avatar, AvatarImage, AvatarFallback } from "./components/avatar"
24
24
  // xterm.js surface — theme + fit-on-resize, transport-agnostic (feed it via
25
25
  // the write/writeln ref handle).
26
26
  export { Terminal } from "./components/terminal"
27
- export type { TerminalHandle, TerminalProps } from "./components/terminal"
27
+ export type { TerminalHandle, TerminalProps, TerminalOptions } from "./components/terminal"
28
28
 
29
29
  // Inline, declarative status banner (persistent) — the counterpart to `toast()`.
30
30
  // `alertVariants` ships from this directive-free module so it stays RSC-callable.