@cimplify/sdk 0.89.0 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +20 -20
  4. package/dist/advanced.mjs +1 -1
  5. package/dist/{chunk-JHJTXAPS.js → chunk-3MBMIEWT.js} +4 -4
  6. package/dist/{chunk-DENWOZEA.mjs → chunk-3XD2FVP4.mjs} +163 -43
  7. package/dist/{chunk-M4ALCELT.mjs → chunk-54H2YYIW.mjs} +2 -2
  8. package/dist/{chunk-T2CJ67QV.js → chunk-DCRN67NO.js} +201 -78
  9. package/dist/{chunk-T7Z25EY4.js → chunk-HKN3YG76.js} +84 -0
  10. package/dist/{chunk-GHFDYM5K.js → chunk-IVJEUGWF.js} +10 -1
  11. package/dist/{chunk-ROBTTS3Q.js → chunk-OAHSF542.js} +40 -1
  12. package/dist/{chunk-APBLFZ2I.mjs → chunk-PYBFTMHI.mjs} +40 -1
  13. package/dist/{chunk-OZDDHSIB.mjs → chunk-RRBY35TJ.mjs} +80 -1
  14. package/dist/{chunk-ZU4ZRYUR.mjs → chunk-T7LDBZZZ.mjs} +10 -1
  15. package/dist/chunk-U4MW6PYU.mjs +23 -0
  16. package/dist/chunk-XREIZ56K.js +26 -0
  17. package/dist/{client-CqZgdUtw.d.ts → client-BQbSKlHS.d.mts} +165 -11
  18. package/dist/{client-CAY2NbRG.d.mts → client-Bw4HtLP1.d.mts} +2 -2
  19. package/dist/{client-D8ZFs-sq.d.mts → client-CeW3acve.d.ts} +165 -11
  20. package/dist/{client-CUbwb6Om.d.ts → client-Otzv_rgP.d.ts} +2 -2
  21. package/dist/{index-CmWOz9bi.d.mts → index-CmIboQjl.d.mts} +3 -3
  22. package/dist/{index-Bc5qUxAt.d.ts → index-DgoRwz8l.d.ts} +3 -3
  23. package/dist/index.d.mts +49 -7
  24. package/dist/index.d.ts +49 -7
  25. package/dist/index.js +109 -82
  26. package/dist/index.mjs +11 -5
  27. package/dist/mock/cli.mjs +61 -1
  28. package/dist/mock/library.js +61 -1
  29. package/dist/mock/library.mjs +61 -1
  30. package/dist/mock/msw.js +61 -1
  31. package/dist/mock/msw.mjs +61 -1
  32. package/dist/{payment-Dcgb9n1n.d.mts → payment-BFtLqiG2.d.mts} +1 -1
  33. package/dist/{payment-Xq0WDZXh.d.ts → payment-Cq8FT1B6.d.ts} +1 -1
  34. package/dist/{price-DAAjKgAp.d.ts → price-DaFXT-Lr.d.ts} +1 -1
  35. package/dist/{price--Ma_WO5_.d.mts → price-DleDWk_s.d.mts} +1 -1
  36. package/dist/{product-BakHcsP2.d.ts → product-bZ8G5Z8D.d.mts} +95 -2
  37. package/dist/{product-BakHcsP2.d.mts → product-bZ8G5Z8D.d.ts} +95 -2
  38. package/dist/react.d.mts +471 -8
  39. package/dist/react.d.ts +471 -8
  40. package/dist/react.js +2787 -402
  41. package/dist/react.mjs +2699 -337
  42. package/dist/{server-Bk_hlefX.d.mts → server-D-iK-bNt.d.mts} +8 -1
  43. package/dist/{server-Bei5lBrc.d.ts → server-DwsY9hlX.d.ts} +8 -1
  44. package/dist/server.d.mts +4 -4
  45. package/dist/server.d.ts +4 -4
  46. package/dist/server.js +7 -7
  47. package/dist/server.mjs +2 -2
  48. package/dist/styles.css +1 -1
  49. package/dist/testing/msw.d.mts +2 -2
  50. package/dist/testing/msw.d.ts +2 -2
  51. package/dist/testing/msw.js +3 -2
  52. package/dist/testing/msw.mjs +2 -1
  53. package/dist/testing/suite.d.mts +6 -6
  54. package/dist/testing/suite.d.ts +6 -6
  55. package/dist/testing/suite.js +24 -23
  56. package/dist/testing/suite.mjs +5 -4
  57. package/dist/testing.d.mts +5 -5
  58. package/dist/testing.d.ts +5 -5
  59. package/dist/testing.js +80 -79
  60. package/dist/testing.mjs +6 -5
  61. package/dist/utils.d.mts +4 -4
  62. package/dist/utils.d.ts +4 -4
  63. package/package.json +4 -1
  64. package/registry/booking-page.json +1 -1
  65. package/registry/chat-widget.json +1 -1
  66. package/registry/customer-input-fields.json +1 -1
  67. package/registry/date-slot-picker.json +1 -1
  68. package/registry/product-customizer.json +1 -1
  69. package/registry/rental-service-card.json +1 -1
package/dist/react.mjs CHANGED
@@ -1,12 +1,14 @@
1
1
  "use client";
