@brokr/sdk 2.1.0 → 2.1.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.
Files changed (77) hide show
  1. package/dist/feature.js +22 -2
  2. package/dist/feature.mjs +22 -2
  3. package/dist/index.js +22 -2
  4. package/dist/index.mjs +22 -2
  5. package/dist/next.js +22 -2
  6. package/dist/next.mjs +22 -2
  7. package/dist/react-styles.js +540 -95
  8. package/dist/react-styles.mjs +540 -95
  9. package/dist/react.js +665 -331
  10. package/dist/react.mjs +548 -217
  11. package/dist/runtime.js +22 -2
  12. package/dist/runtime.mjs +22 -2
  13. package/dist/src/chat/config.d.ts +2 -0
  14. package/dist/src/chat/config.d.ts.map +1 -1
  15. package/dist/src/gateway.d.ts.map +1 -1
  16. package/dist/src/models.d.ts +2 -0
  17. package/dist/src/models.d.ts.map +1 -1
  18. package/dist/src/react/chat/AIChat.d.ts.map +1 -1
  19. package/dist/src/react/chat/ChatContext.d.ts +3 -6
  20. package/dist/src/react/chat/ChatContext.d.ts.map +1 -1
  21. package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -1
  22. package/dist/src/react/chat/ModelSelector.d.ts +1 -1
  23. package/dist/src/react/chat/ModelSelector.d.ts.map +1 -1
  24. package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -1
  25. package/dist/src/react/chat/memory.d.ts +12 -0
  26. package/dist/src/react/chat/memory.d.ts.map +1 -0
  27. package/dist/src/react/chat/types.d.ts +6 -0
  28. package/dist/src/react/chat/types.d.ts.map +1 -1
  29. package/dist/src/react/chat/useChat.d.ts +8 -6
  30. package/dist/src/react/chat/useChat.d.ts.map +1 -1
  31. package/dist/src/react/composites/FabAI.d.ts +6 -1
  32. package/dist/src/react/composites/FabAI.d.ts.map +1 -1
  33. package/dist/src/react/composites/fab-context.d.ts +26 -0
  34. package/dist/src/react/composites/fab-context.d.ts.map +1 -0
  35. package/dist/src/react/css/account.d.ts +1 -1
  36. package/dist/src/react/css/account.d.ts.map +1 -1
  37. package/dist/src/react/css/activity-feed.d.ts +2 -0
  38. package/dist/src/react/css/activity-feed.d.ts.map +1 -0
  39. package/dist/src/react/css/auth.d.ts +1 -1
  40. package/dist/src/react/css/auth.d.ts.map +1 -1
  41. package/dist/src/react/css/chat-extras.d.ts +1 -1
  42. package/dist/src/react/css/chat-extras.d.ts.map +1 -1
  43. package/dist/src/react/css/chat.d.ts +1 -1
  44. package/dist/src/react/css/chat.d.ts.map +1 -1
  45. package/dist/src/react/css/composites.d.ts +1 -1
  46. package/dist/src/react/css/composites.d.ts.map +1 -1
  47. package/dist/src/react/css/data-table.d.ts +2 -0
  48. package/dist/src/react/css/data-table.d.ts.map +1 -0
  49. package/dist/src/react/css/gates.d.ts +1 -1
  50. package/dist/src/react/css/gates.d.ts.map +1 -1
  51. package/dist/src/react/css/index.d.ts.map +1 -1
  52. package/dist/src/react/css/markdown.d.ts +1 -1
  53. package/dist/src/react/css/markdown.d.ts.map +1 -1
  54. package/dist/src/react/css/primitives.d.ts +1 -1
  55. package/dist/src/react/css/primitives.d.ts.map +1 -1
  56. package/dist/src/react/css/reset.d.ts +1 -1
  57. package/dist/src/react/css/reset.d.ts.map +1 -1
  58. package/dist/src/react/css/responsive.d.ts +1 -1
  59. package/dist/src/react/css/responsive.d.ts.map +1 -1
  60. package/dist/src/react/css/skeleton.d.ts +1 -1
  61. package/dist/src/react/css/skeleton.d.ts.map +1 -1
  62. package/dist/src/react/css/stats-grid.d.ts +2 -0
  63. package/dist/src/react/css/stats-grid.d.ts.map +1 -0
  64. package/dist/src/react/css/usage-grid.d.ts +2 -0
  65. package/dist/src/react/css/usage-grid.d.ts.map +1 -0
  66. package/dist/src/react/index.d.ts +4 -1
  67. package/dist/src/react/index.d.ts.map +1 -1
  68. package/dist/src/react/notifications/ActivityFeed.d.ts +14 -0
  69. package/dist/src/react/notifications/ActivityFeed.d.ts.map +1 -0
  70. package/dist/src/react/payments/UsageGrid.d.ts +11 -0
  71. package/dist/src/react/payments/UsageGrid.d.ts.map +1 -0
  72. package/dist/src/react/primitives/DataTable.d.ts +28 -0
  73. package/dist/src/react/primitives/DataTable.d.ts.map +1 -0
  74. package/dist/src/react/primitives/StatsGrid.d.ts +18 -0
  75. package/dist/src/react/primitives/StatsGrid.d.ts.map +1 -0
  76. package/dist/tsconfig.tsbuildinfo +1 -1
  77. package/package.json +1 -1
package/dist/react.mjs CHANGED
@@ -4197,6 +4197,7 @@ import React2, {
4197
4197
  } from "react";
4198
4198
 
4199
4199
  // src/models.ts
4200
+ var STACK_DEFAULT = "__stack_default__";
4200
4201
  var providers = [
4201
4202
  { id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
4202
4203
  { id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
@@ -6047,7 +6048,7 @@ function PlanCard({
6047
6048
  const handleClick = useCallback12(() => {
6048
6049
  void onSelect(plan.slug);
6049
6050
  }, [plan.slug, onSelect]);
6050
- return /* @__PURE__ */ React21.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ React21.createElement(
6051
+ return /* @__PURE__ */ React21.createElement("article", { className: "brokr-card brokr-plan-card", "data-highlight": isHighlighted, key: plan.slug }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React21.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React21.createElement("strong", null, plan.name), plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Current") : null, isHighlighted && !plan.isCurrent ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-badge" }, "Recommended") : null), /* @__PURE__ */ React21.createElement("div", { className: "brokr-plan-price" }, /* @__PURE__ */ React21.createElement("span", { className: "brokr-plan-value" }, plan.price === 0 ? "$0" : `$${(plan.price / 100).toFixed(0)}`), /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, "/", plan.interval)), plan.trialDays ? /* @__PURE__ */ React21.createElement("span", { className: "brokr-copy" }, plan.trialDays, " day trial included.") : null), /* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))), /* @__PURE__ */ React21.createElement(
6051
6052
  "button",
6052
6053
  {
6053
6054
  className: plan.isCurrent ? "brokr-button-secondary" : "brokr-button",
@@ -6056,7 +6057,7 @@ function PlanCard({
6056
6057
  type: "button"
6057
6058
  },
6058
6059
  plan.isCurrent ? "Current plan" : isPending ? "Redirecting" : "Choose plan"
6059
- ), /* @__PURE__ */ React21.createElement("ul", { className: "brokr-feature-list" }, features.map(([key, value]) => /* @__PURE__ */ React21.createElement("li", { className: "brokr-feature-item", key }, /* @__PURE__ */ React21.createElement(CheckIcon, { size: 14 }), /* @__PURE__ */ React21.createElement("span", null, featureLabel(key), " \xB7 ", featureValueLabel(value))))));
6060
+ ));
6060
6061
  }
