@wix/himalaya-cli 0.1029.0 → 0.1031.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 (185) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-config.d.ts +7 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/wix-gateway.d.ts +32 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/wix-gateway.rules.d.ts +2 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +7 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +64 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +41 -1
  7. package/dist/cli.mjs +298 -114
  8. package/dist/preview-assets.generated.bin +0 -0
  9. package/guides/component-reference.json +0 -7
  10. package/guides/component-reference.md +0 -2
  11. package/guides/sdk-reference.json +50 -2
  12. package/package.json +2 -2
  13. package/templates/beauty-salon/dev/index.ts +575 -214
  14. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +4 -1
  15. package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +7 -1
  16. package/templates/beauty-salon/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  17. package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +36 -5
  18. package/templates/beauty-salon/tier5-src/wix-spaces-app-language/index.ts +4 -2
  19. package/templates/beauty-salon-owner/config.ts +1 -1
  20. package/templates/beauty-salon-owner/dev/index.ts +247 -9
  21. package/templates/beauty-salon-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  22. package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +405 -20
  23. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +4 -1
  24. package/templates/beauty-salon-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  25. package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  26. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  27. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  28. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  29. package/templates/branded-lite/dev/index.ts +699 -228
  30. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +4 -1
  31. package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +7 -1
  32. package/templates/branded-lite/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  33. package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +11 -6
  34. package/templates/branded-lite/tier5-src/himi-branded-lite-reviews/index.ts +11 -6
  35. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +36 -5
  36. package/templates/branded-lite/tier5-src/wix-spaces-app-language/index.ts +4 -2
  37. package/templates/community-club/dev/index.ts +451 -204
  38. package/templates/community-club/tier5-src/_shared/collection-contract.ts +4 -1
  39. package/templates/community-club/tier5-src/_shared/owner-load-error.ts +7 -1
  40. package/templates/community-club/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  41. package/templates/community-club/tier5-src/wix-spaces-app-language/index.ts +4 -2
  42. package/templates/community-club-owner/config.ts +1 -1
  43. package/templates/community-club-owner/dev/index.ts +247 -9
  44. package/templates/community-club-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  45. package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +405 -20
  46. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +4 -1
  47. package/templates/community-club-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  48. package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  49. package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  50. package/templates/community-club-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  51. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  52. package/templates/events-meetups/dev/index.ts +451 -204
  53. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +4 -1
  54. package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +7 -1
  55. package/templates/events-meetups/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  56. package/templates/events-meetups/tier5-src/wix-spaces-app-language/index.ts +4 -2
  57. package/templates/events-meetups-owner/config.ts +1 -1
  58. package/templates/events-meetups-owner/dev/index.ts +247 -9
  59. package/templates/events-meetups-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  60. package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +405 -20
  61. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +4 -1
  62. package/templates/events-meetups-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  63. package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  64. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  65. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  66. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  67. package/templates/gym-fitness/dev/index.ts +575 -214
  68. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +4 -1
  69. package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +7 -1
  70. package/templates/gym-fitness/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  71. package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +36 -5
  72. package/templates/gym-fitness/tier5-src/wix-spaces-app-language/index.ts +4 -2
  73. package/templates/gym-fitness-owner/config.ts +1 -1
  74. package/templates/gym-fitness-owner/dev/index.ts +247 -9
  75. package/templates/gym-fitness-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  76. package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +405 -20
  77. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +4 -1
  78. package/templates/gym-fitness-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  79. package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  80. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  81. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  82. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  83. package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +7 -1
  84. package/templates/nonprofit-cause/dev/index.ts +451 -204
  85. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +4 -1
  86. package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +7 -1
  87. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  88. package/templates/nonprofit-cause/tier5-src/wix-spaces-app-language/index.ts +4 -2
  89. package/templates/nonprofit-cause-owner/config.ts +1 -1
  90. package/templates/nonprofit-cause-owner/dev/index.ts +247 -9
  91. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  92. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +405 -20
  93. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +4 -1
  94. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  95. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  96. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  97. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  98. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  99. package/templates/owner-lite/config.ts +1 -1
  100. package/templates/owner-lite/dev/index.ts +247 -9
  101. package/templates/owner-lite/tier5-src/_shared/aria-attachments.ts +179 -0
  102. package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +405 -20
  103. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +4 -1
  104. package/templates/owner-lite/tier5-src/_shared/owner-bookings.ts +20 -0
  105. package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +7 -1
  106. package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  107. package/templates/owner-lite/tier5-src/himi-owner-lite-home/index.ts +2 -12
  108. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  109. package/templates/podcast-creator/dev/index.ts +451 -204
  110. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +4 -1
  111. package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +7 -1
  112. package/templates/podcast-creator/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  113. package/templates/podcast-creator/tier5-src/wix-spaces-app-language/index.ts +4 -2
  114. package/templates/podcast-creator-owner/config.ts +1 -1
  115. package/templates/podcast-creator-owner/dev/index.ts +247 -9
  116. package/templates/podcast-creator-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  117. package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +405 -20
  118. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +4 -1
  119. package/templates/podcast-creator-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  120. package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  121. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  122. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  123. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  124. package/templates/portfolio-creative/dev/index.ts +575 -214
  125. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +4 -1
  126. package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +7 -1
  127. package/templates/portfolio-creative/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  128. package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +36 -5
  129. package/templates/portfolio-creative/tier5-src/wix-spaces-app-language/index.ts +4 -2
  130. package/templates/portfolio-creative-owner/config.ts +1 -1
  131. package/templates/portfolio-creative-owner/dev/index.ts +247 -9
  132. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  133. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +405 -20
  134. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +4 -1
  135. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  136. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  137. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  138. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  139. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  140. package/templates/restaurant/dev/index.ts +451 -204
  141. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +4 -1
  142. package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +7 -1
  143. package/templates/restaurant/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  144. package/templates/restaurant/tier5-src/wix-spaces-app-language/index.ts +4 -2
  145. package/templates/restaurant-owner/config.ts +1 -1
  146. package/templates/restaurant-owner/dev/index.ts +247 -9
  147. package/templates/restaurant-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  148. package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +405 -20
  149. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +4 -1
  150. package/templates/restaurant-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  151. package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  152. package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  153. package/templates/restaurant-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  154. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  155. package/templates/retail-store/dev/index.ts +575 -214
  156. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +4 -1
  157. package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +7 -1
  158. package/templates/retail-store/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  159. package/templates/retail-store/tier5-src/himi-store-product/index.ts +36 -5
  160. package/templates/retail-store/tier5-src/wix-spaces-app-language/index.ts +4 -2
  161. package/templates/retail-store-owner/config.ts +1 -1
  162. package/templates/retail-store-owner/dev/index.ts +247 -9
  163. package/templates/retail-store-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  164. package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +405 -20
  165. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +4 -1
  166. package/templates/retail-store-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  167. package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  168. package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  169. package/templates/retail-store-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  170. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
  171. package/templates/yoga-studio/dev/index.ts +451 -204
  172. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +4 -1
  173. package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +7 -1
  174. package/templates/yoga-studio/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
  175. package/templates/yoga-studio/tier5-src/wix-spaces-app-language/index.ts +4 -2
  176. package/templates/yoga-studio-owner/config.ts +1 -1
  177. package/templates/yoga-studio-owner/dev/index.ts +247 -9
  178. package/templates/yoga-studio-owner/tier5-src/_shared/aria-attachments.ts +179 -0
  179. package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +405 -20
  180. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +4 -1
  181. package/templates/yoga-studio-owner/tier5-src/_shared/owner-bookings.ts +20 -0
  182. package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +7 -1
  183. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +40 -1
  184. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-home/index.ts +2 -12
  185. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +10 -4
