@hachej/boring-ui-kit 0.4.57 → 0.4.59

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 (59) hide show
  1. package/bin/boring-ui-blocks.mjs +49 -4
  2. package/dist/chat-react-conversations.d.ts +7 -1
  3. package/dist/chat-react-conversations.js +58 -18
  4. package/package.json +6 -6
  5. package/r/_shared.json +19 -0
  6. package/r/agent-target.json +4 -4
  7. package/r/app-shell.json +17 -17
  8. package/r/board-viewer.json +10 -10
  9. package/r/button.json +4 -4
  10. package/r/data-table.json +21 -0
  11. package/r/diagram-viewer.json +6 -4
  12. package/r/dock.json +1 -1
  13. package/r/documents.json +13 -14
  14. package/r/drawing-viewer.json +6 -4
  15. package/r/empty-state.json +22 -0
  16. package/r/feedback.json +14 -14
  17. package/r/file-tree.json +4 -4
  18. package/r/highlights.json +3 -3
  19. package/r/html-viewer.json +3 -3
  20. package/r/kpi-stat.json +22 -0
  21. package/r/list-row.json +21 -0
  22. package/r/looks-viewer.json +6 -6
  23. package/r/maquette-viewer.json +7 -6
  24. package/r/markdown-editor.json +10 -9
  25. package/r/mockup-viewer.json +5 -5
  26. package/r/page-header.json +22 -0
  27. package/r/pi-ambient.json +12 -12
  28. package/r/pi-app-kinds.json +10 -10
  29. package/r/pi-app.json +17 -17
  30. package/r/pi-artifacts.json +4 -4
  31. package/r/pi-chat-approval.json +4 -4
  32. package/r/pi-chat-attachments.json +4 -4
  33. package/r/pi-chat-markdown.json +3 -3
  34. package/r/pi-chat-mentions.json +4 -4
  35. package/r/pi-chat-pickers.json +4 -4
  36. package/r/pi-chat-plugins.json +11 -11
  37. package/r/pi-chat-question.json +3 -3
  38. package/r/pi-chat-slash.json +5 -5
  39. package/r/pi-chat-subagent.json +4 -4
  40. package/r/pi-chat-voice.json +7 -7
  41. package/r/pi-chat.json +20 -20
  42. package/r/pi-workspace.json +3 -3
  43. package/r/placed-agent.json +7 -7
  44. package/r/provider-setup.json +4 -4
  45. package/r/record-viewer.json +5 -3
  46. package/r/registry.json +494 -280
  47. package/r/shared-chat.json +11 -11
  48. package/r/spaces.json +7 -5
  49. package/r/task-list-viewer.json +28 -3
  50. package/r/theme.json +115 -50
  51. package/r/version-history.json +2 -2
  52. package/r/viewer-code.json +5 -5
  53. package/r/viewer-frame.json +9 -9
  54. package/r/viewer-html.json +7 -7
  55. package/r/viewer-image.json +5 -5
  56. package/r/viewer-markdown.json +7 -7
  57. package/r/viewer-pdf.json +6 -6
  58. package/r/viewers.json +6 -6
  59. package/r/windows.json +4 -4
@@ -24,8 +24,46 @@ const borderWidth = /(?<![\w-])border(?:-[trblxyse])?(?:-(?:\d+|\[(?:\d|\.)[^\]]
24
24
  const color = /#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|oklch|oklab)\([^)]*\)|\b(?:bg|text|border|ring|fill|stroke|outline|decoration|shadow)-(?:white|black|(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3})(?:\/\d+)?\b/g;
25
25
  const font = /\bfont-\[(?!var\(|--)[^\]]+\]|\bfont-family\s*:\s*(?!\s*(?:var\(|inherit\b))[^;\n}]+|\bfontFamily\s*:\s*['"](?!var\()[^'"]+['"]|\bfont\s*:\s*[^;\n}]*\b(?:serif|monospace|system-ui)\b[^;\n}]*/g;
26
26
 
