@wix/himalaya-cli 0.1502.0 → 0.1504.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.
@@ -26,11 +26,26 @@ type State = {
26
26
  liveState:string; liveReason:string; realtimeEventCount:number; lastRealtimeEventType:string; pendingIntent:PendingIntent|null; scrollAnchor:string; scrollSend:number; usedSuggestionIds:string[]; visitorOptions:BusinessVisitorOptions|null; quickReplies:BusinessSuggestion[]; hasQuickReplies:boolean; noQuickReplies:boolean;
27
27
  liveLabel:string; hasActiveRoom:boolean; noActiveRoom:boolean; isGuest:boolean; notGuest:boolean; realtimeDiagnostic:string; noError:boolean; noNotice:boolean; noMessages:boolean; hasMessages:boolean; noEmpty:boolean; notLoading:boolean;
28
28
  hostedName:string; hasHostedName:boolean; noHostedName:boolean;
29
+ /** Who signs business messages: the hosted site, else the name the screen's `init` passes. */
30
+ businessName:string; businessInitials:string;
29
31
  sendDisabled:boolean; sendEnabled:boolean; noLive:boolean; loadingHidden:boolean; errorText:string; loadFailure:string;
30
32
  };
31
33
  // A hosted build serves many businesses: the title is the site the host opened, not the app's own name.
32
34
  const hostedName=hostSiteFromInput((globalThis as {himalaya?:{input?:unknown}}).himalaya?.input)?.name??'';