@@ -0,0 +1,179 @@
1
+ // Photo attachments for the Aria composer — the pure half.
2
+ //
3
+ // Everything here is a decision that can be made without touching the device
4
+ // or the network: which files may be attached, how many, how large, what a chip
5
+ // in the composer says in each of its three states, and how a picked image is
6
+ // turned into the bytes an upload sends. The half that DOES touch the world —
7
+ // minting the upload, sending the bytes, picking — lives in `aria-chat.ts`
8
+ // (the mint, through `@himalaya/wix-genie-chat`) and in each app's adapter (the
9
+ // picker and the byte transfer, which are device capabilities the adapter's
10
+ // screen has to declare).
11
+ //
12
+ // This file is copied verbatim into every generated starter app, so it explains
13
+ // itself without pointing at anything only this repo has.
14
+
15
+ /** Image types the composer accepts. The first-party clients' own allowlist
16
+ * for images: png, jpeg (and the non-standard `image/jpg` some pickers still
17
+ * report), webp, gif and heic. Anything else is REFUSED with a line saying so,
18
+ * never silently dropped — the native Aria app drops unsupported files without a
19
+ * word, and an owner who picked something and sees nothing happen assumes the
20
+ * button is broken. */
21
+ export const ARIA_IMAGE_TYPES: readonly string[] = [
22
+ "image/png",
23
+ "image/jpeg",
24
+ "image/jpg",
25
+ "image/webp",
26
+ "image/gif",
27
+ "image/heic",
28
+ ];
29
+
30
+ /** At most this many attachments ride one message. The native Aria app caps at
31
+ * 10; nothing on the wire says otherwise. */
32
+ export const ARIA_MAX_ATTACHMENTS = 10;
33
+
34
+ /** The largest image the composer will try to send, in bytes.
35
+ *
36
+ * Not a product choice: it is the ceiling of the transfer this slice uses. A
37
+ * picked photo crosses the worker bridge as a base64 string and is sent inline,
38
+ * and the inline upload refuses anything over 8 MiB (the `MAX_INLINE_BASE64_BYTES`
39
+ * bound of the pre-signed upload capability, which it enforces on every arm).
40
+ * Checking it HERE is what turns that refusal into a sentence the owner can act
41
+ * on, instead of a chip that fails for no stated reason. */
42
+ export const ARIA_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
43
+
44
+ /** A chip in the composer. Every field is always present — a `$item.` path that
45
+ * is missing on some rows does not substitute — and each `…Hidden` flag
46
+ * collapses the part it names. */
47
+ export interface AriaAttachmentChip {
48
+ /** Minted by the worker, never derived from the file. The native Aria app keyed
49
+ * concurrent uploads by file NAME and collided on the second `IMG_0001.jpg`. */
50
+ id: string;
51
+ name: string;
52
+ mimeType: string;
53
+ state: "uploading" | "ready" | "failed";
54
+ /** The chip's thumbnail as a `data:` URI, set only when the picked image is
55
+ * small; "" otherwise, which collapses the preview. The full bytes an upload
56
+ * and a retry send are held by the worker, never in the chip, because the chip
57
+ * rides the service state and a large photo there would exceed the web
58
+ * runtime's frame limit. */
59
+ thumbUrl: string;
60
+ thumbHidden: boolean;
61
+ /** "Uploading…" / "Couldn't upload" / "" — collapses when empty. */
62
+ statusText: string;
63
+ statusHidden: boolean;
64
+ /** The retry control shows ONLY on a failed chip. */
65
+ retryHidden: boolean;
66
+ /** `genie-upload://<id>` once the bytes have landed; "" until then. The send
67
+ * carries a chip only when this is set. */
68
+ ref: string;
69
+ }
70
+
71
+ /** What an upload reports back, delivered as a fresh action so it lands on the
72
+ * CURRENT state rather than on the snapshot the upload started from. */
73
+ export interface AriaAttachmentOutcome {
74
+ id: string;
75
+ ok: boolean;
76
+ /** Set when `ok`. */
77
+ ref?: string;
78
+ }
79
+
80
+ export interface ParsedDataUri { mimeType: string; base64: string; bytes: number }
81
+
82
+ /** `data:<mime>;base64,<payload>` → its parts, or null for anything else.
83
+ *
84
+ * Only the base64 form: every picker this composer is wired to returns it, and
85
+ * a percent-encoded `data:` URI would need decoding the bridge cannot do. The
86
+ * byte count is computed from the payload length, so a multi-megabyte photo is
87
+ * sized without being decoded. */
88
+ export function parseDataUri(uri: string): ParsedDataUri | null {
89
+ const m = /^data:([^;,]*)((?:;[^;,]*)*?);base64,/i.exec(uri ?? "");
90
+ if (!m) return null;
91
+ const base64 = uri.slice(m[0].length).replace(/\s+/g, "");
92
+ const pad = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
93
+ const bytes = Math.max(0, Math.floor((base64.length * 3) / 4) - pad);
94
+ return { mimeType: (m[1] || "").trim().toLowerCase(), base64, bytes };
95
+ }
96
+
97
+ /** Normalize a MIME type a picker reported.
98
+ *
99
+ * Some pickers hand back a platform type identifier where a MIME type belongs
100
+ * (`public.jpeg`, `public.heic`), and the service validates the field as a MIME
101
+ * type. Mapped for the image identifiers this composer accepts; anything else
102
+ * is returned lowercased and left for the allowlist to judge. */
103
+ export function normalizeMime(mime: string): string {
104
+ const v = (mime ?? "").trim().toLowerCase();
105
+ switch (v) {
106
+ case "public.jpeg": return "image/jpeg";
107
+ case "public.png": return "image/png";
108
+ case "public.heic": return "image/heic";
109
+ case "com.compuserve.gif": return "image/gif";
110
+ case "org.webmproject.webp": return "image/webp";
111
+ case "image/jpg": return "image/jpeg";
112
+ default: return v;
113
+ }
114
+ }
115
+
116
+ /** A file name for an image the picker did not name, by type. The service
117
+ * requires one (1–255 characters), and it is what the transcript shows. */
118
+ export function imageFileName(mimeType: string, n: number, given?: string): string {
119
+ const name = (given ?? "").trim();
120
+ if (name) return name.slice(0, 255);
121
+ const ext = mimeType === "image/png" ? "png"
122
+ : mimeType === "image/webp" ? "webp"
123
+ : mimeType === "image/gif" ? "gif"
124
+ : mimeType === "image/heic" ? "heic"
125
+ : "jpg";
126
+ return `photo-${n}.${ext}`;
127
+ }
128
+
129
+ /** Why a picked file cannot be attached, or "" when it can. */
130
+ export type AttachRefusal = "" | "type" | "size" | "count";
131
+
132
+ export function refusalFor(parsed: ParsedDataUri | null, mimeType: string, attached: number): AttachRefusal {
133
+ if (attached >= ARIA_MAX_ATTACHMENTS) return "count";
134
+ if (!parsed || !ARIA_IMAGE_TYPES.includes(mimeType)) return "type";
135
+ if (parsed.bytes > ARIA_MAX_IMAGE_BYTES) return "size";
136
+ return "";
137
+ }
138
+
139
+ /** A chip in a given state. One constructor so the flags can never disagree
140
+ * with the state they describe — a failed chip without its retry control is
141
+ * the shape of bug this rules out. */
142
+ export function chipIn(
143
+ base: Pick<AriaAttachmentChip, "id" | "name" | "mimeType" | "thumbUrl">,
144
+ state: AriaAttachmentChip["state"],
145
+ labels: { uploading: string; failed: string },
146
+ ref = "",
147
+ ): AriaAttachmentChip {
148
+ const statusText = state === "uploading" ? labels.uploading : state === "failed" ? labels.failed : "";
149
+ return {
150
+ ...base,
151
+ state,
152
+ thumbHidden: !base.thumbUrl,
153
+ statusText,
154
+ statusHidden: !statusText,
155
+ retryHidden: state !== "failed",
156
+ ref: state === "ready" ? ref : "",
157
+ };
158
+ }
159
+
160
+ /** Why Send is refused while attachments are pending, or "" when it is not.
161
+ *
162
+ * An upload still running would be dropped from the message, and a failed one
163
+ * would be sent as if the owner had never picked it — in both cases the
164
+ * assistant answers about a photo it never received. So the send waits, and
165
+ * says what it is waiting for. */
166
+ export function sendBlocker(chips: readonly AriaAttachmentChip[]): "" | "uploading" | "failed" {
167
+ if (chips.some((c) => c.state === "uploading")) return "uploading";
168
+ if (chips.some((c) => c.state === "failed")) return "failed";
169
+ return "";
170
+ }
171
+
172
+ /** The one line a bubble shows for its attachments: a paperclip and the file
173
+ * names. Text, not thumbnails, and not by choice: a `genie-upload://`
174
+ * reference has no published download url, so a sent photo cannot be shown
175
+ * again from the server's copy — and a resumed conversation has nothing else. */
176
+ export function attachmentLine(names: readonly string[]): string {
177
+ const shown = names.map((n) => n.trim()).filter(Boolean);
178
+ return shown.length ? `📎 ${shown.join(", ")}` : "";
179
+ }