27
- /** The repository's rules (registry blocks and kit sources). */
27
+ /**
28
+ * `design-no-x-scroll`: a file that renders a table holds no horizontal scroll container (`overflow-x-auto`, `overflow-x-scroll`, the
29
+ * CSS or React style property). Tables truncate their cells and stack below their container width instead. Lexical and file-scoped: the
30
+ * shadcn `table` wrapper (`overflow-x-auto`) must not be imported by a file that renders a table.
31
+ */
32
+ const tableMarkup = /<table\b|<Table\b|@\/components\/ui\/table\b|\bDataTable\b|role=["']table["']|\bdisplay\s*:\s*['"]?table\b/;
33
+ const horizontalScroll = /(?<![\w-])overflow-x-(?:auto|scroll)(?![\w-])|\boverflowX\s*:\s*['"]?(?:auto|scroll)\b|\boverflow-x\s*:\s*(?:auto|scroll)\b/g;
34
+ export function noXScroll(source) {
35
+ return tableMarkup.test(source) ? [...source.matchAll(horizontalScroll)].map(match => ({ index: match.index, value: match[0] })) : [];
36
+ }
37
+
38
+
39
+ // The flat card: a Card's own className carries no border, shadow or ring. The shadcn Card's ring lives in its generated file, which blocks never restyle.
40
+ const cardFlat = /<Card\b[^>]*?\bclassName\s*=\s*(?:"[^"]*\b(?:border|shadow|ring)\b[^"]*"|\{[^}\n]*\b(?:border|shadow|ring)\b[^}\n]*\})/g;
41
+
42
+ /** `<Card` tags opened inside another open `<Card>` (a self-closing Card and a Card in a prop are not nesting). Lexical, over JSX tags. */
43
+ function nestedCards(source) {
44
+ const found = [], open = [], tag = /<(\/?)Card\b/g;
45
+ for (let match; (match = tag.exec(source));) {
46
+ if (match[1]) { open.pop(); continue; }
47
+ let depth = 0, quote = null, end = match.index + match[0].length;
48
+ for (; end < source.length; end++) {
49
+ const char = source[end];
50
+ if (quote) { if (char === quote) quote = null; }
51
+ else if (/["'`]/.test(char)) quote = char;
52
+ else if (char === '{') depth++;
53
+ else if (char === '}') depth--;
54
+ else if (char === '>' && depth === 0) break;
55
+ }
56
+ if (open.length) found.push({ index: match.index, value: source.slice(match.index, end + 1).split('\n')[0] });
57
+ if (source[end - 1] !== '/') open.push(match.index);
58
+ tag.lastIndex = end + 1;
59
+ }
60
+ return found;
61
+ }
62
+
63
+ /** The repository's rules (registry blocks and kit sources). A rule is a global regex, or a function from the source to `{ index, value }` findings. */
28
64
  export const DESIGN_RULES = [
65
+ ['design-card-flat', cardFlat],
66
+ ['design-no-nested-card', nestedCards],
29
67
  ['shadow', shadow],
30
68
  ['radius', radius],
31
69
  ['border-width', borderWidth],
@@ -34,6 +72,7 @@ export const DESIGN_RULES = [
34
72
  ['motion', /\b(?:duration|delay)-\d+\b|\b(?:transition(?:-[\w-]+)?|animation(?:-[\w-]+)?|transitionDuration|animationDuration)\s*:\s*[^;\n}]*?\b\d*\.?\d+m?s\b[^;\n}]*/g],
35
73
  ['style', /\bradix(?:-[\w-]+)?\b/gi],
36
74
  ['primitive', /\b(?:function|const|class)\s+(?:Button|IconButton|ActionMenu|\w*Popover|\w*Tooltip|\w*Dialog|\w*Menu)\b|<(?:button|input|textarea|select)\b/g],
75
+ ['design-no-x-scroll', noXScroll],
37
76
  ];
38
77
 
39
78
  /**
@@ -44,6 +83,8 @@ export const DESIGN_RULES = [
44
83
  */
45
84
  const LENGTH = String.raw`(?<![\w.#-])(?!1px\b)\d*\.?\d+(?:px|rem|em)\b`;
46
85
  export const APP_RULES = [
86
+ ['design-card-flat', cardFlat],
87
+ ['design-no-nested-card', nestedCards],
47
88
  ['color', color],
48
89
  ['arbitrary-value', /(?<![\w-])(?:bg|text|border(?:-[trblxyse])?|ring(?:-offset)?|outline|fill|stroke|decoration|shadow|drop-shadow|inset-shadow|rounded(?:-[trblse]{1,2})?|font|tracking|leading|from|via|to|divide(?:-[xy])?|placeholder|caret|accent)-\[(?!var\(|--|#|(?:rgb|rgba|hsl|hsla|oklch|oklab)\()[^\]]+\]/g],
49
90
  ['border-width', /(?<![\w-])border(?:-[trblxyse])?-(?!0(?![\w.]))\d+(?![\w-])/g],
@@ -51,15 +92,19 @@ export const APP_RULES = [
51
92
  ['font', /\bfont-family\s*:\s*(?!\s*(?:var\(|inherit\b))[^;\n}]+|\bfontFamily\s*:\s*['"](?!var\()[^'"]+['"]|\bfont\s*:\s*(?!\s*(?:var\(|inherit\b))[^;\n}]+/g],
52
93
  ['literal-value', /\b(?:border-radius|borderRadius|box-shadow|boxShadow|font-size|fontSize|letter-spacing|letterSpacing)\s*:\s*['"]?(?!\s*(?:var\(|calc\(\s*var\(|inherit\b|initial\b|unset\b|none\b|0(?![\w.])))(?=[^;\n}"']*\d)[^;\n}"']*/g],
53
94
  ['dark', /(?<![\w-])dark:[^\s"'`]+/g],
95
+ ['design-no-x-scroll', noXScroll],
54
96
  ];
55
97
 
56
98
  /** Findings `{ file, line, column, rule, value }` of `rules` in `text`; CSS block comments are ignored. */
57
99
  export function scanDesign(file, text, rules = DESIGN_RULES) {
58
100
  const findings = [];
59
101
  const source = text.replace(/\/\*[\s\S]*?\*\//g, match => match.replace(/[^\n]/g, ' '));
60
- for (const [rule, pattern] of rules) for (const match of source.matchAll(pattern)) {
61
- const before = text.slice(0, match.index), line = before.split('\n').length;
62
- findings.push({ file, line, column: match.index - before.lastIndexOf('\n'), rule, value: match[0] });
102
+ for (const [rule, pattern] of rules) {
103
+ const matches = typeof pattern === 'function' ? pattern(source) : [...source.matchAll(pattern)].map(match => ({ index: match.index, value: match[0] }));
104
+ for (const { index, value } of matches) {
105
+ const before = text.slice(0, index), line = before.split('\n').length;
106
+ findings.push({ file, line, column: index - before.lastIndexOf('\n'), rule, value });
107
+ }
63
108
  }
64
109
  return findings;
65
110
  }
@@ -11,10 +11,16 @@ export interface ConversationsClient {
11
11
  readonly activeId: string | undefined;
12
12
  /** New or fork is on its way: the conversation on screen is about to be left, so it must take no message (`PiChat` disables its composer). */
13
13
  readonly creating: boolean;
14
- /** The conversation New or fork just opened, for up to three seconds: `PiChat` puts the cursor in its box once, then calls `openedSeen`, so a later remount does not. */
14
+ /** The conversation New or fork just opened, for up to three seconds: `PiChat` puts the cursor in its box once, then calls `openedSeen`, so a later remount does not. Undefined while a dead key or IME composition is open in the hidden field, however long it stays open. */
15
15
  readonly opened: string | undefined;
16
16
  /** Ends `opened` and returns what the person typed since New, for the new box. */
17
17
  readonly openedSeen: () => string;
18
+ /** New chat was refused or got no answer in time, in the conversation on screen. `typed`: the person's text was dropped. `PiChat` and `AmbientChat` say so. */
19
+ readonly newFailed: {
20
+ readonly typed: boolean;
21
+ } | undefined;
22
+ /** The person closed that message. */
23
+ readonly newFailedSeen: () => void;
18
24
  readonly reload: () => Promise<void>;
19
25
  readonly onSelect: (id: string) => void;
20
26
  readonly onNew: () => void;
@@ -11,15 +11,15 @@ const OPENED_MS = 3000;
11
11
  /**
12
12
  * The clicked button keeps the focus until the new box is back, so a Space typed then would press New again, and the box is held, so the start of a message would be lost.
13
13
  * The focus goes to a hidden field until then: it outlives the chat's remount, and dead keys, IME and Backspace work there. Not on a phone, where it would open the keyboard.
14
+ * A composition still open must finish there: its accent is only a preview in the value, and the keyboard commits the letter to whatever has the focus.
14
15
  */
15
- function holdTyping() {
16
+ function holdTyping(onComposing) {
16
17
  const page = globalThis.document, focused = page?.activeElement;
17
18
  if (!page || focused?.matches('input, textarea, select, [contenteditable]'))
18
19
  return undefined;
19
- const from = focused?.matches('button, [role="button"]') ? focused : undefined;
20
20
  focused?.blur();
21
21
  if (globalThis.matchMedia?.('(pointer: coarse)').matches)
22
- return { from, field: undefined };
22
+ return { field: undefined, composing: false };
23
23
  const field = page.createElement('textarea');
24
24
  field.dataset.testid = 'new-chat-typing';
25
25
  field.tabIndex = -1;
@@ -27,29 +27,43 @@ function holdTyping() {
27
27
  field.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:0;opacity:0;font-size:16px';
28
28
  field.addEventListener('keydown', event => { if (event.key === 'Enter')
29
29
  event.preventDefault(); });
30
+ const held = { field, composing: false };
31
+ const composing = (value) => { held.composing = value; onComposing(value); };
32
+ field.addEventListener('compositionstart', () => composing(true));
33
+ field.addEventListener('compositionend', () => composing(false));
34
+ field.addEventListener('blur', () => composing(false));
30
35
  page.body.append(field);
31
36
  field.focus();
32
- return { from, field };
37
+ return held;
33
38
  }
34
39
  export function useConversationsClient({ endpoint, fetch, activeId, onSelect, onChange, pollMs = 4000, openTimeoutMs = 15000 }) {
35
40
  const [items, setItems] = useState();
36
41
  const [creating, setCreating] = useState(false);
37
42
  const [opened, setOpened] = useState();
43
+ const [failedNew, setFailedNew] = useState();
38
44
  const typing = useRef(undefined);
39
- // What was typed goes nowhere else, so nothing reaches the conversation being left. The focus goes back unless the person moved.
45
+ const [composing, setComposing] = useState(false);
46
+ // What was typed goes nowhere else, so nothing reaches the conversation being left. The focus does not go back to the button: the next Space would press New again, and a create that works would take the rest of the text as a partial message.
40
47
  const dropTyping = useCallback(() => {
41
48
  const held = typing.current;
42
49
  typing.current = undefined;
43
50
  held?.field?.remove();
44
- if (globalThis.document?.activeElement === globalThis.document?.body)
45
- held?.from?.focus();
51
+ setComposing(false);
46
52
  }, []);
47
53
  useEffect(() => dropTyping, [dropTyping]);
48
54
  const openedTimer = useRef(undefined);
49
55
  const markOpened = useCallback((id) => {
50
56
  clearTimeout(openedTimer.current);
57
+ const expire = () => {
58
+ if (typing.current?.composing) {
59
+ openedTimer.current = setTimeout(expire, OPENED_MS);
60
+ return;
61
+ }
62
+ dropTyping();
63
+ setOpened(undefined);
64
+ };
51
65
  if (id !== undefined)
52
- openedTimer.current = setTimeout(() => { dropTyping(); setOpened(undefined); }, OPENED_MS);
66
+ openedTimer.current = setTimeout(expire, OPENED_MS);
53
67
  else
54
68
  dropTyping();
55
69
  setOpened(id);
@@ -57,9 +71,20 @@ export function useConversationsClient({ endpoint, fetch, activeId, onSelect, on
57
71
  // The New or fork on its way; forgetting it gives the composer back, and its late answer then moves nobody.
58
72
  const opening = useRef(undefined);
59
73
  const forget = useCallback(() => { opening.current = undefined; setCreating(false); }, []);
60
- const latest = useRef({ fetch, onSelect, onChange, activeId, openTimeoutMs });
61
- useLayoutEffect(() => { latest.current = { fetch, onSelect, onChange, activeId, openTimeoutMs }; });
62
74
  const base = endpoint === undefined ? undefined : String(endpoint);
75
+ const latest = useRef({ fetch, onSelect, onChange, activeId, openTimeoutMs, base });
76
+ // Every conversation this person has had open here: a late create's cleanup must never remove one of them.
77
+ const visited = useRef(new Set());
78
+ // Unmounted, it no longer knows what was opened since: its late cleanup removes nothing.
79
+ const mounted = useRef(true);
80
+ useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
81
+ useLayoutEffect(() => { latest.current = { fetch, onSelect, onChange, activeId, openTimeoutMs, base }; if (activeId !== undefined)
82
+ visited.current.add(activeId); });
83
+ // The text is not given back: it stays out of the draft being left, and the message says it is gone.
84
+ const failNew = useCallback(() => {
85
+ setFailedNew({ at: latest.current.activeId, typed: Boolean(typing.current?.field?.value) });
86
+ dropTyping();
87
+ }, [dropTyping]);
63
88
  const call = useCallback(async (params, body, signal) => {
64
89
  if (base === undefined)
65
90
  throw new Error('No conversations endpoint');
@@ -86,7 +111,7 @@ export function useConversationsClient({ endpoint, fetch, activeId, onSelect, on
86
111
  const reload = useCallback(() => list(), [list]);
87
112
  // Another endpoint (another owner's list) starts empty; switching conversations only reloads.
88
113
  // eslint-disable-next-line react-hooks/set-state-in-effect -- a new endpoint clears the old owner's list once, after commit
89
- useEffect(() => { setItems(undefined); markOpened(undefined); forget(); }, [base, forget, markOpened]);
114
+ useEffect(() => { setItems(undefined); setFailedNew(undefined); markOpened(undefined); forget(); }, [base, forget, markOpened]);
90
115
  useEffect(() => {
91
116
  if (base === undefined)
92
117
  return;
@@ -105,34 +130,48 @@ export function useConversationsClient({ endpoint, fetch, activeId, onSelect, on
105
130
  const open = async (name, body) => {
106
131
  if (opening.current)
107
132
  return;
133
+ const gaveUp = name === 'create' ? failNew : dropTyping;
108
134
  const run = {};
109
135
  opening.current = run;
110
136
  setCreating(true);
111
137
  const timer = setTimeout(() => { if (opening.current === run) {
112
138
  forget();
113
- dropTyping();
139
+ gaveUp();
114
140
  } }, latest.current.openTimeoutMs);
115
141
  let selected = false;
116
142
  try {
117
143
  const { conversationId } = await op(name, body);
118
- await changed();
144
+ // The conversation exists now: a host refresh that fails does not undo that.
145
+ await changed().catch(() => { });
119
146
  if (conversationId !== undefined && opening.current === run) {
120
147
  selected = true;
121
148
  markOpened(String(conversationId));
122
149
  latest.current.onSelect(String(conversationId));
123
150
  }
151
+ // Nobody waits for it any more, so it would stay in the list as an empty row. Only that row goes: not one the person has had open here, not one with a message (another tab), not on another owner's list.
152
+ else if (name === 'create' && conversationId !== undefined) {
153
+ const unused = () => mounted.current && latest.current.base === base && !visited.current.has(String(conversationId));
154
+ const row = unused() ? (await call({})).conversations?.find(served => String(served.id) === String(conversationId)) : undefined;
155
+ if (row?.lastMessage === null && unused()) {
156
+ await op('delete', { conversationId });
157
+ await changed();
158
+ }
159
+ }
124
160
  }
125
161
  finally {
126
162
  clearTimeout(timer);
127
163
  if (opening.current === run) {
128
164
  forget();
129
165
  if (!selected)
130
- dropTyping();
166
+ gaveUp();
131
167
  }
132
168
  }
133
169
  };
134
170
  return {
135
- items, activeId, creating, opened, reload,
171
+ items, activeId, creating, reload,
172
+ opened: composing ? undefined : opened,
173
+ newFailed: failedNew && failedNew.at === activeId ? { typed: failedNew.typed } : undefined,
174
+ newFailedSeen: () => setFailedNew(undefined),
136
175
  openedSeen: () => {
137
176
  const typed = typing.current?.field?.value ?? '';
138
177
  typing.current?.field?.remove();
@@ -140,11 +179,12 @@ export function useConversationsClient({ endpoint, fetch, activeId, onSelect, on
140
179
  markOpened(undefined);
141
180
  return typed;
142
181
  },
143
- onSelect: (id) => { forget(); markOpened(undefined); latest.current.onSelect(id); },
182
+ onSelect: (id) => { forget(); setFailedNew(undefined); markOpened(undefined); latest.current.onSelect(id); },
144
183
  onNew: () => {
145
184
  if (!opening.current) {
185
+ setFailedNew(undefined);
146
186
  markOpened(undefined);
147
- typing.current = holdTyping();
187
+ typing.current = holdTyping(setComposing);
148
188
  }
149
189
  else
150
190
  typing.current?.field?.focus();
@@ -161,7 +201,7 @@ export function useConversationsClient({ endpoint, fetch, activeId, onSelect, on
161
201
  await open('fork', { conversationId: from, at });
162
202
  },
163
203
  };
164
- }, [items, activeId, creating, opened, call, reload, forget, markOpened, dropTyping]);
204
+ }, [items, activeId, creating, opened, composing, failedNew, base, call, reload, forget, markOpened, dropTyping, failNew]);
165
205
  }
166
206
  /** Compatibility spelling retained for one release. */
167
207
  export const useConversations = useConversationsClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-ui-kit",
3
- "version": "0.4.57",
3
+ "version": "0.4.59",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "type": "module",
@@ -154,7 +154,7 @@
154
154
  }
155
155
  },
156
156
  "peerDependencies": {
157
- "@hachej/boring-files": "^0.4.57",
157
+ "@hachej/boring-files": "^0.4.59",
158
158
  "@earendil-works/pi-durable": "~1.4.0",
159
159
  "react": "^19.0.0",
160
160
  "react-dom": "^19.0.0",
@@ -173,13 +173,13 @@
173
173
  "@tiptap/extension-floating-menu": "^3.31.4",
174
174
  "@earendil-works/chord": "~1.4.0",
175
175
  "@earendil-works/pi-ai": "~1.4.0",
176
- "tldraw": "^5.5.2",
177
- "@tldraw/editor": "^5.5.2",
178
- "@tldraw/store": "^5.5.2",
176
+ "tldraw": "^5.6.0",
177
+ "@tldraw/editor": "^5.6.0",
178
+ "@tldraw/store": "^5.6.0",
179
179
  "@json-render/core": "^0.21.0",
180
180
  "@json-render/react": "^0.21.0",
181
181
  "zod": "^4.6.5",
182
- "@tldraw/tlschema": "^5.5.2",
182
+ "@tldraw/tlschema": "^5.6.0",
183
183
  "lucide-react": "^1.51.0",
184
184
  "@base-ui/react": "1.9.0"
185
185
  },
package/r/_shared.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "_shared",
4
+ "title": "Kit icons",
5
+ "description": "One fixed semantic icon-symbol map for all kit blocks and bundled primitives. Real Phosphor defaults; hosts may replace this shared module or use existing icon props, independently of theme tokens.",
6
+ "dependencies": [
7
+ "@phosphor-icons/react@2.1.10",
8
+ "react@^19.0.0"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "registry/_shared/icons.ts",
13
+ "content": "'use client';\n\n// Fixed semantic symbols for registry blocks and bundled shadcn primitives. Hosts may swap this module\n// or use existing icon props; theme tokens and DESIGN.md alone do not select an icon library.\nimport { createElement, forwardRef } from 'react';\nimport { CircleIcon as PhosphorCircle } from '@phosphor-icons/react';\nimport type { IconProps } from '@phosphor-icons/react';\n\nexport {\n WarningCircleIcon as AlertCircleIcon,\n ArchiveIcon,\n TrayArrowUpIcon as ArchiveRestoreIcon,\n ArrowDownIcon,\n ArrowLeftIcon,\n ArrowUpIcon,\n ArrowUpRightIcon,\n BellIcon,\n CubeIcon as BoxIcon,\n BrainIcon,\n CameraIcon,\n CheckIcon,\n CaretDownIcon as ChevronDownIcon,\n CaretLeftIcon as ChevronLeftIcon,\n CaretRightIcon as ChevronRightIcon,\n CaretUpIcon as ChevronUpIcon,\n CaretDoubleLeftIcon as ChevronsLeftIcon,\n CaretDoubleRightIcon as ChevronsRightIcon,\n CaretUpDownIcon as ChevronsUpDownIcon,\n WarningCircleIcon as CircleAlertIcon,\n CheckCircleIcon as CircleCheckIcon,\n ProhibitIcon as CircleSlashIcon,\n ClockIcon,\n CodeIcon as CodeXmlIcon,\n CompassIcon,\n CopyIcon,\n ArrowElbowDownRightIcon as CornerDownRightIcon,\n DownloadSimpleIcon as DownloadIcon,\n DotsThreeIcon as EllipsisIcon,\n ArrowSquareOutIcon as ExternalLinkIcon,\n EyeIcon,\n FileAudioIcon,\n FileCodeIcon,\n FileIcon,\n FileXlsIcon as FileSpreadsheetIcon,\n FileTextIcon,\n FolderIcon,\n GitForkIcon,\n GlobeIcon,\n DotsSixIcon as GripIcon,\n DotsSixVerticalIcon as GripVerticalIcon,\n HammerIcon,\n HandIcon,\n ClockCounterClockwiseIcon as HistoryIcon,\n ImageIcon,\n ImageBrokenIcon as ImageOffIcon,\n TrayIcon as InboxIcon,\n InfoIcon,\n SquaresFourIcon as LayoutDashboardIcon,\n LayoutIcon as LayoutTemplateIcon,\n BooksIcon as LibraryIcon,\n QueueIcon as ListEndIcon,\n ListIcon,\n ListChecksIcon as ListTodoIcon,\n CircleNotchIcon as Loader2Icon,\n CircleNotchIcon as LoaderCircleIcon,\n CrosshairIcon as LocateFixedIcon,\n SignOutIcon as LogOutIcon,\n ArrowsOutSimpleIcon as Maximize2Icon,\n ArrowsOutIcon as MaximizeIcon,\n ListIcon as MenuIcon,\n ChatCircleDotsIcon as MessageCircleQuestionIcon,\n ChatIcon as MessageSquareIcon,\n ChatDotsIcon as MessageSquarePlusIcon,\n ChatTextIcon as MessageSquareTextIcon,\n ChatsIcon as MessagesSquareIcon,\n MicrophoneIcon as MicIcon,\n ArrowsInSimpleIcon as Minimize2Icon,\n MinusIcon,\n MoonIcon,\n DotsThreeIcon as MoreHorizontalIcon,\n CursorIcon as MousePointer2Icon,\n XCircleIcon as OctagonXIcon,\n PaletteIcon,\n SquareSplitVerticalIcon as PanelBottomIcon,\n SidebarSimpleIcon as PanelLeftIcon,\n SquareSplitHorizontalIcon as PanelRightIcon,\n AppWindowIcon as PanelsTopLeftIcon,\n PaperclipIcon,\n PauseIcon,\n PenNibIcon as PenToolIcon,\n PencilIcon,\n PencilLineIcon,\n PictureInPictureIcon as PictureInPicture2Icon,\n PushPinIcon as PinIcon,\n PlayIcon,\n PlusIcon,\n PresentationIcon,\n ArrowUUpRightIcon as Redo2Icon,\n ArrowsClockwiseIcon as RefreshCwIcon,\n ArrowBendUpLeftIcon as ReplyIcon,\n ArrowCounterClockwiseIcon as RotateCcwIcon,\n PathIcon as RouteIcon,\n RowsIcon as Rows3Icon,\n FloppyDiskIcon as SaveIcon,\n ScanIcon,\n MagnifyingGlassIcon as SearchCheckIcon,\n MagnifyingGlassIcon as SearchIcon,\n PaperPlaneRightIcon as SendIcon,\n GearIcon as SettingsIcon,\n ShapesIcon,\n ShareNetworkIcon as Share2Icon,\n ShieldWarningIcon as ShieldAlertIcon,\n LineSegmentIcon as SlashIcon,\n SparkleIcon,\n SparkleIcon as SparklesIcon,\n SquareIcon,\n NotePencilIcon as SquarePenIcon,\n NoteIcon as StickyNoteIcon,\n ThumbsDownIcon,\n ThumbsUpIcon,\n TrashIcon as Trash2Icon,\n WarningIcon as TriangleAlertIcon,\n TextTIcon as TypeIcon,\n ArrowUUpLeftIcon as Undo2Icon,\n UserIcon,\n UserMinusIcon,\n FlowArrowIcon as WorkflowIcon,\n WrenchIcon,\n XIcon,\n MagnifyingGlassPlusIcon as ZoomInIcon,\n MagnifyingGlassMinusIcon as ZoomOutIcon,\n ArrowDownIcon as ArrowDown,\n ArrowUpIcon as ArrowUp,\n CaretRightIcon as ChevronRight,\n TrayIcon as Inbox,\n MinusIcon as Minus,\n SquareSplitVerticalIcon as PanelBottom,\n SquareSplitHorizontalIcon as PanelRight,\n SparkleIcon as Sparkles,\n} from '@phosphor-icons/react';\n\n// Every kit CircleIcon is a filled status/recording dot. Phosphor's regular circle is a\n// path outline, so SVG fill/strokeWidth (used by existing callers) cannot fill its centre.\n// Keep the native SVG props and ref, while allowing an explicit native weight override.\nexport const CircleIcon = forwardRef<SVGSVGElement, IconProps>((props, ref) =>\n createElement(PhosphorCircle, { weight: 'fill', ...props, ref }));\n",
14
+ "type": "registry:lib",
15
+ "target": "components/_shared/icons.ts"
16
+ }
17
+ ],
18
+ "type": "registry:lib"
19
+ }
@@ -4,15 +4,15 @@
4
4
  "title": "Point and dictate on app data",
5
5
  "description": "Tap explicitly marked JSON app context, dictate once and show the same app agent’s native reply without opening chat. Host-controlled presentation; no feedback report or second controller.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.4.57",
8
- "@hachej/boring-feedback@^0.4.57",
7
+ "@hachej/boring-ui-kit@^0.4.59",
8
+ "@hachej/boring-feedback@^0.4.59",
9
9
  "@earendil-works/pi-durable@~1.4.0",
10
10
  "react@^19.0.0",
11
11
  "react-dom@^19.0.0"
12
12
  ],
13
13
  "registryDependencies": [
14
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.57/r/app-shell.json",
15
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.57/r/pi-chat.json"
14
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.59/r/app-shell.json",
15
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.59/r/pi-chat.json"
16
16
  ],
17
17
  "files": [
18
18
  {