@actioneer/ads 0.2.0 → 0.2.2

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/bin/ads-init.mjs CHANGED
@@ -17,14 +17,15 @@
17
17
  * • drop a consumer-mode `ads-lint.config.mjs`
18
18
  * (`libraryPackage: "@actioneer/ads"`, `libraryRoot: null`)
19
19
  *
20
- * Per the DES-13 testing decision, the write-plan is exercised end-to-end by a
21
- * manual run rather than dedicated unit tests this pass.
20
+ * The pure write-plan (`planInit`) and the package-manager detection are covered
21
+ * by unit tests in ads-init.test.mjs; the applier's IO is exercised end-to-end by
22
+ * a manual run and the release rehearsal.
22
23
  */
23
24
 
24
25
  import fs from "node:fs";
25
26
  import path from "node:path";
26
27
  import { execFileSync } from "node:child_process";
27
- import { pathToFileURL } from "node:url";
28
+ import { fileURLToPath } from "node:url";
28
29
 
29
30
  const PKG = "@actioneer/ads";
30
31
  const STYLE_IMPORT = `@import "${PKG}/styles.css";`;
@@ -39,6 +40,7 @@ const STYLE_IMPORT = `@import "${PKG}/styles.css";`;
39
40
  * @property {string|null} layoutPath repo-relative path to the root layout (or null)
40
41
  * @property {string|null} layoutContent
41
42
  * @property {boolean} hasAdsLintConfig
43
+ * @property {"npm"|"pnpm"|"yarn"|"bun"} [packageManager] detected consumer PM (default npm)
42
44
  *
43
45
  * @typedef {Object} Edit
44
46
  * @property {"install"|"write-file"|"edit-file"|"manual"} type
@@ -50,8 +52,45 @@ const STYLE_IMPORT = `@import "${PKG}/styles.css";`;
50
52
  * @property {number} [findIndex] edit-file: positional replace offset (disambiguates repeated tokens)
51
53
  * @property {string} [addImport] edit-file: import line to prepend if absent
52
54
  * @property {string[]} [packages] install: package specifiers
55
+ * @property {"npm"|"pnpm"|"yarn"|"bun"} [manager] install: package manager to shell out to
53
56
  */
54
57
 
58
+ /**
59
+ * Detect the consumer's package manager so `init` installs with THEIR tool
60
+ * rather than always shelling out to `npm` (which strands pnpm/yarn/bun users
61
+ * with a stray package-lock.json + a mixed lockfile). Precedence, most→least
62
+ * authoritative: the corepack `packageManager` field, the invoking PM's user
63
+ * agent (set when run via `pnpm dlx` / `yarn dlx` / `bunx`), then a lockfile on
64
+ * disk. Defaults to npm.
65
+ * @param {{packageManagerField?:string|null, userAgent?:string|null, lockfiles?:string[]}} [signals]
66
+ * @returns {"npm"|"pnpm"|"yarn"|"bun"}
67
+ */
68
+ export function detectPackageManager({ packageManagerField, userAgent, lockfiles } = {}) {
69
+ const field = /^(pnpm|yarn|bun|npm)(@|$)/.exec(packageManagerField ?? "");
70
+ if (field) return field[1];
71
+ const ua = /^(pnpm|yarn|bun)\//.exec(userAgent ?? "");
72
+ if (ua) return ua[1];
73
+ const locks = new Set(lockfiles ?? []);
74
+ if (locks.has("pnpm-lock.yaml")) return "pnpm";
75
+ if (locks.has("yarn.lock")) return "yarn";
76
+ if (locks.has("bun.lockb") || locks.has("bun.lock")) return "bun";
77
+ return "npm";
78
+ }
79
+
80
+ /**
81
+ * The install invocation for a given manager: npm uses `install`, the rest use
82
+ * `add`.
83
+ * @param {"npm"|"pnpm"|"yarn"|"bun"} manager
84
+ * @param {string[]} packages
85
+ * @returns {[string, string[]]} [command, args]
86
+ */
87
+ export function installCommand(manager, packages) {
88
+ const verb = manager === "npm" ? "install" : "add";
89
+ return [manager, [verb, ...packages]];
90
+ }
91
+
92
+ const LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"];
93
+
55
94
  /** @param {RepoState} state @returns {Edit[]} */