2
- import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-OZDDHSIB.mjs';
3
- export { getVariantDisplayName } from './chunk-OZDDHSIB.mjs';
4
- import { createCimplifyClient } from './chunk-DENWOZEA.mjs';
5
- import { ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-ZU4ZRYUR.mjs';
2
+ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayService, serviceBillingMultiplier, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-RRBY35TJ.mjs';
3
+ export { getVariantDisplayName } from './chunk-RRBY35TJ.mjs';
4
+ import { messagePreview } from './chunk-U4MW6PYU.mjs';
5
+ export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
6
+ import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, MUTATION_ERROR } from './chunk-3XD2FVP4.mjs';
7
+ import { ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-T7LDBZZZ.mjs';
6
8
  import { parsePrice, formatMoney, formatPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-KFLGQGMC.mjs';
7
9
  import { isSupportedCurrency } from './chunk-K5EN2W2Q.mjs';
8
10
  import './chunk-3G6RQLXK.mjs';
9
- import React14, { createContext, useRef, useState, useCallback, useEffect, useContext, useMemo, useId, useSyncExternalStore } from 'react';
11
+ import React14, { createContext, lazy, memo, Suspense, useContext, useMemo, useRef, useState, useCallback, useEffect, useId, useSyncExternalStore } from 'react';
10
12
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
11
13
  import * as ReactDOM from 'react-dom';
12
14
  import { clsx } from 'clsx';
@@ -24,6 +26,8 @@ import { FilterXSS, safeAttrValue as safeAttrValue$1 } from 'xss';
24
26
  import { Dialog } from '@base-ui/react/dialog';
25
27
  import { Field } from '@base-ui/react/field';
26
28
  import { Input } from '@base-ui/react/input';
29
+ import DOMPurify from 'dompurify';
30
+ import { marked } from 'marked';
27
31
 
28
32
  // src/ads/identity.ts
29
33
  var COOKIE_NAME = "_cimplify_uid";
@@ -719,6 +723,7 @@ function mapServerCart(serverCart, fallbackCurrency) {
719
723
  scheduledStart: item.scheduled_start || void 0,
720
724
  scheduledEnd: item.scheduled_end || void 0,
721
725
  staffId: item.staff_id || void 0,
726
+ units: item.units || void 0,
722
727
  lineType: item.line_type,
723
728
  confirmationCode: item.confirmation_code || void 0,
724
729
  serviceStatus: item.service_status || void 0,
@@ -766,18 +771,18 @@ function deriveView(base, ops) {
766
771
  };
767
772
  }
768
773
  async function syncFromServer(entry) {
769
- if (entry.syncing) return;
770
- entry.syncing = true;
771
- try {
774
+ const run = async () => {
772
775
  const result = await entry.client.cart.get();
773
776
  if (!result.ok) throw result.error;
774
777
  entry.collection.setBase(mapServerCart(result.value, entry.currency));
775
- } finally {
776
- entry.syncing = false;
777
- }
778
+ };
779
+ const next = entry.syncTail.then(run, run);
780
+ entry.syncTail = next.catch(() => {
781
+ });
782
+ return next;
778
783
  }
779
784
  function emitMutationError(entry, operation, opId, idempotencyKey, error) {
780
- entry.client.events.emit("mutation:error", {
785
+ entry.client.events.emit(MUTATION_ERROR, {
781
786
  service: "cart",
782
787
  operation,
783
788
  opId,
@@ -814,9 +819,15 @@ function getOrCreateStore(params) {
814
819
  optimisticEnabled,
815
820
  initialized: false,
816
821
  initializePromise: null,
817
- syncing: false,
822
+ syncTail: Promise.resolve(),
818
823
  failedOpRemovers: /* @__PURE__ */ new Set()
819
824
  };
825
+ client.events.on(SESSION_CLAIMED, () => {
826
+ if (entry.initialized) void syncFromServer(entry);
827
+ });
828
+ client.events.on(SESSION_CLEARED, () => {
829
+ if (entry.initialized) void syncFromServer(entry);
830
+ });
820
831
  cartStores.set(storeKey, entry);
821
832
  return entry;
822
833
  }
@@ -875,7 +886,8 @@ async function performAddItem(entry, product, quantity, options) {
875
886
  specialInstructions: normalizedOptions.specialInstructions,
876
887
  scheduledStart: normalizedOptions.scheduledStart,
877
888
  scheduledEnd: normalizedOptions.scheduledEnd,
878
- staffId: normalizedOptions.staffId
889
+ staffId: normalizedOptions.staffId,
890
+ units: normalizedOptions.units
879
891
  });
880
892
  const idempotencyKey = `idem_${typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`}`;
881
893
  const patch = { kind: "add_item", tempItem, lineKey };
@@ -897,6 +909,7 @@ async function performAddItem(entry, product, quantity, options) {
897
909
  scheduled_end: normalizedOptions.scheduledEnd,
898
910
  staff_id: normalizedOptions.staffId,
899
911
  resource_id: normalizedOptions.resourceId,
912
+ units: normalizedOptions.units,
900
913
  customer_inputs: normalizedOptions.customerInputs
901
914
  },
902
915
  { idempotencyKey }
@@ -4279,7 +4292,7 @@ function Price({
4279
4292
  ] });
4280
4293
  }
4281
4294
  function createCachedQuery(config) {
4282
- const cache = /* @__PURE__ */ new Map();
4295
+ const cache2 = /* @__PURE__ */ new Map();
4283
4296
  const inflight = /* @__PURE__ */ new Map();
4284
4297
  const gcMaxAge = config.gcMaxAge ?? 3e5;
4285
4298
  let lastSweep = 0;
@@ -4288,8 +4301,8 @@ function createCachedQuery(config) {
4288
4301
  if (now - lastSweep < 6e4) return;
4289
4302
  lastSweep = now;
4290
4303
  const cutoff = now - gcMaxAge;
4291
- for (const [key, entry] of cache) {
4292
- if (entry.ts < cutoff) cache.delete(key);
4304
+ for (const [key, entry] of cache2) {
4305
+ if (entry.ts < cutoff) cache2.delete(key);
4293
4306
  }
4294
4307
  }
4295
4308
  function useCachedQuery(options = {}) {
@@ -4307,13 +4320,13 @@ function createCachedQuery(config) {
4307
4320
  const optionsRef = useRef(options);
4308
4321
  optionsRef.current = options;
4309
4322
  if (config.locationSensitive && previousLocationRef.current !== locationId) {
4310
- cache.clear();
4323
+ cache2.clear();
4311
4324
  inflight.clear();
4312
4325
  previousLocationRef.current = locationId;
4313
4326
  }
4314
4327
  sweep();
4315
4328
  const cacheKey2 = config.key(client, options);
4316
- const cached = cache.get(cacheKey2);
4329
+ const cached = cache2.get(cacheKey2);
4317
4330
  const [data, setData] = useState(cached?.data ?? null);
4318
4331
  const [isLoading, setIsLoading] = useState(enabled && !cached);
4319
4332
  const [error, setError] = useState(null);
@@ -4326,7 +4339,7 @@ function createCachedQuery(config) {
4326
4339
  const nextRequestId = ++requestIdRef.current;
4327
4340
  setError(null);
4328
4341
  if (!force) {
4329
- const hit = cache.get(cacheKey2);
4342
+ const hit = cache2.get(cacheKey2);
4330
4343
  if (hit) {
4331
4344
  setData(hit.data);
4332
4345
  setIsLoading(false);
@@ -4343,7 +4356,7 @@ function createCachedQuery(config) {
4343
4356
  });
4344
4357
  }
4345
4358
  const value = await promise;
4346
- cache.set(cacheKey2, { data: value, ts: Date.now() });
4359
+ cache2.set(cacheKey2, { data: value, ts: Date.now() });
4347
4360
  if (nextRequestId === requestIdRef.current) {
4348
4361
  setData(value);
4349
4362
  setError(null);
@@ -4364,23 +4377,23 @@ function createCachedQuery(config) {
4364
4377
  void load(false);
4365
4378
  }, [load]);
4366
4379
  const refetch = useCallback(async () => {
4367
- cache.delete(cacheKey2);
4380
+ cache2.delete(cacheKey2);
4368
4381
  await load(true);
4369
4382
  }, [cacheKey2, load]);
4370
4383
  return { data, isLoading, error, refetch };
4371
4384
  }
4372
4385
  useCachedQuery.prefetch = (client, options) => {
4373
4386
  const key = config.key(client, options);
4374
- if (cache.has(key) || inflight.has(key)) return void 0;
4387
+ if (cache2.has(key) || inflight.has(key)) return void 0;
4375
4388
  const promise = config.fetch(client, options);
4376
4389
  inflight.set(key, promise);
4377
4390
  return promise.then((value) => {
4378
- cache.set(key, { data: value, ts: Date.now() });
4391
+ cache2.set(key, { data: value, ts: Date.now() });
4379
4392
  }).catch(() => {
4380
4393
  }).finally(() => inflight.delete(key));
4381
4394
  };
4382
4395
  useCachedQuery.clear = () => {
4383
- cache.clear();
4396
+ cache2.clear();
4384
4397
  inflight.clear();
4385
4398
  };
4386
4399
  return useCachedQuery;
@@ -4508,6 +4521,9 @@ function useCategories(options = {}) {
4508
4521
  return { categories: data ?? [], isLoading, error, refetch };
4509
4522
  }
4510
4523
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "refunded"]);
4524
+ function isTerminalOrderStatus(status) {
4525
+ return TERMINAL_STATUSES.has(status);
4526
+ }
4511
4527
  var orderCache = /* @__PURE__ */ new Map();
4512
4528
  var orderInflight = /* @__PURE__ */ new Map();
4513
4529
  function buildOrderCacheKey(client, orderId) {
@@ -5349,6 +5365,9 @@ function useOrders(options = {}) {
5349
5365
  const { data, isLoading, error, refetch } = ordersQuery(options);
5350
5366
  return { orders: data ?? [], isLoading, error, refetch };
5351
5367
  }
5368
+ function clearOrdersCache() {
5369
+ ordersQuery.clear();
5370
+ }
5352
5371
  function useSubscriptions(options = {}) {
5353
5372
  const { client } = useCimplifyClient();
5354
5373
  const { status, enabled = true } = options;
@@ -5444,14 +5463,24 @@ function useServices(options = {}) {
5444
5463
  }
5445
5464
 
5446
5465
  // src/react/hooks/use-available-slots.ts
5466
+ function narrowingUnits(units) {
5467
+ return units != null && units > 1 ? units : void 0;
5468
+ }
5447
5469
  var slotsQuery = createCachedQuery({
5448
5470
  name: "useAvailableSlots",
5449
- key: (client, options) => cacheKey(client.getPublicKey(), options.serviceId, options.date, options.participantCount),
5471
+ key: (client, options) => cacheKey(
5472
+ client.getPublicKey(),
5473
+ options.serviceId,
5474
+ options.date,
5475
+ options.participantCount,
5476
+ narrowingUnits(options.units)
5477
+ ),
5450
5478
  fetch: async (client, options) => {
5451
5479
  const result = await client.scheduling.getAvailableSlots({
5452
5480
  service_id: options.serviceId,
5453
5481
  date: options.date,
5454
- participant_count: options.participantCount
5482
+ participant_count: options.participantCount,
5483
+ units: narrowingUnits(options.units)
5455
5484
  });
5456
5485
  if (!result.ok) throw result.error;
5457
5486
  return result.value;
@@ -5468,6 +5497,9 @@ function useAvailableSlots(serviceId, date, options = {}) {
5468
5497
  }
5469
5498
 
5470
5499
  // src/react/hooks/use-service-availability.ts
5500
+ function narrowingUnits2(units) {
5501
+ return units != null && units > 1 ? units : void 0;
5502
+ }
5471
5503
  var serviceAvailabilityQuery = createCachedQuery({
5472
5504
  name: "useServiceAvailability",
5473
5505
  key: (client, options) => cacheKey(
@@ -5475,7 +5507,8 @@ var serviceAvailabilityQuery = createCachedQuery({
5475
5507
  options.serviceId,
5476
5508
  options.startDate,
5477
5509
  options.endDate,
5478
- options.participantCount
5510
+ options.participantCount,
5511
+ narrowingUnits2(options.units)
5479
5512
  ),
5480
5513
  fetch: async (client, options) => {
5481
5514
  const result = await client.scheduling.getServiceAvailability({
@@ -5483,7 +5516,8 @@ var serviceAvailabilityQuery = createCachedQuery({
5483
5516
  start_date: options.startDate,
5484
5517
  end_date: options.endDate,
5485
5518
  location_id: options.locationId,
5486
- participant_count: options.participantCount
5519
+ participant_count: options.participantCount,
5520
+ units: narrowingUnits2(options.units)
5487
5521
  });
5488
5522
  if (!result.ok) throw result.error;
5489
5523
  return result.value.availability;
@@ -5854,6 +5888,7 @@ var POLL_IDLE_INTERVAL_MS = 15e3;
5854
5888
  var IDLE_TIMEOUT_MS = 6e4;
5855
5889
  var WS_MAX_RECONNECT_ATTEMPTS = 10;
5856
5890
  var WS_PATH = "/api/v1/support/conversation/ws";
5891
+ var AWAIT_REPLY_TIMEOUT_MS = 3e4;
5857
5892
  function useChat(options = {}) {
5858
5893
  const context = useOptionalCimplifyClient();
5859
5894
  const client = options.client ?? context?.client;
@@ -5862,6 +5897,13 @@ function useChat(options = {}) {
5862
5897
  const [isSending, setIsSending] = useState(false);
5863
5898
  const [error, setError] = useState(null);
5864
5899
  const [conversationOpened, setConversationOpened] = useState(false);
5900
+ const [humanRequested, setHumanRequested] = useState(false);
5901
+ const [awaitingReply, setAwaitingReply] = useState(false);
5902
+ const [seenAt, setSeenAt] = useState(null);
5903
+ const [mayHaveOlder, setMayHaveOlder] = useState(false);
5904
+ const [claimedAs, setClaimedAs] = useState(
5905
+ () => client?.support.claimedCustomerName ?? null
5906
+ );
5865
5907
  const pollRef = useRef(null);
5866
5908
  const lastActivityRef = useRef(Date.now());
5867
5909
  const openedRef = useRef(false);
@@ -5870,17 +5912,43 @@ function useChat(options = {}) {
5870
5912
  const wsRef = useRef(null);
5871
5913
  const wsReconnectAttemptRef = useRef(0);
5872
5914
  const wsReconnectTimerRef = useRef(null);
5915
+ const aiEnabledRef = useRef(false);
5916
+ const humanRequestedRef = useRef(false);
5917
+ const awaitReplyTimerRef = useRef(null);
5918
+ const claimUnsubRef = useRef(null);
5873
5919
  messagesRef.current = messages;
5874
5920
  clientRef.current = client;
5921
+ humanRequestedRef.current = humanRequested;
5875
5922
  const pollInterval = options.pollInterval ?? POLL_INTERVAL_MS;
5876
5923
  const pollIdleInterval = options.pollIdleInterval ?? POLL_IDLE_INTERVAL_MS;
5877
- const appendMessages = useCallback((incoming) => {
5878
- setMessages((prev) => {
5879
- const ids = new Set(prev.map((m) => m.id));
5880
- const fresh = incoming.filter((m) => !ids.has(m.id));
5881
- return fresh.length > 0 ? [...prev, ...fresh] : prev;
5882
- });
5924
+ const stopAwaitingReply = useCallback(() => {
5925
+ if (awaitReplyTimerRef.current) {
5926
+ clearTimeout(awaitReplyTimerRef.current);
5927
+ awaitReplyTimerRef.current = null;
5928
+ }
5929
+ setAwaitingReply(false);
5883
5930
  }, []);
5931
+ const startAwaitingReply = useCallback(() => {
5932
+ setAwaitingReply(true);
5933
+ if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
5934
+ awaitReplyTimerRef.current = setTimeout(() => {
5935
+ awaitReplyTimerRef.current = null;
5936
+ setAwaitingReply(false);
5937
+ }, AWAIT_REPLY_TIMEOUT_MS);
5938
+ }, []);
5939
+ const appendMessages = useCallback(
5940
+ (incoming) => {
5941
+ if (incoming.some((m) => m.sender_type === "bot" || m.sender_type === "agent")) {
5942
+ stopAwaitingReply();
5943
+ }
5944
+ setMessages((prev) => {
5945
+ const ids = new Set(prev.map((m) => m.id));
5946
+ const fresh = incoming.filter((m) => !ids.has(m.id));
5947
+ return fresh.length > 0 ? [...prev, ...fresh] : prev;
5948
+ });
5949
+ },
5950
+ [stopAwaitingReply]
5951
+ );
5884
5952
  const fetchNewMessages = useCallback(async () => {
5885
5953
  const c = clientRef.current;
5886
5954
  if (!c) return;
@@ -5908,6 +5976,17 @@ function useChat(options = {}) {
5908
5976
  pollRef.current = null;
5909
5977
  }
5910
5978
  }, []);
5979
+ const resyncConversation = useCallback(async () => {
5980
+ const c = clientRef.current;
5981
+ if (!c || !openedRef.current) return;
5982
+ const result = await c.support.openConversation();
5983
+ if (!result.ok) return;
5984
+ setMessages(result.value.messages);
5985
+ aiEnabledRef.current = result.value.conversation.ai_enabled;
5986
+ setHumanRequested(result.value.conversation.needs_human_review ?? false);
5987
+ setSeenAt(result.value.conversation.team_read_at ?? null);
5988
+ setMayHaveOlder(result.value.messages.length >= 50);
5989
+ }, []);
5911
5990
  const connectWebSocket = useCallback(() => {
5912
5991
  const c = clientRef.current;
5913
5992
  if (!c || typeof WebSocket === "undefined") {
@@ -5929,12 +6008,21 @@ function useChat(options = {}) {
5929
6008
  const envelope = JSON.parse(event.data);
5930
6009
  if (envelope.type === "message.created" && envelope.data) {
5931
6010
  appendMessages([envelope.data]);
6011
+ } else if (envelope.type === "thread.read") {
6012
+ const at = envelope.data?.last_read_at;
6013
+ if (at) setSeenAt((prev) => !prev || prev < at ? at : prev);
6014
+ } else if (envelope.type === "thread.responding") {
6015
+ const active = envelope.data?.active;
6016
+ if (active) startAwaitingReply();
6017
+ else stopAwaitingReply();
5932
6018
  } else if (envelope.type === "resync") {
5933
6019
  const latest = envelope.latest_message_at;
5934
6020
  const last = messagesRef.current[messagesRef.current.length - 1];
5935
6021
  if (latest && (!last || new Date(last.created_at).getTime() < new Date(latest).getTime())) {
5936
6022
  fetchNewMessages();
5937
6023
  }
6024
+ } else if (envelope.type === "rekey") {
6025
+ void resyncConversation();
5938
6026
  }
5939
6027
  } catch {
5940
6028
  }
@@ -5954,11 +6042,57 @@ function useChat(options = {}) {
5954
6042
  ws.onerror = () => {
5955
6043
  };
5956
6044
  wsRef.current = ws;
5957
- }, [appendMessages, startPolling, stopPolling, fetchNewMessages]);
6045
+ }, [appendMessages, startPolling, stopPolling, fetchNewMessages, resyncConversation]);
6046
+ const resetConversation = useCallback(() => {
6047
+ stopPolling();
6048
+ if (wsReconnectTimerRef.current) {
6049
+ clearTimeout(wsReconnectTimerRef.current);
6050
+ wsReconnectTimerRef.current = null;
6051
+ }
6052
+ if (wsRef.current) {
6053
+ wsRef.current.onclose = null;
6054
+ wsRef.current.close();
6055
+ wsRef.current = null;
6056
+ }
6057
+ wsReconnectAttemptRef.current = 0;
6058
+ openedRef.current = false;
6059
+ stopAwaitingReply();
6060
+ setMessages([]);
6061
+ setConversationOpened(false);
6062
+ setHumanRequested(false);
6063
+ setSeenAt(null);
6064
+ setMayHaveOlder(false);
6065
+ setError(null);
6066
+ setClaimedAs(null);
6067
+ }, [stopPolling, stopAwaitingReply]);
6068
+ const watchSessionClaims = useCallback(() => {
6069
+ const c = clientRef.current;
6070
+ if (!c || claimUnsubRef.current) return;
6071
+ const unsubClaimed = c.events.on(SESSION_CLAIMED, (event) => {
6072
+ setClaimedAs(event.customerName || null);
6073
+ if (!openedRef.current) return;
6074
+ connectWebSocket();
6075
+ void resyncConversation();
6076
+ });
6077
+ const unsubCleared = c.events.on(SESSION_CLEARED, resetConversation);
6078
+ claimUnsubRef.current = () => {
6079
+ unsubClaimed();
6080
+ unsubCleared();
6081
+ };
6082
+ }, [connectWebSocket, resyncConversation, resetConversation]);
6083
+ const signOut = useCallback(async () => {
6084
+ const c = clientRef.current;
6085
+ if (!c) return false;
6086
+ const result = await c.link.logout();
6087
+ return result.ok;
6088
+ }, []);
5958
6089
  useEffect(() => {
5959
6090
  return () => {
5960
6091
  if (pollRef.current) clearInterval(pollRef.current);
5961
6092
  if (wsReconnectTimerRef.current) clearTimeout(wsReconnectTimerRef.current);
6093
+ if (awaitReplyTimerRef.current) clearTimeout(awaitReplyTimerRef.current);
6094
+ claimUnsubRef.current?.();
6095
+ claimUnsubRef.current = null;
5962
6096
  if (wsRef.current) {
5963
6097
  wsRef.current.onclose = null;
5964
6098
  wsRef.current.close();
@@ -5974,35 +6108,53 @@ function useChat(options = {}) {
5974
6108
  if (result.ok) {
5975
6109
  setMessages(result.value.messages);
5976
6110
  setConversationOpened(true);
6111
+ aiEnabledRef.current = result.value.conversation.ai_enabled;
6112
+ setHumanRequested(result.value.conversation.needs_human_review ?? false);
6113
+ setSeenAt(result.value.conversation.team_read_at ?? null);
6114
+ setMayHaveOlder(result.value.messages.length >= 50);
6115
+ setClaimedAs(client.support.claimedCustomerName);
5977
6116
  connectWebSocket();
6117
+ watchSessionClaims();
5978
6118
  } else {
5979
6119
  openedRef.current = false;
5980
6120
  setError(result.error);
5981
6121
  }
5982
6122
  setIsLoading(false);
5983
- }, [client, connectWebSocket]);
6123
+ }, [client, connectWebSocket, watchSessionClaims]);
5984
6124
  const send = useCallback(
5985
- async (content) => {
5986
- if (!client || !content.trim()) return;
6125
+ async (content, opts) => {
6126
+ const attachments = opts?.attachments ?? [];
6127
+ if (!client || !content.trim() && attachments.length === 0) return false;
5987
6128
  setError(null);
5988
6129
  lastActivityRef.current = Date.now();
5989
6130
  if (!openedRef.current) {
5990
6131
  await openConversation();
5991
- if (!openedRef.current) return;
6132
+ if (!openedRef.current) return false;
5992
6133
  }
6134
+ const replyTo = opts?.replyTo;
5993
6135
  const optimisticId = `opt_${Date.now()}`;
5994
6136
  const optimistic = {
5995
6137
  id: optimisticId,
5996
6138
  sender_type: "customer",
5997
6139
  content: content.trim(),
5998
- content_type: "text",
5999
- attachments: [],
6140
+ content_type: attachments.length > 0 ? "image" : "text",
6141
+ attachments,
6000
6142
  metadata: {},
6143
+ // Same shape the server echoes back, so the bubble doesn't reflow
6144
+ // when the confirmed message replaces it.
6145
+ reply_to: replyTo ? {
6146
+ id: replyTo.id,
6147
+ sender_type: replyTo.sender_type,
6148
+ preview: messagePreview(replyTo.content)
6149
+ } : void 0,
6001
6150
  created_at: (/* @__PURE__ */ new Date()).toISOString()
6002
6151
  };
6003
6152
  setMessages((prev) => [...prev, optimistic]);
6004
6153
  setIsSending(true);
6005
- const result = await client.support.sendMessage(content.trim());
6154
+ const result = await client.support.sendMessage(content.trim(), {
6155
+ replyToMessageId: replyTo?.id,
6156
+ attachments: attachments.length > 0 ? attachments : void 0
6157
+ });
6006
6158
  if (result.ok) {
6007
6159
  const confirmed = result.value;
6008
6160
  setMessages(
@@ -6013,13 +6165,17 @@ function useChat(options = {}) {
6013
6165
  prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== optimisticId) : prev.map((m) => m.id === optimisticId ? confirmed : m)
6014
6166
  )
6015
6167
  );
6168
+ if (aiEnabledRef.current && !humanRequestedRef.current) {
6169
+ startAwaitingReply();
6170
+ }
6016
6171
  } else {
6017
6172
  setMessages((prev) => prev.filter((m) => m.id !== optimisticId));
6018
6173
  setError(result.error);
6019
6174
  }
6020
6175
  setIsSending(false);
6176
+ return result.ok;
6021
6177
  },
6022
- [client, openConversation]
6178
+ [client, openConversation, startAwaitingReply]
6023
6179
  );
6024
6180
  const startConversation = useCallback(
6025
6181
  async (starterText) => {
@@ -6030,14 +6186,67 @@ function useChat(options = {}) {
6030
6186
  },
6031
6187
  [openConversation, send]
6032
6188
  );
6189
+ const react = useCallback(
6190
+ async (message, emoji) => {
6191
+ if (!client || message.id.startsWith("opt_")) return false;
6192
+ const result = await client.support.reactToMessage(message.id, emoji);
6193
+ if (result.ok) appendMessages([result.value]);
6194
+ return result.ok;
6195
+ },
6196
+ [client, appendMessages]
6197
+ );
6198
+ const requestHuman = useCallback(async () => {
6199
+ if (!client) return false;
6200
+ setError(null);
6201
+ if (!openedRef.current) {
6202
+ await openConversation();
6203
+ if (!openedRef.current) return false;
6204
+ }
6205
+ const result = await client.support.requestHuman();
6206
+ if (result.ok) {
6207
+ setHumanRequested(true);
6208
+ return true;
6209
+ }
6210
+ setError(result.error);
6211
+ return false;
6212
+ }, [client, openConversation]);
6213
+ const loadOlder = useCallback(async () => {
6214
+ const c = clientRef.current;
6215
+ const earliest = messagesRef.current[0];
6216
+ if (!c || !earliest) return false;
6217
+ const result = await c.support.getMessages({ before: earliest.created_at, limit: 50 });
6218
+ if (!result.ok) {
6219
+ setError(result.error);
6220
+ return false;
6221
+ }
6222
+ const older = result.value;
6223
+ setMayHaveOlder(older.length >= 50);
6224
+ if (older.length === 0) return false;
6225
+ setMessages((prev) => {
6226
+ const ids = new Set(prev.map((m) => m.id));
6227
+ const fresh = older.filter((m) => !ids.has(m.id));
6228
+ return fresh.length > 0 ? [...fresh, ...prev] : prev;
6229
+ });
6230
+ return true;
6231
+ }, []);
6232
+ const agentArrived = messages.some((m) => m.sender_type === "agent");
6033
6233
  return {
6034
6234
  messages,
6035
6235
  isLoading,
6036
6236
  isSending,
6037
6237
  error,
6038
6238
  isActive: conversationOpened && messages.length > 0,
6239
+ humanRequested: humanRequested && !agentArrived,
6240
+ awaitingReply,
6241
+ seenAt,
6242
+ mayHaveOlder,
6243
+ claimedAs,
6244
+ signOut,
6039
6245
  send,
6040
- startConversation
6246
+ startConversation,
6247
+ react,
6248
+ requestHuman,
6249
+ loadOlder
6041
6250
  };
6042
6251
  }
6043
6252
  var ElementsContext = createContext({
@@ -7767,6 +7976,177 @@ function VolumePricing({
7767
7976
  }
7768
7977
  );
7769
7978
  }
7979
+ function createPlacesSessionToken() {
7980
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
7981
+ return crypto.randomUUID();
7982
+ }
7983
+ return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
7984
+ }
7985
+ var MIN_QUERY_LENGTH = 3;
7986
+ var DEBOUNCE_MS = 300;
7987
+ var BLUR_CLOSE_MS = 120;
7988
+ function AddressAutocomplete({
7989
+ value,
7990
+ onSelect,
7991
+ onDirty,
7992
+ placeholder,
7993
+ required,
7994
+ autoFocus,
7995
+ id,
7996
+ className,
7997
+ classNames
7998
+ }) {
7999
+ const clientContext = useOptionalCimplifyClient();
8000
+ const [inputText, setInputText] = React14.useState(value?.formatted_address ?? "");
8001
+ const [suggestions, setSuggestions] = React14.useState([]);
8002
+ const [isOpen, setIsOpen] = React14.useState(false);
8003
+ const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
8004
+ const debounceRef = React14.useRef(null);
8005
+ const seqRef = React14.useRef(0);
8006
+ const sessionTokenRef = React14.useRef(createPlacesSessionToken());
8007
+ const blurTimeoutRef = React14.useRef(null);
8008
+ React14.useEffect(() => {
8009
+ return () => {
8010
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8011
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8012
+ seqRef.current += 1;
8013
+ };
8014
+ }, []);
8015
+ const fetchSuggestions = React14.useCallback(
8016
+ async (query) => {
8017
+ const client = clientContext?.client;
8018
+ if (!client || query.length < MIN_QUERY_LENGTH) {
8019
+ setSuggestions([]);
8020
+ setIsOpen(false);
8021
+ return;
8022
+ }
8023
+ const seq = ++seqRef.current;
8024
+ const result = await client.places.autocomplete(query, sessionTokenRef.current);
8025
+ if (seqRef.current !== seq) return;
8026
+ if (result.ok && result.value.predictions.length > 0) {
8027
+ setSuggestions(result.value.predictions);
8028
+ setHighlightedIndex(0);
8029
+ setIsOpen(true);
8030
+ } else {
8031
+ setSuggestions([]);
8032
+ setIsOpen(false);
8033
+ }
8034
+ },
8035
+ [clientContext]
8036
+ );
8037
+ const handleInputChange = (nextValue) => {
8038
+ setInputText(nextValue);
8039
+ onDirty?.();
8040
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8041
+ debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), DEBOUNCE_MS);
8042
+ };
8043
+ const selectSuggestion = React14.useCallback(
8044
+ async (suggestion) => {
8045
+ if (debounceRef.current) clearTimeout(debounceRef.current);
8046
+ if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8047
+ const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8048
+ setInputText(fallback);
8049
+ setSuggestions([]);
8050
+ setIsOpen(false);
8051
+ const client = clientContext?.client;
8052
+ if (!client) return;
8053
+ const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8054
+ sessionTokenRef.current = createPlacesSessionToken();
8055
+ if (result.ok) {
8056
+ const d = result.value;
8057
+ const address = {
8058
+ formatted_address: d.formatted_address || fallback,
8059
+ street_address: d.street_address,
8060
+ apartment: d.apartment,
8061
+ city: d.city,
8062
+ region: d.region,
8063
+ postal_code: d.postal_code,
8064
+ country: d.country,
8065
+ latitude: d.latitude,
8066
+ longitude: d.longitude,
8067
+ place_id: d.place_id
8068
+ };
8069
+ setInputText(address.formatted_address);
8070
+ onSelect(address);
8071
+ }
8072
+ },
8073
+ [clientContext, onSelect]
8074
+ );
8075
+ return /* @__PURE__ */ jsxs("div", { "data-cimplify-address-autocomplete": true, className: cn("relative", className, classNames?.root), children: [
8076
+ /* @__PURE__ */ jsx(
8077
+ "input",
8078
+ {
8079
+ id,
8080
+ type: "text",
8081
+ value: inputText,
8082
+ onChange: (e) => handleInputChange(e.target.value),
8083
+ onFocus: () => {
8084
+ if (suggestions.length > 0) setIsOpen(true);
8085
+ },
8086
+ onBlur: () => {
8087
+ blurTimeoutRef.current = setTimeout(() => setIsOpen(false), BLUR_CLOSE_MS);
8088
+ },
8089
+ onKeyDown: (e) => {
8090
+ if (e.key === "Escape") {
8091
+ setIsOpen(false);
8092
+ return;
8093
+ }
8094
+ if (!isOpen || suggestions.length === 0) return;
8095
+ if (e.key === "ArrowDown") {
8096
+ e.preventDefault();
8097
+ setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8098
+ } else if (e.key === "ArrowUp") {
8099
+ e.preventDefault();
8100
+ setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8101
+ } else if (e.key === "Enter" && highlightedIndex >= 0) {
8102
+ e.preventDefault();
8103
+ void selectSuggestion(suggestions[highlightedIndex]);
8104
+ }
8105
+ },
8106
+ placeholder: placeholder ?? "Search for an address...",
8107
+ required,
8108
+ autoFocus,
8109
+ autoComplete: "off",
8110
+ "aria-autocomplete": "list",
8111
+ "aria-expanded": isOpen && suggestions.length > 0,
8112
+ "data-cimplify-address-input": true,
8113
+ className: classNames?.input
8114
+ }
8115
+ ),
8116
+ isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
8117
+ "div",
8118
+ {
8119
+ role: "listbox",
8120
+ "data-cimplify-address-list": true,
8121
+ className: cn(
8122
+ "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg",
8123
+ classNames?.list
8124
+ ),
8125
+ children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
8126
+ "button",
8127
+ {
8128
+ type: "button",
8129
+ role: "option",
8130
+ "aria-selected": i === highlightedIndex,
8131
+ onMouseDown: (e) => {
8132
+ e.preventDefault();
8133
+ void selectSuggestion(s);
8134
+ },
8135
+ "data-cimplify-address-option": true,
8136
+ className: cn(
8137
+ "block w-full px-3 py-2.5 text-left text-sm transition-colors",
8138
+ i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70",
8139
+ classNames?.option,
8140
+ i === highlightedIndex && classNames?.activeOption
8141
+ ),
8142
+ children: s.description
8143
+ },
8144
+ s.place_id
8145
+ ))
8146
+ }
8147
+ )
8148
+ ] });
8149
+ }
7770
8150
  function parseValue(value) {
7771
8151
  if (!value) return void 0;
7772
8152
  const date = parseISO(value);
@@ -8527,129 +8907,18 @@ function AddressInput({
8527
8907
  classNames,
8528
8908
  inputClass
8529
8909
  }) {
8530
- const clientContext = useOptionalCimplifyClient();
8531
8910
  const addr = value && typeof value === "object" ? value : null;
8532
- const [inputText, setInputText] = React14.useState(addr?.formatted_address ?? "");
8533
- const [suggestions, setSuggestions] = React14.useState([]);
8534
- const [isOpen, setIsOpen] = React14.useState(false);
8535
- const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
8536
- const debounceRef = React14.useRef(null);
8537
- const seqRef = React14.useRef(0);
8538
- const sessionTokenRef = React14.useRef(createSessionToken());
8539
- const blurTimeoutRef = React14.useRef(null);
8540
- React14.useEffect(() => {
8541
- return () => {
8542
- if (debounceRef.current) clearTimeout(debounceRef.current);
8543
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8544
- seqRef.current += 1;
8545
- };
8546
- }, []);
8547
- const fetchSuggestions = React14.useCallback(async (query) => {
8548
- const client = clientContext?.client;
8549
- if (!client || query.length < 3) {
8550
- setSuggestions([]);
8551
- setIsOpen(false);
8552
- return;
8553
- }
8554
- const seq = ++seqRef.current;
8555
- const result = await client.places.autocomplete(query, sessionTokenRef.current);
8556
- if (seqRef.current !== seq) return;
8557
- if (result.ok && result.value.predictions.length > 0) {
8558
- setSuggestions(result.value.predictions);
8559
- setHighlightedIndex(0);
8560
- setIsOpen(true);
8561
- } else {
8562
- setSuggestions([]);
8563
- setIsOpen(false);
8564
- }
8565
- }, [clientContext]);
8566
- const handleInputChange = (nextValue) => {
8567
- setInputText(nextValue);
8568
- if (debounceRef.current) clearTimeout(debounceRef.current);
8569
- debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
8570
- };
8571
- const selectSuggestion = React14.useCallback(async (suggestion) => {
8572
- if (debounceRef.current) clearTimeout(debounceRef.current);
8573
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8574
- const fallback = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8575
- setInputText(fallback);
8576
- setSuggestions([]);
8577
- setIsOpen(false);
8578
- const client = clientContext?.client;
8579
- if (!client) return;
8580
- const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8581
- sessionTokenRef.current = createSessionToken();
8582
- if (result.ok) {
8583
- const d = result.value;
8584
- const addressValue = {
8585
- formatted_address: d.formatted_address || fallback,
8586
- street_address: d.street_address,
8587
- apartment: d.apartment,
8588
- city: d.city,
8589
- region: d.region,
8590
- postal_code: d.postal_code,
8591
- country: d.country,
8592
- latitude: d.latitude,
8593
- longitude: d.longitude,
8594
- place_id: d.place_id
8595
- };
8596
- setInputText(addressValue.formatted_address);
8597
- onValueChange(addressValue);
8598
- }
8599
- }, [clientContext, onValueChange]);
8600
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 relative", classNames?.addressInput), children: [
8911
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", classNames?.addressInput), children: [
8601
8912
  /* @__PURE__ */ jsx(
8602
- "input",
8913
+ AddressAutocomplete,
8603
8914
  {
8604
- type: "text",
8605
- value: inputText,
8606
- onChange: (e) => handleInputChange(e.target.value),
8607
- onFocus: () => {
8608
- if (suggestions.length > 0) setIsOpen(true);
8609
- },
8610
- onBlur: () => {
8611
- blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
8612
- },
8613
- onKeyDown: (e) => {
8614
- if (e.key === "Escape") {
8615
- setIsOpen(false);
8616
- return;
8617
- }
8618
- if (!isOpen || suggestions.length === 0) return;
8619
- if (e.key === "ArrowDown") {
8620
- e.preventDefault();
8621
- setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8622
- } else if (e.key === "ArrowUp") {
8623
- e.preventDefault();
8624
- setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8625
- } else if (e.key === "Enter" && highlightedIndex >= 0) {
8626
- e.preventDefault();
8627
- void selectSuggestion(suggestions[highlightedIndex]);
8628
- }
8629
- },
8915
+ value: addr,
8916
+ onSelect: onValueChange,
8630
8917
  placeholder: field.placeholder || "Search for an address...",
8631
8918
  required: field.is_required,
8632
- autoComplete: "off",
8633
- "aria-autocomplete": "list",
8634
- "aria-expanded": isOpen && suggestions.length > 0,
8635
- className: inputClass
8919
+ classNames: { input: inputClass }
8636
8920
  }
8637
8921
  ),
8638
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
8639
- "button",
8640
- {
8641
- type: "button",
8642
- role: "option",
8643
- "aria-selected": i === highlightedIndex,
8644
- onMouseDown: (e) => {
8645
- e.preventDefault();
8646
- void selectSuggestion(s);
8647
- },
8648
- className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
8649
- children: s.description
8650
- },
8651
- s.place_id
8652
- )) }),
8653
8922
  addr && /* @__PURE__ */ jsx(
8654
8923
  "input",
8655
8924
  {
@@ -8669,121 +8938,22 @@ function LocationInput({
8669
8938
  classNames,
8670
8939
  inputClass
8671
8940
  }) {
8672
- const clientContext = useOptionalCimplifyClient();
8673
8941
  const loc = value && typeof value === "object" ? value : null;
8674
- const [inputText, setInputText] = React14.useState(loc?.label ?? "");
8675
- const [suggestions, setSuggestions] = React14.useState([]);
8676
- const [isOpen, setIsOpen] = React14.useState(false);
8677
- const [highlightedIndex, setHighlightedIndex] = React14.useState(-1);
8678
- const debounceRef = React14.useRef(null);
8679
- const seqRef = React14.useRef(0);
8680
- const sessionTokenRef = React14.useRef(createSessionToken());
8681
- const blurTimeoutRef = React14.useRef(null);
8682
- React14.useEffect(() => {
8683
- return () => {
8684
- if (debounceRef.current) clearTimeout(debounceRef.current);
8685
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8686
- seqRef.current += 1;
8687
- };
8688
- }, []);
8689
- const fetchSuggestions = React14.useCallback(async (query) => {
8690
- const client = clientContext?.client;
8691
- if (!client || query.length < 3) {
8692
- setSuggestions([]);
8693
- setIsOpen(false);
8694
- return;
8695
- }
8696
- const seq = ++seqRef.current;
8697
- const result = await client.places.autocomplete(query, sessionTokenRef.current);
8698
- if (seqRef.current !== seq) return;
8699
- if (result.ok && result.value.predictions.length > 0) {
8700
- setSuggestions(result.value.predictions);
8701
- setHighlightedIndex(0);
8702
- setIsOpen(true);
8703
- } else {
8704
- setSuggestions([]);
8705
- setIsOpen(false);
8706
- }
8707
- }, [clientContext]);
8708
- const handleInputChange = (nextValue) => {
8709
- setInputText(nextValue);
8710
- if (debounceRef.current) clearTimeout(debounceRef.current);
8711
- debounceRef.current = setTimeout(() => void fetchSuggestions(nextValue.trim()), 300);
8712
- };
8713
- const selectSuggestion = React14.useCallback(async (suggestion) => {
8714
- if (debounceRef.current) clearTimeout(debounceRef.current);
8715
- if (blurTimeoutRef.current) clearTimeout(blurTimeoutRef.current);
8716
- const label = suggestion.description.split(",")[0]?.trim() || suggestion.description;
8717
- setInputText(label);
8718
- setSuggestions([]);
8719
- setIsOpen(false);
8720
- const client = clientContext?.client;
8721
- if (!client) return;
8722
- const result = await client.places.details(suggestion.place_id, sessionTokenRef.current);
8723
- sessionTokenRef.current = createSessionToken();
8724
- if (result.ok) {
8725
- const d = result.value;
8726
- onValueChange({
8727
- latitude: d.latitude,
8728
- longitude: d.longitude,
8729
- label: d.formatted_address || label
8730
- });
8731
- setInputText(d.formatted_address || label);
8732
- }
8733
- }, [clientContext, onValueChange]);
8734
- return /* @__PURE__ */ jsxs("div", { className: cn("relative", classNames?.locationInput), children: [
8942
+ return /* @__PURE__ */ jsxs("div", { className: cn(classNames?.locationInput), children: [
8735
8943
  /* @__PURE__ */ jsx(
8736
- "input",
8944
+ AddressAutocomplete,
8737
8945
  {
8738
- type: "text",
8739
- value: inputText,
8740
- onChange: (e) => handleInputChange(e.target.value),
8741
- onFocus: () => {
8742
- if (suggestions.length > 0) setIsOpen(true);
8743
- },
8744
- onBlur: () => {
8745
- blurTimeoutRef.current = setTimeout(() => setIsOpen(false), 120);
8746
- },
8747
- onKeyDown: (e) => {
8748
- if (e.key === "Escape") {
8749
- setIsOpen(false);
8750
- return;
8751
- }
8752
- if (!isOpen || suggestions.length === 0) return;
8753
- if (e.key === "ArrowDown") {
8754
- e.preventDefault();
8755
- setHighlightedIndex((i) => i < suggestions.length - 1 ? i + 1 : 0);
8756
- } else if (e.key === "ArrowUp") {
8757
- e.preventDefault();
8758
- setHighlightedIndex((i) => i > 0 ? i - 1 : suggestions.length - 1);
8759
- } else if (e.key === "Enter" && highlightedIndex >= 0) {
8760
- e.preventDefault();
8761
- void selectSuggestion(suggestions[highlightedIndex]);
8762
- }
8763
- },
8946
+ value: loc ? { formatted_address: loc.label ?? "", latitude: loc.latitude, longitude: loc.longitude } : null,
8947
+ onSelect: (address) => onValueChange({
8948
+ latitude: address.latitude,
8949
+ longitude: address.longitude,
8950
+ label: address.formatted_address
8951
+ }),
8764
8952
  placeholder: field.placeholder || "Search for a location...",
8765
8953
  required: field.is_required,
8766
- autoComplete: "off",
8767
- "aria-autocomplete": "list",
8768
- "aria-expanded": isOpen && suggestions.length > 0,
8769
- className: inputClass
8954
+ classNames: { input: inputClass }
8770
8955
  }
8771
8956
  ),
8772
- isOpen && suggestions.length > 0 && /* @__PURE__ */ jsx("div", { role: "listbox", className: "absolute left-0 right-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-border bg-background shadow-lg", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
8773
- "button",
8774
- {
8775
- type: "button",
8776
- role: "option",
8777
- "aria-selected": i === highlightedIndex,
8778
- onMouseDown: (e) => {
8779
- e.preventDefault();
8780
- void selectSuggestion(s);
8781
- },
8782
- className: cn("block w-full px-3 py-2.5 text-left text-sm transition-colors", i === highlightedIndex ? "bg-muted text-foreground" : "text-foreground hover:bg-muted/70"),
8783
- children: s.description
8784
- },
8785
- s.place_id
8786
- )) }),
8787
8957
  loc && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: [
8788
8958
  loc.latitude.toFixed(4),
8789
8959
  ", ",
@@ -8914,12 +9084,6 @@ function SignatureInput({
8914
9084
  !sig?.data_url && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.placeholder || "Draw your signature above" })
8915
9085
  ] });
8916
9086
  }
8917
- function createSessionToken() {
8918
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
8919
- return crypto.randomUUID();
8920
- }
8921
- return `places_${Date.now()}_${Math.random().toString(16).slice(2)}`;
8922
- }
8923
9087
  function getTimeOfDay(timeStr) {
8924
9088
  const hour = parseInt(timeStr.split("T").pop()?.split(":")[0] ?? timeStr.split(":")[0], 10);
8925
9089
  if (hour < 12) return "morning";
@@ -9165,6 +9329,7 @@ function DateSlotPicker({
9165
9329
  serviceId,
9166
9330
  daysToShow = 7,
9167
9331
  participantCount,
9332
+ units,
9168
9333
  selectedSlot,
9169
9334
  onSlotSelect,
9170
9335
  availability: availabilityProp,
@@ -9199,6 +9364,7 @@ function DateSlotPicker({
9199
9364
  dateRange.endDate,
9200
9365
  {
9201
9366
  participantCount,
9367
+ units,
9202
9368
  enabled: availabilityProp === void 0
9203
9369
  }
9204
9370
  );
@@ -9335,6 +9501,7 @@ function ProductCustomizer({
9335
9501
  onAddToCart,
9336
9502
  onVariantChange,
9337
9503
  showSpecialInstructions = true,
9504
+ submitLabel,
9338
9505
  className,
9339
9506
  classNames
9340
9507
  }) {
@@ -9355,6 +9522,7 @@ function ProductCustomizer({
9355
9522
  const [specialInstructions, setSpecialInstructions] = useState("");
9356
9523
  const [selectedSlot, setSelectedSlot] = useState(null);
9357
9524
  const [selectedStaffId, setSelectedStaffId] = useState(null);
9525
+ const [unitsCount, setUnitsCount] = useState(1);
9358
9526
  const cart = useCart();
9359
9527
  const currency = getProductCurrency(product);
9360
9528
  const productType = product.type || "product";
@@ -9362,6 +9530,7 @@ function ProductCustomizer({
9362
9530
  const isBundle = productType === "bundle";
9363
9531
  const isDigital = productType === "digital";
9364
9532
  const isService = productType === "service";
9533
+ const offersUnits = isService && isMultiDayService(product);
9365
9534
  const isStandard = !isComposite && !isBundle;
9366
9535
  const hasVariants = isStandard && product.variants && product.variants.length > 0;
9367
9536
  const hasAddOns = isStandard && product.add_ons && product.add_ons.length > 0;
@@ -9383,6 +9552,7 @@ function ProductCustomizer({
9383
9552
  setSpecialInstructions("");
9384
9553
  setSelectedSlot(null);
9385
9554
  setSelectedStaffId(null);
9555
+ setUnitsCount(1);
9386
9556
  }, [product.id, product.min_order_quantity]);
9387
9557
  const slotStaff = useMemo(
9388
9558
  () => (selectedSlot?.available_staff ?? []).map((member) => ({
@@ -9472,7 +9642,17 @@ function ProductCustomizer({
9472
9642
  if (perUnit === void 0 || perUnit === null) return void 0;
9473
9643
  return parsePrice(perUnit) * quantity;
9474
9644
  }, [quote, quantity]);
9475
- const displayTotalPrice = quotedTotalPrice ?? localTotalPrice;
9645
+ const displayTotalPrice = useMemo(() => {
9646
+ const total = quotedTotalPrice ?? localTotalPrice;
9647
+ const multiplier = serviceBillingMultiplier(product, {
9648
+ quantity,
9649
+ units: unitsCount,
9650
+ scheduledStart: selectedSlot?.start_time,
9651
+ scheduledEnd: selectedSlot?.end_time
9652
+ });
9653
+ if (multiplier === null) return total;
9654
+ return total / Math.max(1, quantity) * multiplier;
9655
+ }, [quotedTotalPrice, localTotalPrice, product, quantity, unitsCount, selectedSlot]);
9476
9656
  const handleVariantChange = useCallback(
9477
9657
  (variantId, variant) => {
9478
9658
  setSelectedVariantId(variantId);
@@ -9504,7 +9684,8 @@ function ProductCustomizer({
9504
9684
  scheduledStart: selectedSlot.start_time,
9505
9685
  scheduledEnd: selectedSlot.end_time,
9506
9686
  staffId: selectedStaffId ?? selectedSlot.available_staff?.[0]?.staff_id,
9507
- resourceId: selectedSlot.available_resources?.[0]?.resource_id
9687
+ resourceId: selectedSlot.available_resources?.[0]?.resource_id,
9688
+ units: unitsCount > 1 ? unitsCount : void 0
9508
9689
  } : {},
9509
9690
  customerInputs: Object.keys(customerInputValues).length > 0 ? Object.entries(customerInputValues).filter(([, v]) => v !== void 0 && v !== "").map(([fieldId, value]) => ({ field_id: fieldId, value })) : void 0,
9510
9691
  specialInstructions: specialInstructions.trim() || void 0
@@ -9525,7 +9706,7 @@ function ProductCustomizer({
9525
9706
  } finally {
9526
9707
  setIsSubmitting(false);
9527
9708
  }
9528
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, isSubmitting, onAddToCart, cart]);
9709
+ }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, unitsCount, isSubmitting, onAddToCart, cart]);
9529
9710
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
9530
9711
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
9531
9712
  CompositeSelector,
@@ -9562,9 +9743,17 @@ function ProductCustomizer({
9562
9743
  setSelectedStaffId(null);
9563
9744
  },
9564
9745
  participantCount: quantity,
9565
- currency
9746
+ units: offersUnits ? unitsCount : void 0,
9747
+ currency,
9748
+ schedulingMode: product.scheduling_mode ?? (offersUnits ? "multi_day" : "intraday"),
9749
+ durationUnit: product.duration_unit,
9750
+ durationValue: product.duration_value
9566
9751
  }
9567
9752
  ),
9753
+ offersUnits && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-units": true, className: classNames?.units, children: [
9754
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Units" }),
9755
+ /* @__PURE__ */ jsx(QuantitySelector, { value: unitsCount, onChange: setUnitsCount, min: 1 })
9756
+ ] }),
9568
9757
  isService && slotStaff.length > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-staff": true, children: [
9569
9758
  /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium uppercase tracking-wider text-muted-foreground mb-3", children: "Choose your specialist" }),
9570
9759
  /* @__PURE__ */ jsx(
@@ -9723,8 +9912,10 @@ function ProductCustomizer({
9723
9912
  isAdded && classNames?.submitButtonAdded,
9724
9913
  classNames?.submitButton
9725
9914
  ),
9726
- children: isAdded ? "Added to Cart" : /* @__PURE__ */ jsxs(Fragment, { children: [
9727
- "Add to Cart \xB7 ",
9915
+ children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
9916
+ submitLabel ?? "Add to Cart",
9917
+ " \xB7",
9918
+ " ",
9728
9919
  /* @__PURE__ */ jsx(Price, { amount: displayTotalPrice, currency })
9729
9920
  ] })
9730
9921
  }
@@ -9884,13 +10075,13 @@ function StoreVideo({
9884
10075
  autoplay = true,
9885
10076
  loop = true,
9886
10077
  muted = true,
9887
- lazy = true,
10078
+ lazy: lazy2 = true,
9888
10079
  className
9889
10080
  }) {
9890
10081
  const ref = useRef(null);
9891
- const [inView, setInView] = useState(!lazy);
10082
+ const [inView, setInView] = useState(!lazy2);
9892
10083
  useEffect(() => {
9893
- if (!lazy || inView) return;
10084
+ if (!lazy2 || inView) return;
9894
10085
  const node = ref.current;
9895
10086
  if (!node || typeof IntersectionObserver === "undefined") {
9896
10087
  setInView(true);
@@ -9907,7 +10098,7 @@ function StoreVideo({
9907
10098
  );
9908
10099
  observer.observe(node);
9909
10100
  return () => observer.disconnect();
9910
- }, [lazy, inView]);
10101
+ }, [lazy2, inView]);
9911
10102
  return /* @__PURE__ */ jsx(
9912
10103
  "div",
9913
10104
  {
@@ -9925,7 +10116,7 @@ function StoreVideo({
9925
10116
  muted,
9926
10117
  playsInline: true,
9927
10118
  controls,
9928
- preload: lazy ? "metadata" : "auto",
10119
+ preload: lazy2 ? "metadata" : "auto",
9929
10120
  "aria-label": alt,
9930
10121
  style: { width: "100%", height: "100%", objectFit: "cover", display: "block" },
9931
10122
  children: poster ? /* @__PURE__ */ jsx(
@@ -11616,18 +11807,18 @@ function RentalServiceCard({
11616
11807
  const currency = getProductCurrency(product);
11617
11808
  const hasDeposit = product.deposit_type && product.deposit_type !== "none" && product.deposit_amount;
11618
11809
  const unit = product.duration_unit ? UNIT_LABELS2[product.duration_unit] || product.duration_unit : "day";
11810
+ const priceUnit = product.price_basis === "per_person" ? "guest" : product.price_basis === "flat" ? "" : unit;
11619
11811
  const status = product.inventory_status;
11620
11812
  return /* @__PURE__ */ jsxs(CardShell, { product, renderLink, className, children: [
11621
11813
  /* @__PURE__ */ jsxs(CardImage, { src: image, alt: product.name, aspectRatio: "16/9", renderImage, children: [
11622
11814
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none" }),
11623
- /* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
11815
+ priceUnit && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm", children: [
11624
11816
  "Per ",
11625
- unit
11817
+ priceUnit
11626
11818
  ] }),
11627
11819
  /* @__PURE__ */ jsxs("span", { className: "absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm", children: [
11628
11820
  /* @__PURE__ */ jsx(Price, { amount: product.default_price, currency }),
11629
- "/",
11630
- unit
11821
+ priceUnit && `/${priceUnit}`
11631
11822
  ] }),
11632
11823
  product.general_service_capacity != null && product.general_service_capacity > 1 && /* @__PURE__ */ jsxs("span", { className: "absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-blue-50/90 text-blue-700 border border-blue-200/50 backdrop-blur px-2 py-0.5 rounded-md", children: [
11633
11824
  "Up to ",
@@ -12221,6 +12412,15 @@ function ProductGrid({
12221
12412
  ] });
12222
12413
  }
12223
12414
 
12415
+ // src/react/utils/attachment.ts
12416
+ var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".svg"];
12417
+ function isImageAttachment(attachment) {
12418
+ if (attachment.mime_type?.startsWith("image/")) return true;
12419
+ if (attachment.mime_type) return false;
12420
+ const path = attachment.url.split("?")[0].split("#")[0].toLowerCase();
12421
+ return IMAGE_EXTENSIONS.some((extension) => path.endsWith(extension));
12422
+ }
12423
+
12224
12424
  // src/react/utils/boundary-labels.ts
12225
12425
  var PRESET_LABELS = {
12226
12426
  check_in_out: { start: "Check-in", end: "Check-out" },
@@ -13402,6 +13602,7 @@ function BookingPage({
13402
13602
  const [selectedDate, setSelectedDate] = useState(null);
13403
13603
  const [selectedStaffId, setSelectedStaffId] = useState(null);
13404
13604
  const [selectedResourceId, setSelectedResourceId] = useState(null);
13605
+ const [bookingUnits, setBookingUnits] = useState(1);
13405
13606
  const [isSubmitting, setIsSubmitting] = useState(false);
13406
13607
  const [error, setError] = useState(null);
13407
13608
  const hasResourceStep = resources && resources.length > 0;
@@ -13495,7 +13696,8 @@ function BookingPage({
13495
13696
  scheduledStart: selectedSlot.start_time,
13496
13697
  scheduledEnd: selectedSlot.end_time,
13497
13698
  staffId: selectedStaffId || void 0,
13498
- resourceId: selectedResourceId || void 0
13699
+ resourceId: selectedResourceId || void 0,
13700
+ units: bookingUnits > 1 ? bookingUnits : void 0
13499
13701
  });
13500
13702
  onBooked?.(selectedSlot, selectedStaffId);
13501
13703
  }
@@ -13591,6 +13793,31 @@ function BookingPage({
13591
13793
  ] }),
13592
13794
  step === STEP.CONFIRM && selectedSlot && /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-step": "confirm", className: classNames?.step, children: [
13593
13795
  /* @__PURE__ */ jsx("h2", { "data-cimplify-booking-step-title": true, className: classNames?.stepTitle, children: "Confirm your booking" }),
13796
+ /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-units": true, className: classNames?.summaryRow, children: [
13797
+ /* @__PURE__ */ jsx("span", { children: "Units" }),
13798
+ /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-units-stepper": true, children: [
13799
+ /* @__PURE__ */ jsx(
13800
+ "button",
13801
+ {
13802
+ type: "button",
13803
+ "data-cimplify-booking-units-decrement": true,
13804
+ onClick: () => setBookingUnits((current) => Math.max(1, current - 1)),
13805
+ disabled: bookingUnits <= 1,
13806
+ children: "\u2212"
13807
+ }
13808
+ ),
13809
+ /* @__PURE__ */ jsx("span", { "data-cimplify-booking-units-value": true, children: bookingUnits }),
13810
+ /* @__PURE__ */ jsx(
13811
+ "button",
13812
+ {
13813
+ type: "button",
13814
+ "data-cimplify-booking-units-increment": true,
13815
+ onClick: () => setBookingUnits((current) => current + 1),
13816
+ children: "+"
13817
+ }
13818
+ )
13819
+ ] })
13820
+ ] }),
13594
13821
  /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-summary": true, className: classNames?.summary, children: [
13595
13822
  /* @__PURE__ */ jsxs("div", { "data-cimplify-booking-summary-row": true, className: classNames?.summaryRow, children: [
13596
13823
  /* @__PURE__ */ jsx("span", { children: "Service" }),
@@ -16727,6 +16954,112 @@ function CurrencySelector({
16727
16954
  );
16728
16955
  }) });
16729
16956
  }
16957
+ var Streamdown = lazy(() => import('streamdown').then((m) => ({ default: m.Streamdown })));
16958
+ function detectLookbehindSupport() {
16959
+ try {
16960
+ new RegExp("(?<=a)b");
16961
+ return true;
16962
+ } catch {
16963
+ return false;
16964
+ }
16965
+ }
16966
+ var supportsRegExpLookbehind = detectLookbehindSupport();
16967
+ marked.setOptions({
16968
+ gfm: true,
16969
+ breaks: true
16970
+ });
16971
+ var ALLOWED_TAGS = [
16972
+ "a",
16973
+ "b",
16974
+ "blockquote",
16975
+ "br",
16976
+ "code",
16977
+ "del",
16978
+ "em",
16979
+ "h1",
16980
+ "h2",
16981
+ "h3",
16982
+ "h4",
16983
+ "hr",
16984
+ "i",
16985
+ "li",
16986
+ "ol",
16987
+ "p",
16988
+ "pre",
16989
+ "strong",
16990
+ "table",
16991
+ "tbody",
16992
+ "td",
16993
+ "th",
16994
+ "thead",
16995
+ "tr",
16996
+ "ul"
16997
+ ];
16998
+ var ALLOWED_ATTR = ["href", "rel", "target", "title", "start"];
16999
+ var hooksInstalled = false;
17000
+ function installHooks() {
17001
+ if (hooksInstalled) return;
17002
+ hooksInstalled = true;
17003
+ DOMPurify.addHook("afterSanitizeAttributes", (node) => {
17004
+ if (!(node instanceof HTMLAnchorElement)) return;
17005
+ if (!node.getAttribute("href")) return;
17006
+ node.setAttribute("rel", "noreferrer noopener");
17007
+ node.setAttribute("target", "_blank");
17008
+ });
17009
+ }
17010
+ var CACHE_LIMIT = 300;
17011
+ var cache = /* @__PURE__ */ new Map();
17012
+ function renderMarkdown(text) {
17013
+ const cached = cache.get(text);
17014
+ if (cached !== void 0) return cached;
17015
+ installHooks();
17016
+ const html = DOMPurify.sanitize(marked.parse(text), {
17017
+ ALLOWED_TAGS,
17018
+ ALLOWED_ATTR
17019
+ });
17020
+ if (cache.size >= CACHE_LIMIT) {
17021
+ const oldest = cache.keys().next().value;
17022
+ if (oldest !== void 0) cache.delete(oldest);
17023
+ }
17024
+ cache.set(text, html);
17025
+ return html;
17026
+ }
17027
+ function SettledMarkdown({ text, className }) {
17028
+ const html = useMemo(() => renderMarkdown(text), [text]);
17029
+ return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
17030
+ }
17031
+ var ChatMarkdown = memo(function ChatMarkdown2({
17032
+ text,
17033
+ className,
17034
+ streaming = false
17035
+ }) {
17036
+ if (streaming && supportsRegExpLookbehind) {
17037
+ return (
17038
+ // While the streamdown chunk loads, the settled renderer keeps the
17039
+ // text on screen — no blank frame at stream start.
17040
+ /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SettledMarkdown, { text, className }), children: /* @__PURE__ */ jsx(Streamdown, { className, children: text }) })
17041
+ );
17042
+ }
17043
+ return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
17044
+ });
17045
+
17046
+ // src/react/chat-model.ts
17047
+ function partitionMessages(messages) {
17048
+ const bubbles = [];
17049
+ const reactionsByTarget = /* @__PURE__ */ new Map();
17050
+ for (const message of messages) {
17051
+ if (message.content_type === "reaction") {
17052
+ const targetId = message.reply_to?.id;
17053
+ if (!targetId) continue;
17054
+ const list = reactionsByTarget.get(targetId);
17055
+ if (list) list.push(message);
17056
+ else reactionsByTarget.set(targetId, [message]);
17057
+ continue;
17058
+ }
17059
+ bubbles.push(message);
17060
+ }
17061
+ return { bubbles, reactionsByTarget };
17062
+ }
16730
17063
  function ChatWidget({
16731
17064
  client: clientProp,
16732
17065
  businessName = "Support",
@@ -16743,24 +17076,46 @@ function ChatWidget({
16743
17076
  const context = useOptionalCimplifyClient();
16744
17077
  const client = clientProp ?? context?.client;
16745
17078
  const [open, setOpen] = useState(defaultOpen);
16746
- const [unread, setUnread] = useState(0);
16747
- const { messages, isLoading, isSending, error, isActive, send, startConversation } = useChat({ client, pollInterval });
16748
- const prevCountRef = useRef(messages.length);
16749
- useEffect(() => {
16750
- if (!open && messages.length > prevCountRef.current) {
16751
- const newMessages = messages.slice(prevCountRef.current);
16752
- const incomingCount = newMessages.filter(
16753
- (m) => m.sender_type !== "customer"
16754
- ).length;
16755
- if (incomingCount > 0) setUnread((n) => n + incomingCount);
16756
- }
16757
- prevCountRef.current = messages.length;
16758
- }, [messages, open]);
17079
+ const [readCount, setReadCount] = useState(0);
17080
+ const {
17081
+ messages,
17082
+ isLoading,
17083
+ isSending,
17084
+ awaitingReply,
17085
+ seenAt,
17086
+ error,
17087
+ isActive,
17088
+ claimedAs,
17089
+ signOut,
17090
+ send,
17091
+ react,
17092
+ startConversation
17093
+ } = useChat({ client, pollInterval });
17094
+ const showTyping = isSending || awaitingReply;
17095
+ const attachPhoto = useCallback(
17096
+ async (file) => {
17097
+ if (!client || !file.type.startsWith("image/") || file.size > 5 * 1024 * 1024) return;
17098
+ const uploaded = await client.uploads.upload(file);
17099
+ if (!uploaded.ok) return;
17100
+ const attachment = {
17101
+ upload_id: uploaded.value.id,
17102
+ url: uploaded.value.url,
17103
+ mime_type: uploaded.value.content_type,
17104
+ filename: uploaded.value.filename,
17105
+ size: uploaded.value.size_bytes
17106
+ };
17107
+ await send("", { attachments: [attachment] });
17108
+ },
17109
+ [client, send]
17110
+ );
17111
+ const unread = open ? 0 : messages.slice(readCount).filter((m) => m.sender_type !== "customer").length;
16759
17112
  const handleOpen = useCallback(() => {
16760
17113
  setOpen(true);
16761
- setUnread(0);
16762
17114
  }, []);
16763
- const handleClose = useCallback(() => setOpen(false), []);
17115
+ const handleClose = useCallback(() => {
17116
+ setReadCount(messages.length);
17117
+ setOpen(false);
17118
+ }, [messages.length]);
16764
17119
  const isLeft = position === "bottom-left";
16765
17120
  return /* @__PURE__ */ jsxs(
16766
17121
  "div",
@@ -16791,11 +17146,17 @@ function ChatWidget({
16791
17146
  messages,
16792
17147
  isLoading,
16793
17148
  isSending,
17149
+ isTyping: showTyping,
17150
+ seenAt,
16794
17151
  error,
16795
17152
  isActive,
16796
17153
  isLeft,
17154
+ claimedAs,
17155
+ onSignOut: () => void signOut(),
16797
17156
  onClose: handleClose,
16798
17157
  onSend: send,
17158
+ onReact: react,
17159
+ onAttach: attachPhoto,
16799
17160
  onStartConversation: startConversation,
16800
17161
  classNames
16801
17162
  }
@@ -16848,11 +17209,17 @@ function Panel({
16848
17209
  messages,
16849
17210
  isLoading,
16850
17211
  isSending,
17212
+ isTyping,
17213
+ seenAt,
16851
17214
  error,
16852
17215
  isActive,
16853
17216
  isLeft,
17217
+ claimedAs,
17218
+ onSignOut,
16854
17219
  onClose,
16855
17220
  onSend,
17221
+ onReact,
17222
+ onAttach,
16856
17223
  onStartConversation,
16857
17224
  classNames
16858
17225
  }) {
@@ -16872,6 +17239,8 @@ function Panel({
16872
17239
  Header,
16873
17240
  {
16874
17241
  businessName,
17242
+ claimedAs,
17243
+ onSignOut,
16875
17244
  onClose,
16876
17245
  className: classNames?.header
16877
17246
  }
@@ -16890,7 +17259,9 @@ function Panel({
16890
17259
  {
16891
17260
  messages,
16892
17261
  isLoading,
16893
- isSending,
17262
+ isSending: isTyping,
17263
+ seenAt,
17264
+ onReact,
16894
17265
  className: classNames?.messages
16895
17266
  }
16896
17267
  ),
@@ -16901,6 +17272,7 @@ function Panel({
16901
17272
  placeholder,
16902
17273
  isSending,
16903
17274
  onSend,
17275
+ onAttach,
16904
17276
  className: classNames?.input
16905
17277
  }
16906
17278
  ),
@@ -16915,6 +17287,8 @@ function Panel({
16915
17287
  }
16916
17288
  function Header({
16917
17289
  businessName,
17290
+ claimedAs,
17291
+ onSignOut,
16918
17292
  onClose,
16919
17293
  className
16920
17294
  }) {
@@ -16929,7 +17303,22 @@ function Header({
16929
17303
  /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-white/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
16930
17304
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
16931
17305
  /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
16932
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
17306
+ claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-white/60", children: [
17307
+ /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
17308
+ "Chatting as ",
17309
+ claimedAs
17310
+ ] }),
17311
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
17312
+ /* @__PURE__ */ jsx(
17313
+ "button",
17314
+ {
17315
+ type: "button",
17316
+ onClick: onSignOut,
17317
+ className: "flex-shrink-0 underline underline-offset-2 hover:text-white",
17318
+ children: "Not you?"
17319
+ }
17320
+ )
17321
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-white/60", children: [
16933
17322
  /* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
16934
17323
  "Online"
16935
17324
  ] })
@@ -16996,16 +17385,25 @@ function Welcome({
16996
17385
  }
16997
17386
  );
16998
17387
  }
17388
+ var QUICK_REACTIONS = ["\u{1F44D}", "\u2764\uFE0F"];
17389
+ function lastSeenCustomerMessageId(bubbles, seenAt) {
17390
+ if (!seenAt || !bubbles.some((m) => m.sender_type === "agent")) return null;
17391
+ for (let i = bubbles.length - 1; i >= 0; i--) {
17392
+ if (bubbles[i].sender_type !== "customer") continue;
17393
+ return bubbles[i].created_at <= seenAt ? bubbles[i].id : null;
17394
+ }
17395
+ return null;
17396
+ }
16999
17397
  function MessageList({
17000
17398
  messages,
17001
17399
  isLoading,
17002
17400
  isSending,
17401
+ seenAt,
17402
+ onReact,
17003
17403
  className
17004
17404
  }) {
17005
- const bottomRef = useRef(null);
17006
- useEffect(() => {
17007
- bottomRef.current?.scrollIntoView({ behavior: "smooth" });
17008
- }, [messages.length, isSending]);
17405
+ const { bubbles, reactionsByTarget } = partitionMessages(messages);
17406
+ const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
17009
17407
  return /* @__PURE__ */ jsxs(
17010
17408
  "div",
17011
17409
  {
@@ -17015,44 +17413,123 @@ function MessageList({
17015
17413
  ),
17016
17414
  children: [
17017
17415
  isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
17018
- messages.map((msg, i) => {
17416
+ bubbles.map((msg, i) => {
17019
17417
  const isCustomer = msg.sender_type === "customer";
17020
- const prev = messages[i - 1];
17418
+ const prev = bubbles[i - 1];
17021
17419
  const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
17022
17420
  const isOptimistic = msg.id.startsWith("opt_");
17421
+ const reactions = reactionsByTarget.get(msg.id);
17023
17422
  return /* @__PURE__ */ jsxs("div", { children: [
17024
17423
  !sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
17025
- /* @__PURE__ */ jsx(
17424
+ /* @__PURE__ */ jsxs(
17026
17425
  "div",
17027
17426
  {
17028
17427
  className: cn(
17029
- "flex max-w-[85%]",
17428
+ "group relative flex max-w-[85%]",
17030
17429
  isCustomer ? "ml-auto" : "mr-auto"
17031
17430
  ),
17032
- children: /* @__PURE__ */ jsxs(
17033
- "div",
17034
- {
17035
- className: cn(
17036
- "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17037
- isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17038
- isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17039
- isOptimistic && "opacity-70"
17040
- ),
17041
- children: [
17042
- !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
17043
- /* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17044
- "AI"
17045
- ] }),
17046
- /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content })
17047
- ]
17048
- }
17049
- )
17431
+ children: [
17432
+ !isCustomer && !isOptimistic && /* @__PURE__ */ jsx("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
17433
+ "button",
17434
+ {
17435
+ type: "button",
17436
+ "aria-label": `React ${emoji}`,
17437
+ onClick: () => void onReact(msg, emoji),
17438
+ className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
17439
+ children: emoji
17440
+ },
17441
+ emoji
17442
+ )) }),
17443
+ /* @__PURE__ */ jsxs(
17444
+ "div",
17445
+ {
17446
+ className: cn(
17447
+ "rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
17448
+ isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
17449
+ isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
17450
+ isOptimistic && "opacity-70"
17451
+ ),
17452
+ children: [
17453
+ !isCustomer && !sameSenderAsPrev && /* @__PURE__ */ jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-600", children: [
17454
+ /* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
17455
+ "AI"
17456
+ ] }),
17457
+ msg.reply_to && /* @__PURE__ */ jsxs(
17458
+ "div",
17459
+ {
17460
+ className: cn(
17461
+ "mb-1.5 flex gap-1.5 rounded-lg px-2 py-1 text-xs",
17462
+ isCustomer ? "bg-background/15" : "bg-muted"
17463
+ ),
17464
+ children: [
17465
+ /* @__PURE__ */ jsx(
17466
+ "span",
17467
+ {
17468
+ className: cn(
17469
+ "w-[3px] shrink-0 self-stretch rounded-full",
17470
+ isCustomer ? "bg-background/40" : "bg-muted-foreground/40"
17471
+ )
17472
+ }
17473
+ ),
17474
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate opacity-80", children: msg.reply_to.preview })
17475
+ ]
17476
+ }
17477
+ ),
17478
+ msg.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-1.5", msg.content && "mb-1.5"), children: msg.attachments.map(
17479
+ (attachment, index) => attachment.mime_type?.startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
17480
+ "img",
17481
+ {
17482
+ src: attachment.url,
17483
+ alt: attachment.filename ?? "Photo",
17484
+ loading: "lazy",
17485
+ className: "max-h-48 max-w-full rounded-lg object-cover"
17486
+ }
17487
+ ) }, index) : /* @__PURE__ */ jsx(
17488
+ "a",
17489
+ {
17490
+ href: attachment.url,
17491
+ target: "_blank",
17492
+ rel: "noreferrer",
17493
+ className: "truncate text-xs underline",
17494
+ children: attachment.filename ?? "Attachment"
17495
+ },
17496
+ index
17497
+ )
17498
+ ) }),
17499
+ isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
17500
+ ChatMarkdown,
17501
+ {
17502
+ text: msg.content,
17503
+ className: "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline"
17504
+ }
17505
+ )
17506
+ ]
17507
+ }
17508
+ )
17509
+ ]
17050
17510
  }
17051
- )
17511
+ ),
17512
+ reactions && reactions.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mt-0.5 flex gap-1", isCustomer ? "justify-end" : "justify-start"), children: reactions.map((reaction) => /* @__PURE__ */ jsx(
17513
+ "span",
17514
+ {
17515
+ className: "rounded-full border border-border bg-background px-1.5 py-0.5 text-xs shadow-sm",
17516
+ children: reaction.content
17517
+ },
17518
+ reaction.id
17519
+ )) }),
17520
+ msg.id === seenMessageId && /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-right text-[10px] font-medium text-muted-foreground", children: "Seen" })
17052
17521
  ] }, msg.id);
17053
17522
  }),
17054
17523
  isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
17055
- /* @__PURE__ */ jsx("div", { ref: bottomRef })
17524
+ /* @__PURE__ */ jsx(
17525
+ "div",
17526
+ {
17527
+ ref: (node) => {
17528
+ node?.scrollIntoView({ behavior: "smooth" });
17529
+ }
17530
+ },
17531
+ `${messages.length}:${isSending}`
17532
+ )
17056
17533
  ]
17057
17534
  }
17058
17535
  );
@@ -17061,10 +17538,24 @@ function ChatInput({
17061
17538
  placeholder,
17062
17539
  isSending,
17063
17540
  onSend,
17541
+ onAttach,
17064
17542
  className
17065
17543
  }) {
17066
17544
  const [value, setValue] = useState("");
17545
+ const [uploading, setUploading] = useState(false);
17067
17546
  const textareaRef = useRef(null);
17547
+ const fileRef = useRef(null);
17548
+ const handleAttach = useCallback(
17549
+ async (file) => {
17550
+ setUploading(true);
17551
+ try {
17552
+ await onAttach(file);
17553
+ } finally {
17554
+ setUploading(false);
17555
+ }
17556
+ },
17557
+ [onAttach]
17558
+ );
17068
17559
  const handleSend = useCallback(() => {
17069
17560
  const trimmed = value.trim();
17070
17561
  if (!trimmed || isSending) return;
@@ -17098,6 +17589,37 @@ function ChatInput({
17098
17589
  className
17099
17590
  ),
17100
17591
  children: [
17592
+ /* @__PURE__ */ jsx(
17593
+ "input",
17594
+ {
17595
+ ref: fileRef,
17596
+ type: "file",
17597
+ accept: "image/*",
17598
+ className: "hidden",
17599
+ "aria-hidden": "true",
17600
+ tabIndex: -1,
17601
+ onChange: (e) => {
17602
+ const file = e.target.files?.[0];
17603
+ e.target.value = "";
17604
+ if (file) void handleAttach(file);
17605
+ }
17606
+ }
17607
+ ),
17608
+ /* @__PURE__ */ jsx(
17609
+ "button",
17610
+ {
17611
+ type: "button",
17612
+ "aria-label": "Attach a photo",
17613
+ disabled: uploading,
17614
+ onClick: () => fileRef.current?.click(),
17615
+ className: cn(
17616
+ "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px]",
17617
+ "bg-muted text-muted-foreground transition-colors hover:text-foreground",
17618
+ uploading && "animate-pulse"
17619
+ ),
17620
+ children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-[18px] w-[18px]" })
17621
+ }
17622
+ ),
17101
17623
  /* @__PURE__ */ jsx(
17102
17624
  "div",
17103
17625
  {
@@ -17200,6 +17722,25 @@ function SendIcon({ className }) {
17200
17722
  }
17201
17723
  );
17202
17724
  }
17725
+ function ImageIcon({ className }) {
17726
+ return /* @__PURE__ */ jsxs(
17727
+ "svg",
17728
+ {
17729
+ className,
17730
+ viewBox: "0 0 24 24",
17731
+ fill: "none",
17732
+ stroke: "currentColor",
17733
+ strokeWidth: 2,
17734
+ strokeLinecap: "round",
17735
+ strokeLinejoin: "round",
17736
+ children: [
17737
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
17738
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
17739
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
17740
+ ]
17741
+ }
17742
+ );
17743
+ }
17203
17744
  function SparkleIcon({ className }) {
17204
17745
  return /* @__PURE__ */ jsx(
17205
17746
  "svg",
@@ -17214,4 +17755,1825 @@ function SparkleIcon({ className }) {
17214
17755
  );
17215
17756
  }
17216
17757
 
17217
- export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BundleProductCard, BundleProductLayout, BundleSelector, Button, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, cn, defaultBoundaryKind, getBoundaryLabels, prefetchProduct, prefetchProducts, resolveBoundaryKind, roomToResource, sanitizeRichTextHtml, stripHtmlToText, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };
17758
+ // src/react/genui/catalog.ts
17759
+ function genUICatalog(registry) {
17760
+ return new Map(
17761
+ registry.components.map((component) => [
17762
+ component.name,
17763
+ {
17764
+ params: component.params.map((param) => ({
17765
+ name: param.name,
17766
+ required: param.required ?? false
17767
+ }))
17768
+ }
17769
+ ])
17770
+ );
17771
+ }
17772
+
17773
+ // src/genui/lang/parser/ast.ts
17774
+ function isRuntimeExpr(node) {
17775
+ switch (node.k) {
17776
+ case "StateRef":
17777
+ case "RuntimeRef":
17778
+ case "BinOp":
17779
+ case "UnaryOp":
17780
+ case "Ternary":
17781
+ case "Member":
17782
+ case "Index":
17783
+ case "Assign":
17784
+ return true;
17785
+ default:
17786
+ return false;
17787
+ }
17788
+ }
17789
+ var AST_KINDS = /* @__PURE__ */ new Set([
17790
+ "Comp",
17791
+ "Ref",
17792
+ "StateRef",
17793
+ "RuntimeRef",
17794
+ "BinOp",
17795
+ "UnaryOp",
17796
+ "Ternary",
17797
+ "Member",
17798
+ "Index",
17799
+ "Assign",
17800
+ "Str",
17801
+ "Num",
17802
+ "Bool",
17803
+ "Null",
17804
+ "Arr",
17805
+ "Obj",
17806
+ "Ph"
17807
+ ]);
17808
+ function isASTNode(value) {
17809
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
17810
+ return AST_KINDS.has(value.k);
17811
+ }
17812
+ function walkAST(node, visit) {
17813
+ const walk = (current) => {
17814
+ visit(current);
17815
+ switch (current.k) {
17816
+ case "Comp":
17817
+ current.args.forEach(walk);
17818
+ Object.values(current.mappedProps ?? {}).forEach(walk);
17819
+ break;
17820
+ case "Arr":
17821
+ current.els.forEach(walk);
17822
+ break;
17823
+ case "Obj":
17824
+ current.entries.forEach(([, value]) => walk(value));
17825
+ break;
17826
+ case "BinOp":
17827
+ walk(current.left);
17828
+ walk(current.right);
17829
+ break;
17830
+ case "UnaryOp":
17831
+ walk(current.operand);
17832
+ break;
17833
+ case "Ternary":
17834
+ walk(current.cond);
17835
+ walk(current.then);
17836
+ walk(current.else);
17837
+ break;
17838
+ case "Member":
17839
+ walk(current.obj);
17840
+ break;
17841
+ case "Index":
17842
+ walk(current.obj);
17843
+ walk(current.index);
17844
+ break;
17845
+ case "Assign":
17846
+ walk(current.value);
17847
+ break;
17848
+ }
17849
+ };
17850
+ walk(node);
17851
+ }
17852
+
17853
+ // src/genui/lang/parser/builtins.ts
17854
+ function resolveField(obj, path) {
17855
+ if (!path || obj == null) return void 0;
17856
+ if (!path.includes(".")) return obj[path];
17857
+ let cur = obj;
17858
+ for (const p of path.split(".")) {
17859
+ if (cur == null) return void 0;
17860
+ cur = cur[p];
17861
+ }
17862
+ return cur;
17863
+ }
17864
+ function toNumber2(val) {
17865
+ if (typeof val === "number") return val;
17866
+ if (typeof val === "string") {
17867
+ const n = Number(val);
17868
+ return isNaN(n) ? 0 : n;
17869
+ }
17870
+ if (typeof val === "boolean") return val ? 1 : 0;
17871
+ return 0;
17872
+ }
17873
+ var BUILTINS = {
17874
+ Count: {
17875
+ name: "Count",
17876
+ signature: "Count(array) \u2192 number",
17877
+ description: "Returns array length",
17878
+ fn: (arr) => Array.isArray(arr) ? arr.length : 0
17879
+ },
17880
+ First: {
17881
+ name: "First",
17882
+ signature: "First(array) \u2192 element",
17883
+ description: "Returns first element of array",
17884
+ fn: (arr) => Array.isArray(arr) ? arr[0] ?? null : null
17885
+ },
17886
+ Last: {
17887
+ name: "Last",
17888
+ signature: "Last(array) \u2192 element",
17889
+ description: "Returns last element of array",
17890
+ fn: (arr) => Array.isArray(arr) ? arr[arr.length - 1] ?? null : null
17891
+ },
17892
+ Sum: {
17893
+ name: "Sum",
17894
+ signature: "Sum(numbers[]) \u2192 number",
17895
+ description: "Sum of numeric array",
17896
+ fn: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + toNumber2(b), 0) : 0
17897
+ },
17898
+ Avg: {
17899
+ name: "Avg",
17900
+ signature: "Avg(numbers[]) \u2192 number",
17901
+ description: "Average of numeric array",
17902
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((a, b) => a + toNumber2(b), 0) / arr.length : 0
17903
+ },
17904
+ Min: {
17905
+ name: "Min",
17906
+ signature: "Min(numbers[]) \u2192 number",
17907
+ description: "Minimum value in array",
17908
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.min(acc, toNumber2(b)), toNumber2(arr[0])) : 0
17909
+ },
17910
+ Max: {
17911
+ name: "Max",
17912
+ signature: "Max(numbers[]) \u2192 number",
17913
+ description: "Maximum value in array",
17914
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.max(acc, toNumber2(b)), toNumber2(arr[0])) : 0
17915
+ },
17916
+ Sort: {
17917
+ name: "Sort",
17918
+ signature: "Sort(array, field, direction?) \u2192 sorted array",
17919
+ description: 'Sort array by field. Direction: "asc" (default) or "desc"',
17920
+ fn: (arr, field, dir) => {
17921
+ if (!Array.isArray(arr)) return arr;
17922
+ const f = String(field ?? "");
17923
+ const desc = String(dir ?? "asc") === "desc";
17924
+ return [...arr].sort((a, b) => {
17925
+ const av = f ? resolveField(a, f) : a;
17926
+ const bv = f ? resolveField(b, f) : b;
17927
+ const aIsNumeric = typeof av === "number" || typeof av === "string" && !isNaN(Number(av)) && av !== "";
17928
+ const bIsNumeric = typeof bv === "number" || typeof bv === "string" && !isNaN(Number(bv)) && bv !== "";
17929
+ if (aIsNumeric && bIsNumeric) {
17930
+ const diff = toNumber2(av) - toNumber2(bv);
17931
+ return desc ? -diff : diff;
17932
+ }
17933
+ const cmp = String(av ?? "").localeCompare(String(bv ?? ""));
17934
+ return desc ? -cmp : cmp;
17935
+ });
17936
+ }
17937
+ },
17938
+ Filter: {
17939
+ name: "Filter",
17940
+ signature: 'Filter(array, field, operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "contains", value) \u2192 filtered array',
17941
+ description: "Filter array by field value",
17942
+ fn: (arr, field, op, value) => {
17943
+ if (!Array.isArray(arr)) return [];
17944
+ const f = String(field ?? "");
17945
+ const o = String(op ?? "==");
17946
+ return arr.filter((item) => {
17947
+ const v = f ? resolveField(item, f) : item;
17948
+ switch (o) {
17949
+ case "==":
17950
+ return v == value;
17951
+ case "!=":
17952
+ return v != value;
17953
+ case ">":
17954
+ return toNumber2(v) > toNumber2(value);
17955
+ case "<":
17956
+ return toNumber2(v) < toNumber2(value);
17957
+ case ">=":
17958
+ return toNumber2(v) >= toNumber2(value);
17959
+ case "<=":
17960
+ return toNumber2(v) <= toNumber2(value);
17961
+ case "contains":
17962
+ return String(v ?? "").includes(String(value ?? ""));
17963
+ default:
17964
+ return false;
17965
+ }
17966
+ });
17967
+ }
17968
+ },
17969
+ Round: {
17970
+ name: "Round",
17971
+ signature: "Round(number, decimals?) \u2192 number",
17972
+ description: "Round to N decimal places (default 0)",
17973
+ fn: (n, decimals) => {
17974
+ const num = toNumber2(n);
17975
+ const d = decimals != null ? toNumber2(decimals) : 0;
17976
+ const factor = Math.pow(10, d);
17977
+ return Math.round(num * factor) / factor;
17978
+ }
17979
+ },
17980
+ Abs: {
17981
+ name: "Abs",
17982
+ signature: "Abs(number) \u2192 number",
17983
+ description: "Absolute value",
17984
+ fn: (n) => Math.abs(toNumber2(n))
17985
+ },
17986
+ Floor: {
17987
+ name: "Floor",
17988
+ signature: "Floor(number) \u2192 number",
17989
+ description: "Round down to nearest integer",
17990
+ fn: (n) => Math.floor(toNumber2(n))
17991
+ },
17992
+ Ceil: {
17993
+ name: "Ceil",
17994
+ signature: "Ceil(number) \u2192 number",
17995
+ description: "Round up to nearest integer",
17996
+ fn: (n) => Math.ceil(toNumber2(n))
17997
+ }
17998
+ };
17999
+ var LAZY_BUILTINS = /* @__PURE__ */ new Set(["Each"]);
18000
+ var ACTION_STEPS = {
18001
+ Run: "run",
18002
+ ToAssistant: "continue_conversation",
18003
+ OpenUrl: "open_url",
18004
+ Set: "set",
18005
+ Reset: "reset"
18006
+ };
18007
+ var ACTION_NAMES = /* @__PURE__ */ new Set(["Action", ...Object.keys(ACTION_STEPS)]);
18008
+ var BUILTIN_NAMES = /* @__PURE__ */ new Set([
18009
+ ...Object.keys(BUILTINS),
18010
+ ...LAZY_BUILTINS,
18011
+ ...ACTION_NAMES
18012
+ ]);
18013
+ function isBuiltin(name) {
18014
+ return BUILTIN_NAMES.has(name);
18015
+ }
18016
+ var RESERVED_CALLS = { Query: "Query", Mutation: "Mutation" };
18017
+ function isReservedCall(name) {
18018
+ return name in RESERVED_CALLS;
18019
+ }
18020
+
18021
+ // src/genui/lang/parser/expressions.ts
18022
+ var PREC_TERNARY = 1;
18023
+ var PREC_OR = 2;
18024
+ var PREC_AND = 3;
18025
+ var PREC_EQ = 4;
18026
+ var PREC_CMP = 5;
18027
+ var PREC_ADD = 6;
18028
+ var PREC_MUL = 7;
18029
+ var PREC_UNARY = 8;
18030
+ var PREC_MEMBER = 9;
18031
+ function parseExpression(tokens) {
18032
+ let pos = 0;
18033
+ const cur = () => tokens[pos] ?? { t: 13 /* EOF */ };
18034
+ const adv = () => {
18035
+ const tok = cur();
18036
+ pos++;
18037
+ return tok;
18038
+ };
18039
+ const eat = (kind) => {
18040
+ if (cur().t === kind) pos++;
18041
+ };
18042
+ function getInfixPrec(tok) {
18043
+ switch (tok.t) {
18044
+ case 34 /* Question */:
18045
+ return PREC_TERNARY;
18046
+ case 32 /* Or */:
18047
+ return PREC_OR;
18048
+ case 31 /* And */:
18049
+ return PREC_AND;
18050
+ case 25 /* EqEq */:
18051
+ case 26 /* NotEq */:
18052
+ return PREC_EQ;
18053
+ case 27 /* Greater */:
18054
+ case 28 /* Less */:
18055
+ case 29 /* GreaterEq */:
18056
+ case 30 /* LessEq */:
18057
+ return PREC_CMP;
18058
+ case 20 /* Plus */:
18059
+ case 21 /* Minus */:
18060
+ return PREC_ADD;
18061
+ case 22 /* Star */:
18062
+ case 23 /* Slash */:
18063
+ case 24 /* Percent */:
18064
+ return PREC_MUL;
18065
+ case 19 /* Dot */:
18066
+ case 3 /* LBrack */:
18067
+ return PREC_MEMBER;
18068
+ default:
18069
+ return 0;
18070
+ }
18071
+ }
18072
+ function parseExpr(minPrec = 0) {
18073
+ let left = parsePrefix();
18074
+ while (getInfixPrec(cur()) > minPrec) {
18075
+ left = parseInfix(left);
18076
+ }
18077
+ return left;
18078
+ }
18079
+ function parsePrefix() {
18080
+ const tok = cur();
18081
+ if (tok.t === 14 /* Str */) {
18082
+ adv();
18083
+ return { k: "Str", v: tok.v };
18084
+ }
18085
+ if (tok.t === 15 /* Num */) {
18086
+ adv();
18087
+ return { k: "Num", v: tok.v };
18088
+ }
18089
+ if (tok.t === 10 /* True */) {
18090
+ adv();
18091
+ return { k: "Bool", v: true };
18092
+ }
18093
+ if (tok.t === 11 /* False */) {
18094
+ adv();
18095
+ return { k: "Bool", v: false };
18096
+ }
18097
+ if (tok.t === 12 /* Null */) {
18098
+ adv();
18099
+ return { k: "Null" };
18100
+ }
18101
+ if (tok.t === 3 /* LBrack */) return parseArr();
18102
+ if (tok.t === 5 /* LBrace */) return parseObj();
18103
+ if (tok.t === 18 /* StateVar */) {
18104
+ const name = tok.v;
18105
+ adv();
18106
+ if (cur().t === 9 /* Equals */) {
18107
+ adv();
18108
+ const value = parseExpr(0);
18109
+ return { k: "Assign", target: name, value };
18110
+ }
18111
+ return { k: "StateRef", n: name };
18112
+ }
18113
+ if (tok.t === 17 /* Type */) {
18114
+ const name = tok.v;
18115
+ if (tokens[pos + 1]?.t === 1 /* LParen */ && (!isBuiltin(name) || name === "Action"))
18116
+ return parseComp();
18117
+ adv();
18118
+ return { k: "Ref", n: name };
18119
+ }
18120
+ if (tok.t === 35 /* BuiltinCall */) {
18121
+ if (tokens[pos + 1]?.t === 1 /* LParen */) return parseComp();
18122
+ adv();
18123
+ return { k: "Ref", n: tok.v };
18124
+ }
18125
+ if (tok.t === 16 /* Ident */) {
18126
+ adv();
18127
+ return { k: "Ref", n: tok.v };
18128
+ }
18129
+ if (tok.t === 33 /* Not */) {
18130
+ adv();
18131
+ return { k: "UnaryOp", op: "!", operand: parseExpr(PREC_UNARY) };
18132
+ }
18133
+ if (tok.t === 21 /* Minus */) {
18134
+ adv();
18135
+ return { k: "UnaryOp", op: "-", operand: parseExpr(PREC_UNARY) };
18136
+ }
18137
+ if (tok.t === 1 /* LParen */) {
18138
+ adv();
18139
+ const inner = parseExpr(0);
18140
+ eat(2 /* RParen */);
18141
+ return inner;
18142
+ }
18143
+ adv();
18144
+ return { k: "Null" };
18145
+ }
18146
+ function parseInfix(left) {
18147
+ const tok = cur();
18148
+ if (tok.t === 20 /* Plus */) {
18149
+ adv();
18150
+ return { k: "BinOp", op: "+", left, right: parseExpr(PREC_ADD) };
18151
+ }
18152
+ if (tok.t === 21 /* Minus */) {
18153
+ adv();
18154
+ return { k: "BinOp", op: "-", left, right: parseExpr(PREC_ADD) };
18155
+ }
18156
+ if (tok.t === 22 /* Star */) {
18157
+ adv();
18158
+ return { k: "BinOp", op: "*", left, right: parseExpr(PREC_MUL) };
18159
+ }
18160
+ if (tok.t === 23 /* Slash */) {
18161
+ adv();
18162
+ return { k: "BinOp", op: "/", left, right: parseExpr(PREC_MUL) };
18163
+ }
18164
+ if (tok.t === 24 /* Percent */) {
18165
+ adv();
18166
+ return { k: "BinOp", op: "%", left, right: parseExpr(PREC_MUL) };
18167
+ }
18168
+ if (tok.t === 25 /* EqEq */) {
18169
+ adv();
18170
+ return { k: "BinOp", op: "==", left, right: parseExpr(PREC_EQ) };
18171
+ }
18172
+ if (tok.t === 26 /* NotEq */) {
18173
+ adv();
18174
+ return { k: "BinOp", op: "!=", left, right: parseExpr(PREC_EQ) };
18175
+ }
18176
+ if (tok.t === 27 /* Greater */) {
18177
+ adv();
18178
+ return { k: "BinOp", op: ">", left, right: parseExpr(PREC_CMP) };
18179
+ }
18180
+ if (tok.t === 28 /* Less */) {
18181
+ adv();
18182
+ return { k: "BinOp", op: "<", left, right: parseExpr(PREC_CMP) };
18183
+ }
18184
+ if (tok.t === 29 /* GreaterEq */) {
18185
+ adv();
18186
+ return { k: "BinOp", op: ">=", left, right: parseExpr(PREC_CMP) };
18187
+ }
18188
+ if (tok.t === 30 /* LessEq */) {
18189
+ adv();
18190
+ return { k: "BinOp", op: "<=", left, right: parseExpr(PREC_CMP) };
18191
+ }
18192
+ if (tok.t === 31 /* And */) {
18193
+ adv();
18194
+ return { k: "BinOp", op: "&&", left, right: parseExpr(PREC_AND) };
18195
+ }
18196
+ if (tok.t === 32 /* Or */) {
18197
+ adv();
18198
+ return { k: "BinOp", op: "||", left, right: parseExpr(PREC_OR) };
18199
+ }
18200
+ if (tok.t === 34 /* Question */) {
18201
+ adv();
18202
+ const then = parseExpr(0);
18203
+ eat(8 /* Colon */);
18204
+ const els = parseExpr(0);
18205
+ return { k: "Ternary", cond: left, then, else: els };
18206
+ }
18207
+ if (tok.t === 19 /* Dot */) {
18208
+ adv();
18209
+ const fieldTok = cur();
18210
+ const field = fieldTok.t === 16 /* Ident */ || fieldTok.t === 17 /* Type */ || fieldTok.t === 14 /* Str */ || fieldTok.t === 15 /* Num */ ? (adv(), String(fieldTok.v)) : fieldTok.t === 18 /* StateVar */ ? (adv(), fieldTok.v.replace(/^\$/, "")) : (adv(), "?");
18211
+ return { k: "Member", obj: left, field };
18212
+ }
18213
+ if (tok.t === 3 /* LBrack */) {
18214
+ adv();
18215
+ const index = parseExpr(0);
18216
+ eat(4 /* RBrack */);
18217
+ return { k: "Index", obj: left, index };
18218
+ }
18219
+ return left;
18220
+ }
18221
+ function parseComp() {
18222
+ const name = cur().v;
18223
+ adv();
18224
+ eat(1 /* LParen */);
18225
+ const args = [];
18226
+ while (cur().t !== 2 /* RParen */ && cur().t !== 13 /* EOF */) {
18227
+ args.push(parseExpr(0));
18228
+ if (cur().t === 7 /* Comma */) adv();
18229
+ }
18230
+ eat(2 /* RParen */);
18231
+ return { k: "Comp", name, args };
18232
+ }
18233
+ function parseArr() {
18234
+ adv();
18235
+ const els = [];
18236
+ while (cur().t !== 4 /* RBrack */ && cur().t !== 13 /* EOF */) {
18237
+ els.push(parseExpr(0));
18238
+ if (cur().t === 7 /* Comma */) adv();
18239
+ }
18240
+ eat(4 /* RBrack */);
18241
+ return { k: "Arr", els };
18242
+ }
18243
+ function parseObj() {
18244
+ adv();
18245
+ const entries = [];
18246
+ while (cur().t !== 6 /* RBrace */ && cur().t !== 13 /* EOF */) {
18247
+ const kt = cur();
18248
+ const key = kt.t === 16 /* Ident */ || kt.t === 14 /* Str */ || kt.t === 17 /* Type */ || kt.t === 15 /* Num */ ? (adv(), String(kt.v)) : kt.t === 18 /* StateVar */ ? (adv(), kt.v.replace(/^\$/, "")) : (adv(), "?");
18249
+ eat(8 /* Colon */);
18250
+ entries.push([key, parseExpr(0)]);
18251
+ if (cur().t === 7 /* Comma */) adv();
18252
+ }
18253
+ eat(6 /* RBrace */);
18254
+ return { k: "Obj", entries };
18255
+ }
18256
+ return parseExpr(0);
18257
+ }
18258
+
18259
+ // src/genui/lang/parser/lexer.ts
18260
+ function tokenize(src) {
18261
+ const tokens = [];
18262
+ let i = 0;
18263
+ const n = src.length;
18264
+ while (i < n) {
18265
+ while (i < n && (src[i] === " " || src[i] === " " || src[i] === "\r")) i++;
18266
+ if (i >= n) break;
18267
+ const c = src[i];
18268
+ if (c === "\n") {
18269
+ tokens.push({ t: 0 /* Newline */ });
18270
+ i++;
18271
+ continue;
18272
+ }
18273
+ if (c === "(") {
18274
+ tokens.push({ t: 1 /* LParen */ });
18275
+ i++;
18276
+ continue;
18277
+ }
18278
+ if (c === ")") {
18279
+ tokens.push({ t: 2 /* RParen */ });
18280
+ i++;
18281
+ continue;
18282
+ }
18283
+ if (c === "[") {
18284
+ tokens.push({ t: 3 /* LBrack */ });
18285
+ i++;
18286
+ continue;
18287
+ }
18288
+ if (c === "]") {
18289
+ tokens.push({ t: 4 /* RBrack */ });
18290
+ i++;
18291
+ continue;
18292
+ }
18293
+ if (c === "{") {
18294
+ tokens.push({ t: 5 /* LBrace */ });
18295
+ i++;
18296
+ continue;
18297
+ }
18298
+ if (c === "}") {
18299
+ tokens.push({ t: 6 /* RBrace */ });
18300
+ i++;
18301
+ continue;
18302
+ }
18303
+ if (c === ",") {
18304
+ tokens.push({ t: 7 /* Comma */ });
18305
+ i++;
18306
+ continue;
18307
+ }
18308
+ if (c === ":") {
18309
+ tokens.push({ t: 8 /* Colon */ });
18310
+ i++;
18311
+ continue;
18312
+ }
18313
+ if (c === "=") {
18314
+ if (i + 1 < n && src[i + 1] === "=") {
18315
+ tokens.push({ t: 25 /* EqEq */ });
18316
+ i += 2;
18317
+ } else {
18318
+ tokens.push({ t: 9 /* Equals */ });
18319
+ i++;
18320
+ }
18321
+ continue;
18322
+ }
18323
+ if (c === "!") {
18324
+ if (i + 1 < n && src[i + 1] === "=") {
18325
+ tokens.push({ t: 26 /* NotEq */ });
18326
+ i += 2;
18327
+ } else {
18328
+ tokens.push({ t: 33 /* Not */ });
18329
+ i++;
18330
+ }
18331
+ continue;
18332
+ }
18333
+ if (c === ">") {
18334
+ if (i + 1 < n && src[i + 1] === "=") {
18335
+ tokens.push({ t: 29 /* GreaterEq */ });
18336
+ i += 2;
18337
+ } else {
18338
+ tokens.push({ t: 27 /* Greater */ });
18339
+ i++;
18340
+ }
18341
+ continue;
18342
+ }
18343
+ if (c === "<") {
18344
+ if (i + 1 < n && src[i + 1] === "=") {
18345
+ tokens.push({ t: 30 /* LessEq */ });
18346
+ i += 2;
18347
+ } else {
18348
+ tokens.push({ t: 28 /* Less */ });
18349
+ i++;
18350
+ }
18351
+ continue;
18352
+ }
18353
+ if (c === "&") {
18354
+ if (i + 1 < n && src[i + 1] === "&") {
18355
+ tokens.push({ t: 31 /* And */ });
18356
+ i += 2;
18357
+ } else {
18358
+ tokens.push({ t: 31 /* And */ });
18359
+ i++;
18360
+ }
18361
+ continue;
18362
+ }
18363
+ if (c === "|") {
18364
+ if (i + 1 < n && src[i + 1] === "|") {
18365
+ tokens.push({ t: 32 /* Or */ });
18366
+ i += 2;
18367
+ } else {
18368
+ tokens.push({ t: 32 /* Or */ });
18369
+ i++;
18370
+ }
18371
+ continue;
18372
+ }
18373
+ if (c === ".") {
18374
+ tokens.push({ t: 19 /* Dot */ });
18375
+ i++;
18376
+ continue;
18377
+ }
18378
+ if (c === "?") {
18379
+ tokens.push({ t: 34 /* Question */ });
18380
+ i++;
18381
+ continue;
18382
+ }
18383
+ if (c === "+") {
18384
+ tokens.push({ t: 20 /* Plus */ });
18385
+ i++;
18386
+ continue;
18387
+ }
18388
+ if (c === "*") {
18389
+ tokens.push({ t: 22 /* Star */ });
18390
+ i++;
18391
+ continue;
18392
+ }
18393
+ if (c === "/") {
18394
+ tokens.push({ t: 23 /* Slash */ });
18395
+ i++;
18396
+ continue;
18397
+ }
18398
+ if (c === "%") {
18399
+ tokens.push({ t: 24 /* Percent */ });
18400
+ i++;
18401
+ continue;
18402
+ }
18403
+ if (c === '"') {
18404
+ const start = i;
18405
+ i++;
18406
+ let isClosed = false;
18407
+ while (i < n) {
18408
+ if (src[i] === "\\") {
18409
+ i += 2;
18410
+ } else if (src[i] === '"') {
18411
+ i++;
18412
+ isClosed = true;
18413
+ break;
18414
+ } else {
18415
+ i++;
18416
+ }
18417
+ }
18418
+ const rawString = src.slice(start, i);
18419
+ try {
18420
+ const validJsonString = isClosed ? rawString : rawString + '"';
18421
+ tokens.push({ t: 14 /* Str */, v: JSON.parse(validJsonString) });
18422
+ } catch {
18423
+ const stripped = rawString.replace(/^"|"$/g, "");
18424
+ tokens.push({ t: 14 /* Str */, v: stripped });
18425
+ }
18426
+ continue;
18427
+ }
18428
+ if (c === "'") {
18429
+ i++;
18430
+ let result = "";
18431
+ while (i < n) {
18432
+ if (src[i] === "\\") {
18433
+ i++;
18434
+ if (i < n) {
18435
+ const esc = src[i];
18436
+ if (esc === "'") result += "'";
18437
+ else if (esc === "\\") result += "\\";
18438
+ else if (esc === "n") result += "\n";
18439
+ else if (esc === "t") result += " ";
18440
+ else result += esc;
18441
+ i++;
18442
+ }
18443
+ } else if (src[i] === "'") {
18444
+ i++;
18445
+ break;
18446
+ } else {
18447
+ result += src[i];
18448
+ i++;
18449
+ }
18450
+ }
18451
+ tokens.push({ t: 14 /* Str */, v: result });
18452
+ continue;
18453
+ }
18454
+ if (c === "-") {
18455
+ const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
18456
+ const afterValue = prev != null && (prev.t === 15 /* Num */ || prev.t === 14 /* Str */ || prev.t === 16 /* Ident */ || prev.t === 17 /* Type */ || prev.t === 2 /* RParen */ || prev.t === 4 /* RBrack */ || prev.t === 10 /* True */ || prev.t === 11 /* False */ || prev.t === 12 /* Null */ || prev.t === 18 /* StateVar */ || prev.t === 35 /* BuiltinCall */);
18457
+ if (!afterValue && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") ; else {
18458
+ tokens.push({ t: 21 /* Minus */ });
18459
+ i++;
18460
+ continue;
18461
+ }
18462
+ }
18463
+ const isDigit = c >= "0" && c <= "9";
18464
+ const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
18465
+ if (isDigit || isNegDigit) {
18466
+ const start = i;
18467
+ if (src[i] === "-") i++;
18468
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18469
+ if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
18470
+ i++;
18471
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18472
+ }
18473
+ if (i < n && (src[i] === "e" || src[i] === "E")) {
18474
+ i++;
18475
+ if (i < n && (src[i] === "+" || src[i] === "-")) i++;
18476
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
18477
+ }
18478
+ tokens.push({ t: 15 /* Num */, v: +src.slice(start, i) });
18479
+ continue;
18480
+ }
18481
+ if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18482
+ const start = i;
18483
+ i++;
18484
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18485
+ i++;
18486
+ tokens.push({ t: 18 /* StateVar */, v: src.slice(start, i) });
18487
+ continue;
18488
+ }
18489
+ const isAlpha = c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
18490
+ if (isAlpha) {
18491
+ const start = i;
18492
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18493
+ i++;
18494
+ const word = src.slice(start, i);
18495
+ if (word === "true") {
18496
+ tokens.push({ t: 10 /* True */ });
18497
+ continue;
18498
+ }
18499
+ if (word === "false") {
18500
+ tokens.push({ t: 11 /* False */ });
18501
+ continue;
18502
+ }
18503
+ if (word === "null") {
18504
+ tokens.push({ t: 12 /* Null */ });
18505
+ continue;
18506
+ }
18507
+ const kind = c >= "A" && c <= "Z" ? 17 /* Type */ : 16 /* Ident */;
18508
+ tokens.push({ t: kind, v: word });
18509
+ continue;
18510
+ }
18511
+ if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
18512
+ i++;
18513
+ const start = i;
18514
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_"))
18515
+ i++;
18516
+ tokens.push({ t: 35 /* BuiltinCall */, v: src.slice(start, i) });
18517
+ continue;
18518
+ }
18519
+ i++;
18520
+ }
18521
+ tokens.push({ t: 13 /* EOF */ });
18522
+ return tokens;
18523
+ }
18524
+
18525
+ // src/genui/lang/parser/types.ts
18526
+ function isElementNode(value) {
18527
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
18528
+ const node = value;
18529
+ return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
18530
+ }
18531
+
18532
+ // src/genui/lang/parser/materialize.ts
18533
+ function containsDynamicValue(v) {
18534
+ if (v == null || typeof v !== "object") return false;
18535
+ if (isASTNode(v)) return true;
18536
+ if (Array.isArray(v)) return v.some(containsDynamicValue);
18537
+ if (isElementNode(v)) {
18538
+ return Object.values(v.props).some(containsDynamicValue);
18539
+ }
18540
+ const obj = v;
18541
+ return Object.values(obj).some(containsDynamicValue);
18542
+ }
18543
+ function resolveRef(name, ctx, mode) {
18544
+ if (ctx.visited.has(name)) {
18545
+ ctx.unres.push(name);
18546
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18547
+ }
18548
+ if (!ctx.syms.has(name)) {
18549
+ ctx.unres.push(name);
18550
+ return mode === "expr" ? { k: "Ph", n: name } : null;
18551
+ }
18552
+ const target = ctx.syms.get(name);
18553
+ ctx.unreached?.delete(name);
18554
+ if (target.k === "Comp" && isReservedCall(target.name)) {
18555
+ const refType = target.name === RESERVED_CALLS.Mutation ? "mutation" : "query";
18556
+ return { k: "RuntimeRef", n: name, refType };
18557
+ }
18558
+ ctx.visited.add(name);
18559
+ const prevStatementId = ctx.currentStatementId;
18560
+ ctx.currentStatementId = name;
18561
+ try {
18562
+ const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
18563
+ if (mode === "value" && isElementNode(result)) {
18564
+ result.statementId = name;
18565
+ }
18566
+ return result;
18567
+ } finally {
18568
+ ctx.currentStatementId = prevStatementId;
18569
+ ctx.visited.delete(name);
18570
+ }
18571
+ }
18572
+ function materializeLazyBuiltin(node, ctx, scopedRefs) {
18573
+ if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
18574
+ const varArg = node.args[1];
18575
+ const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
18576
+ if (!varName) return null;
18577
+ const nextScopedRefs = new Set(scopedRefs);
18578
+ nextScopedRefs.add(varName);
18579
+ const recursedArgs = node.args.map(
18580
+ (a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs)
18581
+ );
18582
+ return { ...node, args: recursedArgs };
18583
+ }
18584
+ function materializeExprInternal(node, ctx, scopedRefs) {
18585
+ switch (node.k) {
18586
+ case "Ref":
18587
+ return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
18588
+ case "Ph":
18589
+ return node;
18590
+ case "Comp": {
18591
+ const lazy2 = materializeLazyBuiltin(node, ctx, scopedRefs);
18592
+ if (lazy2) return lazy2;
18593
+ const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
18594
+ if (isBuiltin(node.name) || isReservedCall(node.name)) {
18595
+ return { ...node, args: recursedArgs };
18596
+ }
18597
+ const def = ctx.cat?.get(node.name);
18598
+ if (def) {
18599
+ const mappedProps = {};
18600
+ for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) {
18601
+ mappedProps[def.params[i].name] = recursedArgs[i];
18602
+ }
18603
+ return { ...node, args: recursedArgs, mappedProps };
18604
+ }
18605
+ ctx.errors.push({
18606
+ code: "unknown-component",
18607
+ component: node.name,
18608
+ path: "",
18609
+ message: `Unknown component "${node.name}" \u2014 not found in catalog or builtins`,
18610
+ statementId: ctx.currentStatementId
18611
+ });
18612
+ return { ...node, args: recursedArgs };
18613
+ }
18614
+ case "Arr":
18615
+ return { ...node, els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs)) };
18616
+ case "Obj":
18617
+ return {
18618
+ ...node,
18619
+ entries: node.entries.map(
18620
+ ([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)]
18621
+ )
18622
+ };
18623
+ case "BinOp":
18624
+ return {
18625
+ ...node,
18626
+ left: materializeExprInternal(node.left, ctx, scopedRefs),
18627
+ right: materializeExprInternal(node.right, ctx, scopedRefs)
18628
+ };
18629
+ case "UnaryOp":
18630
+ return { ...node, operand: materializeExprInternal(node.operand, ctx, scopedRefs) };
18631
+ case "Ternary":
18632
+ return {
18633
+ ...node,
18634
+ cond: materializeExprInternal(node.cond, ctx, scopedRefs),
18635
+ then: materializeExprInternal(node.then, ctx, scopedRefs),
18636
+ else: materializeExprInternal(node.else, ctx, scopedRefs)
18637
+ };
18638
+ case "Member":
18639
+ return { ...node, obj: materializeExprInternal(node.obj, ctx, scopedRefs) };
18640
+ case "Index":
18641
+ return {
18642
+ ...node,
18643
+ obj: materializeExprInternal(node.obj, ctx, scopedRefs),
18644
+ index: materializeExprInternal(node.index, ctx, scopedRefs)
18645
+ };
18646
+ case "Assign":
18647
+ return { ...node, value: materializeExprInternal(node.value, ctx, scopedRefs) };
18648
+ // Literals, StateRef, RuntimeRef — pass through unchanged
18649
+ default:
18650
+ return node;
18651
+ }
18652
+ }
18653
+ function materializeExpr(node, ctx) {
18654
+ return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
18655
+ }
18656
+ function materializeValue(node, ctx) {
18657
+ switch (node.k) {
18658
+ // ── Ref resolution ───────────────────────────────────────────────────
18659
+ case "Ref":
18660
+ return resolveRef(node.n, ctx, "value");
18661
+ // ── Literals → plain values ──────────────────────────────────────────
18662
+ case "Str":
18663
+ return node.v;
18664
+ case "Num":
18665
+ return node.v;
18666
+ case "Bool":
18667
+ return node.v;
18668
+ case "Null":
18669
+ return null;
18670
+ case "Ph":
18671
+ return null;
18672
+ // ── Collections ──────────────────────────────────────────────────────
18673
+ case "Arr": {
18674
+ const items = [];
18675
+ for (const e of node.els) {
18676
+ if (e.k === "Ph") continue;
18677
+ const value = materializeValue(e, ctx);
18678
+ if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
18679
+ items.push(value);
18680
+ }
18681
+ return items;
18682
+ }
18683
+ case "Obj": {
18684
+ const o = {};
18685
+ for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
18686
+ return o;
18687
+ }
18688
+ // ── Component nodes ──────────────────────────────────────────────────
18689
+ case "Comp": {
18690
+ const { name, args } = node;
18691
+ if (isBuiltin(name)) {
18692
+ const lazy2 = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
18693
+ if (lazy2) return lazy2;
18694
+ return { ...node, args: args.map((a) => materializeExpr(a, ctx)) };
18695
+ }
18696
+ if (isReservedCall(name)) {
18697
+ ctx.errors.push({
18698
+ code: "inline-reserved",
18699
+ component: name,
18700
+ path: "",
18701
+ message: `${name}() must be declared as a top-level statement, not used inline as a value`,
18702
+ statementId: ctx.currentStatementId
18703
+ });
18704
+ return null;
18705
+ }
18706
+ const def = ctx.cat?.get(name);
18707
+ const props = {};
18708
+ if (def) {
18709
+ for (let i = 0; i < def.params.length && i < args.length; i++) {
18710
+ props[def.params[i].name] = materializeValue(args[i], ctx);
18711
+ }
18712
+ if (args.length > def.params.length) {
18713
+ const excessCount = args.length - def.params.length;
18714
+ ctx.errors.push({
18715
+ code: "excess-args",
18716
+ component: name,
18717
+ path: "",
18718
+ message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
18719
+ statementId: ctx.currentStatementId
18720
+ });
18721
+ }
18722
+ const missingRequired = def.params.filter(
18723
+ (p) => p.required && (!(p.name in props) || props[p.name] === null)
18724
+ );
18725
+ if (missingRequired.length) {
18726
+ const stillInvalid = missingRequired.filter((p) => {
18727
+ if (p.defaultValue !== void 0) {
18728
+ props[p.name] = p.defaultValue;
18729
+ return false;
18730
+ }
18731
+ return true;
18732
+ });
18733
+ if (stillInvalid.length) {
18734
+ for (const p of stillInvalid) {
18735
+ const isNull = p.name in props;
18736
+ ctx.errors.push({
18737
+ code: isNull ? "null-required" : "missing-required",
18738
+ component: name,
18739
+ path: `/${p.name}`,
18740
+ message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
18741
+ statementId: ctx.currentStatementId
18742
+ });
18743
+ }
18744
+ return null;
18745
+ }
18746
+ }
18747
+ } else if (!isBuiltin(name) && !isReservedCall(name)) {
18748
+ ctx.errors.push({
18749
+ code: "unknown-component",
18750
+ component: name,
18751
+ path: "",
18752
+ message: `Unknown component "${name}" \u2014 not found in catalog or builtins`,
18753
+ statementId: ctx.currentStatementId
18754
+ });
18755
+ return null;
18756
+ }
18757
+ const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
18758
+ return { type: "element", typeName: name, props, partial: ctx.partial, hasDynamicProps };
18759
+ }
18760
+ // ── Runtime expression nodes → preserve as ASTNode, normalize children ─
18761
+ default: {
18762
+ if (isRuntimeExpr(node)) {
18763
+ return materializeExpr(node, ctx);
18764
+ }
18765
+ return node;
18766
+ }
18767
+ }
18768
+ }
18769
+
18770
+ // src/genui/lang/parser/statements.ts
18771
+ function autoClose(input) {
18772
+ const stack = [];
18773
+ let inStr = false;
18774
+ let esc = false;
18775
+ for (let i = 0; i < input.length; i++) {
18776
+ const c = input[i];
18777
+ if (esc) {
18778
+ esc = false;
18779
+ continue;
18780
+ }
18781
+ if (c === "\\" && inStr) {
18782
+ esc = true;
18783
+ continue;
18784
+ }
18785
+ if (inStr) {
18786
+ if (c === inStr) inStr = false;
18787
+ continue;
18788
+ }
18789
+ if (c === '"' || c === "'") {
18790
+ inStr = c;
18791
+ continue;
18792
+ }
18793
+ if (c === "(" || c === "[" || c === "{") stack.push(c);
18794
+ else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
18795
+ else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
18796
+ else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
18797
+ }
18798
+ const wasIncomplete = !!inStr || stack.length > 0;
18799
+ if (!wasIncomplete) return { text: input, wasIncomplete: false };
18800
+ let out = input;
18801
+ if (inStr) {
18802
+ if (esc) out += "\\";
18803
+ out += inStr;
18804
+ }
18805
+ for (let j = stack.length - 1; j >= 0; j--)
18806
+ out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
18807
+ return { text: out, wasIncomplete: true };
18808
+ }
18809
+ function split(tokens) {
18810
+ const stmts = [];
18811
+ let pos = 0;
18812
+ while (pos < tokens.length) {
18813
+ while (pos < tokens.length && tokens[pos].t === 0 /* Newline */) pos++;
18814
+ if (pos >= tokens.length || tokens[pos].t === 13 /* EOF */) break;
18815
+ const tok = tokens[pos];
18816
+ if (tok.t !== 16 /* Ident */ && tok.t !== 17 /* Type */ && tok.t !== 18 /* StateVar */) {
18817
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
18818
+ continue;
18819
+ }
18820
+ const id = tok.v;
18821
+ const idTokenType = tok.t;
18822
+ pos++;
18823
+ if (pos >= tokens.length || tokens[pos].t !== 9 /* Equals */) {
18824
+ while (pos < tokens.length && tokens[pos].t !== 0 /* Newline */ && tokens[pos].t !== 13 /* EOF */) pos++;
18825
+ continue;
18826
+ }
18827
+ pos++;
18828
+ const expr = [];
18829
+ let depth = 0;
18830
+ let ternaryDepth = 0;
18831
+ while (pos < tokens.length && tokens[pos].t !== 13 /* EOF */) {
18832
+ const tt = tokens[pos].t;
18833
+ if (tt === 0 /* Newline */ && depth <= 0 && ternaryDepth <= 0) {
18834
+ let peek = pos + 1;
18835
+ while (peek < tokens.length && tokens[peek].t === 0 /* Newline */) peek++;
18836
+ const nextT = peek < tokens.length ? tokens[peek].t : 13 /* EOF */;
18837
+ if (nextT === 34 /* Question */ || nextT === 8 /* Colon */ && ternaryDepth > 0) {
18838
+ pos++;
18839
+ continue;
18840
+ }
18841
+ break;
18842
+ }
18843
+ if (tt === 0 /* Newline */) {
18844
+ pos++;
18845
+ continue;
18846
+ }
18847
+ if (tt === 1 /* LParen */ || tt === 3 /* LBrack */ || tt === 5 /* LBrace */) depth++;
18848
+ else if ((tt === 2 /* RParen */ || tt === 4 /* RBrack */ || tt === 6 /* RBrace */) && depth > 0) depth--;
18849
+ else if (tt === 34 /* Question */ && depth === 0) ternaryDepth++;
18850
+ else if (tt === 8 /* Colon */ && depth === 0 && ternaryDepth > 0) ternaryDepth--;
18851
+ expr.push(tokens[pos++]);
18852
+ }
18853
+ if (expr.length) stmts.push({ id, idTokenType, tokens: expr });
18854
+ }
18855
+ return stmts;
18856
+ }
18857
+
18858
+ // src/genui/lang/parser/parser.ts
18859
+ function emptyResult(incomplete = true) {
18860
+ return {
18861
+ root: null,
18862
+ meta: {
18863
+ incomplete,
18864
+ unresolved: [],
18865
+ orphaned: [],
18866
+ statementCount: 0,
18867
+ errors: []
18868
+ },
18869
+ stateDeclarations: {},
18870
+ queryStatements: [],
18871
+ mutationStatements: []
18872
+ };
18873
+ }
18874
+ function collectQueryDeps(node) {
18875
+ if (!isASTNode(node)) return [];
18876
+ const refs = /* @__PURE__ */ new Set();
18877
+ walkAST(node, (current) => {
18878
+ if (current.k === "StateRef") refs.add(current.n);
18879
+ });
18880
+ return [...refs];
18881
+ }
18882
+ function classifyStatement(raw, expr) {
18883
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
18884
+ const deps = collectQueryDeps(expr.args[1]);
18885
+ return {
18886
+ kind: "query",
18887
+ id: raw.id,
18888
+ call: { callee: RESERVED_CALLS.Query, args: expr.args },
18889
+ expr,
18890
+ deps: deps.length > 0 ? deps : void 0
18891
+ };
18892
+ }
18893
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) {
18894
+ return {
18895
+ kind: "mutation",
18896
+ id: raw.id,
18897
+ call: { callee: RESERVED_CALLS.Mutation, args: expr.args },
18898
+ expr
18899
+ };
18900
+ }
18901
+ if (raw.idTokenType === 18 /* StateVar */) {
18902
+ return { kind: "state", id: raw.id, init: expr };
18903
+ }
18904
+ return { kind: "value", id: raw.id, expr };
18905
+ }
18906
+ function extractStatements(stmts, ctx) {
18907
+ const stateDeclarations = {};
18908
+ const queryStatements = [];
18909
+ const mutationStatements = [];
18910
+ for (const stmt of stmts) {
18911
+ switch (stmt.kind) {
18912
+ case "state":
18913
+ stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
18914
+ break;
18915
+ case "query":
18916
+ queryStatements.push({
18917
+ statementId: stmt.id,
18918
+ toolAST: stmt.call.args[0] ?? null,
18919
+ argsAST: stmt.call.args[1] ?? null,
18920
+ defaultsAST: stmt.call.args[2] ?? null,
18921
+ refreshAST: stmt.call.args[3] ?? null,
18922
+ deps: stmt.deps,
18923
+ complete: true
18924
+ });
18925
+ break;
18926
+ case "mutation":
18927
+ mutationStatements.push({
18928
+ statementId: stmt.id,
18929
+ toolAST: stmt.call.args[0] ?? null,
18930
+ argsAST: stmt.call.args[1] ?? null
18931
+ });
18932
+ break;
18933
+ }
18934
+ }
18935
+ for (const stmt of stmts) {
18936
+ const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
18937
+ for (const node of nodes) {
18938
+ for (const dep of collectQueryDeps(node)) {
18939
+ if (!(dep in stateDeclarations)) {
18940
+ stateDeclarations[dep] = null;
18941
+ }
18942
+ }
18943
+ }
18944
+ }
18945
+ return { stateDeclarations, queryStatements, mutationStatements };
18946
+ }
18947
+ var DEFAULT_ROOT_STATEMENT_ID = "root";
18948
+ function isComponentStatement(stmt) {
18949
+ return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
18950
+ }
18951
+ function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
18952
+ if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
18953
+ if (rootName && stmtMap.has(rootName)) return rootName;
18954
+ const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
18955
+ if (preferredComponent) return preferredComponent.id;
18956
+ const firstComponent = typedStmts.find(isComponentStatement);
18957
+ return firstComponent?.id ?? firstId;
18958
+ }
18959
+ function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
18960
+ const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
18961
+ if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
18962
+ const syms = /* @__PURE__ */ new Map();
18963
+ for (const [id, stmt] of stmtMap) {
18964
+ syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
18965
+ }
18966
+ const unres = [];
18967
+ const errors = [];
18968
+ const unreached = /* @__PURE__ */ new Set();
18969
+ for (const [id, stmt] of stmtMap) {
18970
+ if (id === entryId) continue;
18971
+ if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
18972
+ unreached.add(id);
18973
+ }
18974
+ const ctx = {
18975
+ syms,
18976
+ cat,
18977
+ errors,
18978
+ unres,
18979
+ visited: /* @__PURE__ */ new Set(),
18980
+ partial: wasIncomplete,
18981
+ currentStatementId: entryId,
18982
+ unreached
18983
+ };
18984
+ const materialized = materializeValue(syms.get(entryId), ctx);
18985
+ const root = isElementNode(materialized) ? materialized : null;
18986
+ if (root) root.statementId = entryId;
18987
+ const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(
18988
+ typedStmts,
18989
+ ctx
18990
+ );
18991
+ const orphaned = [...unreached];
18992
+ return {
18993
+ root,
18994
+ meta: {
18995
+ incomplete: wasIncomplete,
18996
+ unresolved: unres,
18997
+ orphaned,
18998
+ statementCount: stmtCount,
18999
+ errors
19000
+ },
19001
+ stateDeclarations,
19002
+ queryStatements,
19003
+ mutationStatements
19004
+ };
19005
+ }
19006
+ function skipString(input, start) {
19007
+ if (input[start] !== '"') return start;
19008
+ let i = start + 1;
19009
+ while (i < input.length) {
19010
+ const c = input[i];
19011
+ if (c === "\\") {
19012
+ i += 2;
19013
+ } else if (c === '"') {
19014
+ return i + 1;
19015
+ } else {
19016
+ i++;
19017
+ }
19018
+ }
19019
+ return i;
19020
+ }
19021
+ function stripFences(input) {
19022
+ const blocks = [];
19023
+ let i = 0;
19024
+ while (i < input.length) {
19025
+ let fenceStart = -1;
19026
+ while (i < input.length) {
19027
+ const nextI = skipString(input, i);
19028
+ if (nextI > i) {
19029
+ i = nextI;
19030
+ continue;
19031
+ }
19032
+ const c = input[i];
19033
+ if (c === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
19034
+ fenceStart = i;
19035
+ break;
19036
+ }
19037
+ i++;
19038
+ }
19039
+ if (fenceStart === -1) break;
19040
+ let j = fenceStart + 3;
19041
+ while (j < input.length && input[j] !== "\n") j++;
19042
+ if (j >= input.length) {
19043
+ blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
19044
+ i = input.length;
19045
+ break;
19046
+ }
19047
+ j++;
19048
+ let closePos = -1;
19049
+ let k = j;
19050
+ while (k < input.length) {
19051
+ const nextK = skipString(input, k);
19052
+ if (nextK > k) {
19053
+ k = nextK;
19054
+ continue;
19055
+ }
19056
+ const c = input[k];
19057
+ if (c === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
19058
+ closePos = k;
19059
+ break;
19060
+ }
19061
+ k++;
19062
+ }
19063
+ if (closePos !== -1) {
19064
+ blocks.push(input.slice(j, closePos));
19065
+ i = closePos + 3;
19066
+ } else {
19067
+ blocks.push(input.slice(j));
19068
+ i = input.length;
19069
+ }
19070
+ }
19071
+ if (blocks.length > 0) return blocks.join("\n");
19072
+ if (input.startsWith("```")) {
19073
+ let j = 3;
19074
+ while (j < input.length && input[j] !== "\n") j++;
19075
+ const start = j < input.length ? j + 1 : 3;
19076
+ const body = input.slice(start);
19077
+ const trailingFence = body.lastIndexOf("```");
19078
+ if (trailingFence !== -1) {
19079
+ return body.slice(0, trailingFence);
19080
+ }
19081
+ return body;
19082
+ }
19083
+ return input;
19084
+ }
19085
+ function stripComments(input) {
19086
+ let inStr = false;
19087
+ return input.split("\n").map((line) => {
19088
+ for (let i = 0; i < line.length; i++) {
19089
+ const c = line[i];
19090
+ if (inStr) {
19091
+ if (c === "\\" && i + 1 < line.length) {
19092
+ i++;
19093
+ continue;
19094
+ }
19095
+ if (c === inStr) inStr = false;
19096
+ continue;
19097
+ }
19098
+ if (c === '"' || c === "'") {
19099
+ inStr = c;
19100
+ continue;
19101
+ }
19102
+ if (c === "/" && line[i + 1] === "/") {
19103
+ return line.substring(0, i).trimEnd();
19104
+ }
19105
+ if (c === "#") {
19106
+ return line.substring(0, i).trimEnd();
19107
+ }
19108
+ }
19109
+ return line;
19110
+ }).join("\n");
19111
+ }
19112
+ function preprocess(input) {
19113
+ return stripComments(stripFences(input.trim())).trim();
19114
+ }
19115
+ function parse(input, cat, rootName) {
19116
+ const trimmed = preprocess(input);
19117
+ if (!trimmed) return emptyResult();
19118
+ const { text, wasIncomplete } = autoClose(trimmed);
19119
+ const stmts = split(tokenize(text));
19120
+ if (!stmts.length) return emptyResult(wasIncomplete);
19121
+ const stmtMap = /* @__PURE__ */ new Map();
19122
+ let firstId = "";
19123
+ for (const s of stmts) {
19124
+ const expr = parseExpression(s.tokens);
19125
+ const stmt = classifyStatement(s, expr);
19126
+ stmtMap.set(s.id, stmt);
19127
+ if (!firstId) firstId = s.id;
19128
+ }
19129
+ const typedStmts = [...stmtMap.values()];
19130
+ return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName);
19131
+ }
19132
+ function createStreamParser(cat, rootName) {
19133
+ let buf = "";
19134
+ let cleaned = "";
19135
+ let completedEnd = 0;
19136
+ const completedStmtMap = /* @__PURE__ */ new Map();
19137
+ let firstId = "";
19138
+ function addStmt(text) {
19139
+ const t = text.trim();
19140
+ if (!t) return;
19141
+ for (const s of split(tokenize(t))) {
19142
+ const expr = parseExpression(s.tokens);
19143
+ const stmt = classifyStatement(s, expr);
19144
+ completedStmtMap.set(s.id, stmt);
19145
+ if (!firstId) firstId = s.id;
19146
+ }
19147
+ }
19148
+ function refreshCleaned() {
19149
+ const next = preprocess(buf);
19150
+ if (!next.startsWith(cleaned.slice(0, completedEnd))) {
19151
+ completedEnd = 0;
19152
+ completedStmtMap.clear();
19153
+ firstId = "";
19154
+ }
19155
+ cleaned = next;
19156
+ }
19157
+ function scanNewCompleted() {
19158
+ let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
19159
+ let stmtStart = completedEnd;
19160
+ for (let i = completedEnd; i < cleaned.length; i++) {
19161
+ const c = cleaned[i];
19162
+ if (esc) {
19163
+ esc = false;
19164
+ continue;
19165
+ }
19166
+ if (c === "\\" && inStr) {
19167
+ esc = true;
19168
+ continue;
19169
+ }
19170
+ if (inStr) {
19171
+ if (c === inStr) inStr = false;
19172
+ continue;
19173
+ }
19174
+ if (c === '"' || c === "'") {
19175
+ inStr = c;
19176
+ continue;
19177
+ }
19178
+ if (c === "(" || c === "[" || c === "{") depth++;
19179
+ else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
19180
+ else if (c === "?" && depth === 0) ternaryDepth++;
19181
+ else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
19182
+ else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
19183
+ let peek = i + 1;
19184
+ while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n"))
19185
+ peek++;
19186
+ if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) {
19187
+ continue;
19188
+ }
19189
+ const t = cleaned.slice(stmtStart, i).trim();
19190
+ if (t) addStmt(t);
19191
+ stmtStart = i + 1;
19192
+ completedEnd = i + 1;
19193
+ }
19194
+ }
19195
+ return stmtStart;
19196
+ }
19197
+ function currentResult() {
19198
+ refreshCleaned();
19199
+ const pendingStart = scanNewCompleted();
19200
+ const pendingText = cleaned.slice(pendingStart).trim();
19201
+ if (!pendingText) {
19202
+ if (completedStmtMap.size === 0) return emptyResult();
19203
+ return buildResult(
19204
+ completedStmtMap,
19205
+ [...completedStmtMap.values()],
19206
+ firstId,
19207
+ false,
19208
+ completedStmtMap.size,
19209
+ cat,
19210
+ rootName
19211
+ );
19212
+ }
19213
+ const { text: closed, wasIncomplete } = autoClose(pendingText);
19214
+ const stmts = split(tokenize(closed));
19215
+ if (!stmts.length) {
19216
+ if (completedStmtMap.size === 0) return emptyResult(wasIncomplete);
19217
+ return buildResult(
19218
+ completedStmtMap,
19219
+ [...completedStmtMap.values()],
19220
+ firstId,
19221
+ wasIncomplete,
19222
+ completedStmtMap.size,
19223
+ cat,
19224
+ rootName
19225
+ );
19226
+ }
19227
+ const allStmtMap = new Map(completedStmtMap);
19228
+ for (const s of stmts) {
19229
+ const expr = parseExpression(s.tokens);
19230
+ const stmt = classifyStatement(s, expr);
19231
+ allStmtMap.set(s.id, stmt);
19232
+ }
19233
+ const allTypedStmts = [...allStmtMap.values()];
19234
+ const fid = firstId || stmts[0].id;
19235
+ return buildResult(
19236
+ allStmtMap,
19237
+ allTypedStmts,
19238
+ fid,
19239
+ wasIncomplete,
19240
+ allStmtMap.size,
19241
+ cat,
19242
+ rootName
19243
+ );
19244
+ }
19245
+ function reset() {
19246
+ buf = "";
19247
+ cleaned = "";
19248
+ completedEnd = 0;
19249
+ completedStmtMap.clear();
19250
+ firstId = "";
19251
+ }
19252
+ return {
19253
+ push(chunk) {
19254
+ buf += chunk;
19255
+ return currentResult();
19256
+ },
19257
+ set(fullText) {
19258
+ if (fullText.length < buf.length || !fullText.startsWith(buf)) {
19259
+ reset();
19260
+ }
19261
+ const delta = fullText.slice(buf.length);
19262
+ if (delta) buf += delta;
19263
+ return currentResult();
19264
+ },
19265
+ getResult: currentResult
19266
+ };
19267
+ }
19268
+ function useGenUIProgram(text, catalog) {
19269
+ const parserRef = useRef(
19270
+ null
19271
+ );
19272
+ if (!parserRef.current || parserRef.current.catalog !== catalog) {
19273
+ parserRef.current = { catalog, parser: createStreamParser(catalog, "root") };
19274
+ }
19275
+ const lastGoodRef = useRef(null);
19276
+ return useMemo(() => {
19277
+ const result = parserRef.current.parser.set(text);
19278
+ if (result.root) {
19279
+ lastGoodRef.current = result.root;
19280
+ }
19281
+ return { result, root: result.root ?? lastGoodRef.current };
19282
+ }, [text, catalog]);
19283
+ }
19284
+ var noop = () => {
19285
+ };
19286
+ function childKey(value, index) {
19287
+ if (isElementNode(value) && value.statementId) return `s:${value.statementId}`;
19288
+ return `i:${index}`;
19289
+ }
19290
+ var RenderDepthContext = createContext(0);
19291
+ var MAX_DEPTH = 64;
19292
+ var GenUINode = memo(function GenUINode2({
19293
+ node,
19294
+ components,
19295
+ onAction,
19296
+ isStreaming,
19297
+ onTelemetry
19298
+ }) {
19299
+ const depth = useContext(RenderDepthContext);
19300
+ if (depth > MAX_DEPTH) {
19301
+ onTelemetry?.({ kind: "dropped-node", typeName: node.typeName });
19302
+ return null;
19303
+ }
19304
+ const renderer = components[node.typeName];
19305
+ if (!renderer) {
19306
+ onTelemetry?.({ kind: "unknown-component", typeName: node.typeName });
19307
+ return null;
19308
+ }
19309
+ const render = (value) => {
19310
+ if (value == null) return null;
19311
+ if (isElementNode(value)) {
19312
+ return /* @__PURE__ */ jsx(RenderDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx(
19313
+ GenUINode2,
19314
+ {
19315
+ node: value,
19316
+ components,
19317
+ onAction,
19318
+ isStreaming,
19319
+ onTelemetry
19320
+ }
19321
+ ) });
19322
+ }
19323
+ if (Array.isArray(value)) {
19324
+ return value.map((item, index) => /* @__PURE__ */ jsx(React14.Fragment, { children: render(item) }, childKey(item, index)));
19325
+ }
19326
+ if (typeof value === "object" && isRuntimeExpr(value)) return null;
19327
+ if (typeof value === "string" || typeof value === "number") return String(value);
19328
+ return null;
19329
+ };
19330
+ return renderer({ node, ctx: { render, onAction, isStreaming } });
19331
+ });
19332
+ function GenUIView({
19333
+ root,
19334
+ components,
19335
+ onAction = noop,
19336
+ isStreaming = false,
19337
+ onTelemetry,
19338
+ placeholder = null
19339
+ }) {
19340
+ if (!root) return placeholder;
19341
+ return /* @__PURE__ */ jsx(
19342
+ GenUINode,
19343
+ {
19344
+ node: root,
19345
+ components,
19346
+ onAction,
19347
+ isStreaming,
19348
+ onTelemetry
19349
+ }
19350
+ );
19351
+ }
19352
+
19353
+ // src/genui/registry.ts
19354
+ var CIMPLIFY_GENUI_REGISTRY = {
19355
+ version: "3",
19356
+ components: [
19357
+ {
19358
+ name: "Stack",
19359
+ description: 'Layout container. direction: "column" (default) or "row"; gap in points (default 12)',
19360
+ params: [{ name: "children", required: true }, { name: "direction" }, { name: "gap" }]
19361
+ },
19362
+ {
19363
+ name: "Card",
19364
+ description: "Elevated surface grouping related content, with an optional title",
19365
+ params: [{ name: "children", required: true }, { name: "title" }]
19366
+ },
19367
+ {
19368
+ name: "TextContent",
19369
+ description: 'Text block. size: "display", "heading", "title", "body" (default), "label", "caption"',
19370
+ params: [{ name: "text", required: true }, { name: "size" }]
19371
+ },
19372
+ {
19373
+ name: "Stat",
19374
+ description: "Compact stat: small label over a bold value, optional sub line and tone",
19375
+ params: [
19376
+ { name: "label", required: true },
19377
+ { name: "value", required: true },
19378
+ { name: "sub" },
19379
+ { name: "tone" }
19380
+ ]
19381
+ },
19382
+ {
19383
+ name: "Metric",
19384
+ description: "KPI card with label, value, optional percent change and SF Symbol icon",
19385
+ params: [
19386
+ { name: "label", required: true },
19387
+ { name: "value", required: true },
19388
+ { name: "change" },
19389
+ { name: "icon" }
19390
+ ]
19391
+ },
19392
+ {
19393
+ name: "KeyValue",
19394
+ description: "Label/value row; set mono true for numeric values",
19395
+ params: [
19396
+ { name: "label", required: true },
19397
+ { name: "value", required: true },
19398
+ { name: "mono" }
19399
+ ]
19400
+ },
19401
+ {
19402
+ name: "Badge",
19403
+ description: "Small status chip. tone: neutral, positive, info, indigo, violet, warning, danger",
19404
+ params: [{ name: "label", required: true }, { name: "tone" }]
19405
+ },
19406
+ {
19407
+ name: "Callout",
19408
+ description: "Inline alert with title, optional message. tone: info (default), success, warning, danger, accent, neutral",
19409
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "tone" }]
19410
+ },
19411
+ {
19412
+ name: "Button",
19413
+ description: 'Action button. action: "action:name" string or Action([...]) steps. variant: primary, cta, secondary (default), ghost, destructive',
19414
+ params: [
19415
+ { name: "label", required: true },
19416
+ { name: "action", required: true },
19417
+ { name: "variant" }
19418
+ ]
19419
+ },
19420
+ {
19421
+ name: "Buttons",
19422
+ description: 'Row (default) or column of Buttons; direction: "row" or "column"',
19423
+ params: [{ name: "children", required: true }, { name: "direction" }]
19424
+ },
19425
+ {
19426
+ name: "Divider",
19427
+ description: "Hairline separator",
19428
+ params: []
19429
+ },
19430
+ {
19431
+ name: "Table",
19432
+ description: "Data table: headers is an array of strings, rows an array of cell arrays",
19433
+ params: [
19434
+ { name: "headers", required: true },
19435
+ { name: "rows", required: true }
19436
+ ]
19437
+ },
19438
+ {
19439
+ name: "BarChart",
19440
+ description: "Bar chart: labels (x axis strings) and values (numbers), optional title",
19441
+ params: [
19442
+ { name: "labels", required: true },
19443
+ { name: "values", required: true },
19444
+ { name: "title" }
19445
+ ]
19446
+ },
19447
+ {
19448
+ name: "LineChart",
19449
+ description: "Line chart with area fill: labels and values, optional title",
19450
+ params: [
19451
+ { name: "labels", required: true },
19452
+ { name: "values", required: true },
19453
+ { name: "title" }
19454
+ ]
19455
+ },
19456
+ {
19457
+ name: "Money",
19458
+ description: "Formatted money amount, never abbreviated. currency: ISO code (default USD)",
19459
+ params: [{ name: "amount", required: true }, { name: "currency" }]
19460
+ },
19461
+ {
19462
+ name: "Image",
19463
+ description: "Remote image by URL with optional caption",
19464
+ params: [{ name: "url", required: true }, { name: "caption" }]
19465
+ },
19466
+ {
19467
+ name: "ListRow",
19468
+ description: "Entity row for orders/customers/products/invoices: initials avatar (or SF Symbol tile via symbol), title + subtitle, trailing value and badge with tone",
19469
+ params: [
19470
+ { name: "title", required: true },
19471
+ { name: "subtitle" },
19472
+ { name: "value" },
19473
+ { name: "badge" },
19474
+ { name: "tone" },
19475
+ { name: "symbol" }
19476
+ ]
19477
+ },
19478
+ {
19479
+ name: "Timeline",
19480
+ description: "Event history (order journey, payment trail): events is an array of {title, detail?, time?, tone?} objects, newest first",
19481
+ params: [{ name: "events", required: true }]
19482
+ },
19483
+ {
19484
+ name: "Progress",
19485
+ description: "Amount toward a bound (goal, stock cover, capacity): numeric value and target (default 100), optional detail caption and tone",
19486
+ params: [
19487
+ { name: "label", required: true },
19488
+ { name: "value", required: true },
19489
+ { name: "target" },
19490
+ { name: "detail" },
19491
+ { name: "tone" }
19492
+ ]
19493
+ },
19494
+ {
19495
+ name: "DonutChart",
19496
+ description: "Share of a whole (sales by channel, revenue by category): labels + values, optional title and center display string (e.g. the formatted total)",
19497
+ params: [
19498
+ { name: "labels", required: true },
19499
+ { name: "values", required: true },
19500
+ { name: "title" },
19501
+ { name: "center" }
19502
+ ]
19503
+ },
19504
+ {
19505
+ name: "StatusTrack",
19506
+ description: "Stage tracker (order fulfilment, invoice lifecycle): steps are stage names, current is the active step name or index",
19507
+ params: [
19508
+ { name: "steps", required: true },
19509
+ { name: "current", required: true }
19510
+ ]
19511
+ },
19512
+ {
19513
+ name: "LineItems",
19514
+ description: "Order/quote/invoice money block: items is an array of {qty?, name, amount} with display-ready amount strings, adjustments an array of {label, amount, tone?}, total the anchored display string",
19515
+ params: [
19516
+ { name: "items", required: true },
19517
+ { name: "total", required: true },
19518
+ { name: "adjustments" }
19519
+ ]
19520
+ },
19521
+ {
19522
+ name: "Grid",
19523
+ description: "Tile layout for Stat/Metric children, columns default 2",
19524
+ params: [{ name: "children", required: true }, { name: "columns" }]
19525
+ },
19526
+ {
19527
+ name: "Banner",
19528
+ description: "Hero-stat moment on a tonal wash: big value, label, optional percent delta. tone: positive (default), warning, danger, info, neutral",
19529
+ params: [
19530
+ { name: "value", required: true },
19531
+ { name: "label", required: true },
19532
+ { name: "delta" },
19533
+ { name: "tone" }
19534
+ ]
19535
+ },
19536
+ {
19537
+ name: "EmptyState",
19538
+ description: "Nothing-to-show state with an SF Symbol and optional message",
19539
+ params: [{ name: "title", required: true }, { name: "message" }, { name: "symbol" }]
19540
+ }
19541
+ ]
19542
+ };
19543
+
19544
+ // src/genui/manifest.ts
19545
+ function genUIManifest(registry) {
19546
+ return {
19547
+ version: registry.version,
19548
+ components: registry.components.map((component) => ({
19549
+ name: component.name,
19550
+ description: component.description,
19551
+ params: component.params.map((param) => ({
19552
+ name: param.name,
19553
+ required: param.required ?? false
19554
+ }))
19555
+ }))
19556
+ };
19557
+ }
19558
+ function encodeString(value) {
19559
+ return JSON.stringify(value).replace(/\//g, "\\/");
19560
+ }
19561
+ function canonicalize(value) {
19562
+ if (value === null) return "null";
19563
+ if (typeof value === "string") return encodeString(value);
19564
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
19565
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
19566
+ const keys = Object.keys(value).sort();
19567
+ const entries = keys.map((key) => `${encodeString(key)}:${canonicalize(value[key])}`);
19568
+ return `{${entries.join(",")}}`;
19569
+ }
19570
+ function genUIManifestJson(manifest) {
19571
+ return canonicalize(manifest);
19572
+ }
19573
+ async function genUIFingerprint(manifest) {
19574
+ const bytes = new TextEncoder().encode(genUIManifestJson(manifest));
19575
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
19576
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
19577
+ }
19578
+
19579
+ export { AccommodationCard, AccountContent, AccountDashboardPage, AccountHero, AccountIdentityStrip, AccountLineItem, AccountLineItems, AccountNav, AccountOrderDetailPage, AccountOrderView, AccountOrdersPage, AccountShell, AccountSidebar, AccountSignedOutPrompt, AccountSubscriptionDetailPage, AccountSubscriptionsPage, Ad, AdProvider, AddOnSelector, AddressAutocomplete, AddressElement, AuthElement, AvailabilityBadge, BackLink, BillingPlanSelector, BookingCard, BookingList, BookingPage, BookingSummary, BookingsPage, BoxIcon24, BundleProductCard, BundleProductLayout, BundleSelector, Button, CIMPLIFY_GENUI_REGISTRY, CardImage, CardShell, CardVariant, CartDrawer, CartDrawerProvider, CartPage, CartSummary, CartTemplate, CatalogueCollectionLayout, CataloguePage, CatalogueTemplate, CategoryFilter, CategoryGrid, ChatMarkdown, ChatWidget, CheckIcon, CheckoutPage, CimplifyCheckout, CimplifyProvider, CimplifySignInButton, CimplifyUserButton, CollectionPage, CollectionTemplate, CompactCartLayout, CompactCatalogueLayout, CompactSearchLayout, CompactServiceCard, CompositeProductCard, CompositeProductLayout, CompositeSelector, ContextCard, ContinuityCard, CurrencySelector, CustomAttributesTable, CustomerInputFields, DashboardIcon, DashboardIcon20, DatePicker, DateSlotPicker, DealBanner, DealsPage, DefaultCartLayout, DefaultCatalogueLayout, DefaultCollectionLayout, DefaultProductLayout, DefaultSearchLayout, DeliveryEstimate, DeliveryTracker, DeliveryTruckIcon, DigitalProductCard, DigitalProductLayout, DiscountInput, ElementsProvider, EmptyState, ErrorState, FeaturedCollectionLayout, FilterTabs, FoodProductCard, FoodProductLayout, GenUIView, HappeningCard, IdentityIcon, IdentityOfframp, InventoryBadge, ItemsCard, LeaseServiceCard, LineItemDetails, LocationPicker, MediaGallery, MetadataStringList, MobileAccountShell, MobileBackButton, MobileMoreButton, MobileTabBar, NextDeliveryCard, OrderDetailHeader, OrderDetailMetaPipe, OrderDetailPage, OrderHistory, OrderHistoryPage, OrderList, OrderRow, OrderSummary, OrderTimeline, OrderTotals, OrdersIcon, OrdersIcon20, OvenIcon, PackageIcon, PaymentElement, Price, PriceRange, ProductCard, ProductCustomizer, ProductGrid, ProductImageGallery, ProductModel3D, ProductPage, ProductSheet, ProductTemplate, PropertiesTable, QuantitySelector, QuickAddButton, RecentlyViewed, RecommendationCarousel, RelatedProductsSection, RentalServiceCard, ResourcePicker, RetailProductCard, RichText, SaleBadge, SalePrice, SaleTeaser, ScheduleServiceCard, SearchInput, SearchPage, SearchTemplate, Section, SectionMore, ServiceProductLayout, SessionMessageBanner, SignOutIcon, Skeleton, SlotPicker, SoldOutOverlay, StaffPicker, StandardServiceCard, Status, StoreNav, StoreVideo, SubscriptionCard, SubscriptionList, SubscriptionRow, SubscriptionsIcon, SubscriptionsIcon20, TagPills, TimePicker, Toast, TwoColumnGrid, VariantSelector, VolumePricing, WelcomeCard, WholesaleProductCard, WholesaleProductLayout, WishlistButton, clearOrdersCache, cn, createPlacesSessionToken, defaultBoundaryKind, genUICatalog, genUIFingerprint, genUIManifest, genUIManifestJson, getBoundaryLabels, isImageAttachment, isTerminalOrderStatus, parse as parseGenUIProgram, partitionMessages, prefetchProduct, prefetchProducts, resolveBoundaryKind, roomToResource, sanitizeRichTextHtml, stripHtmlToText, toAccountLineItem, toAccountLineItems, toAccountOrderView, useActivityState, useAds, useAttributeDefinitions, useAvailableSlots, useBillingPlans, useBookings, useBootstrap, useBundles, useCart, useCartDrawer, useCategories, useChat, useCheckout, useCimplify, useCimplifyClient, useCimplifySession, useCollection, useCollections, useDeals, useDeliveryFee, useDeliveryOptions, useElements, useElementsReady, useFxRate, useGenUIProgram, useLocations, useOptionalCimplify, useOrder, useOrders, useProduct, useProductAvailability, useProductDeals, useProductPrice, useProductSchedules, useProducts, useProductsOnSale, usePropertyFacets, useQuote, useRecommendations, useSearch, useServiceAvailability, useServices, useStoreCredit, useSubscription, useSubscriptions, useTaxonomies, useTaxonomy, useTaxonomyPath, useValidateDiscount, useVariantSelector };