33
- const initial:State={hostedName,hasHostedName:!!hostedName,noHostedName:!hostedName,phase:'loading',error:'',notice:'',member:false,roomId:'',principal:'',scope:'',messages:[],draft:'',busy:false,
35
+ /** One or two capitals for a name, as an avatar shows it ("Hush Studio" → "HS"). */
36
+ // What a reader sees as one character: an emoji joined from several code points (👩‍🍳, a flag) stays whole. Code
37
+ // points are the fallback where the engine has no Intl.Segmenter.
38
+ function graphemes(text:string):string[]{
39
+ // The wrist's engine has no Intl at all, so it is only read behind this check.
40
+ const Segmenter=typeof Intl!=='undefined'?(Intl as unknown as {Segmenter?:new(locale?:string,options?:{granularity:'grapheme'})=>{segment(input:string):Iterable<{segment:string}>}}).Segmenter:undefined;
41
+ return Segmenter?Array.from(new Segmenter(undefined,{granularity:'grapheme'}).segment(text),(part)=>part.segment):[...text];
42
+ }
43
+ function initialsOf(name:string):string{
44
+ const first=(word:string)=>graphemes(word)[0]??'';
45
+ const words=name.trim().split(/\s+/).filter(Boolean);
46
+ return (words.length>1?`${first(words[0]!)}${first(words[words.length-1]!)}`:first(words[0]??'')).toUpperCase();
47
+ }
48
+ const initial:State={hostedName,businessName:hostedName,businessInitials:initialsOf(hostedName),hasHostedName:!!hostedName,noHostedName:!hostedName,phase:'loading',error:'',notice:'',member:false,roomId:'',principal:'',scope:'',messages:[],draft:'',busy:false,
34
49
  liveState:'unavailable',liveReason:'',realtimeEventCount:0,lastRealtimeEventType:'',liveLabel:'',hasActiveRoom:false,noActiveRoom:true,isGuest:false,notGuest:true,realtimeDiagnostic:'',pendingIntent:null,scrollAnchor:'',scrollSend:0,usedSuggestionIds:[],visitorOptions:null,quickReplies:[],hasQuickReplies:false,noQuickReplies:true,noError:true,noNotice:true,noMessages:true,hasMessages:false,noEmpty:true,notLoading:false,sendDisabled:true,sendEnabled:false,noLive:true,loadingHidden:false,errorText:'',loadFailure:''};
35
50
  let currentIdentity:ChatIdentity|null=null;
36
51
  let subscriptionId='';
@@ -309,6 +324,11 @@ const svc=defineService<State,Record<string,(s:State,a?:unknown)=>State|undefine
309
324
  // A retry passes no site: the one the screen gave first stays. A site the screen passes, even an empty one (a
310
325
  // business with no site), replaces it, so an earlier business's links are not routed to this one's screens.
311
326
  if(hasParam(a,'siteUrl')){ownSiteUrl=param(a,'siteUrl');ownScreens.product=param(a,'productScreen');ownScreens.service=param(a,'serviceScreen');}
327
+ // A retry passes no name: the one the screen gave first stays. Under `displayName`, a copy key, so a template
328
+ // sweep that renames the business renames it here too and the initials follow.
329
+ const signedBy=s.hostedName||param(a,'displayName')||s.businessName;
330
+ const signed=(st:State):State=>({...st,businessName:signedBy,businessInitials:initialsOf(signedBy)});
331
+ s=signed(s);
312
332
  const oldPrincipal=s.principal;
313
333
  const initRevision=draftRevision;
314
334
  // Text typed at any point of the load, subscribe step included, outlives the snapshot this started from. So does the
@@ -336,7 +356,7 @@ const svc=defineService<State,Record<string,(s:State,a?:unknown)=>State|undefine
336
356
  const identity=await getCurrentMember();
337
357
  if(myEpoch!==epoch)return undefined;
338
358
  if(!identity.authenticated)throw new Error('member_sign_in_required');
339
- if(oldPrincipal&&(oldPrincipal!==(identity.userId||identity.email)||s.scope!==identity.scope)){s=clearPrivate(s,'guest');myEpoch=epoch;reset=true;}
359
+ if(oldPrincipal&&(oldPrincipal!==(identity.userId||identity.email)||s.scope!==identity.scope)){s=signed(clearPrivate(s,'guest'));myEpoch=epoch;reset=true;}
340
360
  currentIdentity=identity;
341
361
  s=shape({...s,phase:'loading',member:true,principal:identity.userId||identity.email,scope:identity.scope,error:'',notice:''});
342
362
  const existingRoom=await resolveExistingBusinessRoom(myEpoch);
@@ -1,71 +0,0 @@
1
- // himi-branded-lite-inbox — reads the visitor's real Wix Inbox conversations (listConversations).
2
- // a failed query is an error with Retry (or Sign in when Wix refuses the
3
- // visitor) and an empty one is a real empty state — never sample rows presented as
4
- // the customer's own. Samples appear only in the host's explicit sample preview
5
- // (_shared/branded-load-state.ts).
6
- //
7
- // Inbox exposes no list-messages endpoint (only listConversations + single getMessage),
8
- // so this is a conversation LIST — same read-view scope as the Galleries/Forms verticals.
9
- import { defineService } from "../_shared/collection-service.js";
10
- import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
11
- import { listConversations, type WixConversation } from "@himalaya/wix-inbox";
12
-
13
- interface Row { id: string; title: string; subtitle: string }
14
- interface S extends LoadFlags {
15
- conversations: Row[]; emptyText: string;
16
- isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
- * shows only when the site really has none, and the unavailable one only when the
18
- * query failed. A failed query must never render the sentence that claims the
19
- * customer has no content. */
20
- notMockEmpty: boolean; notMockUnavailable: boolean;
21
- }
22
-
23
- // businessDisplayData / participantDisplayData are open maps; pull a human name from
24
- // the usual keys without assuming a fixed shape.
25
- function displayName(m?: Record<string, unknown>): string {
26
- if (!m) return "";
27
- for (const k of ["name", "displayName", "businessName", "nickname", "fullName"]) {
28
- const v = m[k];
29
- if (typeof v === "string" && v.trim()) return v.trim();
30
- }
31
- return "";
32
- }
33
-
34
- function norm(c: WixConversation, i: number): Row {
35
- const title = displayName(c.businessDisplayData) || displayName(c.participantDisplayData) || "Conversation";
36
- const preview = (c.latestMessage?.previewText || c.summary || "").trim();
37
- return { id: c.id || `conv-${i}`, title, subtitle: preview };
38
- }
39
-
40
- const MOCK: Row[] = [
41
- { id: "sample-orders", title: "Orders", subtitle: "Your order is on its way — track it here." },
42
- { id: "sample-support", title: "Support", subtitle: "Hi! Let us know how we can help." },
43
- { id: "sample-team", title: "The Team", subtitle: "Welcome 👋 Thanks for joining us!" },
44
- ];
45
-
46
- /** sample rows reach the screen ONLY in the host's explicit sample preview;
47
- * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
48
- const COPY: LoadCopy<Row> = {
49
- prefix: "inbox",
50
- empty: "No messages yet.",
51
- error: "Your messages couldn't be loaded.",
52
- signIn: "Sign in to see your messages.",
53
- sample: () => MOCK.map((r) => ({ ...r })),
54
- };
55
-
56
- const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInbox", {
57
- state: { ...LOADING, conversations: [], emptyText: "" },
58
- actions: {
59
- load: async (s) => {
60
- try {
61
- const r = await listConversations({ paging: { limit: 50 } });
62
- const conversations = (r.conversations ?? []).map(norm);
63
- return { ...s, ...loaded("conversations", conversations, COPY) };
64
- } catch (err) {
65
- return { ...s, ...loadFailed("conversations", err, COPY) };
66
- }
67
- },
68
- },
69
- });
70
- svc.load();
71
- export { svc };