56
95
  export function planInit(state) {
57
96
  const edits = [];
@@ -59,7 +98,8 @@ export function planInit(state) {
59
98
  // 1. Install the package (+ motion peer for the reduced-motion provider).
60
99
  const missing = ["@actioneer/ads", "motion"].filter((p) => !state.dependencies?.[p]);
61
100
  if (missing.length) {
62
- edits.push({ type: "install", title: `install ${missing.join(", ")}`, packages: missing });
101
+ const manager = state.packageManager ?? "npm";
102
+ edits.push({ type: "install", title: `install ${missing.join(", ")}`, packages: missing, manager });
63
103
  }
64
104
 
65
105
  // 2. Inject the styles.css import IMMEDIATELY AFTER `@import "tailwindcss"`.
@@ -214,6 +254,15 @@ const CANDIDATE_CSS = [
214
254
  ];
215
255
  const CANDIDATE_LAYOUT = ["app/layout.tsx", "src/app/layout.tsx", "app/layout.jsx", "src/app/layout.jsx"];
216
256
 
257
+ // The load-bearing anchor: ADS styles must import into the SAME CSS graph as
258
+ // tailwindcss, right after it (styles.css before tailwindcss leaves @theme
259
+ // inert). Detection keys off this so a non-standard stylesheet is still found.
260
+ const TAILWIND_IMPORT_RE = /@import\s+["']tailwindcss["']/;
261
+ // Dirs the stylesheet scan never descends into.
262
+ const SCAN_IGNORE = new Set([
263
+ "node_modules", ".git", ".next", "dist", "dist-lib", "build", "out", "coverage", ".turbo", ".vercel", ".cache",
264
+ ]);
265
+
217
266
  function toPosix(p) {
218
267
  return p.split(path.sep).join("/");
219
268
  }
@@ -221,6 +270,81 @@ function fileExistsInState(state, relPosix) {
221
270
  return state._existing?.has(relPosix) ?? false;
222
271
  }
223
272
 
273
+ /**
274
+ * Find the global stylesheet to wire ADS styles into. Because the import order
275
+ * is load-bearing, detection PREFERS the stylesheet that already imports
276
+ * tailwindcss — first among the conventional locations, then ANYWHERE in the
277
+ * project (bounded scan) so a non-standard path (e.g. `src/index.css`,
278
+ * `app/tailwind.css`) is auto-wired instead of degrading to a hand-edit note.
279
+ * Falls back to the first conventional file that exists (→ manual, with anchor
280
+ * guidance), else null.
281
+ * @returns {{path: string|null, content: string|null}}
282
+ */
283
+ export function detectGlobalCss(cwd) {
284
+ const read = (rel) => {
285
+ const abs = path.join(cwd, rel);
286
+ return fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : null;
287
+ };
288
+ // 1. Conventional location that already imports tailwindcss (the anchor).
289
+ for (const p of CANDIDATE_CSS) {
290
+ const c = read(p);
291
+ if (c != null && TAILWIND_IMPORT_RE.test(c)) return { path: p, content: c };
292
+ }
293
+ // 2. Any .css in the project importing tailwindcss (covers non-standard paths).
294
+ const scanned = scanForTailwindCss(cwd);
295
+ if (scanned) return scanned;
296
+ // 3. A conventional file exists but has no tailwindcss anchor → manual path.
297
+ for (const p of CANDIDATE_CSS) {
298
+ const c = read(p);
299
+ if (c != null) return { path: p, content: c };
300
+ }
301
+ return { path: null, content: null };
302
+ }
303
+
304
+ /** Bounded walk for the shallowest .css that imports tailwindcss. */
305
+ function scanForTailwindCss(cwd, maxDepth = 5) {
306
+ const hits = [];
307
+ const walk = (rel, depth) => {
308
+ if (depth > maxDepth) return;
309
+ let entries;
310
+ try {
311
+ entries = fs.readdirSync(path.join(cwd, rel || "."), { withFileTypes: true });
312
+ } catch {
313
+ return;
314
+ }
315
+ for (const e of entries) {
316
+ if (e.name.startsWith(".")) continue;
317
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
318
+ if (e.isDirectory()) {
319
+ if (!SCAN_IGNORE.has(e.name)) walk(childRel, depth + 1);
320
+ } else if (e.name.endsWith(".css")) {
321
+ let c;
322
+ try {
323
+ c = fs.readFileSync(path.join(cwd, childRel), "utf8");
324
+ } catch {
325
+ continue;
326
+ }
327
+ if (TAILWIND_IMPORT_RE.test(c)) hits.push({ path: toPosix(childRel), content: c });
328
+ }
329
+ }
330
+ };
331
+ walk("", 0);
332
+ // Shallowest path wins (the app entry over a nested one); stable tiebreak.
333
+ hits.sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
334
+ return hits[0] ?? null;
335
+ }
336
+
337
+ /**
338
+ * Re-check the load-bearing outcome after init: is `@import "…/styles.css"`
339
+ * actually present in the detected global stylesheet? Drives the end-of-run
340
+ * summary so a missed/deferred styles import can't scroll past unnoticed.
341
+ * @returns {{ok: boolean, path: string|null}}
342
+ */
343
+ export function verifyStylesWired(cwd) {
344
+ const css = detectGlobalCss(cwd);
345
+ return { ok: css.content != null && css.content.includes(STYLE_IMPORT), path: css.path };
346
+ }
347
+
224
348
  function readRepoState(cwd) {
225
349
  const read = (rel) => {
226
350
  const abs = path.join(cwd, rel);
@@ -230,7 +354,13 @@ function readRepoState(cwd) {
230
354
  const pkg = pkgRaw ? JSON.parse(pkgRaw) : {};
231
355
  const dependencies = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
232
356
 
233
- const globalCssPath = CANDIDATE_CSS.find((p) => fs.existsSync(path.join(cwd, p))) ?? null;
357
+ const packageManager = detectPackageManager({
358
+ packageManagerField: pkg.packageManager ?? null,
359
+ userAgent: process.env.npm_config_user_agent ?? null,
360
+ lockfiles: LOCKFILES.filter((f) => fs.existsSync(path.join(cwd, f))),
361
+ });
362
+
363
+ const css = detectGlobalCss(cwd);
234
364
  const layoutPath = CANDIDATE_LAYOUT.find((p) => fs.existsSync(path.join(cwd, p))) ?? null;
235
365
 
236
366
  const _existing = new Set();
@@ -240,12 +370,13 @@ function readRepoState(cwd) {
240
370
 
241
371
  return {
242
372
  dependencies,
243
- globalCssPath,
244
- globalCssContent: globalCssPath ? read(globalCssPath) : null,
373
+ globalCssPath: css.path,
374
+ globalCssContent: css.content,
245
375
  layoutPath,
246
376
  layoutContent: layoutPath ? read(layoutPath) : null,
247
377
  hasAdsLintConfig:
248
378
  fs.existsSync(path.join(cwd, "ads-lint.config.mjs")) || fs.existsSync(path.join(cwd, "ads-lint.config.js")),
379
+ packageManager,
249
380
  _existing,
250
381
  };
251
382
  }
@@ -254,8 +385,9 @@ function applyEdit(cwd, edit) {
254
385
  const abs = edit.path ? path.join(cwd, edit.path) : null;
255
386
  switch (edit.type) {
256
387
  case "install": {
257
- console.log(` → npm install ${edit.packages.join(" ")}`);
258
- execFileSync("npm", ["install", ...edit.packages], { cwd, stdio: "inherit" });
388
+ const [cmd, args] = installCommand(edit.manager ?? "npm", edit.packages);
389
+ console.log(` → ${cmd} ${args.join(" ")}`);
390
+ execFileSync(cmd, args, { cwd, stdio: "inherit" });
259
391
  return;
260
392
  }
261
393
  case "write-file": {
@@ -288,6 +420,47 @@ function applyEdit(cwd, edit) {
288
420
  }
289
421
  }
290
422
 
423
+ /**
424
+ * End-of-run summary. The styles import carries EVERY token (colors, type, and
425
+ * the animation curves/speeds), so its outcome gets promoted out of the
426
+ * scrolling edit log into a verified ✓ / ⚠ block that can't be missed — plus any
427
+ * remaining manual steps and the import hint.
428
+ */
429
+ function printSummary(cwd, edits) {
430
+ const wired = verifyStylesWired(cwd);
431
+ const bar = "─".repeat(70);
432
+
433
+ console.log("");
434
+ if (wired.ok) {
435
+ console.log(`✓ ADS styles wired — \`${STYLE_IMPORT}\` is in ${wired.path}.`);
436
+ console.log(` That one import carries every ADS token: colors, typography, and the`);
437
+ console.log(` animation curves/speeds.`);
438
+ } else {
439
+ console.log(bar);
440
+ console.log(`⚠ ACTION REQUIRED — ADS styles are NOT imported yet.`);
441
+ console.log(``);
442
+ console.log(` Nothing will be themed until you add this to your global stylesheet,`);
443
+ console.log(` immediately AFTER the tailwindcss import (the order is load-bearing):`);
444
+ console.log(``);
445
+ console.log(` @import "tailwindcss";`);
446
+ console.log(` ${STYLE_IMPORT}`);
447
+ console.log(``);
448
+ console.log(` Without it NO tokens load — colors, typography, and every animation`);
449
+ console.log(` curve/speed will be missing.`);
450
+ console.log(bar);
451
+ }
452
+
453
+ // Surface any non-styles manual step (e.g. the layout <Providers> wrap) too —
454
+ // the styles step is already covered by the block above.
455
+ const otherManual = edits.filter((e) => e.type === "manual" && !/styles\.css/.test(e.title));
456
+ if (otherManual.length) {
457
+ console.log(`\n Remaining manual step(s):`);
458
+ for (const m of otherManual) console.log(` • ${m.title}`);
459
+ }
460
+
461
+ console.log(`\n Then import components: \`import { Button } from "${PKG}";\``);
462
+ }
463
+
291
464
  function main() {
292
465
  const cmd = process.argv[2];
293
466
  const cwd = process.cwd();
@@ -301,19 +474,36 @@ function main() {
301
474
 
302
475
  if (edits.length === 0) {
303
476
  console.log(`${PKG} init: nothing to do — already configured.`);
304
- return;
477
+ } else {
478
+ console.log(`${PKG} init: applying ${edits.length} edit(s)…`);
479
+ for (const edit of edits) applyEdit(cwd, edit);
480
+ console.log(`\n${PKG} init: done.`);
305
481
  }
306
482
 
307
- console.log(`${PKG} init: applying ${edits.length} edit(s)…`);
308
- for (const edit of edits) applyEdit(cwd, edit);
309
- console.log(`\n${PKG} init: done. Import components with \`import { Button } from "${PKG}";\``);
483
+ // Always verify + summarize the load-bearing styles import (fixes #2: a
484
+ // deferred/missed styles import can no longer scroll past unnoticed).
485
+ printSummary(cwd, edits);
486
+ }
487
+
488
+ /**
489
+ * True when this file is the process entry point (keeps planInit importable
490
+ * without triggering the applier). Compares REAL filesystem paths on both sides:
491
+ * npm installs the bin as a symlink (node_modules/.bin/ads -> …/ads-init.mjs),
492
+ * so process.argv[1] is the symlink while import.meta.url is realpath-resolved —
493
+ * a raw URL/string compare mismatches and `npx @actioneer/ads init` silently
494
+ * no-ops for every consumer. realpath'ing both sides also makes install paths
495
+ * with spaces or non-ASCII compare correctly.
496
+ */
497
+ function invokedAsCli() {
498
+ const entry = process.argv[1];
499
+ if (!entry) return false;
500
+ try {
501
+ return fs.realpathSync(entry) === fs.realpathSync(fileURLToPath(import.meta.url));
502
+ } catch {
503
+ return false;
504
+ }
310
505
  }
311
506
 
312
- // Only run the applier when invoked as a CLI (keeps planInit importable/pure).
313
- // Compare via pathToFileURL so an install path with spaces or non-ASCII (e.g.
314
- // "/Users/My Name/…") still matches — import.meta.url is percent-encoded, so a
315
- // raw `file://${process.argv[1]}` concat would silently fail to match and the
316
- // CLI would no-op.
317
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
507
+ if (invokedAsCli()) {
318
508
  main();
319
509
  }
package/llms.txt CHANGED
@@ -622,3 +622,132 @@ ADS targets landscape tablet (1024) → laptop (1440) → wide display (1920+);
622
622
  - [VoiceVariant] — TypeScript type
623
623
  - [useReasoning] — React hook
624
624
  - [Tool] — utility
625
+
626
+ ### Chat
627
+
628
+ - [ACTIONEER_FILE_ACCEPT] — component [docs](https://ads.actioneer.dev/#/demo/ask)
629
+ - [AssistantActionBar] — component [docs](https://ads.actioneer.dev/#/demo/ask)
630
+ - [AssistantMessage] — component [docs](https://ads.actioneer.dev/#/demo/ask)
631
+ - [AssistantRuntimeProvider] — component [docs](https://ads.actioneer.dev/#/demo/ask)
632
+ - [AttachmentPrimitive] — component [docs](https://ads.actioneer.dev/#/demo/ask)
633
+ - [BranchPicker] — component [docs](https://ads.actioneer.dev/#/demo/ask)
634
+ - [Composer] — component [docs](https://ads.actioneer.dev/#/demo/ask)
635
+ - [ComposerAddAttachment] — component [docs](https://ads.actioneer.dev/#/demo/ask)
636
+ - [ComposerAddDocument] — component [docs](https://ads.actioneer.dev/#/demo/ask)
637
+ - [ComposerAddImage] — component [docs](https://ads.actioneer.dev/#/demo/ask)
638
+ - [ComposerAttachments] — component [docs](https://ads.actioneer.dev/#/demo/ask)
639
+ - [ComposerContextProvider] — component [docs](https://ads.actioneer.dev/#/demo/ask)
640
+ - [ComposerDeepResearch] — component [docs](https://ads.actioneer.dev/#/demo/ask)
641
+ - [ComposerHarness] — component [docs](https://ads.actioneer.dev/#/demo/ask)
642
+ - [ComposerPlaceholdersProvider] — component [docs](https://ads.actioneer.dev/#/demo/ask)
643
+ - [ComposerPrimitive] — component [docs](https://ads.actioneer.dev/#/demo/ask)
644
+ - [ComposerQuotePreview] — component [docs](https://ads.actioneer.dev/#/demo/ask)
645
+ - [ComposerQuotePreviewDismiss] — component [docs](https://ads.actioneer.dev/#/demo/ask)
646
+ - [ComposerQuotePreviewIcon] — component [docs](https://ads.actioneer.dev/#/demo/ask)
647
+ - [ComposerQuotePreviewRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
648
+ - [ComposerQuotePreviewText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
649
+ - [ComposerTriggerPopover] — component [docs](https://ads.actioneer.dev/#/demo/ask)
650
+ - [CONTEXT_MIME] — component [docs](https://ads.actioneer.dev/#/demo/ask)
651
+ - [DeepResearchProvider] — component [docs](https://ads.actioneer.dev/#/demo/ask)
652
+ - [DirectiveText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
653
+ - [EditComposer] — component [docs](https://ads.actioneer.dev/#/demo/ask)
654
+ - [EMPTY_COMPONENTS] — component [docs](https://ads.actioneer.dev/#/demo/ask)
655
+ - [ErrorRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
656
+ - [ErrorText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
657
+ - [File] — component [docs](https://ads.actioneer.dev/#/demo/ask)
658
+ - [FileDownload] — component [docs](https://ads.actioneer.dev/#/demo/ask)
659
+ - [FileIconDisplay] — component [docs](https://ads.actioneer.dev/#/demo/ask)
660
+ - [FileName] — component [docs](https://ads.actioneer.dev/#/demo/ask)
661
+ - [FileRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
662
+ - [FileSize] — component [docs](https://ads.actioneer.dev/#/demo/ask)
663
+ - [FollowUpSuggestion] — component [docs](https://ads.actioneer.dev/#/demo/ask)
664
+ - [HARNESS_MIME] — component [docs](https://ads.actioneer.dev/#/demo/ask)
665
+ - [Image] — component [docs](https://ads.actioneer.dev/#/demo/ask)
666
+ - [ImageActions] — component [docs](https://ads.actioneer.dev/#/demo/ask)
667
+ - [ImageContentFilterError] — component [docs](https://ads.actioneer.dev/#/demo/ask)
668
+ - [ImageFilename] — component [docs](https://ads.actioneer.dev/#/demo/ask)
669
+ - [ImageGenerating] — component [docs](https://ads.actioneer.dev/#/demo/ask)
670
+ - [ImagePreview] — component [docs](https://ads.actioneer.dev/#/demo/ask)
671
+ - [ImageRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
672
+ - [ImageZoom] — component [docs](https://ads.actioneer.dev/#/demo/ask)
673
+ - [MarkdownText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
674
+ - [MessageError] — component [docs](https://ads.actioneer.dev/#/demo/ask)
675
+ - [MessagePrimitive] — component [docs](https://ads.actioneer.dev/#/demo/ask)
676
+ - [MessageTiming] — component [docs](https://ads.actioneer.dev/#/demo/ask)
677
+ - [OpenUiLibraryProvider] — component [docs](https://ads.actioneer.dev/#/demo/ask)
678
+ - [QuoteBlock] — component [docs](https://ads.actioneer.dev/#/demo/ask)
679
+ - [QuoteBlockIcon] — component [docs](https://ads.actioneer.dev/#/demo/ask)
680
+ - [QuoteBlockRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
681
+ - [QuoteBlockText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
682
+ - [RenderAnswerToolUI] — component [docs](https://ads.actioneer.dev/#/demo/ask)
683
+ - [SelectionToolbar] — component [docs](https://ads.actioneer.dev/#/demo/ask)
684
+ - [SelectionToolbarCopy] — component [docs](https://ads.actioneer.dev/#/demo/ask)
685
+ - [SelectionToolbarPrimitive] — component [docs](https://ads.actioneer.dev/#/demo/ask)
686
+ - [SelectionToolbarQuote] — component [docs](https://ads.actioneer.dev/#/demo/ask)
687
+ - [SelectionToolbarRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
688
+ - [SmartMessageText] — component [docs](https://ads.actioneer.dev/#/demo/ask)
689
+ - [Source] — component [docs](https://ads.actioneer.dev/#/demo/ask)
690
+ - [SourceIcon] — component [docs](https://ads.actioneer.dev/#/demo/ask)
691
+ - [Sources] — component [docs](https://ads.actioneer.dev/#/demo/ask)
692
+ - [SourceTitle] — component [docs](https://ads.actioneer.dev/#/demo/ask)
693
+ - [Thread] — component [docs](https://ads.actioneer.dev/#/demo/ask)
694
+ - [ThreadFollowupSuggestions] — component [docs](https://ads.actioneer.dev/#/demo/ask)
695
+ - [ThreadMessage] — component [docs](https://ads.actioneer.dev/#/demo/ask)
696
+ - [ThreadPrimitive] — component [docs](https://ads.actioneer.dev/#/demo/ask)
697
+ - [ThreadSuggestionItem] — component [docs](https://ads.actioneer.dev/#/demo/ask)
698
+ - [ThreadSuggestions] — component [docs](https://ads.actioneer.dev/#/demo/ask)
699
+ - [ToolFallback] — component [docs](https://ads.actioneer.dev/#/demo/ask)
700
+ - [ToolFallbackApproval] — component [docs](https://ads.actioneer.dev/#/demo/ask)
701
+ - [ToolFallbackArgs] — component [docs](https://ads.actioneer.dev/#/demo/ask)
702
+ - [ToolFallbackContent] — component [docs](https://ads.actioneer.dev/#/demo/ask)
703
+ - [ToolFallbackError] — component [docs](https://ads.actioneer.dev/#/demo/ask)
704
+ - [ToolFallbackResult] — component [docs](https://ads.actioneer.dev/#/demo/ask)
705
+ - [ToolFallbackRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
706
+ - [ToolFallbackTrigger] — component [docs](https://ads.actioneer.dev/#/demo/ask)
707
+ - [ToolGroup] — component [docs](https://ads.actioneer.dev/#/demo/ask)
708
+ - [ToolGroupContent] — component [docs](https://ads.actioneer.dev/#/demo/ask)
709
+ - [ToolGroupRoot] — component [docs](https://ads.actioneer.dev/#/demo/ask)
710
+ - [ToolGroupTrigger] — component [docs](https://ads.actioneer.dev/#/demo/ask)
711
+ - [TooltipIconButton] — component [docs](https://ads.actioneer.dev/#/demo/ask)
712
+ - [UserActionBar] — component [docs](https://ads.actioneer.dev/#/demo/ask)
713
+ - [UserMessage] — component [docs](https://ads.actioneer.dev/#/demo/ask)
714
+ - [UserMessageAttachments] — component [docs](https://ads.actioneer.dev/#/demo/ask)
715
+ - [ChatModelAdapter] — TypeScript type
716
+ - [ContextNode] — TypeScript type
717
+ - [CreateDirectiveTextOptions] — TypeScript type
718
+ - [DeepResearchState] — TypeScript type
719
+ - [FileRootProps] — TypeScript type
720
+ - [FollowUpSuggestionProps] — TypeScript type
721
+ - [ImageActionsProps] — TypeScript type
722
+ - [ImageRootProps] — TypeScript type
723
+ - [LocalRuntimeOptions] — TypeScript type
724
+ - [SourceProps] — TypeScript type
725
+ - [StageUsageHandler] — TypeScript type
726
+ - [ThreadComponents] — TypeScript type
727
+ - [ThreadGroupPart] — TypeScript type
728
+ - [ThreadMessageLike] — TypeScript type
729
+ - [ThreadProps] — TypeScript type
730
+ - [ToolCallMessagePartProps] — TypeScript type
731
+ - [ToolFallbackRootProps] — TypeScript type
732
+ - [ToolGroupRootProps] — TypeScript type
733
+ - [TooltipIconButtonProps] — TypeScript type
734
+ - [useAssistantRuntime] — React hook
735
+ - [useAuiState] — React hook
736
+ - [useLocalRuntime] — React hook
737
+ - [ActioneerFileAttachmentAdapter] — utility
738
+ - [ComposerAddContext] — utility
739
+ - [CompositeAttachmentAdapter] — utility
740
+ - [ContextAttachmentAdapter] — utility
741
+ - [createDirectiveText] — utility
742
+ - [fileVariants] — utility
743
+ - [formatFileSize] — utility
744
+ - [getBase64Size] — utility
745
+ - [getMimeTypeIcon] — utility
746
+ - [HarnessAttachmentAdapter] — utility
747
+ - [imageVariants] — utility
748
+ - [isNewChatView] — utility
749
+ - [makeAssistantToolUI] — utility
750
+ - [SimpleImageAttachmentAdapter] — utility
751
+ - [sourceVariants] — utility
752
+ - [ThreadComponentsContext] — utility
753
+ - [toolGroupVariants] — utility