6061
6062
  function Plans({
6062
6063
  columns,
@@ -6283,11 +6284,28 @@ function CancelSubscription({ onCancel }) {
6283
6284
  return /* @__PURE__ */ React28.createElement("div", { className: "brokr-section", style: { gap: "0.75rem" } }, confirming ? /* @__PURE__ */ React28.createElement("div", { className: "brokr-inline-message" }, "Subscription changes are confirmed in billing. Continue?") : null, /* @__PURE__ */ React28.createElement("div", { className: "brokr-brand-row" }, /* @__PURE__ */ React28.createElement("button", { className: "brokr-button-ghost", disabled: isPending, onClick: handlePrimaryClick, type: "button" }, isPending ? "Opening billing" : confirming ? "Continue" : "Cancel subscription"), confirming ? /* @__PURE__ */ React28.createElement("button", { className: "brokr-button-secondary", onClick: handleDismiss, type: "button" }, "Keep subscription") : null));
6284
6285
  }
6285
6286
 
6287
+ // src/react/payments/UsageGrid.tsx
6288
+ import React29, { useMemo as useMemo18 } from "react";
6289
+ function UsageGrid() {
6290
+ const { entitlements, isLoaded } = useBrokr();
6291
+ const meteredFeatures = useMemo18(() => {
6292
+ if (!entitlements?.features) return [];
6293
+ return Object.entries(entitlements.features).filter(([_, check]) => check.limitType === "numeric").map(([slug]) => slug);
6294
+ }, [entitlements]);
6295
+ if (!isLoaded) {
6296
+ return /* @__PURE__ */ React29.createElement("div", { className: "brokr-usage-grid" }, /* @__PURE__ */ React29.createElement(Skeleton, { height: 72 }), /* @__PURE__ */ React29.createElement(Skeleton, { height: 72 }));
6297
+ }
6298
+ if (meteredFeatures.length === 0) {
6299
+ return null;
6300
+ }
6301
+ return /* @__PURE__ */ React29.createElement("div", { className: "brokr-usage-grid" }, meteredFeatures.map((slug) => /* @__PURE__ */ React29.createElement(FeatureMeter, { key: slug, feature: slug })));
6302
+ }
6303
+
6286
6304
  // src/react/chat/AIChat.tsx
6287
- import React35, {
6305
+ import React36, {
6288
6306
  useCallback as useCallback23,
6289
6307
  useEffect as useEffect12,
6290
- useMemo as useMemo23,
6308
+ useMemo as useMemo24,
6291
6309
  useRef as useRef7,
6292
6310
  useState as useState18
6293
6311
  } from "react";
@@ -6305,7 +6323,7 @@ function useChatState() {
6305
6323
  }
6306
6324
 
6307
6325
  // src/react/chat/useChat.ts
6308
- import { useCallback as useCallback16, useEffect as useEffect9, useMemo as useMemo18, useRef as useRef5, useState as useState16, useContext as useContext3 } from "react";
6326
+ import { useCallback as useCallback16, useEffect as useEffect9, useMemo as useMemo19, useRef as useRef5, useState as useState16, useContext as useContext3 } from "react";
6309
6327
 
6310
6328
  // src/ai/types.ts
6311
6329
  function contentToText(content) {
@@ -6410,6 +6428,39 @@ function isSSEResponse(response) {
6410
6428
  return ct.includes("text/event-stream") && response.body !== null;
6411
6429
  }
6412
6430
 
6431
+ // src/react/chat/memory.ts
6432
+ var MAX_MEMORY_CHARS = 2e3;
6433
+ function serializeMemory(obj, prefix = "") {
6434
+ const lines = [];
6435
+ for (const [key, value] of Object.entries(obj)) {
6436
+ if (value === null || value === void 0) continue;
6437
+ const label = prefix ? `${prefix}.${key}` : key;
6438
+ if (Array.isArray(value)) {
6439
+ lines.push(`${label}: ${value.join(", ")}`);
6440
+ } else if (typeof value === "object") {
6441
+ lines.push(...serializeMemory(value, label));
6442
+ } else {
6443
+ lines.push(`${label}: ${String(value)}`);
6444
+ }
6445
+ }
6446
+ return lines;
6447
+ }
6448
+ function buildEffectivePrompt(prompt, memory) {
6449
+ if (!memory || Object.keys(memory).length === 0) return prompt;
6450
+ let block = serializeMemory(memory).join("\n");
6451
+ if (block.length > MAX_MEMORY_CHARS) {
6452
+ block = block.slice(0, MAX_MEMORY_CHARS);
6453
+ if (typeof console !== "undefined") {
6454
+ console.warn("[brokr] memory exceeded 2000 char limit, truncated");
6455
+ }
6456
+ }
6457
+ const memSection = `[User context]
6458
+ ${block}`;
6459
+ return prompt ? `${memSection}
6460
+
6461
+ ${prompt}` : memSection;
6462
+ }
6463
+
6413
6464
  // src/react/chat/useChat.ts
6414
6465
  function makeId(prefix) {
6415
6466
  return `${prefix}_${crypto.randomUUID()}`;
@@ -6427,7 +6478,9 @@ function useChat(config) {
6427
6478
  const {
6428
6479
  endpoint,
6429
6480
  prompt,
6430
- model: activeModel,
6481
+ explicitModel,
6482
+ displayModel: activeModel,
6483
+ memory,
6431
6484
  persist,
6432
6485
  surface,
6433
6486
  subject,
@@ -6462,31 +6515,47 @@ function useChat(config) {
6462
6515
  const bottomRef = useRef5(null);
6463
6516
  const scrollContainerRef = useRef5(null);
6464
6517
  const sentinelRef = useRef5(null);
6465
- const displaySidebarItems = useMemo18(() => {
6466
- if (isControlled) return threadsProp?.map((t) => ({ id: t.id, title: t.title })) ?? [];
6467
- if (isPersist) return serverThreads.map((t) => ({ id: t.id, title: t.title }));
6468
- return memConversations.map((c) => ({ id: c.id, title: c.title || "New chat" }));
6518
+ const displaySidebarItems = useMemo19(() => {
6519
+ if (isControlled) {
6520
+ return threadsProp?.map((t) => ({
6521
+ id: t.id,
6522
+ title: t.title,
6523
+ updatedAt: t.updatedAt ?? null
6524
+ })) ?? [];
6525
+ }
6526
+ if (isPersist) {
6527
+ return serverThreads.map((t) => ({
6528
+ id: t.id,
6529
+ title: t.title,
6530
+ updatedAt: t.updatedAt ?? null
6531
+ }));
6532
+ }
6533
+ return memConversations.map((c) => ({
6534
+ id: c.id,
6535
+ title: c.title || "New chat",
6536
+ updatedAt: new Date(c.updatedAt).toISOString()
6537
+ }));
6469
6538
  }, [isControlled, isPersist, threadsProp, serverThreads, memConversations]);
6470
- const activeId = useMemo18(() => {
6539
+ const activeId = useMemo19(() => {
6471
6540
  if (isControlled) return activeThreadIdProp ?? null;
6472
6541
  if (isPersist) return serverActiveId;
6473
6542
  return memActiveId;
6474
6543
  }, [isControlled, isPersist, activeThreadIdProp, serverActiveId, memActiveId]);
6475
- const displayMessages = useMemo18(() => {
6544
+ const displayMessages = useMemo19(() => {
6476
6545
  if (isControlled) return messagesProp ?? [];
6477
6546
  if (isPersist) return serverMessages;
6478
6547
  return memConversations.find((c) => c.id === memActiveId)?.messages ?? [];
6479
6548
  }, [isControlled, isPersist, messagesProp, serverMessages, memConversations, memActiveId]);
6480
- const activeMemConv = useMemo18(
6549
+ const activeMemConv = useMemo19(
6481
6550
  () => memConversations.find((c) => c.id === memActiveId) ?? null,
6482
6551
  [memConversations, memActiveId]
6483
6552
  );
6484
- const activeThread = useMemo18(() => {
6553
+ const activeThread = useMemo19(() => {
6485
6554
  if (isControlled) return threadsProp?.find((t) => t.id === activeId) ?? null;
6486
6555
  if (isPersist) return serverThreads.find((t) => t.id === activeId) ?? null;
6487
6556
  return null;
6488
6557
  }, [isControlled, isPersist, threadsProp, serverThreads, activeId]);
6489
- const renderedTitle = useMemo18(() => {
6558
+ const renderedTitle = useMemo19(() => {
6490
6559
  if (isPersist && activeThread) return activeThread.title;
6491
6560
  if (!isPersist && activeMemConv) {
6492
6561
  if (activeMemConv.titleState === "loading") return "Naming conversation";
@@ -6646,8 +6715,11 @@ function useChat(config) {
6646
6715
  messages: trimToTokenBudget(
6647
6716
  nextMessages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map(({ content: mc, role }) => ({ role, content: mc }))
6648
6717
  ),
6649
- model: activeModel,
6650
- ...prompt ? { systemPrompt: prompt } : {}
6718
+ ...explicitModel ? { model: explicitModel } : {},
6719
+ ...(() => {
6720
+ const ep = buildEffectivePrompt(prompt, memory);
6721
+ return ep ? { systemPrompt: ep } : {};
6722
+ })()
6651
6723
  })
6652
6724
  });
6653
6725
  if (!response.ok) {
@@ -6692,8 +6764,10 @@ function useChat(config) {
6692
6764
  ensureMemConversation,
6693
6765
  memConversations,
6694
6766
  endpoint,
6767
+ explicitModel,
6695
6768
  activeModel,
6696
6769
  prompt,
6770
+ memory,
6697
6771
  updateMemMessages,
6698
6772
  appendMemDelta,
6699
6773
  animateTitle,
@@ -6715,9 +6789,12 @@ function useChat(config) {
6715
6789
  content,
6716
6790
  surface,
6717
6791
  subject: subject ?? null,
6718
- model: activeModel,
6792
+ ...explicitModel ? { model: explicitModel } : {},
6719
6793
  userId: userId ?? void 0,
6720
- ...prompt ? { systemPrompt: prompt } : {}
6794
+ ...(() => {
6795
+ const ep = buildEffectivePrompt(prompt, memory);
6796
+ return ep ? { systemPrompt: ep } : {};
6797
+ })()
6721
6798
  })
6722
6799
  });
6723
6800
  if (!response.ok) {
@@ -6772,8 +6849,10 @@ function useChat(config) {
6772
6849
  endpoint,
6773
6850
  surface,
6774
6851
  subject,
6852
+ explicitModel,
6775
6853
  activeModel,
6776
6854
  prompt,
6855
+ memory,
6777
6856
  userId,
6778
6857
  setServerMessages,
6779
6858
  setServerActiveId,
@@ -6930,7 +7009,7 @@ function useChat(config) {
6930
7009
  }
6931
7010
 
6932
7011
  // src/react/chat/ModelSelector.tsx
6933
- import React29, { useCallback as useCallback17, useEffect as useEffect10, useMemo as useMemo19, useRef as useRef6, useState as useState17 } from "react";
7012
+ import React30, { useCallback as useCallback17, useEffect as useEffect10, useMemo as useMemo20, useRef as useRef6, useState as useState17 } from "react";
6934
7013
  function ModelOption({
6935
7014
  provider,
6936
7015
  isActive,
@@ -6945,7 +7024,7 @@ function ModelOption({
6945
7024
  onSelect(provider.model);
6946
7025
  }
6947
7026
  }, [isLocked, onCheckout, onSelect, provider.model]);
6948
- return /* @__PURE__ */ React29.createElement(
7027
+ return /* @__PURE__ */ React30.createElement(
6949
7028
  "button",
6950
7029
  {
6951
7030
  "aria-selected": isActive,
@@ -6957,9 +7036,9 @@ function ModelOption({
6957
7036
  title: isLocked ? "Add credits to unlock" : void 0,
6958
7037
  type: "button"
6959
7038
  },
6960
- /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
6961
- /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-option-label" }, provider.label),
6962
- isLocked ? /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement(LockIcon, { size: 13 })) : null
7039
+ /* @__PURE__ */ React30.createElement("img", { alt: "", className: "brokr-model-logo", src: provider.logo }),
7040
+ /* @__PURE__ */ React30.createElement("span", { className: "brokr-model-option-label" }, provider.label),
7041
+ isLocked ? /* @__PURE__ */ React30.createElement("span", { className: "brokr-model-lock", "aria-hidden": "true" }, /* @__PURE__ */ React30.createElement(LockIcon, { size: 13 })) : null
6963
7042
  );
6964
7043
  }
6965
7044
  function ModelSelector({
@@ -6970,12 +7049,17 @@ function ModelSelector({
6970
7049
  }) {
6971
7050
  const [selectorOpen, setSelectorOpen] = useState17(false);
6972
7051
  const selectorRef = useRef6(null);
6973
- const activeProvider = useMemo19(
6974
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
6975
- [activeModel]
7052
+ const isAuto = activeModel === STACK_DEFAULT || !providers.some((p) => p.model === activeModel);
7053
+ const activeProvider = useMemo20(
7054
+ () => isAuto ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7055
+ [activeModel, isAuto]
6976
7056
  );
6977
7057
  const handleModelSelect = useCallback17((model) => {
6978
- setSelectedModel(model);
7058
+ setSelectedModel(model === STACK_DEFAULT ? null : model);
7059
+ setSelectorOpen(false);
7060
+ }, [setSelectedModel]);
7061
+ const handleAutoSelect = useCallback17(() => {
7062
+ setSelectedModel(null);
6979
7063
  setSelectorOpen(false);
6980
7064
  }, [setSelectedModel]);
6981
7065
  useEffect10(() => {
@@ -6996,7 +7080,7 @@ function ModelSelector({
6996
7080
  const handleToggle = useCallback17(() => {
6997
7081
  setSelectorOpen((v) => !v);
6998
7082
  }, []);
6999
- return /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-selector", ref: selectorRef }, /* @__PURE__ */ React29.createElement(
7083
+ return /* @__PURE__ */ React30.createElement("div", { className: "brokr-model-selector", ref: selectorRef }, /* @__PURE__ */ React30.createElement(
7000
7084
  "button",
7001
7085
  {
7002
7086
  "aria-expanded": selectorOpen,
@@ -7005,15 +7089,26 @@ function ModelSelector({
7005
7089
  onClick: handleToggle,
7006
7090
  type: "button"
7007
7091
  },
7008
- activeProvider ? /* @__PURE__ */ React29.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React29.createElement("span", { className: "brokr-model-dot", style: { background: "currentColor" } }),
7009
- activeProvider?.label ?? "Model",
7010
- /* @__PURE__ */ React29.createElement(ChevronDownIcon, { size: 13 })
7011
- ), selectorOpen ? /* @__PURE__ */ React29.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, providers.map((p) => /* @__PURE__ */ React29.createElement(
7092
+ activeProvider ? /* @__PURE__ */ React30.createElement("img", { alt: "", className: "brokr-model-logo", src: activeProvider.logo }) : /* @__PURE__ */ React30.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7093
+ isAuto ? "Auto" : activeProvider?.label ?? "Model",
7094
+ /* @__PURE__ */ React30.createElement(ChevronDownIcon, { size: 13 })
7095
+ ), selectorOpen ? /* @__PURE__ */ React30.createElement("div", { className: "brokr-model-dropdown", role: "listbox" }, /* @__PURE__ */ React30.createElement(
7096
+ "button",
7097
+ {
7098
+ "aria-selected": isAuto,
7099
+ className: "brokr-model-option",
7100
+ "data-active": isAuto,
7101
+ onClick: handleAutoSelect,
7102
+ type: "button"
7103
+ },
7104
+ /* @__PURE__ */ React30.createElement("span", { className: "brokr-model-dot", style: { background: "#6B7280" } }),
7105
+ /* @__PURE__ */ React30.createElement("span", { className: "brokr-model-option-label" }, "Stack Default")
7106
+ ), providers.map((p) => /* @__PURE__ */ React30.createElement(
7012
7107
  ModelOption,
7013
7108
  {
7014
7109
  key: p.id,
7015
7110
  provider: p,
7016
- isActive: p.model === activeModel,
7111
+ isActive: !isAuto && p.model === activeModel,
7017
7112
  isLocked: !availableProviders.some((a) => a.id === p.id),
7018
7113
  onSelect: handleModelSelect,
7019
7114
  onCheckout: checkout
@@ -7022,10 +7117,10 @@ function ModelSelector({
7022
7117
  }
7023
7118
 
7024
7119
  // src/react/chat/ThreadSidebar.tsx
7025
- import React30, { useCallback as useCallback18, useMemo as useMemo20 } from "react";
7120
+ import React31, { useCallback as useCallback18, useMemo as useMemo21 } from "react";
7026
7121
  function ThreadItemButton({ id, title, isActive, onSelect }) {
7027
7122
  const handleClick = useCallback18(() => onSelect(id), [id, onSelect]);
7028
- return /* @__PURE__ */ React30.createElement(
7123
+ return /* @__PURE__ */ React31.createElement(
7029
7124
  "button",
7030
7125
  {
7031
7126
  className: "brokr-ai-chat-conversation",
@@ -7033,9 +7128,40 @@ function ThreadItemButton({ id, title, isActive, onSelect }) {
7033
7128
  onClick: handleClick,
7034
7129
  type: "button"
7035
7130
  },
7036
- title
7131
+ /* @__PURE__ */ React31.createElement(MessageIcon, { size: 12 }),
7132
+ /* @__PURE__ */ React31.createElement("span", { className: "brokr-ai-chat-conversation-label" }, title)
7037
7133
  );
7038
7134
  }
7135
+ var SIDEBAR_GROUP_ORDER = [
7136
+ "today",
7137
+ "yesterday",
7138
+ "this_week",
7139
+ "this_month",
7140
+ "earlier",
7141
+ "undated"
7142
+ ];
7143
+ var SIDEBAR_GROUP_LABELS = {
7144
+ today: "Today",
7145
+ yesterday: "Yesterday",
7146
+ this_week: "This Week",
7147
+ this_month: "This Month",
7148
+ earlier: "Earlier",
7149
+ undated: "Recent"
7150
+ };
7151
+ function resolveSidebarDateGroup(updatedAt) {
7152
+ if (!updatedAt) return "undated";
7153
+ const parsed = new Date(updatedAt);
7154
+ if (Number.isNaN(parsed.getTime())) return "undated";
7155
+ const now = /* @__PURE__ */ new Date();
7156
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
7157
+ const startOfTarget = new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
7158
+ const diffDays = Math.floor((startOfToday.getTime() - startOfTarget.getTime()) / 864e5);
7159
+ if (diffDays <= 0) return "today";
7160
+ if (diffDays === 1) return "yesterday";
7161
+ if (diffDays < 7) return "this_week";
7162
+ if (diffDays < 30) return "this_month";
7163
+ return "earlier";
7164
+ }
7039
7165
  function ThreadSidebar() {
7040
7166
  const {
7041
7167
  startNewChat,
@@ -7067,14 +7193,31 @@ function ThreadSidebar() {
7067
7193
  const handleRenameChange = useCallback18((e) => {
7068
7194
  setRenameValue(e.target.value);
7069
7195
  }, [setRenameValue]);
7070
- const content = useMemo20(() => {
7196
+ const groupedSidebarItems = useMemo21(() => {
7197
+ const grouped = /* @__PURE__ */ new Map();
7198
+ for (const item of displaySidebarItems) {
7199
+ const key = resolveSidebarDateGroup(item.updatedAt);
7200
+ const bucket = grouped.get(key);
7201
+ if (bucket) {
7202
+ bucket.push(item);
7203
+ } else {
7204
+ grouped.set(key, [item]);
7205
+ }
7206
+ }
7207
+ return SIDEBAR_GROUP_ORDER.map((key) => ({
7208
+ key,
7209
+ label: SIDEBAR_GROUP_LABELS[key],
7210
+ items: grouped.get(key) ?? []
7211
+ })).filter((group) => group.items.length > 0);
7212
+ }, [displaySidebarItems]);
7213
+ const content = useMemo21(() => {
7071
7214
  if (threadsLoading && displaySidebarItems.length === 0) {
7072
- return /* @__PURE__ */ React30.createElement("div", { style: { display: "grid", gap: "0.5rem", padding: "0 0.65rem" } }, /* @__PURE__ */ React30.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ React30.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7215
+ return /* @__PURE__ */ React31.createElement("div", { className: "brokr-ai-chat-sidebar-skeleton" }, /* @__PURE__ */ React31.createElement(Skeleton, { width: "75%", height: 14, radius: 6 }), /* @__PURE__ */ React31.createElement(Skeleton, { width: "60%", height: 14, radius: 6 }), /* @__PURE__ */ React31.createElement(Skeleton, { width: "85%", height: 14, radius: 6 }));
7073
7216
  }
7074
7217
  if (displaySidebarItems.length === 0) {
7075
- return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ React30.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7218
+ return /* @__PURE__ */ React31.createElement("div", { className: "brokr-ai-chat-sidebar-empty" }, /* @__PURE__ */ React31.createElement("p", { className: "brokr-ai-chat-sidebar-empty-text" }, "No conversations yet. Start one above."));
7076
7219
  }
7077
- return /* @__PURE__ */ React30.createElement("div", { className: "brokr-ai-chat-conversations" }, displaySidebarItems.map((item) => renamingId === item.id ? /* @__PURE__ */ React30.createElement(
7220
+ return /* @__PURE__ */ React31.createElement("div", { className: "brokr-ai-chat-sidebar-groups" }, groupedSidebarItems.map((group) => /* @__PURE__ */ React31.createElement("section", { className: "brokr-ai-chat-sidebar-group", key: group.key }, /* @__PURE__ */ React31.createElement("span", { className: "brokr-ai-chat-sidebar-kicker" }, group.label), /* @__PURE__ */ React31.createElement("div", { className: "brokr-ai-chat-conversations" }, group.items.map((item) => renamingId === item.id ? /* @__PURE__ */ React31.createElement(
7078
7221
  "input",
7079
7222
  {
7080
7223
  autoFocus: true,
@@ -7085,7 +7228,7 @@ function ThreadSidebar() {
7085
7228
  onKeyDown: handleRenameKeyDown,
7086
7229
  value: renameValue
7087
7230
  }
7088
- ) : /* @__PURE__ */ React30.createElement(
7231
+ ) : /* @__PURE__ */ React31.createElement(
7089
7232
  ThreadItemButton,
7090
7233
  {
7091
7234
  key: item.id,
@@ -7094,29 +7237,30 @@ function ThreadSidebar() {
7094
7237
  isActive: item.id === activeId,
7095
7238
  onSelect: selectThreadAndCloseSidebar
7096
7239
  }
7097
- )));
7240
+ ))))));
7098
7241
  }, [
7099
7242
  threadsLoading,
7100
7243
  displaySidebarItems,
7101
7244
  renamingId,
7102
7245
  renameValue,
7246
+ groupedSidebarItems,
7103
7247
  activeId,
7104
7248
  selectThreadAndCloseSidebar,
7105
7249
  handleRenameBlur,
7106
7250
  handleRenameChange,
7107
7251
  handleRenameKeyDown
7108
7252
  ]);
7109
- return /* @__PURE__ */ React30.createElement(React30.Fragment, null, /* @__PURE__ */ React30.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ React30.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7253
+ return /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement("button", { className: "brokr-ai-chat-sidebar-new-chat", onClick: handleNewChat, type: "button" }, /* @__PURE__ */ React31.createElement(MessageIcon, { size: 16 }), "New chat"), content);
7110
7254
  }
7111
7255
 
7112
7256
  // src/react/chat/MessagePane.tsx
7113
- import React33, { useCallback as useCallback21, useMemo as useMemo22 } from "react";
7257
+ import React34, { useCallback as useCallback21, useMemo as useMemo23 } from "react";
7114
7258
 
7115
7259
  // src/react/chat/MessageBubble.tsx
7116
- import React32, { useCallback as useCallback20 } from "react";
7260
+ import React33, { useCallback as useCallback20 } from "react";
7117
7261
 
7118
7262
  // src/react/chat/MarkdownRenderer.tsx
7119
- import React31, { useCallback as useCallback19, useMemo as useMemo21 } from "react";
7263
+ import React32, { useCallback as useCallback19, useMemo as useMemo22 } from "react";
7120
7264
  function parseInline(text) {
7121
7265
  const nodes = [];
7122
7266
  let remaining = text;
@@ -7124,19 +7268,25 @@ function parseInline(text) {
7124
7268
  while (remaining) {
7125
7269
  const codeMatch = remaining.match(/^`([^`]+)`/);
7126
7270
  if (codeMatch) {
7127
- nodes.push(/* @__PURE__ */ React31.createElement("code", { className: "brokr-md-inline-code", key: key++ }, codeMatch[1]));
7271
+ nodes.push(/* @__PURE__ */ React32.createElement("code", { className: "brokr-md-inline-code", key: key++ }, codeMatch[1]));
7128
7272
  remaining = remaining.slice(codeMatch[0].length);
7129
7273
  continue;
7130
7274
  }
7131
7275
  const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
7132
7276
  if (boldMatch) {
7133
- nodes.push(/* @__PURE__ */ React31.createElement("strong", { key: key++ }, boldMatch[1]));
7277
+ nodes.push(/* @__PURE__ */ React32.createElement("strong", { key: key++ }, boldMatch[1]));
7134
7278
  remaining = remaining.slice(boldMatch[0].length);
7135
7279
  continue;
7136
7280
  }
7281
+ const strikethroughMatch = remaining.match(/^~~(.+?)~~/);
7282
+ if (strikethroughMatch) {
7283
+ nodes.push(/* @__PURE__ */ React32.createElement("del", { key: key++ }, strikethroughMatch[1]));
7284
+ remaining = remaining.slice(strikethroughMatch[0].length);
7285
+ continue;
7286
+ }
7137
7287
  const italicMatch = remaining.match(/^\*(.+?)\*/);
7138
7288
  if (italicMatch) {
7139
- nodes.push(/* @__PURE__ */ React31.createElement("em", { key: key++ }, italicMatch[1]));
7289
+ nodes.push(/* @__PURE__ */ React32.createElement("em", { key: key++ }, italicMatch[1]));
7140
7290
  remaining = remaining.slice(italicMatch[0].length);
7141
7291
  continue;
7142
7292
  }
@@ -7145,12 +7295,12 @@ function parseInline(text) {
7145
7295
  const rawHref = linkMatch[2];
7146
7296
  const isSafe = /^https?:\/\//i.test(rawHref) || /^mailto:/i.test(rawHref);
7147
7297
  nodes.push(
7148
- /* @__PURE__ */ React31.createElement("a", { className: "brokr-md-link", href: isSafe ? rawHref : "#", key: key++, rel: "noopener noreferrer", target: "_blank" }, linkMatch[1])
7298
+ /* @__PURE__ */ React32.createElement("a", { className: "brokr-md-link", href: isSafe ? rawHref : "#", key: key++, rel: "noopener noreferrer", target: "_blank" }, linkMatch[1])
7149
7299
  );
7150
7300
  remaining = remaining.slice(linkMatch[0].length);
7151
7301
  continue;
7152
7302
  }
7153
- const nextSpecial = remaining.search(/[`*\[]/);
7303
+ const nextSpecial = remaining.search(/[`*~\[]/);
7154
7304
  if (nextSpecial === -1) {
7155
7305
  nodes.push(remaining);
7156
7306
  break;
@@ -7169,7 +7319,7 @@ function CodeBlock({ code, language }) {
7169
7319
  const handleCopy = useCallback19(() => {
7170
7320
  void navigator.clipboard?.writeText(code);
7171
7321
  }, [code]);
7172
- return /* @__PURE__ */ React31.createElement("div", { className: "brokr-md-codeblock" }, /* @__PURE__ */ React31.createElement("div", { className: "brokr-md-codeblock-header" }, /* @__PURE__ */ React31.createElement("span", { className: "brokr-md-codeblock-lang" }, language || "code"), /* @__PURE__ */ React31.createElement(
7322
+ return /* @__PURE__ */ React32.createElement("div", { className: "brokr-md-codeblock" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-md-codeblock-header" }, /* @__PURE__ */ React32.createElement("span", { className: "brokr-md-codeblock-lang" }, language || "code"), /* @__PURE__ */ React32.createElement(
7173
7323
  "button",
7174
7324
  {
7175
7325
  "aria-label": "Copy code",
@@ -7177,9 +7327,18 @@ function CodeBlock({ code, language }) {
7177
7327
  onClick: handleCopy,
7178
7328
  type: "button"
7179
7329
  },
7180
- /* @__PURE__ */ React31.createElement(CopyIcon, { size: 13 }),
7330
+ /* @__PURE__ */ React32.createElement(CopyIcon, { size: 13 }),
7181
7331
  "Copy"
7182
- )), /* @__PURE__ */ React31.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ React31.createElement("code", null, code)));
7332
+ )), /* @__PURE__ */ React32.createElement("pre", { className: "brokr-md-codeblock-pre" }, /* @__PURE__ */ React32.createElement("code", null, code)));
7333
+ }
7334
+ function looksLikeTableSeparator(line) {
7335
+ const trimmed = line.trim();
7336
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed);
7337
+ }
7338
+ function splitTableRow(line) {
7339
+ const trimmed = line.trim();
7340
+ const withoutEdges = trimmed.replace(/^\|/, "").replace(/\|$/, "");
7341
+ return withoutEdges.split("|").map((cell) => cell.trim());
7183
7342
  }
7184
7343
  function parseBlocks(text) {
7185
7344
  const blocks = [];
@@ -7210,22 +7369,44 @@ function parseBlocks(text) {
7210
7369
  i++;
7211
7370
  continue;
7212
7371
  }
7372
+ if (line.includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) {
7373
+ const headers = splitTableRow(line);
7374
+ const rows = [];
7375
+ i += 2;
7376
+ while (i < lines.length) {
7377
+ const candidate = lines[i] ?? "";
7378
+ if (!candidate.trim() || !candidate.includes("|")) break;
7379
+ rows.push(splitTableRow(candidate));
7380
+ i++;
7381
+ }
7382
+ blocks.push({ type: "table", content: "", headers, rows });
7383
+ continue;
7384
+ }
7385
+ if (/^[\s]*>\s?/.test(line)) {
7386
+ const quoteLines = [];
7387
+ while (i < lines.length && /^[\s]*>\s?/.test(lines[i])) {
7388
+ quoteLines.push(lines[i].replace(/^[\s]*>\s?/, ""));
7389
+ i++;
7390
+ }
7391
+ blocks.push({ type: "quote", content: "", items: quoteLines });
7392
+ continue;
7393
+ }
7213
7394
  if (/^[\s]*[-*]\s+/.test(line)) {
7214
7395
  const items = [];
7215
7396
  while (i < lines.length && /^[\s]*[-*]\s+/.test(lines[i])) {
7216
7397
  items.push(lines[i].replace(/^[\s]*[-*]\s+/, ""));
7217
7398
  i++;
7218
7399
  }
7219
- blocks.push({ type: "list", content: "", items });
7400
+ blocks.push({ type: "list", content: "", items, ordered: false });
7220
7401
  continue;
7221
7402
  }
7222
- if (/^[\s]*\d+\.\s+/.test(line)) {
7403
+ if (/^[\s]*\d+[.)]\s+/.test(line)) {
7223
7404
  const items = [];
7224
- while (i < lines.length && /^[\s]*\d+\.\s+/.test(lines[i])) {
7225
- items.push(lines[i].replace(/^[\s]*\d+\.\s+/, ""));
7405
+ while (i < lines.length && /^[\s]*\d+[.)]\s+/.test(lines[i])) {
7406
+ items.push(lines[i].replace(/^[\s]*\d+[.)]\s+/, ""));
7226
7407
  i++;
7227
7408
  }
7228
- blocks.push({ type: "list", content: "", items });
7409
+ blocks.push({ type: "list", content: "", items, ordered: true });
7229
7410
  continue;
7230
7411
  }
7231
7412
  if (!line.trim()) {
@@ -7234,7 +7415,7 @@ function parseBlocks(text) {
7234
7415
  }
7235
7416
  const paraLines = [line];
7236
7417
  i++;
7237
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+\.\s+/.test(lines[i])) {
7418
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("```") && !lines[i].match(/^#{1,6}\s/) && !(lines[i].includes("|") && i + 1 < lines.length && looksLikeTableSeparator(lines[i + 1] ?? "")) && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(lines[i].trim()) && !/^[\s]*>\s?/.test(lines[i]) && !/^[\s]*[-*]\s+/.test(lines[i]) && !/^[\s]*\d+[.)]\s+/.test(lines[i])) {
7238
7419
  paraLines.push(lines[i]);
7239
7420
  i++;
7240
7421
  }
@@ -7246,28 +7427,35 @@ function renderBlocks(blocks) {
7246
7427
  return blocks.map((block, idx) => {
7247
7428
  switch (block.type) {
7248
7429
  case "code":
7249
- return /* @__PURE__ */ React31.createElement(CodeBlock, { code: block.content, key: idx, language: block.language });
7430
+ return /* @__PURE__ */ React32.createElement(CodeBlock, { code: block.content, key: idx, language: block.language });
7250
7431
  case "divider":
7251
- return /* @__PURE__ */ React31.createElement("hr", { className: "brokr-md-divider", key: idx });
7432
+ return /* @__PURE__ */ React32.createElement("hr", { className: "brokr-md-divider", key: idx });
7252
7433
  case "heading": {
7253
7434
  const Tag = `h${Math.min(block.level ?? 3, 6)}`;
7254
- return /* @__PURE__ */ React31.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7435
+ return /* @__PURE__ */ React32.createElement(Tag, { className: `brokr-md-heading brokr-md-h${block.level}`, key: idx }, parseInline(block.content));
7255
7436
  }
7256
7437
  case "list":
7257
- return /* @__PURE__ */ React31.createElement("ul", { className: "brokr-md-list", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React31.createElement("li", { key: li }, parseInline(item))));
7438
+ if (block.ordered) {
7439
+ return /* @__PURE__ */ React32.createElement("ol", { className: "brokr-md-list brokr-md-list-ordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React32.createElement("li", { key: li }, parseInline(item))));
7440
+ }
7441
+ return /* @__PURE__ */ React32.createElement("ul", { className: "brokr-md-list brokr-md-list-unordered", key: idx }, block.items?.map((item, li) => /* @__PURE__ */ React32.createElement("li", { key: li }, parseInline(item))));
7442
+ case "quote":
7443
+ return /* @__PURE__ */ React32.createElement("blockquote", { className: "brokr-md-quote", key: idx }, block.items?.map((line, lineIndex) => /* @__PURE__ */ React32.createElement("p", { className: "brokr-md-quote-line", key: lineIndex }, parseInline(line))));
7444
+ case "table":
7445
+ return /* @__PURE__ */ React32.createElement("div", { className: "brokr-md-table-wrap", key: idx }, /* @__PURE__ */ React32.createElement("table", { className: "brokr-md-table" }, /* @__PURE__ */ React32.createElement("thead", null, /* @__PURE__ */ React32.createElement("tr", null, block.headers?.map((header, headerIndex) => /* @__PURE__ */ React32.createElement("th", { key: headerIndex }, parseInline(header))))), /* @__PURE__ */ React32.createElement("tbody", null, block.rows?.map((row, rowIndex) => /* @__PURE__ */ React32.createElement("tr", { key: rowIndex }, row.map((cell, cellIndex) => /* @__PURE__ */ React32.createElement("td", { key: cellIndex }, parseInline(cell))))))));
7258
7446
  case "paragraph":
7259
7447
  default:
7260
- return /* @__PURE__ */ React31.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
7448
+ return /* @__PURE__ */ React32.createElement("p", { className: "brokr-md-paragraph", key: idx }, parseInline(block.content));
7261
7449
  }
7262
7450
  });
7263
7451
  }
7264
7452
  function MarkdownRenderer({ content }) {
7265
- const rendered = useMemo21(() => {
7453
+ const rendered = useMemo22(() => {
7266
7454
  if (!content) return null;
7267
7455
  const blocks = parseBlocks(content);
7268
7456
  return renderBlocks(blocks);
7269
7457
  }, [content]);
7270
- return /* @__PURE__ */ React31.createElement("div", { className: "brokr-md" }, rendered);
7458
+ return /* @__PURE__ */ React32.createElement("div", { className: "brokr-md" }, rendered);
7271
7459
  }
7272
7460
 
7273
7461
  // src/react/chat/MessageBubble.tsx
@@ -7277,10 +7465,10 @@ function MessageBubble({ message, isTyping, user }) {
7277
7465
  });
7278
7466
  }, [message.content]);
7279
7467
  if (message.role === "user") {
7280
- return /* @__PURE__ */ React32.createElement("article", { className: "brokr-ai-chat-message", "data-role": "user" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "user" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-bubble" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-content brokr-ai-chat-message-content-user" }, message.content)), /* @__PURE__ */ React32.createElement(Avatar, { email: user?.email, name: user?.name, src: user?.image })));
7468
+ return /* @__PURE__ */ React33.createElement("article", { className: "brokr-ai-chat-message", "data-role": "user" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "user" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-bubble" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-content brokr-ai-chat-message-content-user" }, message.content)), /* @__PURE__ */ React33.createElement(Avatar, { email: user?.email, name: user?.name, src: user?.image })));
7281
7469
  }
7282
7470
  const mp = message.model ? resolveProviderByModel(message.model) : null;
7283
- return /* @__PURE__ */ React32.createElement("article", { className: "brokr-ai-chat-message", "data-role": "assistant" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "assistant" }, mp ? /* @__PURE__ */ React32.createElement("img", { alt: mp.label, className: "brokr-ai-chat-model-avatar", src: mp.logo }) : null, isTyping ? /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ React32.createElement("span", null), /* @__PURE__ */ React32.createElement("span", null), /* @__PURE__ */ React32.createElement("span", null)) : message.status === "error" && !message.content ? /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-error" }, "Something went wrong generating a reply.")) : /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ React32.createElement("div", { className: "brokr-ai-chat-message-content" }, /* @__PURE__ */ React32.createElement(MarkdownRenderer, { content: message.content })), message.content ? /* @__PURE__ */ React32.createElement(
7471
+ return /* @__PURE__ */ React33.createElement("article", { className: "brokr-ai-chat-message", "data-role": "assistant" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-row", "data-role": "assistant" }, mp ? /* @__PURE__ */ React33.createElement("img", { alt: mp.label, className: "brokr-ai-chat-model-avatar", src: mp.logo }) : null, isTyping ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ React33.createElement("span", null), /* @__PURE__ */ React33.createElement("span", null), /* @__PURE__ */ React33.createElement("span", null)) : message.status === "error" && !message.content ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-error" }, "Something went wrong generating a reply.")) : /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-wrap" }, /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-message-content" }, /* @__PURE__ */ React33.createElement(MarkdownRenderer, { content: message.content })), message.content ? /* @__PURE__ */ React33.createElement(
7284
7472
  "button",
7285
7473
  {
7286
7474
  "aria-label": "Copy message",
@@ -7288,7 +7476,7 @@ function MessageBubble({ message, isTyping, user }) {
7288
7476
  onClick: handleCopy,
7289
7477
  type: "button"
7290
7478
  },
7291
- /* @__PURE__ */ React32.createElement(CopyIcon, { size: 13 })
7479
+ /* @__PURE__ */ React33.createElement(CopyIcon, { size: 13 })
7292
7480
  ) : null)));
7293
7481
  }
7294
7482
 
@@ -7297,7 +7485,7 @@ function StarterPromptButton({ prompt, onSend }) {
7297
7485
  const handleClick = useCallback21(() => {
7298
7486
  void onSend(prompt);
7299
7487
  }, [prompt, onSend]);
7300
- return /* @__PURE__ */ React33.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
7488
+ return /* @__PURE__ */ React34.createElement("button", { className: "brokr-ai-chat-starter", onClick: handleClick, type: "button" }, prompt);
7301
7489
  }
7302
7490
  function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7303
7491
  const {
@@ -7315,10 +7503,10 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7315
7503
  user
7316
7504
  } = useChatState();
7317
7505
  const isEmpty = displayMessages.length === 0;
7318
- const messageElements = useMemo22(() => {
7506
+ const messageElements = useMemo23(() => {
7319
7507
  return visibleMessages.map((message, index) => {
7320
7508
  const isTyping = message.role === "assistant" && !message.content && (isSubmitting && index === visibleMessages.length - 1 || message.status === "pending" || message.status === "streaming");
7321
- return /* @__PURE__ */ React33.createElement(
7509
+ return /* @__PURE__ */ React34.createElement(
7322
7510
  MessageBubble,
7323
7511
  {
7324
7512
  isTyping,
@@ -7329,16 +7517,16 @@ function MessagePane({ starterPrompts, emptyTitle, emptyCopy, subtitle }) {
7329
7517
  );
7330
7518
  });
7331
7519
  }, [visibleMessages, isSubmitting, user]);
7332
- return /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ React33.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ React33.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ React33.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ React33.createElement(StarterPromptButton, { key: sp, prompt: sp, onSend: sendMessage }))) : null) : /* @__PURE__ */ React33.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ React33.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ React33.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ React33.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ React33.createElement("div", { ref: bottomRef })));
7520
+ return /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-thread", "data-empty": isEmpty, ref: scrollContainerRef }, isEmpty ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-empty" }, /* @__PURE__ */ React34.createElement(SparkIcon, { size: 28 }), /* @__PURE__ */ React34.createElement("h2", { className: "brokr-title" }, emptyTitle), subtitle ?? emptyCopy ? /* @__PURE__ */ React34.createElement("p", { className: "brokr-copy" }, subtitle ?? emptyCopy) : null, starterPrompts.length > 0 ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-starters" }, starterPrompts.map((sp) => /* @__PURE__ */ React34.createElement(StarterPromptButton, { key: sp, prompt: sp, onSend: sendMessage }))) : null) : /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-thread-inner" }, (isPersist ? hasMoreMessages : memHasMore) ? /* @__PURE__ */ React34.createElement("div", { ref: sentinelRef, className: "brokr-ai-chat-sentinel" }, loadingOlder ? /* @__PURE__ */ React34.createElement("div", { style: { display: "flex", justifyContent: "center", padding: "0.5rem" } }, /* @__PURE__ */ React34.createElement(Skeleton, { width: 120, height: 12, radius: 6 })) : null) : null, messageElements, /* @__PURE__ */ React34.createElement("div", { ref: bottomRef })));
7333
7521
  }
7334
7522
 
7335
7523
  // src/react/chat/ChatInput.tsx
7336
- import React34, { useCallback as useCallback22, useEffect as useEffect11 } from "react";
7524
+ import React35, { useCallback as useCallback22, useEffect as useEffect11 } from "react";
7337
7525
  function CommandButton({ cmd, chatContext }) {
7338
7526
  const handleClick = useCallback22(() => {
7339
7527
  void cmd.run(chatContext);
7340
7528
  }, [cmd, chatContext]);
7341
- return /* @__PURE__ */ React34.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
7529
+ return /* @__PURE__ */ React35.createElement("button", { className: "brokr-ai-chat-sidebar-button", key: cmd.id, onClick: handleClick, type: "button" }, cmd.text);
7342
7530
  }
7343
7531
  function ChatInput() {
7344
7532
  const {
@@ -7370,7 +7558,7 @@ function ChatInput() {
7370
7558
  const handleChange = useCallback22((e) => {
7371
7559
  setInput(e.target.value);
7372
7560
  }, [setInput]);
7373
- return /* @__PURE__ */ React34.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ React34.createElement(CommandButton, { key: cmd.id, cmd, chatContext }))) : null, /* @__PURE__ */ React34.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ React34.createElement(
7561
+ return /* @__PURE__ */ React35.createElement("form", { className: "brokr-ai-chat-input-area", onSubmit: handleSubmit }, /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-input-container" }, composerCommands.length > 0 ? /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-composer-actions" }, composerCommands.map((cmd) => /* @__PURE__ */ React35.createElement(CommandButton, { key: cmd.id, cmd, chatContext }))) : null, /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-input-row" }, /* @__PURE__ */ React35.createElement(
7374
7562
  "textarea",
7375
7563
  {
7376
7564
  className: "brokr-ai-chat-textarea",
@@ -7381,15 +7569,15 @@ function ChatInput() {
7381
7569
  rows: 1,
7382
7570
  value: input
7383
7571
  }
7384
- ), /* @__PURE__ */ React34.createElement(
7572
+ ), /* @__PURE__ */ React35.createElement(
7385
7573
  "button",
7386
7574
  {
7387
7575
  className: "brokr-ai-chat-send",
7388
7576
  disabled: isSubmitting || !input.trim(),
7389
7577
  type: "submit"
7390
7578
  },
7391
- isSubmitting ? /* @__PURE__ */ React34.createElement(SparkIcon, { size: 16 }) : /* @__PURE__ */ React34.createElement(ArrowRightIcon, { size: 16 })
7392
- ))), error ? /* @__PURE__ */ React34.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
7579
+ isSubmitting ? /* @__PURE__ */ React35.createElement(SparkIcon, { size: 16 }) : /* @__PURE__ */ React35.createElement(ArrowRightIcon, { size: 16 })
7580
+ ))), error ? /* @__PURE__ */ React35.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
7393
7581
  }
7394
7582
 
7395
7583
  // src/react/chat/AIChat.tsx
@@ -7411,6 +7599,7 @@ function AIChat(inlineProps) {
7411
7599
  subtitle,
7412
7600
  model: modelProp,
7413
7601
  modelSelector,
7602
+ memory,
7414
7603
  variant = 1,
7415
7604
  sidebar: sidebarProp,
7416
7605
  threadMenu: threadMenuProp,
@@ -7434,30 +7623,28 @@ function AIChat(inlineProps) {
7434
7623
  const headerVisible = variant !== 3;
7435
7624
  const threadMenuVisible = threadMenuProp !== void 0 ? threadMenuProp : variant !== 3;
7436
7625
  const modelSelectorVisible = (modelSelector !== void 0 ? modelSelector : true) && !modelProp;
7437
- const [selectedModel, setSelectedModel] = useState18(() => {
7438
- if (modelProp) return modelProp;
7439
- return providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7440
- });
7441
- useEffect12(() => {
7442
- if (modelProp) setSelectedModel(modelProp);
7443
- }, [modelProp]);
7444
- const activeModel = modelProp ?? selectedModel;
7445
- const activeProvider = useMemo23(
7446
- () => providers.find((p) => p.model === activeModel) ?? providers[0],
7626
+ const [userSelectedModel, setUserSelectedModel] = useState18(null);
7627
+ const explicitModel = modelProp ?? userSelectedModel ?? void 0;
7628
+ const displayModel = explicitModel ?? providers.find((p) => p.free)?.model ?? providers[0]?.model ?? "";
7629
+ const activeModel = explicitModel ?? STACK_DEFAULT;
7630
+ const activeProvider = useMemo24(
7631
+ () => activeModel === STACK_DEFAULT ? void 0 : providers.find((p) => p.model === activeModel) ?? providers[0],
7447
7632
  [activeModel]
7448
7633
  );
7449
- const hasBalance = useMemo23(
7634
+ const hasBalance = useMemo24(
7450
7635
  () => billing === null || billing.balanceCents === null || billing.balanceCents > 0 || billing.hasPaymentMethod,
7451
7636
  [billing]
7452
7637
  );
7453
- const availableProviders = useMemo23(
7638
+ const availableProviders = useMemo24(
7454
7639
  () => hasBalance ? providers : providers.filter((p) => p.free),
7455
7640
  [hasBalance]
7456
7641
  );
7457
7642
  const chat = useChat({
7458
7643
  endpoint,
7459
7644
  prompt,
7460
- model: activeModel,
7645
+ explicitModel,
7646
+ displayModel,
7647
+ memory,
7461
7648
  persist,
7462
7649
  surface,
7463
7650
  subject,
@@ -7533,10 +7720,10 @@ function AIChat(inlineProps) {
7533
7720
  document.addEventListener("keydown", onKeyDown);
7534
7721
  return () => document.removeEventListener("keydown", onKeyDown);
7535
7722
  }, [sidebarOpen]);
7536
- const finalTitle = useMemo23(() => {
7723
+ const finalTitle = useMemo24(() => {
7537
7724
  return chat.renderedTitle || title;
7538
7725
  }, [chat.renderedTitle, title]);
7539
- const chatContext = useMemo23(() => ({
7726
+ const chatContext = useMemo24(() => ({
7540
7727
  thread: chat.activeThread,
7541
7728
  messages: chat.displayMessages,
7542
7729
  user,
@@ -7544,19 +7731,19 @@ function AIChat(inlineProps) {
7544
7731
  newThread: chat.startNewChat,
7545
7732
  setThread: chat.selectThread
7546
7733
  }), [chat.activeThread, chat.displayMessages, user, chat.sendMessage, chat.startNewChat, chat.selectThread]);
7547
- const headerCommands = useMemo23(
7734
+ const headerCommands = useMemo24(
7548
7735
  () => commands.filter((c) => c.location === "header" && (!c.show || c.show(chatContext))),
7549
7736
  [commands, chatContext]
7550
7737
  );
7551
- const composerCommands = useMemo23(
7738
+ const composerCommands = useMemo24(
7552
7739
  () => commands.filter((c) => c.location === "composer" && (!c.show || c.show(chatContext))),
7553
7740
  [commands, chatContext]
7554
7741
  );
7555
- const threadMenuCommands = useMemo23(
7742
+ const threadMenuCommands = useMemo24(
7556
7743
  () => commands.filter((c) => c.location === "threadMenu" && (!c.show || c.show(chatContext))),
7557
7744
  [commands, chatContext]
7558
7745
  );
7559
- const chatState = useMemo23(() => ({
7746
+ const chatState = useMemo24(() => ({
7560
7747
  displayMessages: chat.displayMessages,
7561
7748
  visibleMessages: chat.displayMessages,
7562
7749
  isSubmitting: chat.isSubmitting,
@@ -7565,10 +7752,10 @@ function AIChat(inlineProps) {
7565
7752
  activeThread: chat.activeThread,
7566
7753
  renderedTitle: finalTitle,
7567
7754
  isTitleLoading: chat.isTitleLoading,
7568
- activeModel,
7755
+ activeModel: displayModel,
7569
7756
  activeProvider,
7570
- selectedModel,
7571
- setSelectedModel,
7757
+ selectedModel: displayModel,
7758
+ setSelectedModel: setUserSelectedModel,
7572
7759
  availableProviders,
7573
7760
  sendMessage: chat.sendMessage,
7574
7761
  startNewChat: chat.startNewChat,
@@ -7608,9 +7795,9 @@ function AIChat(inlineProps) {
7608
7795
  }), [
7609
7796
  chat,
7610
7797
  finalTitle,
7611
- activeModel,
7798
+ displayModel,
7612
7799
  activeProvider,
7613
- selectedModel,
7800
+ userSelectedModel,
7614
7801
  availableProviders,
7615
7802
  sidebarOpen,
7616
7803
  closeSidebar,
@@ -7623,16 +7810,16 @@ function AIChat(inlineProps) {
7623
7810
  checkout,
7624
7811
  user
7625
7812
  ]);
7626
- return /* @__PURE__ */ React35.createElement(ChatProvider, { value: chatState }, /* @__PURE__ */ React35.createElement(
7813
+ return /* @__PURE__ */ React36.createElement(ChatProvider, { value: chatState }, /* @__PURE__ */ React36.createElement(
7627
7814
  "section",
7628
7815
  {
7629
7816
  className: "brokr-ai-chat-shell",
7630
7817
  "data-sidebar": sidebarVisible,
7631
7818
  ref: shellRef
7632
7819
  },
7633
- sidebarVisible ? /* @__PURE__ */ React35.createElement("aside", { className: "brokr-ai-chat-sidebar brokr-ai-chat-sidebar-desktop" }, /* @__PURE__ */ React35.createElement(ThreadSidebar, null)) : null,
7634
- sidebarOpen ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-drawer-backdrop", onClick: closeSidebar }), /* @__PURE__ */ React35.createElement("aside", { className: "brokr-ai-chat-drawer" }, /* @__PURE__ */ React35.createElement(ThreadSidebar, null))) : null,
7635
- /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-stage", "data-noheader": !headerVisible }, headerVisible ? /* @__PURE__ */ React35.createElement(
7820
+ sidebarVisible ? /* @__PURE__ */ React36.createElement("aside", { className: "brokr-ai-chat-sidebar brokr-ai-chat-sidebar-desktop" }, /* @__PURE__ */ React36.createElement(ThreadSidebar, null)) : null,
7821
+ sidebarOpen ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-drawer-backdrop", onClick: closeSidebar }), /* @__PURE__ */ React36.createElement("aside", { className: "brokr-ai-chat-drawer" }, /* @__PURE__ */ React36.createElement(ThreadSidebar, null))) : null,
7822
+ /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-stage", "data-noheader": !headerVisible }, headerVisible ? /* @__PURE__ */ React36.createElement(
7636
7823
  ChatHeader,
7637
7824
  {
7638
7825
  activeId: chat.activeId,
@@ -7647,13 +7834,13 @@ function AIChat(inlineProps) {
7647
7834
  threadMenuRef,
7648
7835
  threadMenuVisible,
7649
7836
  setThreadMenuOpenId,
7650
- activeModel,
7651
- setSelectedModel,
7837
+ activeModel: displayModel,
7838
+ setSelectedModel: setUserSelectedModel,
7652
7839
  availableProviders,
7653
7840
  startRename: chatState.startRename,
7654
7841
  deleteThread: chat.deleteThread
7655
7842
  }
7656
- ) : null, /* @__PURE__ */ React35.createElement(
7843
+ ) : null, /* @__PURE__ */ React36.createElement(
7657
7844
  MessagePane,
7658
7845
  {
7659
7846
  starterPrompts,
@@ -7661,21 +7848,21 @@ function AIChat(inlineProps) {
7661
7848
  emptyCopy,
7662
7849
  subtitle
7663
7850
  }
7664
- ), /* @__PURE__ */ React35.createElement(ChatInput, null))
7851
+ ), /* @__PURE__ */ React36.createElement(ChatInput, null))
7665
7852
  ));
7666
7853
  }
7667
7854
  function CommandButton2({ cmd, chatContext }) {
7668
7855
  const handleClick = useCallback23(() => {
7669
7856
  void cmd.run(chatContext);
7670
7857
  }, [cmd, chatContext]);
7671
- return /* @__PURE__ */ React35.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
7858
+ return /* @__PURE__ */ React36.createElement("button", { className: "brokr-ai-chat-sidebar-button", onClick: handleClick, type: "button" }, cmd.text);
7672
7859
  }
7673
7860
  function MenuCommandItem({ cmd, chatContext, onClose }) {
7674
7861
  const handleClick = useCallback23(() => {
7675
7862
  onClose();
7676
7863
  void cmd.run(chatContext);
7677
7864
  }, [cmd, chatContext, onClose]);
7678
- return /* @__PURE__ */ React35.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
7865
+ return /* @__PURE__ */ React36.createElement("button", { className: "brokr-ai-chat-thread-dropdown-item", onClick: handleClick, type: "button" }, cmd.text);
7679
7866
  }
7680
7867
  function ChatHeader({
7681
7868
  activeId,
@@ -7710,7 +7897,7 @@ function ChatHeader({
7710
7897
  void deleteThread(activeId);
7711
7898
  }
7712
7899
  }, [activeId, deleteThread, setThreadMenuOpenId]);
7713
- return /* @__PURE__ */ React35.createElement("header", { className: "brokr-ai-chat-topbar" }, /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-topbar-left" }, sidebarVisible ? /* @__PURE__ */ React35.createElement(
7900
+ return /* @__PURE__ */ React36.createElement("header", { className: "brokr-ai-chat-topbar" }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-topbar-left" }, sidebarVisible ? /* @__PURE__ */ React36.createElement(
7714
7901
  "button",
7715
7902
  {
7716
7903
  "aria-label": "Open sidebar",
@@ -7718,8 +7905,8 @@ function ChatHeader({
7718
7905
  onClick: handleOpenSidebar,
7719
7906
  type: "button"
7720
7907
  },
7721
- /* @__PURE__ */ React35.createElement(MenuIcon, { size: 18 })
7722
- ) : null), /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ React35.createElement(CommandButton2, { key: cmd.id, cmd, chatContext })), modelSelectorVisible ? /* @__PURE__ */ React35.createElement(
7908
+ /* @__PURE__ */ React36.createElement(MenuIcon, { size: 18 })
7909
+ ) : null), /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-topbar-actions" }, headerCommands.map((cmd) => /* @__PURE__ */ React36.createElement(CommandButton2, { key: cmd.id, cmd, chatContext })), modelSelectorVisible ? /* @__PURE__ */ React36.createElement(
7723
7910
  ModelSelector,
7724
7911
  {
7725
7912
  activeModel,
@@ -7727,7 +7914,7 @@ function ChatHeader({
7727
7914
  checkout,
7728
7915
  setSelectedModel
7729
7916
  }
7730
- ) : null, activeId && threadMenuVisible ? /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-thread-menu-wrap", ref: threadMenuRef }, /* @__PURE__ */ React35.createElement(
7917
+ ) : null, activeId && threadMenuVisible ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-thread-menu-wrap", ref: threadMenuRef }, /* @__PURE__ */ React36.createElement(
7731
7918
  "button",
7732
7919
  {
7733
7920
  "aria-label": "Thread actions",
@@ -7735,17 +7922,17 @@ function ChatHeader({
7735
7922
  onClick: handleToggleMenu,
7736
7923
  type: "button"
7737
7924
  },
7738
- /* @__PURE__ */ React35.createElement(MoreHorizontalIcon, { size: 14 })
7739
- ), threadMenuOpenId ? /* @__PURE__ */ React35.createElement("div", { className: "brokr-ai-chat-thread-dropdown" }, /* @__PURE__ */ React35.createElement(
7925
+ /* @__PURE__ */ React36.createElement(MoreHorizontalIcon, { size: 14 })
7926
+ ), threadMenuOpenId ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-ai-chat-thread-dropdown" }, /* @__PURE__ */ React36.createElement(
7740
7927
  "button",
7741
7928
  {
7742
7929
  className: "brokr-ai-chat-thread-dropdown-item",
7743
7930
  onClick: handleRename,
7744
7931
  type: "button"
7745
7932
  },
7746
- /* @__PURE__ */ React35.createElement(PencilIcon, { size: 13 }),
7933
+ /* @__PURE__ */ React36.createElement(PencilIcon, { size: 13 }),
7747
7934
  "Rename"
7748
- ), threadMenuCommands.map((cmd) => /* @__PURE__ */ React35.createElement(
7935
+ ), threadMenuCommands.map((cmd) => /* @__PURE__ */ React36.createElement(
7749
7936
  MenuCommandItem,
7750
7937
  {
7751
7938
  key: cmd.id,
@@ -7753,7 +7940,7 @@ function ChatHeader({
7753
7940
  chatContext,
7754
7941
  onClose: closeMenu
7755
7942
  }
7756
- )), /* @__PURE__ */ React35.createElement(
7943
+ )), /* @__PURE__ */ React36.createElement(
7757
7944
  "button",
7758
7945
  {
7759
7946
  className: "brokr-ai-chat-thread-dropdown-item",
@@ -7761,38 +7948,77 @@ function ChatHeader({
7761
7948
  onClick: handleDelete,
7762
7949
  type: "button"
7763
7950
  },
7764
- /* @__PURE__ */ React35.createElement(TrashIcon, { size: 13 }),
7951
+ /* @__PURE__ */ React36.createElement(TrashIcon, { size: 13 }),
7765
7952
  "Delete"
7766
7953
  )) : null) : null));
7767
7954
  }
7768
7955
 
7769
7956
  // src/react/composites/FabAI.tsx
7770
- import React36, { useCallback as useCallback24, useMemo as useMemo24, useState as useState19 } from "react";
7771
- function StarterButton({ prompt, onSelect }) {
7772
- const handleClick = useCallback24(() => onSelect(prompt), [prompt, onSelect]);
7773
- return /* @__PURE__ */ React36.createElement("button", { className: "brokr-chat-starter", onClick: handleClick, type: "button" }, prompt);
7957
+ import React37, { useCallback as useCallback24, useMemo as useMemo25, useRef as useRef8, useState as useState19 } from "react";
7958
+
7959
+ // src/react/composites/fab-context.ts
7960
+ function buildFabSystemPrompt(appContext, brandName, existingPrompt) {
7961
+ if (appContext === false) return existingPrompt;
7962
+ const name = appContext?.name ?? brandName;
7963
+ if (!name && !appContext) return existingPrompt;
7964
+ const parts = [];
7965
+ if (name) parts.push(`You are an AI assistant embedded in ${name}.`);
7966
+ if (appContext?.description) parts.push(`This app is ${appContext.description}.`);
7967
+ if (appContext?.currentPage) parts.push(`The user is currently on: ${appContext.currentPage}.`);
7968
+ if (appContext?.facts?.length) parts.push(...appContext.facts);
7969
+ const autoPrompt = parts.join(" ");
7970
+ return existingPrompt ? `${autoPrompt}
7971
+
7972
+ ${existingPrompt}` : autoPrompt;
7973
+ }
7974
+
7975
+ // src/react/composites/FabAI.tsx
7976
+ function ensureAssistantReply(messages, content) {
7977
+ const normalized = content.trim() ? content : "No response received.";
7978
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7979
+ if (messages[index]?.role !== "assistant") continue;
7980
+ const next = [...messages];
7981
+ next[index] = { ...next[index], content: normalized };
7982
+ return next;
7983
+ }
7984
+ return [...messages, { role: "assistant", content: normalized }];
7985
+ }
7986
+ function appendAssistantDelta(messages, delta) {
7987
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
7988
+ if (messages[index]?.role !== "assistant") continue;
7989
+ const next = [...messages];
7990
+ next[index] = {
7991
+ ...next[index],
7992
+ content: `${contentToText(next[index].content)}${delta}`
7993
+ };
7994
+ return next;
7995
+ }
7996
+ return [...messages, { role: "assistant", content: delta }];
7774
7997
  }
7775
7998
  function FabAI({
7999
+ appContext,
7776
8000
  model,
8001
+ memory,
7777
8002
  onSendMessage,
7778
8003
  position = "bottom-right",
7779
- starterPrompts = [],
7780
8004
  systemPrompt
7781
8005
  }) {
7782
- const { can, user } = useBrokr();
8006
+ const { can, user, theme } = useBrokr();
8007
+ const brandName = theme?.brand?.name;
7783
8008
  const [isOpen, setIsOpen] = useState19(false);
7784
8009
  const [input, setInput] = useState19("");
7785
8010
  const [error, setError] = useState19(null);
7786
8011
  const [isSending, setIsSending] = useState19(false);
7787
8012
  const [messages, setMessages] = useState19([]);
7788
- const launcherStyle = useMemo24(
8013
+ const conversationIdRef = useRef8(`fab_${crypto.randomUUID()}`);
8014
+ const launcherStyle = useMemo25(
7789
8015
  () => ({
7790
8016
  left: position === "bottom-left" ? "var(--brokr-space-6)" : void 0,
7791
8017
  right: position === "bottom-right" ? "var(--brokr-space-6)" : void 0
7792
8018
  }),
7793
8019
  [position]
7794
8020
  );
7795
- const canChat = useMemo24(() => can("ai.chat"), [can]);
8021
+ const canChat = useMemo25(() => can("ai.chat"), [can]);
7796
8022
  const toggleOpen = useCallback24(() => {
7797
8023
  if (!canChat) {
7798
8024
  redirectTo("/pricing");
@@ -7808,37 +8034,75 @@ function FabAI({
7808
8034
  }, []);
7809
8035
  const sendPrompt = useCallback24(async (prompt) => {
7810
8036
  const nextPrompt = prompt.trim();
7811
- if (!nextPrompt) return;
7812
- const nextMessages = [...messages, { role: "user", content: nextPrompt }];
8037
+ if (!nextPrompt || isSending) return;
8038
+ const userMessage = { role: "user", content: nextPrompt };
8039
+ const nextMessages = [...messages, userMessage];
8040
+ const optimisticMessages = [...nextMessages, { role: "assistant", content: "" }];
7813
8041
  try {
7814
8042
  setError(null);
7815
8043
  setIsSending(true);
7816
- setMessages(nextMessages);
8044
+ setMessages(optimisticMessages);
7817
8045
  setInput("");
7818
- let responseText = "";
7819
8046
  if (onSendMessage) {
7820
- responseText = await onSendMessage({ messages: nextMessages, model, systemPrompt });
7821
- } else {
7822
- const payload = await postJson(
7823
- "/api/brokr/chat",
7824
- {
7825
- messages: nextMessages,
7826
- model,
7827
- systemPrompt
8047
+ const responseText2 = await onSendMessage({ messages: nextMessages, model, systemPrompt });
8048
+ setMessages((current) => ensureAssistantReply(current, responseText2));
8049
+ return;
8050
+ }
8051
+ const response = await fetch("/api/brokr/chat", {
8052
+ method: "POST",
8053
+ credentials: "include",
8054
+ headers: { "Content-Type": "application/json" },
8055
+ body: JSON.stringify({
8056
+ conversationId: conversationIdRef.current,
8057
+ messages: trimToTokenBudget(
8058
+ nextMessages.filter((message) => message.role === "user" || message.role === "assistant" && Boolean(contentToText(message.content))).map((message) => ({
8059
+ role: message.role,
8060
+ content: contentToText(message.content)
8061
+ }))
8062
+ ),
8063
+ ...model !== void 0 ? { model } : {},
8064
+ ...(() => {
8065
+ const withContext = buildFabSystemPrompt(appContext, brandName, systemPrompt);
8066
+ const ep = buildEffectivePrompt(withContext, memory);
8067
+ return ep ? { systemPrompt: ep } : {};
8068
+ })()
8069
+ })
8070
+ });
8071
+ if (!response.ok) {
8072
+ const payload2 = await response.json().catch(() => ({}));
8073
+ throw new Error(payload2.message ?? payload2.error ?? `Chat failed (${response.status})`);
8074
+ }
8075
+ if (isSSEResponse(response)) {
8076
+ let hasDelta = false;
8077
+ for await (const event of parseSSEStream(response)) {
8078
+ if (event.type === "conversation") {
8079
+ conversationIdRef.current = event.id;
8080
+ } else if (event.type === "delta") {
8081
+ hasDelta = true;
8082
+ setMessages((current) => appendAssistantDelta(current, event.delta));
8083
+ } else if (event.type === "error") {
8084
+ throw new Error(event.message);
7828
8085
  }
7829
- );
7830
- responseText = payload.content ?? payload.response ?? "";
8086
+ }
8087
+ if (!hasDelta) {
8088
+ setMessages((current) => ensureAssistantReply(current, "No response received."));
8089
+ }
8090
+ return;
8091
+ }
8092
+ const payload = await response.json().catch(() => ({}));
8093
+ if (payload.error || payload.message) {
8094
+ throw new Error(payload.message ?? payload.error ?? "AI request failed.");
7831
8095
  }
7832
- setMessages((current) => [...current, {
7833
- role: "assistant",
7834
- content: responseText || "No response received."
7835
- }]);
8096
+ const responseText = payload.content ?? payload.response ?? payload.text ?? "";
8097
+ setMessages((current) => ensureAssistantReply(current, responseText));
7836
8098
  } catch (cause) {
7837
- setError(cause instanceof Error ? cause.message : "Could not send message.");
8099
+ const message = cause instanceof Error ? cause.message : "Could not send message.";
8100
+ setError(message);
8101
+ setMessages((current) => ensureAssistantReply(current, `Error: ${message}`));
7838
8102
  } finally {
7839
8103
  setIsSending(false);
7840
8104
  }
7841
- }, [messages, model, onSendMessage, systemPrompt]);
8105
+ }, [isSending, messages, model, onSendMessage, systemPrompt]);
7842
8106
  const handleSubmit = useCallback24(async (event) => {
7843
8107
  event.preventDefault();
7844
8108
  await sendPrompt(input);
@@ -7849,30 +8113,31 @@ function FabAI({
7849
8113
  void sendPrompt(input);
7850
8114
  }
7851
8115
  }, [input, sendPrompt]);
7852
- const handleStarterPrompt = useCallback24((prompt) => {
7853
- void sendPrompt(prompt);
7854
- }, [sendPrompt]);
7855
- return /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ React36.createElement(
8116
+ return /* @__PURE__ */ React37.createElement(React37.Fragment, null, /* @__PURE__ */ React37.createElement("div", { className: "brokr-chat-fab", style: launcherStyle }, /* @__PURE__ */ React37.createElement(
7856
8117
  "button",
7857
8118
  {
8119
+ "aria-label": isOpen ? "Close AI chat" : "Open AI chat",
7858
8120
  "aria-expanded": isOpen,
7859
8121
  "aria-haspopup": "dialog",
7860
- className: "brokr-button",
8122
+ className: "brokr-chat-fab-trigger",
7861
8123
  onClick: toggleOpen,
7862
8124
  type: "button"
7863
8125
  },
7864
- /* @__PURE__ */ React36.createElement(MessageIcon, { size: 16 }),
7865
- "Ask AI"
7866
- )), isOpen ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ React36.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ React36.createElement(
8126
+ /* @__PURE__ */ React37.createElement(SparkIcon, { size: 18 })
8127
+ )), isOpen ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-panel brokr-chat-panel", role: "dialog" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, "AI Chat"), user?.name ? /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, user.name) : null), /* @__PURE__ */ React37.createElement("button", { className: "brokr-button-ghost", onClick: handleClose, type: "button" }, /* @__PURE__ */ React37.createElement(CloseIcon, { size: 16 }))), /* @__PURE__ */ React37.createElement(
7867
8128
  "div",
7868
8129
  {
7869
8130
  className: "brokr-chat-messages",
7870
8131
  "data-empty": messages.length === 0
7871
8132
  },
7872
- messages.length === 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React36.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React36.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React36.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ React36.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below.")), starterPrompts.length > 0 ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-starters" }, starterPrompts.map((prompt) => /* @__PURE__ */ React36.createElement(StarterButton, { key: prompt, prompt, onSelect: handleStarterPrompt }))) : null) : null,
7873
- messages.map((message, index) => /* @__PURE__ */ React36.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, contentToText(message.content))),
7874
- error ? /* @__PURE__ */ React36.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
7875
- ), /* @__PURE__ */ React36.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React36.createElement(
8133
+ messages.length === 0 ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-chat-empty" }, /* @__PURE__ */ React37.createElement(SparkIcon, { size: 18 }), /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, "Send a message to chat with the AI."), /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, "Ask a question or drop in a starter prompt below."))) : null,
8134
+ messages.map((message, index) => {
8135
+ const text = contentToText(message.content);
8136
+ const isTyping = message.role === "assistant" && !text && isSending && index === messages.length - 1;
8137
+ return /* @__PURE__ */ React37.createElement("div", { className: "brokr-chat-bubble", "data-role": message.role, key: `${message.role}-${index}` }, isTyping ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-ai-chat-typing", "aria-label": "AI is typing" }, /* @__PURE__ */ React37.createElement("span", null), /* @__PURE__ */ React37.createElement("span", null), /* @__PURE__ */ React37.createElement("span", null)) : message.role === "assistant" ? /* @__PURE__ */ React37.createElement(MarkdownRenderer, { content: text }) : text);
8138
+ }),
8139
+ error ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null
8140
+ ), /* @__PURE__ */ React37.createElement("form", { className: "brokr-section", onSubmit: handleSubmit, style: { gap: "var(--brokr-space-3)" } }, /* @__PURE__ */ React37.createElement(
7876
8141
  "textarea",
7877
8142
  {
7878
8143
  className: "brokr-textarea brokr-chat-input",
@@ -7882,15 +8147,15 @@ function FabAI({
7882
8147
  rows: 2,
7883
8148
  value: input
7884
8149
  }
7885
- ), /* @__PURE__ */ React36.createElement("button", { className: "brokr-button", disabled: isSending, type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
8150
+ ), /* @__PURE__ */ React37.createElement("button", { className: "brokr-button", disabled: isSending || !input.trim(), type: "submit" }, isSending ? "Thinking" : "Send"))) : null);
7886
8151
  }
7887
8152
 
7888
8153
  // src/react/composites/SmartUpload.tsx
7889
- import React37, {
8154
+ import React38, {
7890
8155
  useCallback as useCallback25,
7891
8156
  useId as useId2,
7892
- useMemo as useMemo25,
7893
- useRef as useRef8,
8157
+ useMemo as useMemo26,
8158
+ useRef as useRef9,
7894
8159
  useState as useState20
7895
8160
  } from "react";
7896
8161
  function SmartUpload({
@@ -7901,13 +8166,13 @@ function SmartUpload({
7901
8166
  }) {
7902
8167
  const { paymentsMode } = useBrokr();
7903
8168
  const inputId = useId2();
7904
- const inputRef = useRef8(null);
8169
+ const inputRef = useRef9(null);
7905
8170
  const [dragActive, setDragActive] = useState20(false);
7906
8171
  const [error, setError] = useState20(null);
7907
8172
  const [fileName, setFileName] = useState20(null);
7908
8173
  const [progress, setProgress] = useState20(0);
7909
8174
  const [isUploading, setIsUploading] = useState20(false);
7910
- const helperText = useMemo25(() => {
8175
+ const helperText = useMemo26(() => {
7911
8176
  return `${Math.round(maxSize / (1024 * 1024))} MB max file size.`;
7912
8177
  }, [maxSize]);
7913
8178
  const beginUpload = useCallback25((file) => {
@@ -7971,7 +8236,7 @@ function SmartUpload({
7971
8236
  const handleBrowse = useCallback25(() => {
7972
8237
  inputRef.current?.click();
7973
8238
  }, []);
7974
- return /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, "Upload files"), /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ React37.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ React37.createElement(
8239
+ return /* @__PURE__ */ React38.createElement("div", { className: "brokr-card brokr-upload-shell" }, /* @__PURE__ */ React38.createElement("div", { className: "brokr-brand-row", style: { justifyContent: "space-between" } }, /* @__PURE__ */ React38.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React38.createElement("strong", null, "Upload files"), /* @__PURE__ */ React38.createElement("span", { className: "brokr-copy" }, "Drop a file and let Brokr handle the boring part.")), paymentsMode === "sandbox" ? /* @__PURE__ */ React38.createElement("span", { className: "brokr-badge brokr-badge-sandbox" }, "Sandbox") : null), /* @__PURE__ */ React38.createElement(
7975
8240
  "label",
7976
8241
  {
7977
8242
  className: "brokr-upload-dropzone",
@@ -7982,11 +8247,11 @@ function SmartUpload({
7982
8247
  onDragOver: handleDragEnter,
7983
8248
  onDrop: handleDrop
7984
8249
  },
7985
- /* @__PURE__ */ React37.createElement(UploadIcon, { size: 24 }),
7986
- /* @__PURE__ */ React37.createElement("strong", null, "Drag and drop a file here or choose one"),
7987
- /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, helperText),
7988
- /* @__PURE__ */ React37.createElement("button", { className: "brokr-button-secondary", onClick: handleBrowse, type: "button" }, "Choose file")
7989
- ), /* @__PURE__ */ React37.createElement(
8250
+ /* @__PURE__ */ React38.createElement(UploadIcon, { size: 24 }),
8251
+ /* @__PURE__ */ React38.createElement("strong", null, "Drag and drop a file here or choose one"),
8252
+ /* @__PURE__ */ React38.createElement("span", { className: "brokr-copy" }, helperText),
8253
+ /* @__PURE__ */ React38.createElement("button", { className: "brokr-button-secondary", onClick: handleBrowse, type: "button" }, "Choose file")
8254
+ ), /* @__PURE__ */ React38.createElement(
7990
8255
  "input",
7991
8256
  {
7992
8257
  accept,
@@ -7996,11 +8261,11 @@ function SmartUpload({
7996
8261
  ref: inputRef,
7997
8262
  type: "file"
7998
8263
  }
7999
- ), fileName ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React37.createElement("strong", null, fileName), /* @__PURE__ */ React37.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ React37.createElement("div", { className: "brokr-meter-bar" }, /* @__PURE__ */ React37.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ React37.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
8264
+ ), fileName ? /* @__PURE__ */ React38.createElement("div", { className: "brokr-card brokr-upload-file" }, /* @__PURE__ */ React38.createElement("div", { className: "brokr-section", style: { gap: "var(--brokr-space-1)" } }, /* @__PURE__ */ React38.createElement("strong", null, fileName), /* @__PURE__ */ React38.createElement("span", { className: "brokr-copy" }, isUploading ? `Uploading ${progress}%` : progress === 100 ? "Processed" : "Queued")), /* @__PURE__ */ React38.createElement("div", { className: "brokr-meter-bar" }, /* @__PURE__ */ React38.createElement("div", { className: "brokr-meter-fill", style: { width: `${progress}%` } }))) : null, error ? /* @__PURE__ */ React38.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null);
8000
8265
  }
8001
8266
 
8002
8267
  // src/react/composites/FeedbackWidget.tsx
8003
- import React38, { useCallback as useCallback26, useState as useState21 } from "react";
8268
+ import React39, { useCallback as useCallback26, useState as useState21 } from "react";
8004
8269
  function FeedbackWidget({
8005
8270
  context,
8006
8271
  onSubmit
@@ -8050,7 +8315,7 @@ function FeedbackWidget({
8050
8315
  setIsPending(false);
8051
8316
  }
8052
8317
  }, [context, onSubmit, rating, text, user?.id]);
8053
- return /* @__PURE__ */ React38.createElement("form", { className: "brokr-card brokr-feedback-shell", onSubmit: handleSubmit }, /* @__PURE__ */ React38.createElement("div", { className: "brokr-section", style: { gap: "0.5rem" } }, /* @__PURE__ */ React38.createElement("strong", null, "How did this feel?"), /* @__PURE__ */ React38.createElement("p", { className: "brokr-copy" }, "Tight signal only. Tell us what helped or what felt off.")), /* @__PURE__ */ React38.createElement("div", { className: "brokr-feedback-rating" }, /* @__PURE__ */ React38.createElement(
8318
+ return /* @__PURE__ */ React39.createElement("form", { className: "brokr-card brokr-feedback-shell", onSubmit: handleSubmit }, /* @__PURE__ */ React39.createElement("div", { className: "brokr-section", style: { gap: "0.5rem" } }, /* @__PURE__ */ React39.createElement("strong", null, "How did this feel?"), /* @__PURE__ */ React39.createElement("p", { className: "brokr-copy" }, "Tight signal only. Tell us what helped or what felt off.")), /* @__PURE__ */ React39.createElement("div", { className: "brokr-feedback-rating" }, /* @__PURE__ */ React39.createElement(
8054
8319
  "button",
8055
8320
  {
8056
8321
  className: "brokr-rating-button",
@@ -8059,7 +8324,7 @@ function FeedbackWidget({
8059
8324
  type: "button"
8060
8325
  },
8061
8326
  "This worked"
8062
- ), /* @__PURE__ */ React38.createElement(
8327
+ ), /* @__PURE__ */ React39.createElement(
8063
8328
  "button",
8064
8329
  {
8065
8330
  className: "brokr-rating-button",
@@ -8068,7 +8333,7 @@ function FeedbackWidget({
8068
8333
  type: "button"
8069
8334
  },
8070
8335
  "Needs work"
8071
- )), /* @__PURE__ */ React38.createElement(
8336
+ )), /* @__PURE__ */ React39.createElement(
8072
8337
  "textarea",
8073
8338
  {
8074
8339
  className: "brokr-textarea",
@@ -8077,11 +8342,11 @@ function FeedbackWidget({
8077
8342
  rows: 4,
8078
8343
  value: text
8079
8344
  }
8080
- ), error ? /* @__PURE__ */ React38.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, message ? /* @__PURE__ */ React38.createElement("div", { className: "brokr-inline-message" }, message) : null, /* @__PURE__ */ React38.createElement("button", { className: "brokr-button", disabled: isPending, type: "submit" }, isPending ? "Sending" : "Send feedback"));
8345
+ ), error ? /* @__PURE__ */ React39.createElement("div", { className: "brokr-inline-message", "data-tone": "error" }, error) : null, message ? /* @__PURE__ */ React39.createElement("div", { className: "brokr-inline-message" }, message) : null, /* @__PURE__ */ React39.createElement("button", { className: "brokr-button", disabled: isPending, type: "submit" }, isPending ? "Sending" : "Send feedback"));
8081
8346
  }
8082
8347
 
8083
8348
  // src/react/BrokrErrorBoundary.tsx
8084
- import React39 from "react";
8349
+ import React40 from "react";
8085
8350
 
8086
8351
  // src/fix-registry.ts
8087
8352
  var FIX_REGISTRY = {
@@ -8305,7 +8570,7 @@ var BrokrError = class extends Error {
8305
8570
  };
8306
8571
 
8307
8572
  // src/react/BrokrErrorBoundary.tsx
8308
- var BrokrErrorBoundary = class extends React39.Component {
8573
+ var BrokrErrorBoundary = class extends React40.Component {
8309
8574
  constructor() {
8310
8575
  super(...arguments);
8311
8576
  this.state = { hasError: false, error: null };
@@ -8317,11 +8582,11 @@ var BrokrErrorBoundary = class extends React39.Component {
8317
8582
  if (this.state.hasError) {
8318
8583
  if (this.props.fallback) return this.props.fallback;
8319
8584
  const message = this.state.error instanceof BrokrError ? this.state.error.toUserMessage() : "Something went wrong.";
8320
- return React39.createElement(
8585
+ return React40.createElement(
8321
8586
  "div",
8322
8587
  { style: { padding: 24, textAlign: "center" } },
8323
- React39.createElement("p", null, message),
8324
- React39.createElement(
8588
+ React40.createElement("p", null, message),
8589
+ React40.createElement(
8325
8590
  "button",
8326
8591
  {
8327
8592
  onClick: () => this.setState({ hasError: false, error: null }),
@@ -8335,8 +8600,8 @@ var BrokrErrorBoundary = class extends React39.Component {
8335
8600
  }
8336
8601
  };
8337
8602
 
8338
- // src/react/notifications/NotificationBell.tsx
8339
- import React40, { useCallback as useCallback27, useEffect as useEffect13, useMemo as useMemo26, useRef as useRef9, useState as useState22 } from "react";
8603
+ // src/react/notifications/ActivityFeed.tsx
8604
+ import React41, { useCallback as useCallback27, useMemo as useMemo27 } from "react";
8340
8605
 
8341
8606
  // src/react/notifications/use-notifications.ts
8342
8607
  import { useContext as useContext4 } from "react";
@@ -8350,7 +8615,7 @@ function useNotifications() {
8350
8615
  return ctx;
8351
8616
  }
8352
8617
 
8353
- // src/react/notifications/NotificationBell.tsx
8618
+ // src/react/notifications/ActivityFeed.tsx
8354
8619
  function timeAgo(iso) {
8355
8620
  const diff = Date.now() - new Date(iso).getTime();
8356
8621
  const mins = Math.floor(diff / 6e4);
@@ -8361,16 +8626,79 @@ function timeAgo(iso) {
8361
8626
  const days = Math.floor(hours / 24);
8362
8627
  return `${days}d ago`;
8363
8628
  }
8629
+ function FeedItem({
8630
+ item,
8631
+ formatter
8632
+ }) {
8633
+ if (formatter) {
8634
+ return /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-item" }, formatter(item));
8635
+ }
8636
+ return /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-item" }, /* @__PURE__ */ React41.createElement("span", { className: `brokr-feed-dot brokr-feed-dot--${item.variant}` }), /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-content" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-feed-title" }, item.title), item.message ? /* @__PURE__ */ React41.createElement("span", { className: "brokr-feed-message" }, item.message) : null), /* @__PURE__ */ React41.createElement("span", { className: "brokr-feed-time" }, timeAgo(item.createdAt)));
8637
+ }
8638
+ function ActivityFeed({
8639
+ filter,
8640
+ maxItems = 20,
8641
+ formatters,
8642
+ emptyState
8643
+ }) {
8644
+ const { notifications, isLoading } = useNotifications();
8645
+ const items = useMemo27(() => {
8646
+ let filtered = notifications;
8647
+ if (filter && filter.length > 0) {
8648
+ const set = new Set(filter);
8649
+ filtered = notifications.filter(
8650
+ (n) => set.has(n.variant) || n.type && set.has(n.type)
8651
+ );
8652
+ }
8653
+ return [...filtered].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, maxItems);
8654
+ }, [notifications, filter, maxItems]);
8655
+ const getFormatter = useCallback27(
8656
+ (item) => {
8657
+ if (!formatters) return void 0;
8658
+ if (item.type && formatters[item.type]) return formatters[item.type];
8659
+ if (formatters[item.variant]) return formatters[item.variant];
8660
+ return void 0;
8661
+ },
8662
+ [formatters]
8663
+ );
8664
+ if (isLoading) {
8665
+ return /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed" }, [1, 2, 3].map((i) => /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-item", key: i }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-feed-dot" }), /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-content" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-skeleton", style: { width: "60%", height: 12 } }), /* @__PURE__ */ React41.createElement("span", { className: "brokr-skeleton", style: { width: "80%", height: 12 } })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-skeleton", style: { width: 40, height: 12 } }))));
8666
+ }
8667
+ if (items.length === 0) {
8668
+ return /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed" }, /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed-empty" }, emptyState ?? /* @__PURE__ */ React41.createElement("span", { className: "brokr-copy" }, "No activity yet.")));
8669
+ }
8670
+ return /* @__PURE__ */ React41.createElement("div", { className: "brokr-feed" }, items.map((item) => /* @__PURE__ */ React41.createElement(
8671
+ FeedItem,
8672
+ {
8673
+ key: item.id,
8674
+ item,
8675
+ formatter: getFormatter(item)
8676
+ }
8677
+ )));
8678
+ }
8679
+
8680
+ // src/react/notifications/NotificationBell.tsx
8681
+ import React42, { useCallback as useCallback28, useEffect as useEffect13, useMemo as useMemo28, useRef as useRef10, useState as useState22 } from "react";
8682
+ function timeAgo2(iso) {
8683
+ const diff = Date.now() - new Date(iso).getTime();
8684
+ const mins = Math.floor(diff / 6e4);
8685
+ if (mins < 1) return "just now";
8686
+ if (mins < 60) return `${mins}m ago`;
8687
+ const hours = Math.floor(mins / 60);
8688
+ if (hours < 24) return `${hours}h ago`;
8689
+ const days = Math.floor(hours / 24);
8690
+ return `${days}d ago`;
8691
+ }
8364
8692
  function NotifDropdownItem({
8365
8693
  notif,
8366
8694
  registry,
8367
8695
  onClick
8368
8696
  }) {
8369
- const handleClick = useCallback27(() => onClick(notif), [notif, onClick]);
8697
+ const handleClick = useCallback28(() => onClick(notif), [notif, onClick]);
8370
8698
  const notifData = notif.data ?? {};
8371
8699
  const notifType = notifData.type ?? "default";
8372
8700
  const resolved = resolveNotificationType(registry, notifType, notifData);
8373
- return /* @__PURE__ */ React40.createElement(
8701
+ return /* @__PURE__ */ React42.createElement(
8374
8702
  "button",
8375
8703
  {
8376
8704
  type: "button",
@@ -8378,17 +8706,17 @@ function NotifDropdownItem({
8378
8706
  onClick: handleClick,
8379
8707
  role: "menuitem"
8380
8708
  },
8381
- resolved.image ? /* @__PURE__ */ React40.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React40.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8382
- /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8383
- /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-item-time" }, timeAgo(notif.createdAt))
8709
+ resolved.image ? /* @__PURE__ */ React42.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React42.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8710
+ /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8711
+ /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-item-time" }, timeAgo2(notif.createdAt))
8384
8712
  );
8385
8713
  }
8386
8714
  function NotificationBell() {
8387
8715
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8388
8716
  const [open, setOpen] = useState22(false);
8389
- const containerRef = useRef9(null);
8390
- const markReadTimerRef = useRef9(null);
8391
- const toggle = useCallback27(() => setOpen((o) => !o), []);
8717
+ const containerRef = useRef10(null);
8718
+ const markReadTimerRef = useRef10(null);
8719
+ const toggle = useCallback28(() => setOpen((o) => !o), []);
8392
8720
  useEffect13(() => {
8393
8721
  if (markReadTimerRef.current) {
8394
8722
  clearTimeout(markReadTimerRef.current);
@@ -8424,13 +8752,13 @@ function NotificationBell() {
8424
8752
  document.addEventListener("keydown", handleKey);
8425
8753
  return () => document.removeEventListener("keydown", handleKey);
8426
8754
  }, [open]);
8427
- const sorted = useMemo26(
8755
+ const sorted = useMemo28(
8428
8756
  () => [...notifications].sort(
8429
8757
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
8430
8758
  ),
8431
8759
  [notifications]
8432
8760
  );
8433
- const handleItemClick = useCallback27((notif) => {
8761
+ const handleItemClick = useCallback28((notif) => {
8434
8762
  if (!notif.read) markRead(notif.id);
8435
8763
  const notifData = notif.data ?? {};
8436
8764
  const notifType = notif.type ?? notifData.type ?? "default";
@@ -8440,7 +8768,7 @@ function NotificationBell() {
8440
8768
  window.location.assign(href);
8441
8769
  }
8442
8770
  }, [markRead, registry]);
8443
- return /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-bell-wrap", ref: containerRef }, /* @__PURE__ */ React40.createElement(
8771
+ return /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-bell-wrap", ref: containerRef }, /* @__PURE__ */ React42.createElement(
8444
8772
  "button",
8445
8773
  {
8446
8774
  type: "button",
@@ -8450,9 +8778,9 @@ function NotificationBell() {
8450
8778
  "aria-expanded": open,
8451
8779
  "aria-haspopup": "menu"
8452
8780
  },
8453
- /* @__PURE__ */ React40.createElement("svg", { "aria-hidden": "true", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React40.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React40.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })),
8454
- unreadCount > 0 && /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
8455
- ), open && /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-dropdown", role: "menu" }, /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-dropdown-header" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-dropdown-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ React40.createElement(
8781
+ /* @__PURE__ */ React42.createElement("svg", { "aria-hidden": "true", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React42.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React42.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })),
8782
+ unreadCount > 0 && /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-badge" }, unreadCount > 99 ? "99+" : unreadCount)
8783
+ ), open && /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-dropdown", role: "menu" }, /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-dropdown-header" }, /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-dropdown-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ React42.createElement(
8456
8784
  "button",
8457
8785
  {
8458
8786
  type: "button",
@@ -8460,7 +8788,7 @@ function NotificationBell() {
8460
8788
  onClick: markAllRead
8461
8789
  },
8462
8790
  "Mark all read"
8463
- )), /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ React40.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React40.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => /* @__PURE__ */ React40.createElement(
8791
+ )), /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-dropdown-list" }, isLoading ? /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-empty-text" }, "Loading\u2026")) : sorted.length === 0 ? /* @__PURE__ */ React42.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React42.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : sorted.map((notif) => /* @__PURE__ */ React42.createElement(
8464
8792
  NotifDropdownItem,
8465
8793
  {
8466
8794
  key: notif.id,
@@ -8472,7 +8800,7 @@ function NotificationBell() {
8472
8800
  }
8473
8801
 
8474
8802
  // src/react/notifications/NotificationList.tsx
8475
- import React41, { useCallback as useCallback28, useMemo as useMemo27 } from "react";
8803
+ import React43, { useCallback as useCallback29, useMemo as useMemo29 } from "react";
8476
8804
  function formatTimestamp(iso) {
8477
8805
  const date = new Date(iso);
8478
8806
  return new Intl.DateTimeFormat("en-US", {
@@ -8483,38 +8811,38 @@ function formatTimestamp(iso) {
8483
8811
  }).format(date);
8484
8812
  }
8485
8813
  function NotificationListSkeleton() {
8486
- return /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-items" }, [1, 2, 3].map((i) => /* @__PURE__ */ React41.createElement("div", { key: i, className: "brokr-notif-list-row brokr-notif-list-row--skeleton" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-dot brokr-notif-item-dot--skeleton" }), /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-title brokr-skeleton-line", style: { width: "60%" } }), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-message brokr-skeleton-line", style: { width: "80%" } })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-time brokr-skeleton-line", style: { width: 48 } }))));
8814
+ return /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-list-items" }, [1, 2, 3].map((i) => /* @__PURE__ */ React43.createElement("div", { key: i, className: "brokr-notif-list-row brokr-notif-list-row--skeleton" }, /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-dot brokr-notif-item-dot--skeleton" }), /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-title brokr-skeleton-line", style: { width: "60%" } }), /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-message brokr-skeleton-line", style: { width: "80%" } })), /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-time brokr-skeleton-line", style: { width: 48 } }))));
8487
8815
  }
8488
8816
  function NotifListItem({
8489
8817
  notif,
8490
8818
  registry,
8491
8819
  onClick
8492
8820
  }) {
8493
- const handleClick = useCallback28(() => onClick(notif), [notif, onClick]);
8821
+ const handleClick = useCallback29(() => onClick(notif), [notif, onClick]);
8494
8822
  const notifData = notif.data ?? {};
8495
8823
  const notifType = notif.type ?? notifData.type ?? "default";
8496
8824
  const resolved = resolveNotificationType(registry, notifType, notifData);
8497
- return /* @__PURE__ */ React41.createElement(
8825
+ return /* @__PURE__ */ React43.createElement(
8498
8826
  "button",
8499
8827
  {
8500
8828
  type: "button",
8501
8829
  className: `brokr-notif-list-row${notif.read ? "" : " brokr-notif-list-row--unread"}`,
8502
8830
  onClick: handleClick
8503
8831
  },
8504
- resolved.image ? /* @__PURE__ */ React41.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React41.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8505
- /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8506
- /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8832
+ resolved.image ? /* @__PURE__ */ React43.createElement("img", { src: resolved.image.url, alt: resolved.image.alt, className: "brokr-notif-item-logo" }) : /* @__PURE__ */ React43.createElement("span", { className: `brokr-notif-item-dot brokr-notif-item-dot--${notif.variant}` }),
8833
+ /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-item-body" }, /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-title" }, notif.title), /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-message" }, notif.message)),
8834
+ /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-item-time" }, formatTimestamp(notif.createdAt))
8507
8835
  );
8508
8836
  }
8509
8837
  function NotificationList() {
8510
8838
  const { notifications, unreadCount, markRead, markAllRead, isLoading, registry } = useNotifications();
8511
- const sorted = useMemo27(
8839
+ const sorted = useMemo29(
8512
8840
  () => [...notifications].sort(
8513
8841
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
8514
8842
  ),
8515
8843
  [notifications]
8516
8844
  );
8517
- const handleClick = useCallback28((notif) => {
8845
+ const handleClick = useCallback29((notif) => {
8518
8846
  if (!notif.read) markRead(notif.id);
8519
8847
  const notifData = notif.data ?? {};
8520
8848
  const notifType = notif.type ?? notifData.type ?? "default";
@@ -8524,7 +8852,7 @@ function NotificationList() {
8524
8852
  window.location.assign(href);
8525
8853
  }
8526
8854
  }, [markRead, registry]);
8527
- return /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list" }, /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-header" }, /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-list-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ React41.createElement(
8855
+ return /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-list" }, /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-list-header" }, /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-list-title" }, "Notifications"), unreadCount > 0 && /* @__PURE__ */ React43.createElement(
8528
8856
  "button",
8529
8857
  {
8530
8858
  type: "button",
@@ -8532,7 +8860,7 @@ function NotificationList() {
8532
8860
  onClick: markAllRead
8533
8861
  },
8534
8862
  "Mark all read"
8535
- )), isLoading ? /* @__PURE__ */ React41.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React41.createElement("svg", { "aria-hidden": "true", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", style: { opacity: 0.3 } }, /* @__PURE__ */ React41.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React41.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ React41.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ React41.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => /* @__PURE__ */ React41.createElement(
8863
+ )), isLoading ? /* @__PURE__ */ React43.createElement(NotificationListSkeleton, null) : sorted.length === 0 ? /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-empty" }, /* @__PURE__ */ React43.createElement("svg", { "aria-hidden": "true", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", style: { opacity: 0.3 } }, /* @__PURE__ */ React43.createElement("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }), /* @__PURE__ */ React43.createElement("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })), /* @__PURE__ */ React43.createElement("span", { className: "brokr-notif-empty-text" }, "No notifications yet")) : /* @__PURE__ */ React43.createElement("div", { className: "brokr-notif-list-items" }, sorted.map((notif) => /* @__PURE__ */ React43.createElement(
8536
8864
  NotifListItem,
8537
8865
  {
8538
8866
  key: notif.id,
@@ -8572,6 +8900,7 @@ function defineAccount(config) {
8572
8900
  export {
8573
8901
  AIChat,
8574
8902
  AccountPanel,
8903
+ ActivityFeed,
8575
8904
  AuthPageShell,
8576
8905
  AuthWall,
8577
8906
  AutoReloadToggle,
@@ -8601,8 +8930,10 @@ export {
8601
8930
  UpdateBilling,
8602
8931
  UpgradePrompt,
8603
8932
  UsageGate,
8933
+ UsageGrid,
8604
8934
  UserButton,
8605
8935
  defineAccount,
8936
+ defineBrokrTheme,
8606
8937
  defineChat,
8607
8938
  useBrokr,
8608
8939
  useBrokrTheme,