your-ai-workflow-firebase-os 2.0.8 → 2.0.9

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 (139) hide show
  1. package/dist/{Assets-Dd_96avv.js → Assets-COH4Ji57.js} +5 -5
  2. package/dist/{Assets-CBBNIHrw.cjs → Assets-D7fDckSi.cjs} +1 -1
  3. package/dist/{Attachments-C6QrTcNt.cjs → Attachments-BMuQ0CD4.cjs} +1 -1
  4. package/dist/{Attachments-YyjQmhBx.js → Attachments-Co1UkdWB.js} +2 -2
  5. package/dist/{Billing-B6f49cGH.cjs → Billing-DkuJdls5.cjs} +1 -1
  6. package/dist/{Billing-BZafAizg.js → Billing-kk4ZMd3z.js} +4 -4
  7. package/dist/{BookingCalendar-DYsA71vl.cjs → BookingCalendar-BcvJiyaq.cjs} +1 -1
  8. package/dist/{BookingCalendar-_xoRa2tL.js → BookingCalendar-Bej__mIs.js} +1 -1
  9. package/dist/{Button-DSldWACF.js → Button-BGN6q7lY.js} +1 -1
  10. package/dist/{Button-PyTaaoyu.cjs → Button-Ccy4wFZ1.cjs} +1 -1
  11. package/dist/{ConfirmModal-CQ73WYUg.cjs → ConfirmModal-BUNtss4a.cjs} +1 -1
  12. package/dist/{ConfirmModal-C1dM-bzk.js → ConfirmModal-BW9e6Y7d.js} +3 -3
  13. package/dist/{ContactPopup-D5Hh0bfE.cjs → ContactPopup-D8aSSlmS.cjs} +1 -1
  14. package/dist/{ContactPopup-CL4AMwK9.js → ContactPopup-Fs7wzrOX.js} +7 -7
  15. package/dist/{CopyLink-BnYpVy6H.cjs → CopyLink-C5lKLyOU.cjs} +1 -1
  16. package/dist/{CopyLink-rLr_5eNM.js → CopyLink-CNjDLzOl.js} +2 -2
  17. package/dist/{DashboardNav-B0G943ux.js → DashboardNav-CEd8gGPm.js} +3 -3
  18. package/dist/{DashboardNav-DOl9gJuu.cjs → DashboardNav-GgEUpyTa.cjs} +1 -1
  19. package/dist/{EmailAdmin-CWHMMybh.cjs → EmailAdmin-d_gWd48U.cjs} +1 -1
  20. package/dist/{EmailAdmin-humrSTzI.js → EmailAdmin-pbQ-f9Ge.js} +9 -9
  21. package/dist/{EventCard-CrPswwyG.js → EventCard-BSGLYD64.js} +2 -2
  22. package/dist/{EventCard-6eCfvOA-.cjs → EventCard-DAsGsicK.cjs} +1 -1
  23. package/dist/{FileViewerPopup-DQmhXMSg.cjs → FileViewerPopup-CkLUYCNo.cjs} +1 -1
  24. package/dist/{FileViewerPopup-Dnl3td2o.js → FileViewerPopup-gqhRT22v.js} +2 -2
  25. package/dist/{FormNote-BWGLs9lx.js → FormNote-DJ9HBokL.js} +1 -1
  26. package/dist/{FormNote-CX2rSBDB.cjs → FormNote-DP0GK9ya.cjs} +1 -1
  27. package/dist/{FormsAdmin-Tg20B0iA.cjs → FormsAdmin-5KTTp6zo.cjs} +1 -1
  28. package/dist/{FormsAdmin-CHwmKnYU.js → FormsAdmin-BNI63vEk.js} +9 -9
  29. package/dist/{Inbox-Cgq4MALU.js → Inbox-D9Y5K8Se.js} +9 -9
  30. package/dist/{Inbox-BPwIiThg.cjs → Inbox-DjslBakx.cjs} +1 -1
  31. package/dist/{Loader-DP_dL5qC.js → Loader-CVoz1Zf0.js} +1 -1
  32. package/dist/{Loader-p3eNQDUU.cjs → Loader-QHqQsXLr.cjs} +1 -1
  33. package/dist/{ManagePlanButton-BRW7Sevd.cjs → ManagePlanButton-Buwfs2bU.cjs} +1 -1
  34. package/dist/{ManagePlanButton-CNLCxK-o.js → ManagePlanButton-CE4uyh_7.js} +2 -2
  35. package/dist/{MyBookings-C_pqsSEk.js → MyBookings-B4DndHV6.js} +7 -7
  36. package/dist/{MyBookings-BC5sn_fE.cjs → MyBookings-CkEioQIj.cjs} +1 -1
  37. package/dist/{PageTitle-DFGW71q8.js → PageTitle-DCoUQWw6.js} +2 -2
  38. package/dist/{PageTitle-BWmE74WQ.cjs → PageTitle-DQkvCB11.cjs} +1 -1
  39. package/dist/PagesAdmin-D0qgoZtW.js +3 -0
  40. package/dist/{PagesAdmin-jjnDLYmF.js → PagesAdmin-DzLJWKBJ.js} +21 -21
  41. package/dist/PagesAdmin-KUxCI1Mr.cjs +1 -0
  42. package/dist/{PagesAdmin-BZZ3by92.cjs → PagesAdmin-YdYgmuEB.cjs} +1 -1
  43. package/dist/{PaymentsAdmin-CI0zXraR.cjs → PaymentsAdmin-B5l-f0mp.cjs} +1 -1
  44. package/dist/{PaymentsAdmin-DeBMmFFo.js → PaymentsAdmin-C3cEOeIs.js} +15 -15
  45. package/dist/{Popup-DmPEk98h.js → Popup-Bi63lzoS.js} +1 -1
  46. package/dist/{Popup-nq7YNM4N.cjs → Popup-COnHpbrY.cjs} +1 -1
  47. package/dist/{ProviderMark-DJ5mYjYn.cjs → ProviderMark--adeeajP.cjs} +1 -1
  48. package/dist/{ProviderMark-CAeOnBsB.js → ProviderMark-BvEMlZAm.js} +1 -1
  49. package/dist/{PublishApp-mMMwf_in.js → PublishApp-B9Fo6xN0.js} +7 -7
  50. package/dist/{PublishApp-DI96gX9b.cjs → PublishApp-Bc82Ijkq.cjs} +1 -1
  51. package/dist/{Said-DAD7Ctp_.js → Said-BvSRaEfZ.js} +1 -1
  52. package/dist/{Said-DlJOjNMN.cjs → Said-W3KFi5uX.cjs} +1 -1
  53. package/dist/{ScheduleAdmin-dWj_N_tf.cjs → ScheduleAdmin-Dh_hW6p4.cjs} +1 -1
  54. package/dist/{ScheduleAdmin-C4W8Ls0D.js → ScheduleAdmin-bk3GgxKP.js} +14 -14
  55. package/dist/{SchedulePicker-CwKId9JF.cjs → SchedulePicker-CN0755Ek.cjs} +1 -1
  56. package/dist/{SchedulePicker-BOSJEcfL.js → SchedulePicker-DO4PewPJ.js} +2 -2
  57. package/dist/{Setup-DcZzh6Xd.cjs → Setup-R4r6_tbV.cjs} +1 -1
  58. package/dist/{Setup-NOvtplUS.js → Setup-_G-s5y5Q.js} +3 -3
  59. package/dist/{SetupPayments-DrXK0LQa.js → SetupPayments-CkolcOtv.js} +5 -5
  60. package/dist/{SetupPayments-CZ0KqXdR.cjs → SetupPayments-CuFk9REC.cjs} +1 -1
  61. package/dist/{SignInAdmin-CwIUAJrv.cjs → SignInAdmin-B1yLLaQu.cjs} +1 -1
  62. package/dist/{SignInAdmin-Cb65ChHL.js → SignInAdmin-BLEn0rId.js} +9 -9
  63. package/dist/{SupportBar-528kXe-Z.js → SupportBar-DI_XbEsG.js} +4 -4
  64. package/dist/{SupportBar-CNVXSYwT.cjs → SupportBar-DLWruzQb.cjs} +1 -1
  65. package/dist/{SystemTabContent-EegzTuLd.cjs → SystemTabContent-BWhRHoXx.cjs} +1 -1
  66. package/dist/{SystemTabContent-C4azXKMs.js → SystemTabContent-CMyT7mDN.js} +2 -2
  67. package/dist/{TemplateDrive-XDRPkxIj.cjs → TemplateDrive-BJN-ReA_.cjs} +1 -1
  68. package/dist/{TemplateDrive-CfPXKMKX.js → TemplateDrive-DNBwL0oy.js} +10 -10
  69. package/dist/{ThemeAdmin-XLbC9fX7.js → ThemeAdmin-Cnqefrz6.js} +6 -6
  70. package/dist/ThemeAdmin-CoE_c5-P.cjs +1 -0
  71. package/dist/ThemeAdmin-DI7QaZ-0.js +2 -0
  72. package/dist/{ThemeAdmin-BrEozdpZ.cjs → ThemeAdmin-DkgmjW3g.cjs} +1 -1
  73. package/dist/TimeZoneField-Ckn1pKAL.cjs +11 -0
  74. package/dist/{TimeZoneField-Dv389mAe.js → TimeZoneField-oIMwb5_X.js} +119 -107
  75. package/dist/{TransactionsAdmin-X-NjI4NB.js → TransactionsAdmin-Brs-CEcG.js} +11 -11
  76. package/dist/{TransactionsAdmin-DgsG629u.cjs → TransactionsAdmin-C5wQRR1g.cjs} +1 -1
  77. package/dist/{Trouble-qjWYeKzC.js → Trouble-CtFq7tjk.js} +1 -1
  78. package/dist/{Trouble-DokPNHQZ.cjs → Trouble-KQZyf6aY.cjs} +1 -1
  79. package/dist/{Users-BEjl_Vjc.cjs → Users-B16L9cKI.cjs} +1 -1
  80. package/dist/{Users-BUGb7-Kz.js → Users-CbI4K_Le.js} +15 -15
  81. package/dist/{VideoPlayer-BVOkrJBR.cjs → VideoPlayer-CggwpVXH.cjs} +1 -1
  82. package/dist/{VideoPlayer-DB0oz6lM.js → VideoPlayer-QI1Jc3bS.js} +1 -1
  83. package/dist/appIcons-C90OKlV5.cjs +130 -0
  84. package/dist/{appIcons-CUXAX_gJ.js → appIcons-CCArfCDe.js} +761 -735
  85. package/dist/{arrivalRoads-BZk39p8Z.js → arrivalRoads-CGXm9Ntm.js} +0 -0
  86. package/dist/{arrivalRoads-CD7pt03E.cjs → arrivalRoads-Cc6AZM-d.cjs} +0 -0
  87. package/dist/{bookingPages-BxqkyZ-M.js → bookingPages-BFSY3Riv.js} +1 -1
  88. package/dist/{bookingPages-2fn9xPiA.cjs → bookingPages-Dx1ViiwW.cjs} +1 -1
  89. package/dist/{contentFallback-C6xLwXTs.js → contentFallback-BGGpI1Ts.js} +2 -2
  90. package/dist/{contentFallback-DLm_iHo9.cjs → contentFallback-DVCrIUvK.cjs} +1 -1
  91. package/dist/{email-DxxovRtf.js → email-PBHbXXgR.js} +1 -1
  92. package/dist/{email-DD48BP42.cjs → email-Sya6gl4F.cjs} +1 -1
  93. package/dist/lib/basket.d.ts +29 -9
  94. package/dist/lib/recordPrice.d.ts +1 -1
  95. package/dist/{notices-Cwon5Q0Y.cjs → notices-CKm9-Pqt.cjs} +1 -1
  96. package/dist/{notices-C4yqYzBw.js → notices-CysS7BLF.js} +1 -1
  97. package/dist/{ownerMail-CGmDk7aP.cjs → ownerMail-BS9SjJ2u.cjs} +1 -1
  98. package/dist/{ownerMail-Chzy5Ehy.js → ownerMail-DepG5-Qw.js} +2 -2
  99. package/dist/{pageFiles-BClshvXs.cjs → pageFiles-Cn0YQ4Ur.cjs} +1 -1
  100. package/dist/{pageFiles-D-GYqRgZ.js → pageFiles-ZFPSCDZz.js} +1 -1
  101. package/dist/{paidInFull-KuStok9_.cjs → paidInFull-CZywIs-V.cjs} +1 -1
  102. package/dist/{paidInFull-D14Eh0QV.js → paidInFull-mBOh9iwN.js} +1 -1
  103. package/dist/{paidSessions-Cr_93LGa.js → paidSessions-B8e5RHi4.js} +7 -7
  104. package/dist/{paidSessions-QXv7SHeb.cjs → paidSessions-D-zHC4oG.cjs} +1 -1
  105. package/dist/{people-D8nPPMlM.cjs → people-BwWVVLfy.cjs} +1 -1
  106. package/dist/{people-sk2DcHPi.js → people-Wk2opW4G.js} +1 -1
  107. package/dist/{publishSteps-DB1Hxr6H.js → publishSteps-Cf5pSB_H.js} +1 -1
  108. package/dist/{publishSteps-CaZRpC3C.cjs → publishSteps-DvWlF0p_.cjs} +1 -1
  109. package/dist/{recordLock-Dnz61J-_.js → recordLock-CMOiezNC.js} +1 -1
  110. package/dist/{recordLock-CLUp3QNh.cjs → recordLock-CiulizO1.cjs} +1 -1
  111. package/dist/{recordsKey-R5BjLoGQ.cjs → recordsKey-C4E2vOti.cjs} +1 -1
  112. package/dist/{recordsKey-DHJJWWOo.js → recordsKey-Cd216nq1.js} +2 -2
  113. package/dist/{registrationFields-COv8jW4n.cjs → registrationFields-DHHTptcl.cjs} +1 -1
  114. package/dist/{registrationFields-CeTZSerx.js → registrationFields-DdHoMPAQ.js} +1 -1
  115. package/dist/renderer.cjs.js +1 -1
  116. package/dist/renderer.es.js +1 -1
  117. package/dist/{roles-5juCr5sY.cjs → roles-BBDIJzox.cjs} +1 -1
  118. package/dist/{roles-hzcMnn_M.js → roles-DiwP6-WQ.js} +3 -3
  119. package/dist/{scheduleData-BpkfnBjP.cjs → scheduleData-BXjNXVHY.cjs} +1 -1
  120. package/dist/{scheduleData-BLCUm84s.js → scheduleData-DR6A-Q7Z.js} +1 -1
  121. package/dist/server.cjs.js +1 -1
  122. package/dist/server.es.js +1 -1
  123. package/dist/{serviceStatus-5sk8MPjW.js → serviceStatus-C9izcuQz.js} +2 -2
  124. package/dist/{serviceStatus-1pGLLilF.cjs → serviceStatus-Cde96_OO.cjs} +1 -1
  125. package/dist/{serviceSwitches-BWnZBooS.js → serviceSwitches-CZHwvVfP.js} +1 -1
  126. package/dist/{serviceSwitches-Do_pkFCu.cjs → serviceSwitches-mhJbQBrV.cjs} +1 -1
  127. package/dist/vite.cjs.js +1 -1
  128. package/dist/vite.es.js +1 -1
  129. package/dist/your-ai-workflow-firebase-os.cjs.js +8 -8
  130. package/dist/your-ai-workflow-firebase-os.css +1 -1
  131. package/dist/your-ai-workflow-firebase-os.es.js +358 -316
  132. package/package.json +1 -1
  133. package/src/index.css +41 -6
  134. package/dist/PagesAdmin-B-XopEi9.js +0 -3
  135. package/dist/PagesAdmin-nL5ZltFn.cjs +0 -1
  136. package/dist/ThemeAdmin-CwCiTp2I.js +0 -2
  137. package/dist/ThemeAdmin-Db5edTYj.cjs +0 -1
  138. package/dist/TimeZoneField-zR8N2Fj0.cjs +0 -11
  139. package/dist/appIcons-BNovDw6_.cjs +0 -130
@@ -136,23 +136,23 @@ var S = (e) => e.filter((e) => e.kind !== "other" && e.kind !== "helper"), te =
136
136
  "md",
137
137
  "dedent",
138
138
  "outdent"
139
- ]), D = (e, t) => {
139
+ ]), ne = (e, t) => {
140
140
  if (!t || T.has(t)) return !1;
141
141
  let n = t.replace(/\$/g, "\\$");
142
142
  return RegExp(`\\bimport\\b[^;'"\\\`]*?[{,\\s]${n}\\b[^;'"\\\`]*?\\bfrom\\s*['"]|\\b(?:const|let|var)\\s+(?:[{[][^=]*?\\b)?${n}\\b|\\bfunction\\s+${n}\\b`).test(e);
143
- }, O = (e, t) => {
143
+ }, D = (e, t) => {
144
144
  let { char: n, word: r, place: i } = w(e, t);
145
145
  return !!n && "(=:[!&|?{;".includes(n) || n === ">" && e[i - 1] === "=" || T.has(r);
146
- }, ne = (e, t, n = -1, r = -1, i = () => e) => {
146
+ }, re = (e, t, n = -1, r = -1, i = () => e) => {
147
147
  if (/[\w$]/.test(e[t - 1] || "")) {
148
148
  let { word: n } = w(e, t), r = t - n.length;
149
- return e[r - 1] === "." || E.has(n) || D(e, n) && O(e, r);
149
+ return e[r - 1] === "." || E.has(n) || ne(e, n) && D(e, r);
150
150
  }
151
151
  let a = e, o = w(e, t);
152
152
  o.place >= n && o.place < r && (a = i(), o = w(a, a.length));
153
153
  let { char: s, word: c, place: l } = o, u = l + 1 - c.length;
154
- return !/[\w$]/.test(s) || T.has(c) || (E.has(c) || D(e, c)) && O(a, u);
155
- }, k = (e, t) => {
154
+ return !/[\w$]/.test(s) || T.has(c) || (E.has(c) || ne(e, c)) && D(a, u);
155
+ }, O = (e, t) => {
156
156
  let n = e[t + 1];
157
157
  if (n === "/" || n === "*" || n === ">") return -1;
158
158
  let { char: r, word: i, lineStart: a, place: o } = w(e, t), s = r === ">" && e[o - 1] === "=";
@@ -177,13 +177,13 @@ var S = (e) => e.filter((e) => e.kind !== "other" && e.kind !== "helper"), te =
177
177
  }
178
178
  }
179
179
  return -1;
180
- }, re = (e, t) => {
180
+ }, ie = (e, t) => {
181
181
  let { char: n, word: r, place: i } = w(e, t < 0 ? e.length : t);
182
182
  return r === "from" || r === "import" ? !0 : n === "(" && w(e, i).word === "import";
183
- }, ie = (e, t, n, r) => {
183
+ }, ae = (e, t, n, r) => {
184
184
  let i = t.slice(Math.max(n, r - 200), r);
185
185
  return i.length >= 200 || n <= r - 200 ? i : e.slice(-(200 - i.length)) + i;
186
- }, ae = (e, t) => {
186
+ }, oe = (e, t) => {
187
187
  let n = "", r = 0, i = "", a = -1, o = -1, s = (t, a, o = "") => {
188
188
  let s = e.slice(r, t) + o;
189
189
  n += s, i = (i + s).slice(-200), r = a;
@@ -218,20 +218,20 @@ var S = (e) => e.filter((e) => e.kind !== "other" && e.kind !== "helper"), te =
218
218
  continue;
219
219
  }
220
220
  if (p === "/") {
221
- let t = k(e, f);
221
+ let t = O(e, f);
222
222
  t > 0 && (f = t);
223
223
  continue;
224
224
  }
225
225
  if (p === "`") {
226
- if (!ne(e, f, a, o, () => n)) continue;
226
+ if (!re(e, f, a, o, () => n)) continue;
227
227
  c.push(0);
228
228
  } else if (p === "'" || p === "\"") {
229
229
  if (/[A-Za-z0-9]/.test(e[f - 1] || "")) continue;
230
- l = p, u = f, d = t && !re(ie(i, e, r, f), -1);
230
+ l = p, u = f, d = t && !ie(ae(i, e, r, f), -1);
231
231
  } else c.length && (p === "{" ? c[c.length - 1]++ : p === "}" && c[c.length - 1]--);
232
232
  }
233
233
  return l && d && s(u + 1, e.length), n + e.slice(r);
234
- }, oe = (e) => [...ae(e, !0).matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"](\.{1,2}\/[^'"]+)['"]/g)].map((e) => e[1]), se = (e, t) => {
234
+ }, se = (e) => [...oe(e, !0).matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"](\.{1,2}\/[^'"]+)['"]/g)].map((e) => e[1]), ce = (e, t) => {
235
235
  let i = r.resolve(r.dirname(e), t);
236
236
  return [
237
237
  i,
@@ -241,7 +241,7 @@ var S = (e) => e.filter((e) => e.kind !== "other" && e.kind !== "helper"), te =
241
241
  r.join(i, "index.ts")
242
242
  ].find((e) => /\.tsx?$/.test(e) && n.existsSync(e) && n.statSync(e).isFile()) ?? null;
243
243
  };
244
- function A(e, t, n) {
244
+ function k(e, t, n) {
245
245
  let i = r.join(e, "src"), o = new Set(t.map((e) => r.resolve(e.abs))), u = new Set(["App.tsx", "main.tsx"].map((e) => r.join(i, e)).concat(r.join(e, s))), f = [], p = t.filter((e) => e.kind === "microcomponent" || e.file === c || e.file === l).map((e) => e.abs);
246
246
  for (let t = 0; t < te && p.length && f.length < C; t++) {
247
247
  let t = [];
@@ -252,8 +252,8 @@ function A(e, t, n) {
252
252
  } catch {
253
253
  continue;
254
254
  }
255
- for (let n of oe(s)) {
256
- let s = se(i, n);
255
+ for (let n of se(s)) {
256
+ let s = ce(i, n);
257
257
  if (!s || o.has(s) || u.has(s)) continue;
258
258
  let c = d(r.relative(e, s));
259
259
  if (!(!c.startsWith("src/") || c.startsWith(`${a}/`) || c.includes("/node_modules/")) && (o.add(s), f.push({
@@ -269,24 +269,24 @@ function A(e, t, n) {
269
269
  }
270
270
  return f;
271
271
  }
272
- var j = [{
272
+ var A = [{
273
273
  test: (e) => /Content\.tsx$/.test(e),
274
274
  home: o
275
275
  }, {
276
276
  test: (e) => /(Tab|Page|Form|Registration)\.config\.tsx?$/.test(e),
277
277
  home: a
278
- }], M = new Set([
278
+ }], le = new Set([
279
279
  "node_modules",
280
280
  ".git",
281
281
  "dist",
282
282
  "build"
283
- ]), N = (e, t) => r.relative(e, t).split(r.sep).some((e) => M.has(e)), P = [
283
+ ]), j = (e, t) => r.relative(e, t).split(r.sep).some((e) => le.has(e)), M = [
284
284
  "index.tsx",
285
285
  "App.tsx",
286
286
  "src/index.tsx",
287
287
  "src/main.tsx",
288
288
  "src/App.tsx"
289
- ], F = /\.(?:tsx?|jsx?)$/, ce = (e, t, i) => {
289
+ ], N = /\.(?:tsx?|jsx?)$/, P = (e, t, i) => {
290
290
  let a = i.startsWith("@/") ? [r.join(e, i.slice(2)), r.join(e, "src", i.slice(2))] : i.startsWith("src/") ? [r.join(e, i)] : [r.resolve(r.dirname(t), i)];
291
291
  for (let e of a) {
292
292
  let t = e.replace(/\.(?:jsx?|tsx?)$/, ""), i = [
@@ -301,11 +301,11 @@ var j = [{
301
301
  "index.jsx",
302
302
  "index.js"
303
303
  ].map((t) => r.join(e, t))
304
- ].find((e) => F.test(e) && n.existsSync(e) && n.statSync(e).isFile());
304
+ ].find((e) => N.test(e) && n.existsSync(e) && n.statSync(e).isFile());
305
305
  if (i) return i;
306
306
  }
307
307
  return null;
308
- }, le = (e) => [...ae(e, !0).matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"]((?:\.{1,2}|@|src)\/[^'"]+)['"]/g)].map((e) => e[1]), ue = (e) => !/\.[A-Za-z0-9]+$/.test(e) || F.test(e), de = (e, t, i) => {
308
+ }, F = (e) => [...oe(e, !0).matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"]((?:\.{1,2}|@|src)\/[^'"]+)['"]/g)].map((e) => e[1]), ue = (e) => !/\.[A-Za-z0-9]+$/.test(e) || N.test(e), de = (e, t, i) => {
309
309
  if (i.startsWith("@/")) return !0;
310
310
  let a = i.startsWith("src/") ? r.join(e, i) : r.resolve(r.dirname(t), i);
311
311
  return n.existsSync(a) ? !0 : [
@@ -315,7 +315,7 @@ var j = [{
315
315
  ".cts"
316
316
  ].some((e) => n.existsSync(`${a}${e}`));
317
317
  }, fe = (e) => {
318
- let t = [...ee(e).map((e) => r.resolve(e.abs)), ...P.map((t) => r.resolve(e, t)).filter((e) => n.existsSync(e))], i = new Set(t), a = [...i], o = !0;
318
+ let t = [...ee(e).map((e) => r.resolve(e.abs)), ...M.map((t) => r.resolve(e, t)).filter((e) => n.existsSync(e))], i = new Set(t), a = [...i], o = !0;
319
319
  for (; a.length;) {
320
320
  let t = a.pop(), r = "";
321
321
  try {
@@ -323,13 +323,13 @@ var j = [{
323
323
  } catch {
324
324
  continue;
325
325
  }
326
- for (let n of le(r)) {
327
- let r = ce(e, t, n);
326
+ for (let n of F(r)) {
327
+ let r = P(e, t, n);
328
328
  if (!r) {
329
329
  ue(n) && de(e, t, n) && (o = !1);
330
330
  continue;
331
331
  }
332
- i.has(r) || N(e, r) || (i.add(r), a.push(r));
332
+ i.has(r) || j(e, r) || (i.add(r), a.push(r));
333
333
  }
334
334
  }
335
335
  return {
@@ -341,18 +341,18 @@ var j = [{
341
341
  if (!(o > 8 || !n.existsSync(a))) for (let s of n.readdirSync(a, { withFileTypes: !0 })) {
342
342
  let c = r.join(a, s.name);
343
343
  if (s.isDirectory()) {
344
- M.has(s.name) || i(c, o + 1);
344
+ le.has(s.name) || i(c, o + 1);
345
345
  continue;
346
346
  }
347
- if (!F.test(s.name)) continue;
347
+ if (!N.test(s.name)) continue;
348
348
  let l = "";
349
349
  try {
350
350
  l = n.readFileSync(c, "utf8");
351
351
  } catch {
352
352
  continue;
353
353
  }
354
- for (let n of le(l)) {
355
- let r = ce(e, c, n);
354
+ for (let n of F(l)) {
355
+ let r = P(e, c, n);
356
356
  r && t.add(r);
357
357
  }
358
358
  }
@@ -364,11 +364,11 @@ function me(e) {
364
364
  if (!(c > 6 || !n.existsSync(t))) for (let l of n.readdirSync(t, { withFileTypes: !0 })) {
365
365
  let n = r.join(t, l.name), u = d(r.relative(e, n));
366
366
  if (l.isDirectory()) {
367
- M.has(l.name) || s(n, c + 1);
367
+ le.has(l.name) || s(n, c + 1);
368
368
  continue;
369
369
  }
370
370
  if (u === `${a}/${l.name}` || u === `${o}/${l.name}`) continue;
371
- let f = j.find(({ test: e }) => e(l.name));
371
+ let f = A.find(({ test: e }) => e(l.name));
372
372
  f && i.push({
373
373
  abs: n,
374
374
  rel: u,
@@ -384,9 +384,9 @@ function me(e) {
384
384
  }
385
385
  var he = (e) => {
386
386
  let t = r.basename(e);
387
- return j.some((e) => e.test(t));
387
+ return A.some((e) => e.test(t));
388
388
  }, I = (e) => JSON.stringify(e), ge = "// Self-register globally so the library can read it without props.\n// MERGE, never replace: a plain assignment here or in the library's own\n// registries silently wiped every consumer-defined page, form and\n// microcomponent. The project's entries win, whichever ran first — the\n// library's registries let existing entries win, so they never undo these.\nif (typeof window !== 'undefined') {\n window.__FBOS_PAGES__ = { ...(window.__FBOS_PAGES__ || {}), ...pages };\n window.__FBOS_FORMS__ = { ...(window.__FBOS_FORMS__ || {}), ...forms };\n window.__FBOS_REGISTRATION__ = { ...(window.__FBOS_REGISTRATION__ || {}), ...registration };\n window.__FBOS_MICROCOMPONENTS__ = { ...(window.__FBOS_MICROCOMPONENTS__ || {}), ...microcomponents };\n}";
389
- function L(e, t = {}) {
389
+ function _e(e, t = {}) {
390
390
  let n = [], r = [], a = [], o = [], s = [], c = S(e);
391
391
  return t.serve && n.push(`const loaded = await Promise.allSettled([
392
392
  ${c.map((e) => ` import(${I(d(e.abs))}),`).join("\n")}
@@ -495,7 +495,7 @@ export const registrationRegistry = registration;
495
495
  export const microcomponentRegistry = microcomponents;
496
496
  `;
497
497
  }
498
- var _e = (e) => L(ee(e)), ve = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b|generativelanguage\.googleapis\.com|api\.anthropic\.com|api\.openai\.com|@google\/genai|@google\/generative-ai|@anthropic-ai\/sdk|from\s+['"]openai['"]|(?:GEMINI_API_KEY|GEMINI_KEY|CLAUDE_KEY|OPENAI_KEY|ANTHROPIC_API_KEY|OPENAI_API_KEY)\b|fetch\(\s*['"`]\/api\/ai/, ye = (e, t) => `${e}: ${t.trim()} puts AI on a page, and this app has no AI for pages right now — delete it and everything that draws it, and build the rest of the page as normal. Nothing is exported under useAiChat, askAi, useAiEnabled or useAiModels, and a page never calls Gemini, Claude or OpenAI itself or holds a key: a key in a page is published to everyone who opens it. If the owner asked for AI, tell them the app does not put AI on pages yet.`, be = (e) => e.match(ve)?.[0] ?? null, R = /* @__PURE__ */ "AddressField.AddressParts.AdminNotifications.ArrivingEntry.ArrivingPayment.Article.ArticleProps.Bars.BarsItem.BarsProps.Basket.BasketButton.BasketButtonProps.BasketItem.BasketLine.BasketProps.BasketState.BlockedDate.Booking.BookingsState.Button.BuyButton.BuyButtonProps.Buyer.BuyerDetails.BuyerDetailsProps.CancelInput.Card.CaseRecord.CaseState.CasesFromFormOptions.CasesState.Checkout.CheckoutExtra.CheckoutItem.CheckoutOptions.CheckoutProps.CheckoutReturn.CheckoutReturnProps.ConfirmModal.ConfirmModalProps.ContactPopup.CountLimits.CustomSelect.DashboardNav.DataTable.DataTableColumn.DataTableProps.DayKey.EMPTY_ADDRESS.EVERYONE.EmailLook.EmailOptions.EmailStatus.EventCard.FileViewer.FileViewerPopup.FileViewerPopupProps.FileViewerProps.FirebaseOS.FirebaseOSConfig.FirebaseOSProps.HandOver.HandOverProps.History.HistoryLine.HistoryMore.HistoryProps.HistoryState.Holder.Input.Lightbox.LightboxPicture.LightboxProps.Loader.LoaderProps.MadeBy.MadeByProps.ManagePlanButton.MemberSchedule.MenuPage.MyPayment.MyPaymentsState.MySubscription.Navbar.NotifyInput.NumberStepper.NumberStepperProps.OrderRecord.PayButton.PaymentConfirmation.PeopleState.Person.PhoneInputField.Picture.PictureProps.Pipeline.PipelineProps.PipelineStage.PlanOnOffer.PlanPrice.PlanRhythm.Plans.PlansProps.PlansState.Popup.PopupProps.PublicMenu.PublicMenuProps.Reveal.RevealList.RevealListProps.RevealProps.SaleCharge.SaleSubscription.SalesState.SayOptions.SchedulesState.SectionHeading.SendTarget.Skeleton.Slot.SlotOptions.Sortable.SortableProps.StepBar.StepBarProps.StepInfo.StepsState.Stock.StockChange.StockFromOrdersOptions.StockOrder.StockVariant.Stocked.Swap.SwapProps.Swatch.SwatchField.SwatchFieldProps.SwatchProps.TALLY_COUNTERS.TakenTime.Tallies.TallyCounter.TallyCounts.TextArea.TextAreaProps.TimeChips.TimeWindow.UploadBox.UploadBoxProps.VideoPlayer.VideoPlayerProps.acceptBooking.auth.availableSlots.availableSlotsFor.barShares.blockedWindows.bookingPath.canEdit.canSee.cancelBooking.composeAddress.confirmPayment.countOf.dayBlocked.daysLine.db.emailAnyone.emailOwner.emailPerson.emailStatus.eventName.forPerson.forRole.hasAddress.historyCollection.holderOf.hoursSummary.isPublished.keysOf.logHistory.madeBy.meetPath.money.notify.oneEach.openBillingPortal.partySlots.payPath.publish.registerMicrocomponent.say.scheduleName.scheduleTitle.sendTo.shadeOf.shadesOf.slotsFor.startCheckout.storage.takeBack.tally.timeZoneLabel.togetherSlots.troubleSentence.unpublish.unsay.useAuth.useBasket.useBookings.useCase.useCases.useCasesFromForm.useConfig.useHistory.useMemberSchedules.useMyBookings.useMyPayments.usePaymentsEnabled.usePeople.usePlans.usePublicCopy.usePublished.useSales.useSteps.useStock.useStockFromOrders.useTakenSlots.useTallies.useTeamHours.useTheme.useVisitorState.whenWords.withAccess.withStockSetAt".split("."), z = (e) => {
498
+ var ve = (e) => _e(ee(e)), ye = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b|generativelanguage\.googleapis\.com|api\.anthropic\.com|api\.openai\.com|@google\/genai|@google\/generative-ai|@anthropic-ai\/sdk|from\s+['"]openai['"]|(?:GEMINI_API_KEY|GEMINI_KEY|CLAUDE_KEY|OPENAI_KEY|ANTHROPIC_API_KEY|OPENAI_API_KEY)\b|fetch\(\s*['"`]\/api\/ai/, be = (e, t) => `${e}: ${t.trim()} puts AI on a page, and this app has no AI for pages right now — delete it and everything that draws it, and build the rest of the page as normal. Nothing is exported under useAiChat, askAi, useAiEnabled or useAiModels, and a page never calls Gemini, Claude or OpenAI itself or holds a key: a key in a page is published to everyone who opens it. If the owner asked for AI, tell them the app does not put AI on pages yet.`, xe = (e) => e.match(ye)?.[0] ?? null, L = /* @__PURE__ */ "AddressField.AddressParts.AdminNotifications.ArrivingEntry.ArrivingPayment.Article.ArticleProps.Bars.BarsItem.BarsProps.Basket.BasketButton.BasketButtonProps.BasketItem.BasketLine.BasketProps.BasketState.BlockedDate.Booking.BookingsState.Button.BuyButton.BuyButtonProps.Buyer.BuyerDetails.BuyerDetailsProps.CancelInput.Card.CaseRecord.CaseState.CasesFromFormOptions.CasesState.Checkout.CheckoutExtra.CheckoutItem.CheckoutOptions.CheckoutProps.CheckoutReturn.CheckoutReturnProps.ConfirmModal.ConfirmModalProps.ContactPopup.CountLimits.CustomSelect.DashboardNav.DataTable.DataTableColumn.DataTableProps.DayKey.EMPTY_ADDRESS.EVERYONE.EmailLook.EmailOptions.EmailStatus.EventCard.FileViewer.FileViewerPopup.FileViewerPopupProps.FileViewerProps.FirebaseOS.FirebaseOSConfig.FirebaseOSProps.HandOver.HandOverProps.History.HistoryLine.HistoryMore.HistoryProps.HistoryState.Holder.Input.Lightbox.LightboxPicture.LightboxProps.Loader.LoaderProps.MadeBy.MadeByProps.ManagePlanButton.MemberSchedule.MenuPage.MyPayment.MyPaymentsState.MySubscription.Navbar.NotifyInput.NumberStepper.NumberStepperProps.OrderRecord.PayButton.PaymentConfirmation.PeopleState.Person.PhoneInputField.Picture.PictureProps.Pipeline.PipelineProps.PipelineStage.PlanOnOffer.PlanPrice.PlanRhythm.Plans.PlansProps.PlansState.Popup.PopupProps.PublicMenu.PublicMenuProps.Reveal.RevealList.RevealListProps.RevealProps.SaleCharge.SaleSubscription.SalesState.SayOptions.SchedulesState.SectionHeading.SendTarget.Skeleton.Slot.SlotOptions.Sortable.SortableProps.StepBar.StepBarProps.StepInfo.StepsState.Stock.StockChange.StockFromOrdersOptions.StockOrder.StockVariant.Stocked.Swap.SwapProps.Swatch.SwatchField.SwatchFieldProps.SwatchProps.TALLY_COUNTERS.TakenTime.Tallies.TallyCounter.TallyCounts.TextArea.TextAreaProps.TimeChips.TimeWindow.UploadBox.UploadBoxProps.VideoPlayer.VideoPlayerProps.acceptBooking.auth.availableSlots.availableSlotsFor.barShares.blockedWindows.bookingPath.canEdit.canSee.cancelBooking.composeAddress.confirmPayment.countOf.dayBlocked.daysLine.db.emailAnyone.emailOwner.emailPerson.emailStatus.eventName.forPerson.forRole.hasAddress.historyCollection.holderOf.hoursSummary.isPublished.keysOf.logHistory.madeBy.meetPath.money.notify.oneEach.openBillingPortal.partySlots.payPath.publish.registerMicrocomponent.say.scheduleName.scheduleTitle.sendTo.shadeOf.shadesOf.slotsFor.startCheckout.storage.takeBack.tally.timeZoneLabel.togetherSlots.troubleSentence.unpublish.unsay.useAuth.useBasket.useBookings.useCase.useCases.useCasesFromForm.useConfig.useHistory.useMemberSchedules.useMyBookings.useMyPayments.usePaymentsEnabled.usePeople.usePlans.usePublicCopy.usePublished.useSales.useSteps.useStock.useStockFromOrders.useTakenSlots.useTallies.useTeamHours.useTheme.useVisitorState.whenWords.withAccess.withStockSetAt".split("."), R = (e) => {
499
499
  let t = e.split(""), n = [], r = "";
500
500
  for (let i = 0; i < e.length; i++) {
501
501
  let a = e[i];
@@ -540,7 +540,7 @@ var _e = (e) => L(ee(e)), ve = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b
540
540
  }
541
541
  }
542
542
  return t.join("");
543
- }, xe = (e) => e.replace(/(?<!=)>([^<>{};=]*)</g, (e, t) => `>${t.replace(/[^\n]/g, " ")}<`), B = (e, t) => {
543
+ }, Se = (e) => e.replace(/(?<!=)>([^<>{};=]*)</g, (e, t) => `>${t.replace(/[^\n]/g, " ")}<`), z = (e, t) => {
544
544
  let n = {
545
545
  "(": ")",
546
546
  "[": "]",
@@ -554,7 +554,7 @@ var _e = (e) => L(ee(e)), ve = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b
554
554
  else if ((t === ")" || t === "]" || t === "}") && (r--, r === 0)) return t === n ? i : -1;
555
555
  }
556
556
  return -1;
557
- }, Se = /[A-Za-z_$][\w$]*/g, Ce = String.raw`(?:<(?:[^<>()]|<[^<>()]*>)*>\s*)?`, we = new Set([
557
+ }, Ce = /[A-Za-z_$][\w$]*/g, we = String.raw`(?:<(?:[^<>()]|<[^<>()]*>)*>\s*)?`, Te = new Set([
558
558
  "return",
559
559
  "await",
560
560
  "typeof",
@@ -568,7 +568,7 @@ var _e = (e) => L(ee(e)), ve = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b
568
568
  "throw",
569
569
  "delete",
570
570
  "void"
571
- ]), Te = (e, t, n) => {
571
+ ]), Ee = (e, t, n) => {
572
572
  let r = t - 1;
573
573
  for (; r >= 0 && /[ \t]/.test(e[r]);) r--;
574
574
  if (e[r] === "\n" || e[r] === "\r") {
@@ -576,26 +576,26 @@ var _e = (e) => L(ee(e)), ve = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b
576
576
  for (; r >= 0 && /\s/.test(e[r]);) r--;
577
577
  }
578
578
  let i = /[A-Za-z_$][\w$]*$/.exec(e.slice(Math.max(0, r - 40), r + 1));
579
- return !!i && !we.has(i[0]);
580
- }, Ee = (e, t) => {
579
+ return !!i && !Te.has(i[0]);
580
+ }, De = (e, t) => {
581
581
  let n = t - 1;
582
582
  for (; n >= 0 && /\s/.test(e[n]);) n--;
583
583
  if (/[?:=(![&|+\-*/%<>]/.test(e[n] || "") || /\b(?:await|return|yield|typeof|void|new|case|in|of|else|throw|delete)$/.test(e.slice(Math.max(0, n - 9), n + 1))) return !1;
584
- let r = e.indexOf("(", t), i = r < 0 ? -1 : B(e, r);
584
+ let r = e.indexOf("(", t), i = r < 0 ? -1 : z(e, r);
585
585
  return i > 0 && /^\s*(?::[^={;]*)?\{/.test(e.slice(i + 1, i + 200));
586
586
  };
587
- function De(e) {
587
+ function Oe(e) {
588
588
  let t = /* @__PURE__ */ new Set(), n = (e) => {
589
- for (let n of e.match(Se) || []) t.add(n);
589
+ for (let n of e.match(Ce) || []) t.add(n);
590
590
  };
591
591
  for (let t of e.matchAll(/\bimport\s+(?:type\s+)?([\s\S]*?)\s+from\s*["'`]/g)) n(t[1]);
592
592
  for (let n of e.matchAll(/\b(?:const|let|var|function\*?|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g)) t.add(n[1]);
593
593
  for (let t of e.matchAll(/\b(?:const|let|var)\s*([[{])/g)) {
594
- let r = (t.index ?? 0) + t[0].length - 1, i = B(e, r);
594
+ let r = (t.index ?? 0) + t[0].length - 1, i = z(e, r);
595
595
  i > r && n(e.slice(r, i));
596
596
  }
597
597
  for (let t of e.matchAll(/\bfunction\b[^(]*\(/g)) {
598
- let r = (t.index ?? 0) + t[0].length - 1, i = B(e, r);
598
+ let r = (t.index ?? 0) + t[0].length - 1, i = z(e, r);
599
599
  i > r && n(e.slice(r, i));
600
600
  }
601
601
  for (let t of e.matchAll(/\)\s*(?::[^=;{]*?)?=>/g)) {
@@ -611,17 +611,17 @@ function De(e) {
611
611
  for (let n of e.matchAll(/<\s*([A-Z][\w$]*)\s*(?:extends\b|,|>\s*\()/g)) t.add(n[1]);
612
612
  return t;
613
613
  }
614
- var Oe = (e) => {
614
+ var ke = (e) => {
615
615
  let t = e;
616
616
  for (let e = 0; e < 200; e++) {
617
617
  let e = /\b(?:interface\s+[A-Za-z_$][\w$]*[^{=;]*|type\s+[A-Za-z_$][\w$]*(?:<[^>]*>)?\s*=\s*)\{/.exec(t);
618
618
  if (!e) break;
619
- let n = (e.index ?? 0) + e[0].length - 1, r = B(t, n);
619
+ let n = (e.index ?? 0) + e[0].length - 1, r = z(t, n);
620
620
  if (r < 0) break;
621
621
  t = `${t.slice(0, e.index)}${" ".repeat(r + 1 - (e.index ?? 0))}${t.slice(r + 1)}`;
622
622
  }
623
623
  return t;
624
- }, ke = new Set([
624
+ }, Ae = new Set([
625
625
  "useState",
626
626
  "useEffect",
627
627
  "useMemo",
@@ -636,7 +636,7 @@ var Oe = (e) => {
636
636
  "Fragment",
637
637
  "Suspense",
638
638
  "React"
639
- ]), Ae = /(?<![\w$.])React\s*\.\s*(?:use[A-Z][\w$]*|use|createElement|cloneElement|memo|forwardRef|lazy|Fragment|StrictMode|Suspense|Children|Component|PureComponent|createContext|createRef|isValidElement|startTransition)(?![\w$])/, je = new Set([
639
+ ]), je = /(?<![\w$.])React\s*\.\s*(?:use[A-Z][\w$]*|use|createElement|cloneElement|memo|forwardRef|lazy|Fragment|StrictMode|Suspense|Children|Component|PureComponent|createContext|createRef|isValidElement|startTransition)(?![\w$])/, Me = new Set([
640
640
  "Link",
641
641
  "NavLink",
642
642
  "Navigate",
@@ -644,7 +644,7 @@ var Oe = (e) => {
644
644
  "useSearchParams",
645
645
  "useParams",
646
646
  "useLocation"
647
- ]), Me = new Set([
647
+ ]), Ne = new Set([
648
648
  "collection",
649
649
  "doc",
650
650
  "query",
@@ -666,32 +666,32 @@ var Oe = (e) => {
666
666
  "writeBatch",
667
667
  "runTransaction",
668
668
  "documentId"
669
- ]), Ne = new Set(R), Pe = (e) => ke.has(e) ? e === "React" ? "import React from 'react'" : "'react'" : je.has(e) ? "'react-router-dom'" : Me.has(e) ? "'firebase/firestore'" : Ne.has(e) ? "'your-ai-workflow-firebase-os'" : vt.has(e) ? "'lucide-react'" : "";
670
- function Fe(e) {
669
+ ]), Pe = new Set(L), Fe = (e) => Ae.has(e) ? e === "React" ? "import React from 'react'" : "'react'" : Me.has(e) ? "'react-router-dom'" : Ne.has(e) ? "'firebase/firestore'" : Pe.has(e) ? "'your-ai-workflow-firebase-os'" : H.has(e) ? "'lucide-react'" : "";
670
+ function Ie(e) {
671
671
  if (!/^\s*import\b/m.test(e)) return [];
672
- let t = Oe(z(e)), n = De(t), r = xe(t), i = /* @__PURE__ */ new Set();
672
+ let t = ke(R(e)), n = Oe(t), r = Se(t), i = /* @__PURE__ */ new Set();
673
673
  for (let e of t.matchAll(/<([A-Z][\w$]*)(?=[\s/>.])/g)) {
674
674
  let r = t.slice(Math.max(0, (e.index ?? 0) - 12), e.index).replace(/\s+$/, "");
675
675
  /[\w$.)\]]$/.test(r) && !/\b(?:return|yield|case|default|else|do|in|of)$/.test(r) || n.has(e[1]) || i.add(e[1]);
676
676
  }
677
- for (let e of r.matchAll(new RegExp(String.raw`(?<![\w$.])(use[A-Z][\w$]*)\s*${Ce}\(`, "g"))) !n.has(e[1]) && !Ee(r, e.index ?? 0) && i.add(e[1]);
678
- Ae.test(r) && !n.has("React") && i.add("React");
679
- let a = (r.match(/;[ \t\r]*$/gm) || []).length >= 3, o = [...Me, ...[...Ne].filter((e) => /^[a-z]/.test(e))];
680
- for (let e of o) n.has(e) || (e === "db" ? /(?<![\w$.])db(?![\w$])(?!\s*:)/.test(r) : [...r.matchAll(RegExp(`(?<![\\w$.])${e}\\s*\\(`, "g"))].some((e) => !Te(r, e.index ?? 0, a) && !Ee(r, e.index ?? 0))) && i.add(e);
677
+ for (let e of r.matchAll(new RegExp(String.raw`(?<![\w$.])(use[A-Z][\w$]*)\s*${we}\(`, "g"))) !n.has(e[1]) && !De(r, e.index ?? 0) && i.add(e[1]);
678
+ je.test(r) && !n.has("React") && i.add("React");
679
+ let a = (r.match(/;[ \t\r]*$/gm) || []).length >= 3, o = [...Ne, ...[...Pe].filter((e) => /^[a-z]/.test(e))];
680
+ for (let e of o) n.has(e) || (e === "db" ? /(?<![\w$.])db(?![\w$])(?!\s*:)/.test(r) : [...r.matchAll(RegExp(`(?<![\\w$.])${e}\\s*\\(`, "g"))].some((e) => !Ee(r, e.index ?? 0, a) && !De(r, e.index ?? 0))) && i.add(e);
681
681
  return [...i];
682
682
  }
683
- var Ie = (e, t) => {
684
- let n = t.filter((e) => Pe(e)), r = t.filter((e) => !Pe(e)), i = n.map((e) => e === "React" ? Pe(e) : `${e} from ${Pe(e)}`);
683
+ var Le = (e, t) => {
684
+ let n = t.filter((e) => Fe(e)), r = t.filter((e) => !Fe(e)), i = n.map((e) => e === "React" ? Fe(e) : `${e} from ${Fe(e)}`);
685
685
  return `${e}: ${t.map((e) => /^[A-Z]/.test(e) && e !== "React" ? `<${e}>` : e).join(", ")} ${t.length > 1 ? "are" : "is"} used here and never imported or defined — the page stops with "${t[0]} is not defined" the moment that line runs, and the owner sees "This part stopped". ${i.length ? `Add ${i.length > 1 ? "each" : "it"} to the import line it belongs to: ${i.join("; ")}.` : ""}${r.length ? ` ${r.map((e) => `<${e}>`).join(", ")} ${r.length > 1 ? "are" : "is"} not a piece of the app: define ${r.length > 1 ? "each" : "it"} in this file, above the page, or draw what it was meant to draw inline.` : ""} Change nothing else.`;
686
686
  };
687
- function Le(e) {
687
+ function Re(e) {
688
688
  let t = [];
689
689
  for (let n of e.matchAll(/\b(?:function\s+([A-Z][\w$]*)\s*(?:<[^>]*>)?\s*\(|(?:const|let)\s+([A-Z][\w$]*)\s*(?::[^=]*)?=\s*(?:React\.)?(?:memo\s*\(\s*|forwardRef\s*(?:<[^>]*>)?\s*\(\s*)?(?:async\s*)?(?:function\s*\w*\s*)?\()/g)) {
690
- let r = n[1] || n[2], i = (n.index ?? 0) + n[0].length - 1, a = B(e, i);
690
+ let r = n[1] || n[2], i = (n.index ?? 0) + n[0].length - 1, a = z(e, i);
691
691
  if (a < 0) continue;
692
692
  let o = e.slice(a + 1), s = /^\s*(?::[^={]*)?(?:=>)?\s*\{/.exec(o) || (n[2] ? /^\s*(?::[^={]*)?=>\s*\(/.exec(o) : null);
693
693
  if (!s) continue;
694
- let c = a + 1 + s[0].length - 1, l = B(e, c);
694
+ let c = a + 1 + s[0].length - 1, l = z(e, c);
695
695
  l < 0 || t.push({
696
696
  name: r,
697
697
  start: c + 1,
@@ -701,7 +701,7 @@ function Le(e) {
701
701
  }
702
702
  return t;
703
703
  }
704
- var Re = (e) => {
704
+ var ze = (e) => {
705
705
  let t = [], n = 0;
706
706
  for (let r of e) {
707
707
  if (r === "(" || r === "[" || r === "{") {
@@ -712,7 +712,7 @@ var Re = (e) => {
712
712
  }
713
713
  return t;
714
714
  };
715
- function ze(e, t) {
715
+ function Be(e, t) {
716
716
  for (let n of e.matchAll(/\breturn\b/g)) {
717
717
  let r = n.index ?? 0;
718
718
  if (t[r] === 0) {
@@ -724,23 +724,23 @@ function ze(e, t) {
724
724
  let n = e.lastIndexOf("{", r);
725
725
  if (n < 0 || t[n] !== 0) continue;
726
726
  let i = e.slice(Math.max(e.lastIndexOf(";", n), e.lastIndexOf("}", n - 1)) + 1, n);
727
- if (/^\s*(?:else\s+)?if\s*\([\s\S]*\)\s*$|^\s*else\s*$/.test(i)) return B(e, n);
727
+ if (/^\s*(?:else\s+)?if\s*\([\s\S]*\)\s*$|^\s*else\s*$/.test(i)) return z(e, n);
728
728
  }
729
729
  }
730
730
  return -1;
731
731
  }
732
- function Be(e) {
733
- let t = z(e), n = /* @__PURE__ */ new Set();
734
- for (let { name: e, body: r } of Le(t)) {
735
- let t = Re(r), i = ze(r, t);
736
- for (let a of r.matchAll(new RegExp(String.raw`(?<![\w$.])(?:React\s*\.\s*)?(use[A-Z][\w$]*)\s*${Ce}\(`, "g"))) {
732
+ function Ve(e) {
733
+ let t = R(e), n = /* @__PURE__ */ new Set();
734
+ for (let { name: e, body: r } of Re(t)) {
735
+ let t = ze(r), i = Be(r, t);
736
+ for (let a of r.matchAll(new RegExp(String.raw`(?<![\w$.])(?:React\s*\.\s*)?(use[A-Z][\w$]*)\s*${we}\(`, "g"))) {
737
737
  let o = a.index ?? 0;
738
- i >= 0 && o > i && t[o] === 0 ? n.add(`${a[1]}() in ${e}, below its early return`) : Ve(r, o) && n.add(`${a[1]}() inside a .map( in ${e}`);
738
+ i >= 0 && o > i && t[o] === 0 ? n.add(`${a[1]}() in ${e}, below its early return`) : He(r, o) && n.add(`${a[1]}() inside a .map( in ${e}`);
739
739
  }
740
740
  }
741
741
  return [...n];
742
742
  }
743
- var Ve = (e, t) => {
743
+ var He = (e, t) => {
744
744
  let n = 0;
745
745
  for (let r = t - 1; r >= 0; r--) {
746
746
  let t = e[r];
@@ -754,28 +754,28 @@ var Ve = (e, t) => {
754
754
  }
755
755
  }
756
756
  return !1;
757
- }, He = (e, t) => `${e}: ${t.join("; ")} — React counts a component's hooks every time it draws it, and ${t.some((e) => e.includes(".map(")) ? "a hook per row is one more the day the list grows" : "a hook below an early return is one more the moment that return stops happening"} — the page stops with "Rendered more hooks than during the previous render". Move every hook (useState, useEffect, useMemo, the app's own use… calls) to the top of the component, above the first return, and draw the loading line after them; a row that needs state of its own is a component of its own, defined above the page and drawn inside the .map(. Change nothing else.`, Ue = /<(?:input|Input|textarea|TextArea|select|CustomSelect|AddressField|PhoneInputField|NumberStepper|SwatchField|Picture|UploadBox)\b/;
758
- function We(e) {
759
- let t = z(e), n = /* @__PURE__ */ new Set();
760
- for (let { body: e } of Le(t)) {
761
- let t = Re(e);
762
- for (let r of Le(e)) {
757
+ }, Ue = (e, t) => `${e}: ${t.join("; ")} — React counts a component's hooks every time it draws it, and ${t.some((e) => e.includes(".map(")) ? "a hook per row is one more the day the list grows" : "a hook below an early return is one more the moment that return stops happening"} — the page stops with "Rendered more hooks than during the previous render". Move every hook (useState, useEffect, useMemo, the app's own use… calls) to the top of the component, above the first return, and draw the loading line after them; a row that needs state of its own is a component of its own, defined above the page and drawn inside the .map(. Change nothing else.`, We = /<(?:input|Input|textarea|TextArea|select|CustomSelect|AddressField|PhoneInputField|NumberStepper|SwatchField|Picture|UploadBox)\b/;
758
+ function Ge(e) {
759
+ let t = R(e), n = /* @__PURE__ */ new Set();
760
+ for (let { body: e } of Re(t)) {
761
+ let t = ze(e);
762
+ for (let r of Re(e)) {
763
763
  let i = e.lastIndexOf(r.name, r.start);
764
- i < 0 || t[i] !== 0 || Ue.test(r.body) && RegExp(`<${r.name}[\\s/>]`).test(e) && n.add(r.name);
764
+ i < 0 || t[i] !== 0 || We.test(r.body) && RegExp(`<${r.name}[\\s/>]`).test(e) && n.add(r.name);
765
765
  }
766
766
  }
767
767
  return [...n];
768
768
  }
769
- var Ge = (e, t) => `${e}: ${t.map((e) => `<${e}>`).join(", ")} ${t.length > 1 ? "are components" : "is a component"} written INSIDE the page, so React makes ${t.length > 1 ? "each" : "it"} anew every time the page draws — and throws away what was in it: a box loses the cursor after every letter typed, and every picture in it starts loading again at each key pressed. Move ${t.length > 1 ? "each one" : "it"}, whole, above the page's own function, and hand it what it used from the page as props (<${t[0]} value={value} onChange={setValue} />). Change nothing else about what it draws.`;
770
- function Ke(e) {
771
- return !/\bactions\s*\??\.\s*records\b/.test(e) || /\b(?:onSnapshot|getDocs?|useCases|usePublished|useCase)\s*\(/.test(e) ? [] : [...new Set([...e.matchAll(/\b(createdAt|editedAt|stockSetAt)\s*(?:\?\.|\.)\s*(toDate\s*\(|seconds\b|toMillis\s*\()/g)].filter((t) => !qe(e, t[1])).map((e) => `${e[1]}.${e[2].replace(/\s*\($/, "()")}`))];
769
+ var Ke = (e, t) => `${e}: ${t.map((e) => `<${e}>`).join(", ")} ${t.length > 1 ? "are components" : "is a component"} written INSIDE the page, so React makes ${t.length > 1 ? "each" : "it"} anew every time the page draws — and throws away what was in it: a box loses the cursor after every letter typed, and every picture in it starts loading again at each key pressed. Move ${t.length > 1 ? "each one" : "it"}, whole, above the page's own function, and hand it what it used from the page as props (<${t[0]} value={value} onChange={setValue} />). Change nothing else about what it draws.`;
770
+ function qe(e) {
771
+ return !/\bactions\s*\??\.\s*records\b/.test(e) || /\b(?:onSnapshot|getDocs?|useCases|usePublished|useCase)\s*\(/.test(e) ? [] : [...new Set([...e.matchAll(/\b(createdAt|editedAt|stockSetAt)\s*(?:\?\.|\.)\s*(toDate\s*\(|seconds\b|toMillis\s*\()/g)].filter((t) => !Je(e, t[1])).map((e) => `${e[1]}.${e[2].replace(/\s*\($/, "()")}`))];
772
772
  }
773
- var qe = (e, t) => new RegExp(String.raw`\b${t}\s*\??\.\s*(?:toDate|toMillis)\b(?!\s*\()`).test(e) || new RegExp(String.raw`\b${t}\s*\??\.\s*seconds\s*(?:\?|&&|\|\|)`).test(e) || new RegExp(String.raw`\btypeof\s+[\w$.?[\]]*\b${t}\b|\b${t}\s+instanceof\b|['"](?:toDate|toMillis|seconds)['"]\s+in\s+[\w$.?[\]]*\b${t}\b`).test(e), Je = (e, t) => {
773
+ var Je = (e, t) => new RegExp(String.raw`\b${t}\s*\??\.\s*(?:toDate|toMillis)\b(?!\s*\()`).test(e) || new RegExp(String.raw`\b${t}\s*\??\.\s*seconds\s*(?:\?|&&|\|\|)`).test(e) || new RegExp(String.raw`\btypeof\s+[\w$.?[\]]*\b${t}\b|\b${t}\s+instanceof\b|['"](?:toDate|toMillis|seconds)['"]\s+in\s+[\w$.?[\]]*\b${t}\b`).test(e), Ye = (e, t) => {
774
774
  let [n, r] = t[0].split("."), i = t.some((e) => /\(\)$/.test(e)) ? "the page stops as soon as there is one record on it" : "every date on it reads \"Invalid Date\"";
775
775
  return `${e}: ${t.join(", ")} reads a stamp on the page's records as a Firestore Timestamp — the app hands actions.records with createdAt, editedAt and stockSetAt already in milliseconds, a plain number, and a number has no ${r}: ${i}. Read it as new Date(record.${n}) (or format it with toLocaleDateString on that). Change nothing else.`;
776
776
  };
777
- function Ye(e) {
778
- let t = [], n = z(e);
777
+ function Xe(e) {
778
+ let t = [], n = R(e);
779
779
  for (let r of n.matchAll(/<(BuyButton|PayButton)\b/g)) {
780
780
  let i = (r.index ?? 0) + r[0].length;
781
781
  for (let e = 0; i < n.length; i++) if (n[i] === "{") e++;
@@ -783,14 +783,14 @@ function Ye(e) {
783
783
  else if (n[i] === ">" && e === 0) break;
784
784
  let a = /\bamount\s*=\s*\{/.exec(n.slice(r.index, i));
785
785
  if (!a) continue;
786
- let o = (r.index ?? 0) + a.index + a[0].length - 1, s = B(n, o);
786
+ let o = (r.index ?? 0) + a.index + a[0].length - 1, s = z(n, o);
787
787
  if (s < 0) continue;
788
788
  let c = e.slice(o + 1, s).replace(/\s+/g, " ").trim(), l = n.slice(o + 1, s).replace(/\s+/g, " ").trim();
789
- $e(l) ? t.push(`<${r[1]} amount={${c}}> (in cents)`) : it(l) && t.push(`<${r[1]} amount={${c}}> (clamped)`);
789
+ et(l) ? t.push(`<${r[1]} amount={${c}}> (in cents)`) : at(l) && t.push(`<${r[1]} amount={${c}}> (clamped)`);
790
790
  }
791
791
  return [...new Set(t)];
792
792
  }
793
- var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).map((e) => e.replace(/s$/, "")), Ze = new Set([
793
+ var Ze = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).map((e) => e.replace(/s$/, "")), Qe = new Set([
794
794
  "total",
795
795
  "subtotal",
796
796
  "price",
@@ -811,7 +811,7 @@ var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^
811
811
  "pound",
812
812
  "fare",
813
813
  "tip"
814
- ]), V = new Set([
814
+ ]), $e = new Set([
815
815
  "count",
816
816
  "qty",
817
817
  "quantity",
@@ -833,27 +833,27 @@ var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^
833
833
  "children",
834
834
  "number",
835
835
  "num"
836
- ]), Qe = (e) => {
836
+ ]), B = (e) => {
837
837
  if (/^[\w$.?]+$/.test(e)) {
838
- let t = Xe(e.split(/\??\./).pop() || "");
839
- return t.indexOf("per") > 0 && (t = t.slice(0, t.indexOf("per"))), t.some((e) => Ze.has(e)) && !V.has(t[t.length - 1]);
838
+ let t = Ze(e.split(/\??\./).pop() || "");
839
+ return t.indexOf("per") > 0 && (t = t.slice(0, t.indexOf("per"))), t.some((e) => Qe.has(e)) && !$e.has(t[t.length - 1]);
840
840
  }
841
- return (e.match(Se) || []).some((e) => Xe(e).some((e) => Ze.has(e)));
842
- }, $e = (e) => {
841
+ return (e.match(Ce) || []).some((e) => Ze(e).some((e) => Qe.has(e)));
842
+ }, et = (e) => {
843
843
  if (/\/\s*100\b/.test(e)) return !1;
844
844
  for (let t of e.matchAll(/(?<![\w$.])100(?![\w$.])/g)) {
845
- let n = t.index ?? 0, r = et(e, n), i = tt(e, n + t[0].length);
846
- if (!(r.next !== "*" && i.next !== "*") && [...r.operands, ...i.operands].some(Qe)) return !0;
845
+ let n = t.index ?? 0, r = tt(e, n), i = nt(e, n + t[0].length);
846
+ if (!(r.next !== "*" && i.next !== "*") && [...r.operands, ...i.operands].some(B)) return !0;
847
847
  }
848
848
  return !1;
849
- }, et = (e, t) => {
849
+ }, tt = (e, t) => {
850
850
  let n = [], r = "";
851
851
  for (let i = t;;) {
852
852
  let t = i - 1;
853
853
  for (; t >= 0 && e[t] === " ";) t--;
854
854
  if (e[t] !== "*" && e[t] !== "/") break;
855
855
  r ||= e[t];
856
- let { operand: a, start: o } = nt(e, t);
856
+ let { operand: a, start: o } = rt(e, t);
857
857
  if (!a) break;
858
858
  n.push(a), i = o;
859
859
  }
@@ -861,14 +861,14 @@ var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^
861
861
  operands: n,
862
862
  next: r
863
863
  };
864
- }, tt = (e, t) => {
864
+ }, nt = (e, t) => {
865
865
  let n = [], r = "";
866
866
  for (let i = t;;) {
867
867
  let t = i;
868
868
  for (; t < e.length && e[t] === " ";) t++;
869
869
  if (e[t] !== "*" && e[t] !== "/") break;
870
870
  r ||= e[t];
871
- let { operand: a, end: o } = rt(e, t + 1);
871
+ let { operand: a, end: o } = it(e, t + 1);
872
872
  if (!a) break;
873
873
  n.push(a), i = o;
874
874
  }
@@ -876,7 +876,7 @@ var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^
876
876
  operands: n,
877
877
  next: r
878
878
  };
879
- }, nt = (e, t) => {
879
+ }, rt = (e, t) => {
880
880
  let n = t - 1;
881
881
  for (; n >= 0 && e[n] === " ";) n--;
882
882
  let r = n + 1;
@@ -890,80 +890,80 @@ var Xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^
890
890
  operand: e.slice(n + 1, r),
891
891
  start: n + 1
892
892
  };
893
- }, rt = (e, t) => {
893
+ }, it = (e, t) => {
894
894
  let n = t;
895
895
  for (; n < e.length && e[n] === " ";) n++;
896
896
  let r = n;
897
897
  for (; n < e.length && /[\w$.?]/.test(e[n]);) n++;
898
898
  if (e[n] === "(") {
899
- let t = B(e, n);
899
+ let t = z(e, n);
900
900
  n = t < 0 ? e.length : t + 1;
901
901
  }
902
902
  return {
903
903
  operand: e.slice(r, n),
904
904
  end: n
905
905
  };
906
- }, it = (e) => {
906
+ }, at = (e) => {
907
907
  let t = /^Math\s*\.\s*(?:min|max)\s*\(/.exec(e);
908
908
  if (!t) return !1;
909
909
  let n = t[0].length - 1;
910
- return B(e, n) === e.length - 1 ? !e.slice(n + 1, -1).split(",").map((e) => e.trim()).some((e) => /^0(?:\.0*)?$/.test(e)) : !1;
911
- }, at = (e, t) => `${e}: ${t.join(", ")} — amount is ordinary money, the way it is written on the page: amount={25} takes 25 in the owner's own Stripe currency. Multiplied by 100 it charges a hundred times the price, and a total held under a ceiling or a floor charges something other than the basket shows. Pass the total itself (amount={total}), and change nothing else.`;
912
- function ot(e) {
913
- let t = z(e), n = [];
910
+ return z(e, n) === e.length - 1 ? !e.slice(n + 1, -1).split(",").map((e) => e.trim()).some((e) => /^0(?:\.0*)?$/.test(e)) : !1;
911
+ }, ot = (e, t) => `${e}: ${t.join(", ")} — amount is ordinary money, the way it is written on the page: amount={25} takes 25 in the owner's own Stripe currency. Multiplied by 100 it charges a hundred times the price, and a total held under a ceiling or a floor charges something other than the basket shows. Pass the total itself (amount={total}), and change nothing else.`;
912
+ function st(e) {
913
+ let t = R(e), n = [];
914
914
  for (let r of t.matchAll(/\bquery\s*\(/g)) {
915
- let i = (r.index ?? 0) + r[0].length - 1, a = B(t, i);
915
+ let i = (r.index ?? 0) + r[0].length - 1, a = z(t, i);
916
916
  if (a < 0) continue;
917
917
  let o = e.slice(i + 1, a), s = [...o.matchAll(/\bwhere\s*\(\s*['"`]([\w.]+)['"`]/g)].map((e) => e[1]), c = [...o.matchAll(/\borderBy\s*\(\s*['"`]([\w.]+)['"`]/g)].map((e) => e[1]);
918
918
  s.length && c.some((e) => s.some((t) => t !== e)) && n.push(`where('${s[0]}'…) with orderBy('${c.find((e) => e !== s[0]) || c[0]}'…)`);
919
919
  }
920
920
  return [...new Set(n)];
921
921
  }
922
- var st = (e, t) => `${e}: ${t.join("; ")} in one query needs an index that is only made in the Firebase console — which the owner never opens — so Firestore refuses the read and the list never loads. Keep the where() and take the orderBy() out of the query; put what comes back in order in the page instead: const sorted = [...list].sort((a, b) => stamp(b) - stamp(a)). Change nothing else.`, ct = /^(?:pub|mem|admin|user|case|sys)_|^role_[a-z][a-z0-9]*_/, lt = new Set(["chat_threads", "users"]);
923
- function ut(e) {
922
+ var ct = (e, t) => `${e}: ${t.join("; ")} in one query needs an index that is only made in the Firebase console — which the owner never opens — so Firestore refuses the read and the list never loads. Keep the where() and take the orderBy() out of the query; put what comes back in order in the page instead: const sorted = [...list].sort((a, b) => stamp(b) - stamp(a)). Change nothing else.`, lt = /^(?:pub|mem|admin|user|case|sys)_|^role_[a-z][a-z0-9]*_/, ut = new Set(["chat_threads", "users"]);
923
+ function dt(e) {
924
924
  let t = /* @__PURE__ */ new Set();
925
- for (let n of e.matchAll(/\b(?:collection|doc|collectionGroup)\s*\(\s*db\s*,\s*['"`]([A-Za-z][\w-]*)['"`]/g)) !ct.test(n[1]) && !lt.has(n[1]) && t.add(n[1]);
925
+ for (let n of e.matchAll(/\b(?:collection|doc|collectionGroup)\s*\(\s*db\s*,\s*['"`]([A-Za-z][\w-]*)['"`]/g)) !lt.test(n[1]) && !ut.has(n[1]) && t.add(n[1]);
926
926
  return [...t];
927
927
  }
928
- var dt = (e, t) => `${e}: ${t.map((e) => `'${e}'`).join(", ")} ${t.length > 1 ? "have" : "has"} none of the name starts the pasted security rules know — pub_, mem_, admin_, user_, role_…_, case_ — so only an admin can read or write ${t.length > 1 ? "them" : "it"}: it works while the owner tests the app and is refused for every visitor and every member of the team. A page's own records are actions.records, written with actions.saveRecord / updateRecord / deleteRecord; a collection kept BESIDE them takes the start the page's sentence gave (the page's own records' prefix — mem_ for members, pub_ for anyone to read, admin_ for admins only). Rename ${t.length > 1 ? "each" : "it"} with that start everywhere in this file, and change nothing else.`, H = new Set(/* @__PURE__ */ "activeTable.addColumn.addRow.catCollection.categories.collection.confirmDelete.currentDate.currentUser.deleteCategory.deleteEvent.deleteRecord.deleteTable.deleteTask.error.events.goToNext.goToPrev.goToToday.loading.openAddForm.openAddModal.openEditModal.openInlineEditor.records.saveCategory.saveEvent.saveRecord.saveTable.saveTask.setAddForm.setCurrentDate.setFileHandler.setViewingEvent.startEdit.tables.tasks.updateCell.updateRecord.uploadFile.user.sendTo.takeBack.keys.canEdit.canDelete.publish.unpublish.isPublished.draftId.files.deleteFile.openUploadModal.viewFile".split(".")), ft = new Set([
928
+ var ft = (e, t) => `${e}: ${t.map((e) => `'${e}'`).join(", ")} ${t.length > 1 ? "have" : "has"} none of the name starts the pasted security rules know — pub_, mem_, admin_, user_, role_…_, case_ — so only an admin can read or write ${t.length > 1 ? "them" : "it"}: it works while the owner tests the app and is refused for every visitor and every member of the team. A page's own records are actions.records, written with actions.saveRecord / updateRecord / deleteRecord; a collection kept BESIDE them takes the start the page's sentence gave (the page's own records' prefix — mem_ for members, pub_ for anyone to read, admin_ for admins only). Rename ${t.length > 1 ? "each" : "it"} with that start everywhere in this file, and change nothing else.`, V = new Set(/* @__PURE__ */ "activeTable.addColumn.addRow.catCollection.categories.collection.confirmDelete.currentDate.currentUser.deleteCategory.deleteEvent.deleteRecord.deleteTable.deleteTask.error.events.goToNext.goToPrev.goToToday.loading.openAddForm.openAddModal.openEditModal.openInlineEditor.records.saveCategory.saveEvent.saveRecord.saveTable.saveTask.setAddForm.setCurrentDate.setFileHandler.setViewingEvent.startEdit.tables.tasks.updateCell.updateRecord.uploadFile.user.sendTo.takeBack.keys.canEdit.canDelete.publish.unpublish.isPublished.draftId.files.deleteFile.openUploadModal.viewFile".split(".")), pt = new Set([
929
929
  "config",
930
930
  "actions",
931
931
  "children",
932
- ...H
932
+ ...V
933
933
  ]);
934
- function pt(e, t) {
934
+ function mt(e, t) {
935
935
  if (!/\bactions\b/.test(e) && !t) return [];
936
- let n = /* @__PURE__ */ new Set(), r = xe(z(e)), i = gt(r), a = (e) => !i.some(([t, n]) => e >= t && e < n);
937
- for (let e of r.matchAll(/\bactions\s*\??\.\s*([A-Za-z_$][\w$]*)(\s*(?:\|\||\?\?)\s*actions\s*\??\.\s*([A-Za-z_$][\w$]*))?/g)) a(e.index ?? 0) && (e[3] && H.has(e[3]) || H.has(e[1]) || n.add(`actions.${e[1]}`));
936
+ let n = /* @__PURE__ */ new Set(), r = Se(R(e)), i = _t(r), a = (e) => !i.some(([t, n]) => e >= t && e < n);
937
+ for (let e of r.matchAll(/\bactions\s*\??\.\s*([A-Za-z_$][\w$]*)(\s*(?:\|\||\?\?)\s*actions\s*\??\.\s*([A-Za-z_$][\w$]*))?/g)) a(e.index ?? 0) && (e[3] && V.has(e[3]) || V.has(e[1]) || n.add(`actions.${e[1]}`));
938
938
  for (let e of r.matchAll(/\b(?:const|let|var)\s*\{/g)) {
939
- let t = (e.index ?? 0) + e[0].length - 1, i = B(r, t);
940
- if (!(i < 0 || !a(t) || !/^\s*\}?\s*=\s*actions\b(?!\s*\??\.)/.test(r.slice(i, i + 30)))) for (let e of ht(r.slice(t + 1, i))) H.has(e) || n.add(`actions.${e}`);
939
+ let t = (e.index ?? 0) + e[0].length - 1, i = z(r, t);
940
+ if (!(i < 0 || !a(t) || !/^\s*\}?\s*=\s*actions\b(?!\s*\??\.)/.test(r.slice(i, i + 30)))) for (let e of gt(r.slice(t + 1, i))) V.has(e) || n.add(`actions.${e}`);
941
941
  }
942
942
  if (t) {
943
943
  let e = /\bexport\s+default\s+function\s*[\w$]*\s*\(\s*\{/.exec(r) || /\bexport\s+default\s+\(\s*\{/.exec(r);
944
944
  if (e) {
945
- let t = (e.index ?? 0) + e[0].length - 1, i = r.slice(t + 1, B(r, t));
946
- for (let e of ht(i)) ft.has(e) || n.add(`the prop ${e}`);
947
- for (let e of mt(i)) {
945
+ let t = (e.index ?? 0) + e[0].length - 1, i = r.slice(t + 1, z(r, t));
946
+ for (let e of gt(i)) pt.has(e) || n.add(`the prop ${e}`);
947
+ for (let e of ht(i)) {
948
948
  let t = /^\s*actions\s*:\s*\{/.exec(e);
949
949
  if (!t) continue;
950
- let r = B(e, t[0].length - 1);
951
- for (let i of ht(e.slice(t[0].length, r))) H.has(i) || n.add(`actions.${i}`);
950
+ let r = z(e, t[0].length - 1);
951
+ for (let i of gt(e.slice(t[0].length, r))) V.has(i) || n.add(`actions.${i}`);
952
952
  }
953
953
  }
954
954
  }
955
955
  return [...n];
956
956
  }
957
- var mt = (e) => {
957
+ var ht = (e) => {
958
958
  let t = [], n = 0, r = 0;
959
959
  for (let i = 0; i < e.length; i++) {
960
960
  let a = e[i];
961
961
  a === "(" || a === "[" || a === "{" ? n++ : a === ")" || a === "]" || a === "}" ? n-- : a === "," && n === 0 && (t.push(e.slice(r, i)), r = i + 1);
962
962
  }
963
963
  return t.push(e.slice(r)), t;
964
- }, ht = (e) => mt(e).map((e) => e.split(/[:=]/)[0].trim()).filter((e) => /^[A-Za-z_$][\w$]*$/.test(e)), gt = (e) => {
964
+ }, gt = (e) => ht(e).map((e) => e.split(/[:=]/)[0].trim()).filter((e) => /^[A-Za-z_$][\w$]*$/.test(e)), _t = (e) => {
965
965
  let t = [];
966
- for (let n of Le(e)) /(?<![\w$.])actions\b(?!\s*:)/.test(n.params) && [...e.matchAll(new RegExp(String.raw`<${n.name}\b`, "g"))].some((t) => {
966
+ for (let n of Re(e)) /(?<![\w$.])actions\b(?!\s*:)/.test(n.params) && [...e.matchAll(new RegExp(String.raw`<${n.name}\b`, "g"))].some((t) => {
967
967
  let n = (t.index ?? 0) + t[0].length;
968
968
  for (let t = 0; n < e.length; n++) if (e[n] === "{") t++;
969
969
  else if (e[n] === "}") t--;
@@ -971,13 +971,13 @@ var mt = (e) => {
971
971
  return /\bactions\s*=\s*\{\s*[{[]/.test(e.slice(t.index, n));
972
972
  }) && t.push([n.start, n.start + n.body.length]);
973
973
  return t;
974
- }, _t = (e, t) => `${e}: ${t.join(", ")} ${t.length > 1 ? "are" : "is"} never handed to this page — called, it is "not a function" and the press does nothing; read, it is undefined and the list stays empty. A page's content is handed config and actions, nothing else, and actions carries records, loading, error, saveRecord(record), updateRecord(id, change), deleteRecord(id), confirmDelete(id), uploadFile(files), user and collection (and sendTo / takeBack on a page of sent entries). Use the one that does the job — actions.saveRecord(record) to add, actions.records to read — and change nothing else.`, vt = /* @__PURE__ */ new Set();
974
+ }, vt = (e, t) => `${e}: ${t.join(", ")} ${t.length > 1 ? "are" : "is"} never handed to this page — called, it is "not a function" and the press does nothing; read, it is undefined and the list stays empty. A page's content is handed config and actions, nothing else, and actions carries records, loading, error, saveRecord(record), updateRecord(id, change), deleteRecord(id), confirmDelete(id), uploadFile(files), user and collection (and sendTo / takeBack on a page of sent entries). Use the one that does the job — actions.saveRecord(record) to add, actions.records to read — and change nothing else.`, H = /* @__PURE__ */ new Set();
975
975
  {
976
976
  let n = (e) => e.split("-").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
977
- for (let t of e.split("\n")) t.trim() && vt.add(n(t.trim()));
977
+ for (let t of e.split("\n")) t.trim() && H.add(n(t.trim()));
978
978
  for (let e of t.split("\n")) {
979
979
  let t = e.trim().split(/\s+/)[0];
980
- t && vt.add(n(t));
980
+ t && H.add(n(t));
981
981
  }
982
982
  }
983
983
  var yt = new Set([
@@ -1015,7 +1015,7 @@ function Ct(e, t) {
1015
1015
  if (/^\s*type\s/.test(r)) continue;
1016
1016
  let e = r.trim().split(/\s+as\s+/)[0].trim(), i = e.replace(/^Lucide/, "").replace(/Icon$/, ""), a = xt.get(i.toLowerCase());
1017
1017
  if (!a) continue;
1018
- let o = (e) => t ? t.has(e) : !bt.has(e) || vt.has(e);
1018
+ let o = (e) => t ? t.has(e) : !bt.has(e) || H.has(e);
1019
1019
  a !== i && o(a) ? n.push(`the icon ${e} (lucide-react spells it ${e.replace(i, a)})`) : (a !== i || !o(i)) && n.push(`the icon ${e} (the app's lucide-react has no such icon)`);
1020
1020
  }
1021
1021
  }
@@ -1314,6 +1314,7 @@ var dn = (e, t) => `${e}: ${t.join(", ")} ${t.length > 1 ? "are" : "is"} sent fr
1314
1314
  "bare-textarea": "looks",
1315
1315
  "own-swatch": "looks",
1316
1316
  "own-picture-set": "looks",
1317
+ "swapped-upload": "breaks",
1317
1318
  "full-reload": "breaks",
1318
1319
  "typed-id": "breaks",
1319
1320
  "own-stepper": "looks",
@@ -1706,24 +1707,45 @@ function _r(e) {
1706
1707
  let n = e.match(gr);
1707
1708
  return n && t.push(`${n[0]}…}]`), t;
1708
1709
  }
1709
- var vr = /\b(isMain|isPrimary|isCover|mainIndex|mainImageIndex|coverIndex|primaryIndex|mainImageId|mainPictureId)\b/, yr = (e) => /<UploadBox\b/.test(e) && !/<UploadBox\b[^>]*\bsortable\b/.test(e) ? e.match(vr)?.[1] ?? null : null, br = (e, t) => `${e}: ${t} keeps which picture is the MAIN one beside the list of pictures, and the two fall out of step — remove the main picture and it points at the wrong one, or at none. The set is the app's own upload box with one more word: <UploadBox multiple max={10} sortable value={pictures} onChange={…} label="Photos" />. Its tiles drag into order under a mouse and a finger, the FIRST picture is the main one and says so, and every other tile has "Make main". Keep ONE ordered list of links, read pictures[0] as the main picture everywhere, and delete ${t} and any move-left / move-right or star buttons of your own.`, xr = "(?:qty|quantit(?:y|ies)|stock|inventory|onhand|instock|guests|seats|adults|children|kids|tickets|nights|rooms|units|portions|servings|copies|howmany)", Sr = (e) => RegExp(`\\b[\\w.]*${xr}[\\w.]*(?<!\\.length)(?:\\s*(?:\\?\\?|\\|\\|)\\s*\\d+)?\\s*\\)?\\s*\\${e}\\s*1\\b|\\bset\\w*${xr}\\w*\\s*\\(\\s*\\(?\\s*\\w+\\s*\\)?\\s*=>[^;\\n]{0,48}?\\${e}\\s*1\\b`, "i"), Cr = Sr("+"), wr = Sr("-"), Tr = (e) => {
1710
+ var vr = /\b(isMain|isPrimary|isCover|mainIndex|mainImageIndex|coverIndex|primaryIndex|mainImageId|mainPictureId)\b/, yr = (e) => /<UploadBox\b/.test(e) && !/<UploadBox\b[^>]*\bsortable\b/.test(e) ? e.match(vr)?.[1] ?? null : null, br = /variant|option|colou?r|shade|size|style|version/i, xr = /\.find\s*\(|\[\s*[A-Za-z_$][\w$]*\s*\]/;
1711
+ function Sr(e) {
1712
+ let t = [];
1713
+ for (let n of e.matchAll(/<UploadBox\b/g)) {
1714
+ let r = n.index ?? 0;
1715
+ if (Xr(e, r)) continue;
1716
+ let i = ur(Q(e, r), "value").trim(), a = /^\(?\s*([A-Za-z_$][\w$]*)/.exec(i)?.[1];
1717
+ if (!a) continue;
1718
+ let o = [i, new RegExp(String.raw`\b(?:const|let|var)\s+${a.replace(/\$/g, "\\$")}\s*(?::[^=]*)?=\s*([^;]*)`).exec(e)?.[1] || ""].find((e) => xr.test(e) && br.test(`${a} ${e}`));
1719
+ o && t.push({
1720
+ value: i.replace(/\s+/g, " "),
1721
+ list: /([A-Za-z_$][\w$.]*?)\??\.(?:find\s*\(|\[)|([A-Za-z_$][\w$.]*?)\??\s*\[/.exec(o)?.slice(1).find(Boolean) || "variants",
1722
+ chosen: /\.id\s*===\s*([A-Za-z_$][\w$.]*)|([A-Za-z_$][\w$.]*)\s*===\s*[A-Za-z_$][\w$]*\.id\b/.exec(o)?.slice(1).find(Boolean) || "chosenId",
1723
+ root: a
1724
+ });
1725
+ }
1726
+ return t;
1727
+ }
1728
+ var Cr = (e, t) => {
1729
+ let { value: n, list: r, chosen: i, root: a } = t[0];
1730
+ return `${e}: <UploadBox value={${n.length > 48 ? `${n.slice(0, 48)}…` : n}}> is ONE upload box that follows whichever variant is chosen, so a photo still going up when another variant is picked is saved on THAT variant, and the photos going up move across with it. Draw a box for EVERY variant, each saving to its own, and show only the chosen one — {${r}.map((v) => (<div key={v.id} className={v.id === ${i} ? '' : 'hidden'}><UploadBox … /></div>))} — with the same props it has now and v in place of ${a}, so each box's photos land on its own variant. The variant buttons, the heading and everything else stay exactly where they are.`;
1731
+ }, wr = (e, t) => `${e}: ${t} keeps which picture is the MAIN one beside the list of pictures, and the two fall out of step — remove the main picture and it points at the wrong one, or at none. The set is the app's own upload box with one more word: <UploadBox multiple max={10} sortable value={pictures} onChange={…} label="Photos" />. Its tiles drag into order under a mouse and a finger, the FIRST picture is the main one and says so, and every other tile has "Make main". Keep ONE ordered list of links, read pictures[0] as the main picture everywhere, and delete ${t} and any move-left / move-right or star buttons of your own.`, Tr = "(?:qty|quantit(?:y|ies)|stock|inventory|onhand|instock|guests|seats|adults|children|kids|tickets|nights|rooms|units|portions|servings|copies|howmany)", Er = (e) => RegExp(`\\b[\\w.]*${Tr}[\\w.]*(?<!\\.length)(?:\\s*(?:\\?\\?|\\|\\|)\\s*\\d+)?\\s*\\)?\\s*\\${e}\\s*1\\b|\\bset\\w*${Tr}\\w*\\s*\\(\\s*\\(?\\s*\\w+\\s*\\)?\\s*=>[^;\\n]{0,48}?\\${e}\\s*1\\b`, "i"), Dr = Er("+"), Or = Er("-"), kr = (e) => {
1710
1732
  if (/<NumberStepper\b/.test(e) || !/<button\b/i.test(e)) return null;
1711
- let t = e.match(Cr);
1712
- return t && wr.test(e) ? t[0].replace(/\s+/g, " ").trim() : null;
1713
- }, Er = (e, t) => `${e}: "${t}" — a count taken up and down by a − and a + this page drew itself: small under a thumb, nowhere to type a bigger number, and every press a write of its own. It is the app's own piece, imported from 'your-ai-workflow-firebase-os': <NumberStepper label="Stock" name={variant.name} value={variant.stock} min={0} onChange={(stock) => …} onSettled={(stock, from) => …} />. onChange fires on every press and is for the SCREEN (set the page's own state); onSettled fires ONCE when they have stopped, with what it is now and what it was — put the save there, and the history line if the page keeps one, so five presses are one write. Give it max wherever there is a most (what is in stock). Delete the two buttons, the number between them and their handlers; change nothing else on the page.`, Dr = /\bfrom\s+['"](recharts|chart\.js|react-chartjs-2|victory|d3(?:-[\w-]+)?|@nivo\/[\w-]+|apexcharts|react-apexcharts|echarts|echarts-for-react|@visx\/[\w-]+|chartist|plotly\.js|react-plotly\.js)['"]/, Or = /\b(?:width|height)\s*:\s*`\$\{[^`]{1,120}\}%`/, kr = (e) => {
1714
1733
  let t = e.match(Dr);
1734
+ return t && Or.test(e) ? t[0].replace(/\s+/g, " ").trim() : null;
1735
+ }, Ar = (e, t) => `${e}: "${t}" — a count taken up and down by a − and a + this page drew itself: small under a thumb, nowhere to type a bigger number, and every press a write of its own. It is the app's own piece, imported from 'your-ai-workflow-firebase-os': <NumberStepper label="Stock" name={variant.name} value={variant.stock} min={0} onChange={(stock) => …} onSettled={(stock, from) => …} />. onChange fires on every press and is for the SCREEN (set the page's own state); onSettled fires ONCE when they have stopped, with what it is now and what it was — put the save there, and the history line if the page keeps one, so five presses are one write. Give it max wherever there is a most (what is in stock). Delete the two buttons, the number between them and their handlers; change nothing else on the page.`, jr = /\bfrom\s+['"](recharts|chart\.js|react-chartjs-2|victory|d3(?:-[\w-]+)?|@nivo\/[\w-]+|apexcharts|react-apexcharts|echarts|echarts-for-react|@visx\/[\w-]+|chartist|plotly\.js|react-plotly\.js)['"]/, Mr = /\b(?:width|height)\s*:\s*`\$\{[^`]{1,120}\}%`/, Nr = (e) => {
1736
+ let t = e.match(jr);
1715
1737
  if (t) return t[1];
1716
1738
  if (/<Bars\b/.test(e) || !/\buseTallies\s*\(/.test(e)) return null;
1717
- let n = e.match(Or);
1739
+ let n = e.match(Mr);
1718
1740
  return n ? n[0].replace(/\s+/g, " ") : null;
1719
- }, Ar = (e, t) => `${e}: "${t}" — a chart this page drew for itself: its colours are not the owner's theme, and a count is one running total, so there are no dates to draw it over. Numbers side by side are the app's own piece, imported from 'your-ai-workflow-firebase-os': <Bars items={[{ label: 'Views', value: counts.of(product.id).views }, { label: 'Added to bag', value: counts.of(product.id).added }]} size="sm" /> — one bar for each number, the number at its tip, format={money} for prices. Delete the chart, its import and whatever worked its lengths out; change nothing else on the page.`, jr = /\b(?:window\s*\.\s*)?location\s*\.\s*(?:href\s*=(?!=)|assign\s*\(|replace\s*\()\s*['"`]\/(?!\/)[^'"`\s]*/, Mr = (e, t) => `${e}: "${t.replace(/\s+/g, " ")}…" goes to a page of this app by setting the browser's address, which throws the running app away and loads it again — whatever was open or half-typed is gone, and inside a preview the address is not there at all. Go there the app's way: const navigate = useNavigate() from 'react-router-dom' and navigate('/that-address') in the press, or <Link to="/that-address"> round the words. Keep the address, the words and everything else exactly as they are.`, Nr = /\b(?:UIDs?|user(?:'s)? ids?|document ids?|doc ids?|record ids?)\b/i, Pr = /\b(?:label|placeholder|title|aria-label|hint|helper)\s*=\s*(["'])([^"']{0,80})\1|>\s*([^<>{}=;()]{2,80}?)\s*[<{]/g, Fr = (e) => {
1741
+ }, Pr = (e, t) => `${e}: "${t}" — a chart this page drew for itself: its colours are not the owner's theme, and a count is one running total, so there are no dates to draw it over. Numbers side by side are the app's own piece, imported from 'your-ai-workflow-firebase-os': <Bars items={[{ label: 'Views', value: counts.of(product.id).views }, { label: 'Added to bag', value: counts.of(product.id).added }]} size="sm" /> — one bar for each number, the number at its tip, format={money} for prices. Delete the chart, its import and whatever worked its lengths out; change nothing else on the page.`, Fr = /\b(?:window\s*\.\s*)?location\s*\.\s*(?:href\s*=(?!=)|assign\s*\(|replace\s*\()\s*['"`]\/(?!\/)[^'"`\s]*/, Ir = (e, t) => `${e}: "${t.replace(/\s+/g, " ")}…" goes to a page of this app by setting the browser's address, which throws the running app away and loads it again — whatever was open or half-typed is gone, and inside a preview the address is not there at all. Go there the app's way: const navigate = useNavigate() from 'react-router-dom' and navigate('/that-address') in the press, or <Link to="/that-address"> round the words. Keep the address, the words and everything else exactly as they are.`, Lr = /\b(?:UIDs?|user(?:'s)? ids?|document ids?|doc ids?|record ids?)\b/i, Rr = /\b(?:label|placeholder|title|aria-label|hint|helper)\s*=\s*(["'])([^"']{0,80})\1|>\s*([^<>{}=;()]{2,80}?)\s*[<{]/g, zr = (e) => {
1720
1742
  let t = [];
1721
- for (let n of e.matchAll(Pr)) {
1743
+ for (let n of e.matchAll(Rr)) {
1722
1744
  let e = (n[2] ?? n[3] ?? "").trim();
1723
- e && Nr.test(e) && !t.includes(e) && t.push(e);
1745
+ e && Lr.test(e) && !t.includes(e) && t.push(e);
1724
1746
  }
1725
1747
  return t;
1726
- }, Ir = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} puts an id in front of people — nobody knows one, nobody can type one, and it means nothing to whoever reads it. A person is CHOSEN by name: a <CustomSelect> of people from 'your-ai-workflow-firebase-os' (my team read from user_profiles on a page for the team, usePeople('<role>') on a page whose entries are sent to people), the pick saved as the uid AND the name on the record, and only the NAME ever drawn. A record is pointed at by pressing it, never by typing its id. Delete every box that asks for an id and every id shown on the page; change nothing else.`, Lr = (e, t) => `${e}: ${G(t)} — a colour that is DATA (a product's shade) picked or drawn by hand.${t.some((e) => e.startsWith("<input")) ? " The browser draws its colour well in its own chrome, a grey rectangle no theme class reaches, with nowhere to type a hex." : ""}${t.some((e) => !e.startsWith("<input")) ? " A class built from a value does not exist when Tailwind compiles the page, so every chip it draws is blank." : ""} Both are the app's own pieces, imported from 'your-ai-workflow-firebase-os': pick with <SwatchField label="Shades" multiple max={6} value={variant.shades} onChange={(shades) => …} /> — it hands back clean hex strings, a list with multiple and one string without — and show with <Swatch color={shade} name={variant.name} selected={…} onClick={…} />. Keep each shade as that one string on the record. This is not the hard-coded colour rule 8 forbids: that is about the DESIGN's colours, which stay the theme's.`, Rr = /\bfit\s*=\s*(?:["']contain["']|\{\s*["'`]contain["'`]\s*\})/, zr = /(?:^|[\s"'`{:])(?:aspect-[\w[]|h-(?:\d|\[|full|screen|px\b)|inset-0\b)/, Br = /(?:^|[\s"'`{:])(?:aspect-[\w[]|h-(?:\d|\[|full|screen))/, Vr = /(?:^|[\s"'`{:])w-auto\b/, Hr = /\bsrc\s*=\s*\{[^}]*\[\s*(?:\d+|[A-Za-z_$][\w$]*)\s*\]/, Ur = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=[^;\n]*\[\s*(?:\d+|[A-Za-z_$][\w$]*)\s*\]/g, Wr = /\bsrc\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/, Gr = /logo|brand|partner|sponsor|client|badge|avatar|rounded-full/i, Kr = (e, t) => {
1748
+ }, Br = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} puts an id in front of people — nobody knows one, nobody can type one, and it means nothing to whoever reads it. A person is CHOSEN by name: a <CustomSelect> of people from 'your-ai-workflow-firebase-os' (my team read from user_profiles on a page for the team, usePeople('<role>') on a page whose entries are sent to people), the pick saved as the uid AND the name on the record, and only the NAME ever drawn. A record is pointed at by pressing it, never by typing its id. Delete every box that asks for an id and every id shown on the page; change nothing else.`, Vr = (e, t) => `${e}: ${G(t)} — a colour that is DATA (a product's shade) picked or drawn by hand.${t.some((e) => e.startsWith("<input")) ? " The browser draws its colour well in its own chrome, a grey rectangle no theme class reaches, with nowhere to type a hex." : ""}${t.some((e) => !e.startsWith("<input")) ? " A class built from a value does not exist when Tailwind compiles the page, so every chip it draws is blank." : ""} Both are the app's own pieces, imported from 'your-ai-workflow-firebase-os': pick with <SwatchField label="Shades" multiple max={6} value={variant.shades} onChange={(shades) => …} /> — it hands back clean hex strings, a list with multiple and one string without — and show with <Swatch color={shade} name={variant.name} selected={…} onClick={…} />. Keep each shade as that one string on the record. This is not the hard-coded colour rule 8 forbids: that is about the DESIGN's colours, which stay the theme's.`, Hr = /\bfit\s*=\s*(?:["']contain["']|\{\s*["'`]contain["'`]\s*\})/, Ur = /(?:^|[\s"'`{:])(?:aspect-[\w[]|h-(?:\d|\[|full|screen|px\b)|inset-0\b)/, Wr = /(?:^|[\s"'`{:])(?:aspect-[\w[]|h-(?:\d|\[|full|screen))/, Gr = /(?:^|[\s"'`{:])w-auto\b/, Kr = /\bsrc\s*=\s*\{[^}]*\[\s*(?:\d+|[A-Za-z_$][\w$]*)\s*\]/, qr = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=[^;\n]*\[\s*(?:\d+|[A-Za-z_$][\w$]*)\s*\]/g, Jr = /\bsrc\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/, Yr = /logo|brand|partner|sponsor|client|badge|avatar|rounded-full/i, Xr = (e, t) => {
1727
1749
  let n = 0;
1728
1750
  for (let r = t - 1; r >= 0; r--) {
1729
1751
  let t = e[r];
@@ -1737,10 +1759,10 @@ var vr = /\b(isMain|isPrimary|isCover|mainIndex|mainImageIndex|coverIndex|primar
1737
1759
  }
1738
1760
  }
1739
1761
  return !1;
1740
- }, qr = (e) => {
1762
+ }, Zr = (e) => {
1741
1763
  let t = (t) => [...e.matchAll(new RegExp(String.raw`(?:^|[\s"'\x60{:])(?:${t}|size)-(\d+(?:\.5)?|\[[^\]\s]*\]|[a-z]+)(?![\w-])`, "g"))].map((e) => e[1]), n = (e) => e.length > 0 && e.every((e) => /^\d/.test(e) && Number(e) <= 20);
1742
1764
  return n(t("w")) && n(t("h"));
1743
- }, Jr = (e, t) => {
1765
+ }, Qr = (e, t) => {
1744
1766
  let n = 0;
1745
1767
  for (let r = t > 0 ? e.lastIndexOf("<", t - 1) : -1; r >= 0; r = r > 0 ? e.lastIndexOf("<", r - 1) : -1) {
1746
1768
  if (e[r + 1] === "/") {
@@ -1758,19 +1780,19 @@ var vr = /\b(isMain|isPrimary|isCover|mainIndex|mainImageIndex|coverIndex|primar
1758
1780
  }
1759
1781
  }
1760
1782
  return null;
1761
- }, Yr = /\b(?:w-full\s+h-full|h-full\s+w-full|inset-0|size-full)\b/, Xr = /(?:^|[\s"'`{:])aspect-[\w[]/, Zr = /(?:^|[\s"'`{:])h-(?:\d|\[|screen|px\b)/, Qr = /["'`]https?:\/\/[^"'`\s]*(?:firebasestorage\.googleapis\.com|\.(?:png|jpe?g|webp|avif|gif|svg)(?=[?#"'`]))/i;
1762
- function $r(e) {
1783
+ }, $r = /\b(?:w-full\s+h-full|h-full\s+w-full|inset-0|size-full)\b/, ei = /(?:^|[\s"'`{:])aspect-[\w[]/, ti = /(?:^|[\s"'`{:])h-(?:\d|\[|screen|px\b)/, ni = /["'`]https?:\/\/[^"'`\s]*(?:firebasestorage\.googleapis\.com|\.(?:png|jpe?g|webp|avif|gif|svg)(?=[?#"'`]))/i;
1784
+ function ri(e) {
1763
1785
  let t = [], n = /* @__PURE__ */ new Set();
1764
- for (let t of e.matchAll(Ur)) n.add(t[1]);
1765
- let r = !Qr.test(e);
1786
+ for (let t of e.matchAll(qr)) n.add(t[1]);
1787
+ let r = !ni.test(e);
1766
1788
  for (let i of e.matchAll(/<Picture\b/g)) {
1767
1789
  let a = i.index ?? 0, o = Q(e, a);
1768
- if (!/\bsrc\s*=\s*\{/.test(o) || Gr.test(o) || qr(o)) continue;
1769
- let s = Yr.test(o) ? Jr(e, a) : null;
1770
- if (s && qr(s)) continue;
1771
- let c = Wr.exec(o)?.[1], l = Hr.test(o) || !!c && n.has(c) || Kr(e, a), u = Rr.test(o) && zr.test(o), d = !Rr.test(o) && Br.test(o) && !Vr.test(o) && l;
1790
+ if (!/\bsrc\s*=\s*\{/.test(o) || Yr.test(o) || Zr(o)) continue;
1791
+ let s = $r.test(o) ? Qr(e, a) : null;
1792
+ if (s && Zr(s)) continue;
1793
+ let c = Jr.exec(o)?.[1], l = Kr.test(o) || !!c && n.has(c) || Xr(e, a), u = Hr.test(o) && Ur.test(o), d = !Hr.test(o) && Wr.test(o) && !Gr.test(o) && l;
1772
1794
  if (!u && !d) continue;
1773
- let f = s && zr.test(s) ? s : "", p = f && !Zr.test(o) && !Xr.test(o) ? f : o, m = r && Xr.test(p) && !Zr.test(p) && !Zr.test(o);
1795
+ let f = s && Ur.test(s) ? s : "", p = f && !ti.test(o) && !ei.test(o) ? f : o, m = r && ei.test(p) && !ti.test(p) && !ti.test(o);
1774
1796
  t.push({
1775
1797
  tag: o.replace(/\s+/g, " ").slice(0, 96).trim(),
1776
1798
  box: f,
@@ -1779,13 +1801,13 @@ function $r(e) {
1779
1801
  }
1780
1802
  return t;
1781
1803
  }
1782
- var ei = (e) => {
1804
+ var ii = (e) => {
1783
1805
  let t = /^<([A-Za-z][\w.]*)/.exec(e)?.[1] || "div", n = (/\bclassName\s*=\s*\{?\s*["'`]([^"'`]*)["'`]/.exec(e)?.[1] || "").replace(/\s+/g, " ").trim(), r = n.length > 60 ? `${n.slice(0, 60).replace(/\s+\S*$/, "")} …` : n;
1784
1806
  return r ? `<${t} className="${r}">` : `<${t}>`;
1785
- }, ti = (e, t) => {
1786
- let n = t.slice(0, 4).map((e) => `"${e.tag}…"${e.box ? ` in ${ei(e.box)}` : ""}`), r = t.length > 4 ? ` and ${t.length - 4} more like them` : "", i = n.length > 1 ? `${n.slice(0, -1).join(", ")} and ${n[n.length - 1]}${r}` : `${n[0]}${r}`, a = t.some((e) => e.box);
1807
+ }, ai = (e, t) => {
1808
+ let n = t.slice(0, 4).map((e) => `"${e.tag}…"${e.box ? ` in ${ii(e.box)}` : ""}`), r = t.length > 4 ? ` and ${t.length - 4} more like them` : "", i = n.length > 1 ? `${n.slice(0, -1).join(", ")} and ${n[n.length - 1]}${r}` : `${n[0]}${r}`, a = t.some((e) => e.box);
1787
1809
  return `${e}: ${i} ${t.length > 1 ? "frame each picture" : "frames each picture"} in a box of a shape you chose — the picture cropped to it, or sat small inside it with the box's tint showing round it — where in a gallery, a shop, a portfolio or any grid or row of pictures every frame is cut to its OWN picture's shape and the picture fills it exactly. Take the aspect-…/h-… off it${a ? " AND off the box named round it — a picture freed inside a box that keeps its shape sits small in it" : " (and out of any box shaped round it)"}, take off fit="contain" if it has one, and give it only its width and rounding — <Picture src={p.photo} alt={p.name} className="w-full rounded-2xl" /> — and it takes the picture's own shape, held open before the picture lands, with nothing cropped. A row of thumbnails is the one place a height goes, with the width left to the picture (className="h-20 w-auto shrink-0"). Lay pictures of different shapes out as columns that keep their own heights (columns-2 md:columns-3 gap-6, every tile break-inside-avoid mb-6), never rows of equal boxes.${K(t.length)}`;
1788
- }, ni = /\[[^\][]*?\.\.\.\s*([A-Za-z_$][\w$]*)(?![\w$.(])[^\][]*?\.\.\.\s*\1(?![\w$])[^\][]*\]/g, ri = /\b([A-Za-z_$][\w$]*)\.concat\(\s*\1(?![\w$])[^)]*\)/g, ii = "(?:[A-Za-z_$][\\w$]*)?(?:[Pp]roduct|[Ii]tem|[Pp]hoto|[Pp]icture|[Ii]mage|[Gg]aller|[Cc]ard|[Ee]ntr|[Rr]ecord|[Ll]isting|[Ww]ork|[Pp]roject|[Pp]ost|[Ll]ook|[Pp]iece|[Cc]ollection)[\\w$]*", ai = RegExp(`(?:\\bArray\\.from\\(\\s*\\{\\s*length\\s*:[^}]*\\}\\s*,|(?:\\[\\s*\\.\\.\\.\\s*Array\\([^)]*\\)\\s*\\]|\\bArray\\([^)]*\\)(?:\\.fill\\([^)]*\\))?)\\s*\\.map\\()\\s*\\(?[^)=]*\\)?\\s*=>\\s*[({\\s.]*(${ii})\\s*\\[[^\\]]*%\\s*\\1\\.length\\s*\\]`, "g"), oi = (e, t) => {
1810
+ }, oi = /\[[^\][]*?\.\.\.\s*([A-Za-z_$][\w$]*)(?![\w$.(])[^\][]*?\.\.\.\s*\1(?![\w$])[^\][]*\]/g, si = /\b([A-Za-z_$][\w$]*)\.concat\(\s*\1(?![\w$])[^)]*\)/g, ci = "(?:[A-Za-z_$][\\w$]*)?(?:[Pp]roduct|[Ii]tem|[Pp]hoto|[Pp]icture|[Ii]mage|[Gg]aller|[Cc]ard|[Ee]ntr|[Rr]ecord|[Ll]isting|[Ww]ork|[Pp]roject|[Pp]ost|[Ll]ook|[Pp]iece|[Cc]ollection)[\\w$]*", li = RegExp(`(?:\\bArray\\.from\\(\\s*\\{\\s*length\\s*:[^}]*\\}\\s*,|(?:\\[\\s*\\.\\.\\.\\s*Array\\([^)]*\\)\\s*\\]|\\bArray\\([^)]*\\)(?:\\.fill\\([^)]*\\))?)\\s*\\.map\\()\\s*\\(?[^)=]*\\)?\\s*=>\\s*[({\\s.]*(${ci})\\s*\\[[^\\]]*%\\s*\\1\\.length\\s*\\]`, "g"), ui = (e, t) => {
1789
1811
  let n = 0;
1790
1812
  for (let r = t; r < e.length; r++) {
1791
1813
  let t = e[r];
@@ -1793,23 +1815,23 @@ var ei = (e) => {
1793
1815
  else if ((t === "]" || t === "}") && --n === 0) return t === "]" ? r : -1;
1794
1816
  }
1795
1817
  return -1;
1796
- }, si = (e) => {
1818
+ }, di = (e) => {
1797
1819
  let t = [], n = 0, r = -1;
1798
1820
  for (let i = 0; i < e.length; i++) {
1799
1821
  let a = e[i];
1800
1822
  a === "{" || a === "[" ? (n === 0 && a === "{" && (r = i), n++) : (a === "}" || a === "]") && (n--, n === 0 && a === "}" && r >= 0 && (t.push(e.slice(r, i + 1)), r = -1));
1801
1823
  }
1802
1824
  return t;
1803
- }, ci = /https?:\/\/|\.(?:jpe?g|png|webp|avif|gif)\b/i, li = /\b(?:id|_id|key|sku)\s*:\s*(?:'[^']*'|"[^"]*"|`[^`]*`|[\w$.-]+)\s*,?/g;
1804
- function ui(e) {
1825
+ }, fi = /https?:\/\/|\.(?:jpe?g|png|webp|avif|gif)\b/i, pi = /\b(?:id|_id|key|sku)\s*:\s*(?:'[^']*'|"[^"]*"|`[^`]*`|[\w$.-]+)\s*,?/g;
1826
+ function mi(e) {
1805
1827
  let t = [];
1806
1828
  for (let n of e.matchAll(/\[\s*\{/g)) {
1807
- let r = n.index ?? 0, i = oi(e, r);
1829
+ let r = n.index ?? 0, i = ui(e, r);
1808
1830
  if (i < 0) continue;
1809
1831
  let a = /* @__PURE__ */ new Set();
1810
- for (let n of si(e.slice(r + 1, i))) {
1811
- if (!ci.test(n)) continue;
1812
- let e = n.replace(li, "").replace(/\s+/g, "");
1832
+ for (let n of di(e.slice(r + 1, i))) {
1833
+ if (!fi.test(n)) continue;
1834
+ let e = n.replace(pi, "").replace(/\s+/g, "");
1813
1835
  if (a.has(e)) {
1814
1836
  let e = /\b(?:name|title)\s*:\s*(['"`])([^'"`]{1,40})\1/.exec(n);
1815
1837
  t.push(e ? e[2] : n.replace(/\s+/g, " ").slice(0, 60));
@@ -1819,16 +1841,16 @@ function ui(e) {
1819
1841
  }
1820
1842
  return t;
1821
1843
  }
1822
- var di = /^(?:logo|brand|partner|sponsor|press|quote|testimonial|word|letter|tag|client|customer|compan(?:y|ie)|review|award|mention|name|social|stat|by)s?$/i, fi = (e) => (e.match(/[A-Z]{2,}(?![a-z])|[A-Z]?[a-z0-9]+/g) || [e]).pop() || e;
1823
- function pi(e) {
1844
+ var hi = /^(?:logo|brand|partner|sponsor|press|quote|testimonial|word|letter|tag|client|customer|compan(?:y|ie)|review|award|mention|name|social|stat|by)s?$/i, gi = (e) => (e.match(/[A-Z]{2,}(?![a-z])|[A-Z]?[a-z0-9]+/g) || [e]).pop() || e;
1845
+ function _i(e) {
1824
1846
  let t = [
1825
- ni,
1826
- ri,
1827
- ai
1828
- ].flatMap((t) => [...e.matchAll(t)].filter((e) => t === ai || !di.test(fi(e[1]))).map((e) => e[0].replace(/\s+/g, " ").trim())).map((e) => e.length > 64 ? `${e.slice(0, 64)}…` : e);
1829
- return [...new Set([...t, ...ui(e)])];
1847
+ oi,
1848
+ si,
1849
+ li
1850
+ ].flatMap((t) => [...e.matchAll(t)].filter((e) => t === li || !hi.test(gi(e[1]))).map((e) => e[0].replace(/\s+/g, " ").trim())).map((e) => e.length > 64 ? `${e.slice(0, 64)}…` : e);
1851
+ return [...new Set([...t, ...mi(e)])];
1830
1852
  }
1831
- var mi = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*(['"`])(https?:\/\/[^'"`\s]+)\2/g, hi = /\bsrc\s*=\s*(?:\{\s*)?(['"`])(https?:\/\/[^'"`\s]+)\1/, gi = /(?:^|[\s"'`{])(?:[a-z-]+:)?hidden(?=[\s"'`}]|$)/, _i = /^<(?:Popup|Lightbox|Modal|Dialog|Sheet|Drawer)\b/, vi = (e, t) => {
1853
+ var vi = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*(['"`])(https?:\/\/[^'"`\s]+)\2/g, yi = /\bsrc\s*=\s*(?:\{\s*)?(['"`])(https?:\/\/[^'"`\s]+)\1/, bi = /(?:^|[\s"'`{])(?:[a-z-]+:)?hidden(?=[\s"'`}]|$)/, xi = /^<(?:Popup|Lightbox|Modal|Dialog|Sheet|Drawer)\b/, Si = (e, t) => {
1832
1854
  let n = 0;
1833
1855
  for (let r = t > 0 ? e.lastIndexOf("<", t - 1) : -1; r >= 0; r = r > 0 ? e.lastIndexOf("<", r - 1) : -1) {
1834
1856
  if (e[r + 1] === "/") {
@@ -1846,22 +1868,22 @@ var mi = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*(['"`
1846
1868
  }
1847
1869
  }
1848
1870
  return -1;
1849
- }, yi = (e, t) => {
1871
+ }, Ci = (e, t) => {
1850
1872
  let n = Q(e, t);
1851
- if (/\bblur-/.test(n) || gi.test(n)) return !0;
1852
- for (let n = vi(e, t), r = 0; n >= 0 && r < 12; n = vi(e, n), r++) {
1873
+ if (/\bblur-/.test(n) || bi.test(n)) return !0;
1874
+ for (let n = Si(e, t), r = 0; n >= 0 && r < 12; n = Si(e, n), r++) {
1853
1875
  let t = Q(e, n);
1854
- if (_i.test(t) || gi.test(t)) return !0;
1876
+ if (xi.test(t) || bi.test(t)) return !0;
1855
1877
  }
1856
1878
  return !1;
1857
1879
  };
1858
- function bi(e) {
1880
+ function wi(e) {
1859
1881
  let t = /* @__PURE__ */ new Map();
1860
- for (let n of e.matchAll(mi)) t.set(n[1], n[3]);
1882
+ for (let n of e.matchAll(vi)) t.set(n[1], n[3]);
1861
1883
  let n = /* @__PURE__ */ new Map();
1862
1884
  for (let r of e.matchAll(/<Picture\b/g)) {
1863
- let i = r.index ?? 0, a = Q(e, i), o = Wr.exec(a)?.[1], s = hi.exec(a)?.[2] || (o ? t.get(o) : void 0);
1864
- if (!s || Kr(e, i) || yi(e, i)) continue;
1885
+ let i = r.index ?? 0, a = Q(e, i), o = Jr.exec(a)?.[1], s = yi.exec(a)?.[2] || (o ? t.get(o) : void 0);
1886
+ if (!s || Xr(e, i) || Ci(e, i)) continue;
1865
1887
  let c = decodeURIComponent(s.split("?")[0].split("/").pop() || s).split("/").pop() || s, l = o ? `${o} (${c.replace(/^.*~/, "")})` : c.replace(/^.*~/, ""), u = n.get(s);
1866
1888
  n.set(s, {
1867
1889
  label: u?.label || l,
@@ -1870,16 +1892,16 @@ function bi(e) {
1870
1892
  }
1871
1893
  return [...n.values()].filter((e) => e.times > 1).map((e) => e.label);
1872
1894
  }
1873
- var xi = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} drawn by more than one <Picture> — one of the owner's pictures used again to fill another spot. Each picture they gave you is drawn ONCE, in the spot their line for it names (rule 15). Keep that one, and where it was drawn again put a placeholder in its place: one plain frame in a soft tint of the theme (bg-foreground/5) at the shape that spot wants, with a simple inline <svg> line drawing in text-foreground/30 (rule 27) — never another of their pictures moved in to fill it. When they give a picture for that spot, it will take the placeholder's frame.`, Si = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "show" : "shows"} the same thing again to fill out the page — every product, picture and entry appears ONCE (rule 15). Take the repeat out and let the layout fit however many there are: three products are three cards, in fewer columns or centred, never one of them shown twice. A customer who meets the same product twice sees a shop padded out, and wonders which one to buy.`, Ci = /className\s*=\s*(?:\{\s*)?(?:`|"|')(?=[^`"']*\bfixed\b)(?=[^`"']*\binset-0\b)(?![^`"']*(?:pointer-events-none|(?:^|[\s:])-z-))[^`"']*(?:`|"|')/g, wi = /(?:^|[\s"'`])(?:z-0|z-\[\s*(?:0|-\d+)\s*\])(?=[\s"'`]|$)/, Ti = /(?:^|[\s"'`:])-?z-(?:\d|\[)/, Ei = /\bbg-(?:gradient-|\[(?:radial|linear|conic)-gradient|\[url\(|noise\b|grid\b|dots?\b)/;
1874
- function Di(e) {
1895
+ var Ti = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} drawn by more than one <Picture> — one of the owner's pictures used again to fill another spot. Each picture they gave you is drawn ONCE, in the spot their line for it names (rule 15). Keep that one, and where it was drawn again put a placeholder in its place: one plain frame in a soft tint of the theme (bg-foreground/5) at the shape that spot wants, with a simple inline <svg> line drawing in text-foreground/30 (rule 27) — never another of their pictures moved in to fill it. When they give a picture for that spot, it will take the placeholder's frame.`, Ei = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "show" : "shows"} the same thing again to fill out the page — every product, picture and entry appears ONCE (rule 15). Take the repeat out and let the layout fit however many there are: three products are three cards, in fewer columns or centred, never one of them shown twice. A customer who meets the same product twice sees a shop padded out, and wonders which one to buy.`, Di = /className\s*=\s*(?:\{\s*)?(?:`|"|')(?=[^`"']*\bfixed\b)(?=[^`"']*\binset-0\b)(?![^`"']*(?:pointer-events-none|(?:^|[\s:])-z-))[^`"']*(?:`|"|')/g, Oi = /(?:^|[\s"'`])(?:z-0|z-\[\s*(?:0|-\d+)\s*\])(?=[\s"'`]|$)/, ki = /(?:^|[\s"'`:])-?z-(?:\d|\[)/, Ai = /\bbg-(?:gradient-|\[(?:radial|linear|conic)-gradient|\[url\(|noise\b|grid\b|dots?\b)/;
1896
+ function ji(e) {
1875
1897
  let t = 0;
1876
1898
  for (let { attrs: n, end: r } of $(e, "div|section|aside|span")) {
1877
- let i = /\bclassName\s*=/.test(n) ? n.match(Ci) : null;
1878
- i && (wi.test(i[0]) || /\/\s*$/.test(n) && !/\bon[A-Z]\w*\s*=/.test(n) && !Ti.test(i[0]) && Ei.test(i[0]) || /^\s*<(?:Loader|Skeleton)\b[^<]*?\/>\s*<\/(?:motion\.)?\w+>/.test(e.slice(r + 1)) || (t += 1));
1899
+ let i = /\bclassName\s*=/.test(n) ? n.match(Di) : null;
1900
+ i && (Oi.test(i[0]) || /\/\s*$/.test(n) && !/\bon[A-Z]\w*\s*=/.test(n) && !ki.test(i[0]) && Ai.test(i[0]) || /^\s*<(?:Loader|Skeleton)\b[^<]*?\/>\s*<\/(?:motion\.)?\w+>/.test(e.slice(r + 1)) || (t += 1));
1879
1901
  }
1880
1902
  return t;
1881
1903
  }
1882
- var Oi = (e, t) => `${e}: ${t > 1 ? `${t} windows here are` : "a window here is"} drawn by hand — a \`fixed inset-0\` layer of the page's own. Nobody can click out of one, Escape does nothing, the page behind keeps scrolling, and anything else the page draws on top (a confirmation, a second window) lands underneath it. ${K(t)}Replace ${t > 1 ? "each" : "it"} with <Popup open={…} onClose={() => …} title="…"> … </Popup> imported from 'your-ai-workflow-firebase-os' — the same state that opens it now, what is inside it unchanged; side="right" for a basket that slides in from the edge, size="lg" for a product, footer={…} for its one main button; a picture opened large out of a set is <Lightbox pictures={photos} index={at} onIndex={setAt} onClose={() => setAt(null)} /> — and delete the layer, its backdrop, its own close button and the AnimatePresence round it: Popup closes on a click outside, on Escape and on its ✕, and eases in and out by itself (rule 30).`, ki = /\b(?:set|show|hide|add|push|use)?[A-Za-z]*(?:[Tt]oast|[Ss]nackbar)[A-Za-z]*\b/g, Ai = (e) => e.replace(/'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"/g, "''").replace(/`(?:[^`\\$]|\\.|\$(?!\{))*`/g, "``").replace(/>([^<>{}]*)</g, "><"), ji = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} a confirmation this page draws for itself — and one drawn by a page lands UNDER the window that was pressed, so nobody sees it: somebody presses Add to bag, sees nothing happen, presses again, and finds five in their basket. Delete it — the state, the timer and what it draws — and at the moment the thing is done call say('Added to your bag', { action: { label: 'View bag', onClick: () => … } }) imported from 'your-ai-workflow-firebase-os': the app's own brief line, above every window, gone by itself. Let the button that was pressed say it too for a moment ("Added"), and keep the number it changed somewhere that is always on screen (rule 30).`, Mi = /['"`]pub_([A-Za-z0-9_]+)['"`]/g, Ni = /\b(?:setDoc|addDoc|updateDoc|deleteDoc|writeBatch|runTransaction)\s*\(/, Pi = (e, t) => {
1904
+ var Mi = (e, t) => `${e}: ${t > 1 ? `${t} windows here are` : "a window here is"} drawn by hand — a \`fixed inset-0\` layer of the page's own. Nobody can click out of one, Escape does nothing, the page behind keeps scrolling, and anything else the page draws on top (a confirmation, a second window) lands underneath it. ${K(t)}Replace ${t > 1 ? "each" : "it"} with <Popup open={…} onClose={() => …} title="…"> … </Popup> imported from 'your-ai-workflow-firebase-os' — the same state that opens it now, what is inside it unchanged; side="right" for a basket that slides in from the edge, size="lg" for a product, footer={…} for its one main button; a picture opened large out of a set is <Lightbox pictures={photos} index={at} onIndex={setAt} onClose={() => setAt(null)} /> — and delete the layer, its backdrop, its own close button and the AnimatePresence round it: Popup closes on a click outside, on Escape and on its ✕, and eases in and out by itself (rule 30).`, Ni = /\b(?:set|show|hide|add|push|use)?[A-Za-z]*(?:[Tt]oast|[Ss]nackbar)[A-Za-z]*\b/g, Pi = (e) => e.replace(/'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"/g, "''").replace(/`(?:[^`\\$]|\\.|\$(?!\{))*`/g, "``").replace(/>([^<>{}]*)</g, "><"), Fi = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} a confirmation this page draws for itself — and one drawn by a page lands UNDER the window that was pressed, so nobody sees it: somebody presses Add to bag, sees nothing happen, presses again, and finds five in their basket. Delete it — the state, the timer and what it draws — and at the moment the thing is done call say('Added to your bag', { action: { label: 'View bag', onClick: () => … } }) imported from 'your-ai-workflow-firebase-os': the app's own brief line, above every window, gone by itself. Let the button that was pressed say it too for a moment ("Added"), and keep the number it changed somewhere that is always on screen (rule 30).`, Ii = /['"`]pub_([A-Za-z0-9_]+)['"`]/g, Li = /\b(?:setDoc|addDoc|updateDoc|deleteDoc|writeBatch|runTransaction)\s*\(/, Ri = (e, t) => {
1883
1905
  let n = `['"\`]pub_${t}['"\`]`, r = String.raw`(?:setDoc|addDoc|updateDoc|deleteDoc|\.set|\.update|\.delete)\s*\(`;
1884
1906
  if (RegExp(`${r}[^;]{0,200}?${n}`).test(e)) return !0;
1885
1907
  let i = String.raw`(?:doc\s*\(\s*)?(?:doc|collection)\s*\(\s*db\s*,\s*${n}`;
@@ -1892,31 +1914,31 @@ var Oi = (e, t) => `${e}: ${t > 1 ? `${t} windows here are` : "a window here is"
1892
1914
  }
1893
1915
  return !1;
1894
1916
  };
1895
- function Fi(e) {
1896
- if (!Ni.test(e)) return [];
1917
+ function zi(e) {
1918
+ if (!Li.test(e)) return [];
1897
1919
  let t = /* @__PURE__ */ new Set();
1898
- for (let n of e.matchAll(Mi)) t.add(n[1]);
1899
- return [...t].filter((t) => t !== "bookings" && t !== "files" && RegExp(`['"\`](?:mem|admin|role_[A-Za-z0-9]+)_${t}['"\`]`).test(e) && Pi(e, t));
1920
+ for (let n of e.matchAll(Ii)) t.add(n[1]);
1921
+ return [...t].filter((t) => t !== "bookings" && t !== "files" && RegExp(`['"\`](?:mem|admin|role_[A-Za-z0-9]+)_${t}['"\`]`).test(e) && Ri(e, t));
1900
1922
  }
1901
- var Ii = (e, t) => RegExp(`['"\`]((?:mem|admin|role_[A-Za-z0-9]+)_${t})['"\`]`).exec(e)?.[1] || `mem_${t}`, Li = (e, t, n) => {
1902
- let r = Ii(t, n[0]);
1923
+ var Bi = (e, t) => RegExp(`['"\`]((?:mem|admin|role_[A-Za-z0-9]+)_${t})['"\`]`).exec(e)?.[1] || `mem_${t}`, Vi = (e, t, n) => {
1924
+ let r = Bi(t, n[0]);
1903
1925
  return `${e}: this page writes the public copy in ${G(n.map((e) => `pub_${e}`))} itself, and a copy written by a page is how the page that reads it comes to show every thing TWICE: the page's own list holds things the server does not — a record drawn at once under a made-up id while its save is on its way, a list remembered in localStorage, a copy made with addDoc — and a loop that copies "whatever has no copy yet" puts each into pub_${n[0]} as a thing of its own, beside the real one that lands a moment later. The copy is the app's to keep. Delete every write to pub_${n[0]} from this file — on a save, on a delete, in the catch-up effect, where an order is applied — with the helper that strips fields for it, and say once at the top of the component: usePublicCopy('${r}') imported from 'your-ai-workflow-firebase-os', adding { leaveOut: ['…'] } for the fields that helper stripped other than who made the record. The app then keeps pub_${n[0]} matching ${r} while this page is open — each record whole, once, under its own id, only once the server has it — and clears out the repeats already there. Write ${r} exactly as now, never draw a new record under an id made up in the page, and change nothing else (rule 11).`;
1904
- }, Ri = /\bactions\s*\??\.\s*(?:records|loading|error|saveRecord|updateRecord|deleteRecord|collection|user)\b/, zi = (e, t, n) => {
1926
+ }, Hi = /\bactions\s*\??\.\s*(?:records|loading|error|saveRecord|updateRecord|deleteRecord|collection|user)\b/, Ui = (e, t, n) => {
1905
1927
  let r = n.match(/['"](pub_[a-z0-9_]+)['"]/)?.[1], i = r ? `'${r}'` : "'pub_<the other page's collection>' — the sentence that built this page names it";
1906
1928
  return `${e}: ${t.replace(/\s+/g, "")} is read on a page anyone can open, and such a page is handed no actions at all — actions?.records is empty for ever here, so the page shows nothing to every visitor. What it shows is the pub_ copy the other page's app keeps, read by NAME: import { db } from 'your-ai-workflow-firebase-os' and onSnapshot(collection(db, ${i}), …) from 'firebase/firestore' at the top of the component, a loading state until the first answer lands, and those records handed to everything below that asked for actions.records (useStock(records) included). Delete every actions. read from this file (rule 11).`;
1907
- }, Bi = (e) => /\buseStock\s*\(/.test(e) && /\b(?:saveRecord|updateRecord)\b/.test(e) && !/\busePublicCopy\s*\(/.test(e), Vi = (e) => `${e}: this page keeps the products a shop sells — useStock() says so — and keeps NO public copy of them: a shop anyone can open reads only a pub_ collection, and nothing writes one, so it shows no products at all. Say once at the top of the component usePublicCopy(actions.collection) imported from 'your-ai-workflow-firebase-os' (actions.collection is this page's own collection; add { leaveOut: ['…'] } for what is the owner's own business) — the app then keeps the pub_ copy while this page is open, whole, once, under each record's own id. Write nothing to pub_ yourself, and change nothing else (rule 11).`, Hi = /<(?:motion\.)?h1[\s>]/, Ui = /\bfrom\s+['"`]\.\.?\/[^'"`]*['"`]/, Wi = /<(?:div|section|main|article|header)[\s>]/, Gi = (e) => Tn(e) && Wi.test(e) && !Hi.test(e) && !Ui.test(e), Ki = (e) => `${e}: this is a page of the website and it has no <h1> — nothing on it says what page this is. A page anyone can open gets no heading from the app, so its content starts with ONE <h1>: the page's own name, or my line for it, sized to suit the page, never as big as it will go — a calm one (text-2xl md:text-4xl font-semibold tracking-tight text-foreground) on a page that is about its things, a display line only on a page that opens on a big picture or a band of colour — with at most one quiet line under it and clear room over it (pt-10 md:pt-16), never pressed up under the menu. SectionHeading is an <h2> — right for the sections below, never the page's title. Add the <h1> at the top and change nothing else (rule 30).`, qi = RegExp("className\\s*=\\s*(?:\\{\\s*)?(?:`|\"|')([^`\"']*\\bgrid\\b[^`\"']*)(?:`|\"|')\\s*\\}?\\s*>\\s*\\{\\s*(" + ii + ")\\s*\\.(?:slice\\([^)]*\\)\\s*\\.)?map\\(", "g");
1908
- function Ji(e) {
1929
+ }, Wi = (e) => /\buseStock\s*\(/.test(e) && /\b(?:saveRecord|updateRecord)\b/.test(e) && !/\busePublicCopy\s*\(/.test(e), Gi = (e) => `${e}: this page keeps the products a shop sells — useStock() says so — and keeps NO public copy of them: a shop anyone can open reads only a pub_ collection, and nothing writes one, so it shows no products at all. Say once at the top of the component usePublicCopy(actions.collection) imported from 'your-ai-workflow-firebase-os' (actions.collection is this page's own collection; add { leaveOut: ['…'] } for what is the owner's own business) — the app then keeps the pub_ copy while this page is open, whole, once, under each record's own id. Write nothing to pub_ yourself, and change nothing else (rule 11).`, Ki = /<(?:motion\.)?h1[\s>]/, qi = /\bfrom\s+['"`]\.\.?\/[^'"`]*['"`]/, Ji = /<(?:div|section|main|article|header)[\s>]/, Yi = (e) => Tn(e) && Ji.test(e) && !Ki.test(e) && !qi.test(e), Xi = (e) => `${e}: this is a page of the website and it has no <h1> — nothing on it says what page this is. A page anyone can open gets no heading from the app, so its content starts with ONE <h1>: the page's own name, or my line for it, sized to suit the page, never as big as it will go — a calm one (text-2xl md:text-4xl font-semibold tracking-tight text-foreground) on a page that is about its things, a display line only on a page that opens on a big picture or a band of colour — with at most one quiet line under it and clear room over it (pt-10 md:pt-16), never pressed up under the menu. SectionHeading is an <h2> — right for the sections below, never the page's title. Add the <h1> at the top and change nothing else (rule 30).`, Zi = RegExp("className\\s*=\\s*(?:\\{\\s*)?(?:`|\"|')([^`\"']*\\bgrid\\b[^`\"']*)(?:`|\"|')\\s*\\}?\\s*>\\s*\\{\\s*(" + ci + ")\\s*\\.(?:slice\\([^)]*\\)\\s*\\.)?map\\(", "g");
1930
+ function Qi(e) {
1909
1931
  let t = [];
1910
- for (let n of e.matchAll(qi)) {
1932
+ for (let n of e.matchAll(Zi)) {
1911
1933
  let r = n[1];
1912
1934
  if (!(/(?:^|\s)grid-cols-1(?:\s|$)/.test(r) || !/(?:^|\s)grid-cols-\d/.test(r))) continue;
1913
- let i = (n.index ?? 0) + n[0].length - 1, a = zc(e, i);
1935
+ let i = (n.index ?? 0) + n[0].length - 1, a = Uc(e, i);
1914
1936
  a < 0 || !/<Picture\b/.test(e.slice(i, a)) || t.push(`{${n[2]}.map(…)}`);
1915
1937
  }
1916
1938
  return [...new Set(t)];
1917
1939
  }
1918
- var Yi = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} laid out ONE to a row on a phone, so every card is the whole width of the screen and a handful of things is a long scroll — and on a wide screen the cards are as oversized. A set of cards is two across on a phone and more as the screen widens: className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6" (pictures of mixed shapes: "columns-2 md:columns-3 xl:columns-4 gap-4", each tile break-inside-avoid mb-4). Change that one className and nothing else: every card keeps everything it holds, exactly as it looks now — its picture, its words, its swatches or choices, its buttons — and nothing new goes round the grid (rule 30).`, Xi = /\bdraggable(?=[\s/>=])(?!\s*=\s*(?:\{\s*false\s*\}|["']false["']))|\bonDragStart\s*=(?!\s*\{\s*\(?\s*([A-Za-z_$][\w$]*)\s*\)?\s*=>\s*\{?\s*\1\s*\.\s*(?:stopPropagation|preventDefault)\s*\(\s*\)\s*;?\s*\}?\s*\})/, Zi = /\bfrom\s+['"`](react-beautiful-dnd|@hello-pangea\/dnd|@dnd-kit\/[^'"`]+|react-dnd(?:-[^'"`]+)?|sortablejs|react-sortablejs|react-draggable)['"`]/g, Qi = (e, t) => `${e}: ${t.length ? `${G(t)} ${t.length > 1 ? "are" : "is"} not part of this app — the import resolves in a preview and fails when the app is published — and dragging here is already a piece of the app.` : "this page drags things with the browser's own drag and drop (draggable, onDragStart, onDrop) — nothing moves under a FINGER, a keyboard cannot reach it, and a picture inside the card starts a drag of its own and cancels the card's."} Both kinds of dragging are imported from 'your-ai-workflow-firebase-os'. Things moved BETWEEN stages — orders, leads, applications — are <Pipeline stages={[{ id: 'new', label: 'New' }, …]} items={orders} stageOf={(o) => o.stage} onMove={(o, stage) => updateDoc(doc(db, '…', o.id), { stage })} onOpen={(o) => setChosen(o)} card={(o) => <>…what one card says…</>} />. A set put IN ORDER — pictures, lessons, rows — is <Sortable items={pictures} onReorder={(next) => save(next)} className="grid grid-cols-3 gap-3" item={(p) => …} />. Delete every drag handler, the dragged / drag-over state and any "move left / move right" buttons added to make up for them: the pieces work under a mouse, a finger and the arrow keys, show where the thing will land, and put it back with a sentence when the save fails (rule 31).`;
1919
- function $i(e) {
1940
+ var $i = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} laid out ONE to a row on a phone, so every card is the whole width of the screen and a handful of things is a long scroll — and on a wide screen the cards are as oversized. A set of cards is two across on a phone and more as the screen widens: className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6" (pictures of mixed shapes: "columns-2 md:columns-3 xl:columns-4 gap-4", each tile break-inside-avoid mb-4). Change that one className and nothing else: every card keeps everything it holds, exactly as it looks now — its picture, its words, its swatches or choices, its buttons — and nothing new goes round the grid (rule 30).`, ea = /\bdraggable(?=[\s/>=])(?!\s*=\s*(?:\{\s*false\s*\}|["']false["']))|\bonDragStart\s*=(?!\s*\{\s*\(?\s*([A-Za-z_$][\w$]*)\s*\)?\s*=>\s*\{?\s*\1\s*\.\s*(?:stopPropagation|preventDefault)\s*\(\s*\)\s*;?\s*\}?\s*\})/, ta = /\bfrom\s+['"`](react-beautiful-dnd|@hello-pangea\/dnd|@dnd-kit\/[^'"`]+|react-dnd(?:-[^'"`]+)?|sortablejs|react-sortablejs|react-draggable)['"`]/g, na = (e, t) => `${e}: ${t.length ? `${G(t)} ${t.length > 1 ? "are" : "is"} not part of this app — the import resolves in a preview and fails when the app is published — and dragging here is already a piece of the app.` : "this page drags things with the browser's own drag and drop (draggable, onDragStart, onDrop) — nothing moves under a FINGER, a keyboard cannot reach it, and a picture inside the card starts a drag of its own and cancels the card's."} Both kinds of dragging are imported from 'your-ai-workflow-firebase-os'. Things moved BETWEEN stages — orders, leads, applications — are <Pipeline stages={[{ id: 'new', label: 'New' }, …]} items={orders} stageOf={(o) => o.stage} onMove={(o, stage) => updateDoc(doc(db, '…', o.id), { stage })} onOpen={(o) => setChosen(o)} card={(o) => <>…what one card says…</>} />. A set put IN ORDER — pictures, lessons, rows — is <Sortable items={pictures} onReorder={(next) => save(next)} className="grid grid-cols-3 gap-3" item={(p) => …} />. Delete every drag handler, the dragged / drag-over state and any "move left / move right" buttons added to make up for them: the pieces work under a mouse, a finger and the arrow keys, show where the thing will land, and put it back with a sentence when the save fails (rule 31).`;
1941
+ function ra(e) {
1920
1942
  let t = 0;
1921
1943
  for (let n of e.matchAll(/<table[\s>]/g)) {
1922
1944
  let r = e.indexOf("</table>", n.index ?? 0);
@@ -1924,15 +1946,15 @@ function $i(e) {
1924
1946
  }
1925
1947
  return t;
1926
1948
  }
1927
- var ea = (e, t) => `${e}: ${t > 1 ? `${t} lists of records here are each` : "a list of records here is"} drawn as a <table> — on a phone that is a strip of small print to drag sideways, with the button that matters off the edge, and its rows cannot be reached by a keyboard or sorted. Replace ${t > 1 ? "each" : "it"} with <DataTable rows={orders} loading={loading} columns={[{ key: 'number', label: 'Order', value: (o) => o.number }, { key: 'total', label: 'Total', value: (o) => o.total, cell: (o) => money(o.total), align: 'right' }]} onOpen={(o) => setChosen(o)} actions={(o) => …} search="Search orders" empty="No orders yet" /> imported from 'your-ai-workflow-firebase-os': a table whose headings sort on a wide screen, one readable block a record on a phone, soft rows while it loads. \`value\` is the plain value a column sorts and searches by, \`cell\` what it draws when that is more than the value, and \`phone: false\` leaves a column off a phone. Delete the <table>, its overflow-x-auto wrapper and any sorting or searching written for it (rule 31).`, ta = /\bconst\s*\[\s*((?:[A-Za-z_$][\w$]*)?[sS]tep(?:Index|Idx|Number|No|Id)?)\s*,\s*[A-Za-z_$][\w$]*\s*\]\s*=\s*useState\b/g;
1928
- function na(e) {
1929
- return /\buseSteps\s*\(|\.get\(\s*['"`][\w-]*step[\w-]*['"`]\s*\)/i.test(e) ? [] : /<(?:Input|input|TextArea|textarea|select|CustomSelect|UploadBox|AddressField|PhoneInputField|BuyerDetails|SwatchField|NumberStepper)\b/.test(e) ? W(e, ta, 1) : /<(?:Checkout|BuyButton|PayButton|Basket)\b/.test(e) ? W(e, ta, 1).filter((t) => {
1949
+ var ia = (e, t) => `${e}: ${t > 1 ? `${t} lists of records here are each` : "a list of records here is"} drawn as a <table> — on a phone that is a strip of small print to drag sideways, with the button that matters off the edge, and its rows cannot be reached by a keyboard or sorted. Replace ${t > 1 ? "each" : "it"} with <DataTable rows={orders} loading={loading} columns={[{ key: 'number', label: 'Order', value: (o) => o.number }, { key: 'total', label: 'Total', value: (o) => o.total, cell: (o) => money(o.total), align: 'right' }]} onOpen={(o) => setChosen(o)} actions={(o) => …} search="Search orders" empty="No orders yet" /> imported from 'your-ai-workflow-firebase-os': a table whose headings sort on a wide screen, one readable block a record on a phone, soft rows while it loads. \`value\` is the plain value a column sorts and searches by, \`cell\` what it draws when that is more than the value, and \`phone: false\` leaves a column off a phone. Delete the <table>, its overflow-x-auto wrapper and any sorting or searching written for it (rule 31).`, aa = /\bconst\s*\[\s*((?:[A-Za-z_$][\w$]*)?[sS]tep(?:Index|Idx|Number|No|Id)?)\s*,\s*[A-Za-z_$][\w$]*\s*\]\s*=\s*useState\b/g;
1950
+ function oa(e) {
1951
+ return /\buseSteps\s*\(|\.get\(\s*['"`][\w-]*step[\w-]*['"`]\s*\)/i.test(e) ? [] : /<(?:Input|input|TextArea|textarea|select|CustomSelect|UploadBox|AddressField|PhoneInputField|BuyerDetails|SwatchField|NumberStepper)\b/.test(e) ? W(e, aa, 1) : /<(?:Checkout|BuyButton|PayButton|Basket)\b/.test(e) ? W(e, aa, 1).filter((t) => {
1930
1952
  let n = new RegExp(String.raw`\bconst\s*\[\s*${t}\s*,\s*([A-Za-z_$][\w$]*)`).exec(e)?.[1];
1931
1953
  return n ? new RegExp(String.raw`\b${n}\s*\(\s*(?:\(?\s*[A-Za-z_$][\w$]*\s*\)?\s*=>\s*)?[A-Za-z_$][\w$]*\s*[+-]\s*1\b`).test(e) ? !0 : new Set([...e.matchAll(new RegExp(String.raw`\b(?:${n}|useState(?:<[^>]*>)?)\s*\(\s*(['"\x60][^'"\x60]*['"\x60]|\d+)\s*\)`, "g"))].map((e) => e[1])).size >= 3 : !1;
1932
1954
  }) : [];
1933
1955
  }
1934
- var ra = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "keep" : "keeps"} the step somebody is on in useState, so RELOADING the page starts them again at step one, the browser's Back button leaves the page instead of going back a step, and nobody can be sent a link to the step they are stuck on. Replace it with const flow = useSteps([{ id: 'details', label: 'Details' }, { id: 'pictures', label: 'Pictures' }, { id: 'review', label: 'Review' }]) imported from 'your-ai-workflow-firebase-os' — flow.step is the step on screen and it lives in the web address (?step=pictures, beside the ?product=… already there), flow.next() and flow.back() move and the browser's Back button follows, flow.reset() once the thing is sent — and draw the line across the top with <StepBar {...flow.bar} /> in place of numbered circles of your own. What was typed on each step is still useVisitorState (rules 16, 19 and 31).`, ia = /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*)+)/g, aa = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} put on the page with dangerouslySetInnerHTML — whatever anybody managed to type into that record then RUNS as code on every visitor's screen, and takes their session with it. Long writing somebody typed — a post, a description, a method — is <Article text={${t[0]}} /> imported from 'your-ai-workflow-firebase-os': a blank line is a paragraph, "# " a heading, "- " a list, **bold**, [words](link), and a picture on a line of its own as ![what it shows](link) — and it never reads HTML, so nothing typed can run. A short line is plain {text} (rule 31).`;
1935
- function oa(e) {
1956
+ var sa = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "keep" : "keeps"} the step somebody is on in useState, so RELOADING the page starts them again at step one, the browser's Back button leaves the page instead of going back a step, and nobody can be sent a link to the step they are stuck on. Replace it with const flow = useSteps([{ id: 'details', label: 'Details' }, { id: 'pictures', label: 'Pictures' }, { id: 'review', label: 'Review' }]) imported from 'your-ai-workflow-firebase-os' — flow.step is the step on screen and it lives in the web address (?step=pictures, beside the ?product=… already there), flow.next() and flow.back() move and the browser's Back button follows, flow.reset() once the thing is sent — and draw the line across the top with <StepBar {...flow.bar} /> in place of numbered circles of your own. What was typed on each step is still useVisitorState (rules 16, 19 and 31).`, ca = /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*)+)/g, la = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} put on the page with dangerouslySetInnerHTML — whatever anybody managed to type into that record then RUNS as code on every visitor's screen, and takes their session with it. Long writing somebody typed — a post, a description, a method — is <Article text={${t[0]}} /> imported from 'your-ai-workflow-firebase-os': a blank line is a paragraph, "# " a heading, "- " a list, **bold**, [words](link), and a picture on a line of its own as ![what it shows](link) — and it never reads HTML, so nothing typed can run. A short line is plain {text} (rule 31).`;
1957
+ function ua(e) {
1936
1958
  let t = /* @__PURE__ */ new Set();
1937
1959
  if (/\.toFixed\s*\(/.test(e)) for (let n of e.matchAll(/\b(?:function|const)\s+((?:format|to|as)(?:Currency|Price|Money|Amount|Usd|Eur|Gbp)|currencyFormat(?:ter)?|priceFormat(?:ter)?)\b/g)) t.add(`${n[1]}()`);
1938
1960
  for (let n of e.split("\n")) {
@@ -1947,7 +1969,7 @@ function oa(e) {
1947
1969
  }
1948
1970
  return [...t];
1949
1971
  }
1950
- var sa = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "write" : "writes"} a price out by hand. .toFixed throws on a price that was saved as text or is not there yet — and the whole page goes blank; a "$" typed in front is wrong the day the <PayButton> beside it charges in euros; and 1250 reads "$1250.00" while the Transactions screen says "$1,250.00" for the same order. Every price, total and fee is money(amount) imported from 'your-ai-workflow-firebase-os' — money(25) is 25 in the owner's own currency ("£25.00" for an account in pounds), money(1250, 'eur') is "€1,250.00", and something that is not a number is a quiet dash — with the SAME currency word the page passes to its PayButton, and none where it passes none. Delete the formatter of your own and every typed currency sign.${K(t.length)} (rule 31)`;
1972
+ var da = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "write" : "writes"} a price out by hand. .toFixed throws on a price that was saved as text or is not there yet — and the whole page goes blank; a "$" typed in front is wrong the day the <PayButton> beside it charges in euros; and 1250 reads "$1250.00" while the Transactions screen says "$1,250.00" for the same order. Every price, total and fee is money(amount) imported from 'your-ai-workflow-firebase-os' — money(25) is 25 in the owner's own currency ("£25.00" for an account in pounds), money(1250, 'eur') is "€1,250.00", and something that is not a number is a quiet dash — with the SAME currency word the page passes to its PayButton, and none where it passes none. Delete the formatter of your own and every typed currency sign.${K(t.length)} (rule 31)`;
1951
1973
  function $(e, t) {
1952
1974
  let n = [], r = RegExp(`<((?:motion\\.)?(?:${t}))(?=[\\s/>])`, "g");
1953
1975
  for (let t of e.matchAll(r)) {
@@ -1975,33 +1997,33 @@ function $(e, t) {
1975
1997
  }
1976
1998
  return n;
1977
1999
  }
1978
- var ca = /onClick\s*=\s*\{\s*\(?\s*([A-Za-z_$][\w$]*)\s*\)?\s*=>\s*\{?\s*\1\s*\.\s*(?:stopPropagation|preventDefault)\s*\(\s*\)\s*;?\s*\}?\s*\}/;
1979
- function la(e) {
2000
+ var fa = /onClick\s*=\s*\{\s*\(?\s*([A-Za-z_$][\w$]*)\s*\)?\s*=>\s*\{?\s*\1\s*\.\s*(?:stopPropagation|preventDefault)\s*\(\s*\)\s*;?\s*\}?\s*\}/;
2001
+ function pa(e) {
1980
2002
  let t = /* @__PURE__ */ new Set();
1981
- for (let { tag: n, attrs: r } of $(e, "div|span|li|tr|td|p|h[1-6]|section|article|figure|header|footer|img|label")) /\bonClick\s*=/.test(r) && (/\brole\s*=/.test(r) || ca.test(r) || n === "label" && /\bhtmlFor\s*=/.test(r) || t.add(`<${n} onClick>`));
2003
+ for (let { tag: n, attrs: r } of $(e, "div|span|li|tr|td|p|h[1-6]|section|article|figure|header|footer|img|label")) /\bonClick\s*=/.test(r) && (/\brole\s*=/.test(r) || fa.test(r) || n === "label" && /\bhtmlFor\s*=/.test(r) || t.add(`<${n} onClick>`));
1982
2004
  return [...t];
1983
2005
  }
1984
- var ua = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} pressed with a mouse and by nothing else — a keyboard never reaches ${t.length > 1 ? "them" : "it"} and a screen reader does not know ${t.length > 1 ? "they" : "it"} can be pressed. Whatever is pressed is a <button type="button" className="text-left …"> (a <Link to="…"> where it goes to another page), with the same classes and the same onClick. Where a whole card is pressable AND has buttons of its own inside it — a button cannot sit in a button — keep the element and give it role="button" tabIndex={0} onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); open(); } }}. A row of records that opens is <DataTable onOpen={…}>, and a card in a pipeline is <Pipeline onOpen={…}>, which do this themselves.${K(t.length)} (rule 30)`, da = "(?![©®™])\\p{Extended_Pictographic}", fa = "✕✖✗✘✓✔←→↑↓↗⟳↻⟲▶◀▼▲▸◂▾▴‹›»«★☆⌄", pa = RegExp(`^(?:${da}|[${fa}ו]|[\\uFE0F\\u200D\\s])+$`, "u"), ma = RegExp(`${da}|[${fa}×]`, "u"), ha = RegExp(`^([${fa}])\\uFE0F?\\s+\\S`, "u"), ga = RegExp(`\\S\\s+([${fa}])\\uFE0F?$`, "u");
1985
- function _a(e) {
2006
+ var ma = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} pressed with a mouse and by nothing else — a keyboard never reaches ${t.length > 1 ? "them" : "it"} and a screen reader does not know ${t.length > 1 ? "they" : "it"} can be pressed. Whatever is pressed is a <button type="button" className="text-left …"> (a <Link to="…"> where it goes to another page), with the same classes and the same onClick. Where a whole card is pressable AND has buttons of its own inside it — a button cannot sit in a button — keep the element and give it role="button" tabIndex={0} onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); open(); } }}. A row of records that opens is <DataTable onOpen={…}>, and a card in a pipeline is <Pipeline onOpen={…}>, which do this themselves.${K(t.length)} (rule 30)`, ha = "(?![©®™])\\p{Extended_Pictographic}", ga = "✕✖✗✘✓✔←→↑↓↗⟳↻⟲▶◀▼▲▸◂▾▴‹›»«★☆⌄", _a = RegExp(`^(?:${ha}|[${ga}ו]|[\\uFE0F\\u200D\\s])+$`, "u"), va = RegExp(`${ha}|[${ga}×]`, "u"), ya = RegExp(`^([${ga}])\\uFE0F?\\s+\\S`, "u"), ba = RegExp(`\\S\\s+([${ga}])\\uFE0F?$`, "u");
2007
+ function xa(e) {
1986
2008
  let t = /* @__PURE__ */ new Set(), n = (e) => t.add(e.replace(/\s+/g, " ").trim().slice(0, 28));
1987
2009
  for (let t of e.matchAll(/>([^<>{}]+)</g)) {
1988
2010
  let e = t[1].trim();
1989
- if (!(!e || !ma.test(e))) {
1990
- if (pa.test(e)) {
2011
+ if (!(!e || !va.test(e))) {
2012
+ if (_a.test(e)) {
1991
2013
  e !== "•" && n(e);
1992
2014
  continue;
1993
2015
  }
1994
- (ha.test(e) || ga.test(e)) && n(e);
2016
+ (ya.test(e) || ba.test(e)) && n(e);
1995
2017
  }
1996
2018
  }
1997
2019
  for (let t of e.matchAll(/(['"`])([^'"`\n]{1,8})\1/g)) {
1998
2020
  let e = t[2].trim();
1999
- e && e !== "×" && e !== "•" && ma.test(e) && pa.test(e) && n(e);
2021
+ e && e !== "×" && e !== "•" && va.test(e) && _a.test(e) && n(e);
2000
2022
  }
2001
2023
  return [...t];
2002
2024
  }
2003
- var va = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are characters" : "is a character"} standing in for an icon. A character is drawn by the visitor's own phone — in ITS colours and ITS style, never the theme's, a different drawing on every device — and it is the first thing that makes a page look home-made. An icon is a lucide-react icon: import { X, Check, ArrowRight, ArrowLeft, ShoppingBag, Camera, Package, Star } from 'lucide-react' and draw <X className="w-4 h-4" /> for ✕, <Check /> for ✓, <ArrowRight /> and <ArrowLeft /> for → and ←, and the icon OF the thing for an emoji (🛍️ is <ShoppingBag />, 📷 is <Camera />, 📦 is <Package />) — beside the same words, inside the same button. A turning ⟳ is <Loader /> from 'your-ai-workflow-firebase-os'. And where rule 27 would not have put an icon at all, take it out and leave the words.${K(t.length)} (rule 30)`, ya = /(?:window\s*\.\s*)?(?:local|session)Storage\s*(?:\.\s*(?:get|set|remove)Item|\[)/g, ba = (e) => `${(e.split("/").pop() || "").replace(/\.[jt]sx?$/, "").replace(/Content$/, "").toLowerCase() || "page"}-draft`, xa = (e, t) => `${e}: ${G(t)} — this page keeps what somebody is half-way through in the browser's storage itself. Written by hand it comes apart the same way every time: the whole form is saved on every change, so the EMPTY form is stored the moment the page opens and a draft is offered back to somebody who has typed nothing; Discard empties the form, the next save puts the empty one straight back, and the message returns for ever. It also reads storage while the page is being drawn, so the first paint disagrees with the second, and it throws outright in a private window. The app keeps it instead: const { value, setValue, clear, ready } = useVisitorState('${ba(e)}', {…the empty form…}), imported from 'your-ai-workflow-firebase-os'. Write it with setValue as they type — never from an effect that runs on load — draw the empty form until ready turns true, and clear() the moment the save lands AND the moment they discard it. Offer a draft back only while ready is true and what is in it is not the empty form: nothing is kept until something is really typed, and what was discarded stays discarded. Take every line that touches storage out of this file (rule 19).`, Sa = /\banimate-spin\b(?![-[])/, Ca = (e) => `${e}: this page sets something spinning itself (animate-spin) — a ring of its own, a character, an icon — so its loading looks like no other screen of the app, and keeps turning for somebody who asked their device for less motion. Loading is the app's: <Loader /> for a ring in a line (size="sm" | "md" | "lg"), <Loader block label="Loading orders" /> in the space a list will take, <Skeleton className="h-24" /> — one per card, row or number still to come — wherever the shape is known, and <Button isLoading={saving}> for a button that is sending; all imported from 'your-ai-workflow-firebase-os'. Replace every animate-spin with the one that fits and delete the element it was on (rule 30).`, wa = /(?<![\w-])text-\[(?:(?:[0-9]|1[01])(?:\.\d+)?px|0?\.(?:[0-6]\d*|7[0-4]\d*)r?em)\]/g, Ta = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} smaller than small print — on a phone nobody reads it, and it fails every accessibility check the owner's clients may run. The smallest words on any page are text-xs (12px), and anything somebody has to READ — a name, a price, a note, a status — is text-sm or larger. Change every one, and where that makes a card too full, say less on the card and the rest in its <Popup> — never shrink the words to fit.${K(t.length)} (rule 30)`;
2004
- function Ea(e, t) {
2025
+ var Sa = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are characters" : "is a character"} standing in for an icon. A character is drawn by the visitor's own phone — in ITS colours and ITS style, never the theme's, a different drawing on every device — and it is the first thing that makes a page look home-made. An icon is a lucide-react icon: import { X, Check, ArrowRight, ArrowLeft, ShoppingBag, Camera, Package, Star } from 'lucide-react' and draw <X className="w-4 h-4" /> for ✕, <Check /> for ✓, <ArrowRight /> and <ArrowLeft /> for → and ←, and the icon OF the thing for an emoji (🛍️ is <ShoppingBag />, 📷 is <Camera />, 📦 is <Package />) — beside the same words, inside the same button. A turning ⟳ is <Loader /> from 'your-ai-workflow-firebase-os'. And where rule 27 would not have put an icon at all, take it out and leave the words.${K(t.length)} (rule 30)`, Ca = /(?:window\s*\.\s*)?(?:local|session)Storage\s*(?:\.\s*(?:get|set|remove)Item|\[)/g, wa = (e) => `${(e.split("/").pop() || "").replace(/\.[jt]sx?$/, "").replace(/Content$/, "").toLowerCase() || "page"}-draft`, Ta = (e, t) => `${e}: ${G(t)} — this page keeps what somebody is half-way through in the browser's storage itself. Written by hand it comes apart the same way every time: the whole form is saved on every change, so the EMPTY form is stored the moment the page opens and a draft is offered back to somebody who has typed nothing; Discard empties the form, the next save puts the empty one straight back, and the message returns for ever. It also reads storage while the page is being drawn, so the first paint disagrees with the second, and it throws outright in a private window. The app keeps it instead: const { value, setValue, clear, ready } = useVisitorState('${wa(e)}', {…the empty form…}), imported from 'your-ai-workflow-firebase-os'. Write it with setValue as they type — never from an effect that runs on load — draw the empty form until ready turns true, and clear() the moment the save lands AND the moment they discard it. Offer a draft back only while ready is true and what is in it is not the empty form: nothing is kept until something is really typed, and what was discarded stays discarded. Take every line that touches storage out of this file (rule 19).`, Ea = /\banimate-spin\b(?![-[])/, Da = (e) => `${e}: this page sets something spinning itself (animate-spin) — a ring of its own, a character, an icon — so its loading looks like no other screen of the app, and keeps turning for somebody who asked their device for less motion. Loading is the app's: <Loader /> for a ring in a line (size="sm" | "md" | "lg"), <Loader block label="Loading orders" /> in the space a list will take, <Skeleton className="h-24" /> — one per card, row or number still to come — wherever the shape is known, and <Button isLoading={saving}> for a button that is sending; all imported from 'your-ai-workflow-firebase-os'. Replace every animate-spin with the one that fits and delete the element it was on (rule 30).`, Oa = /(?<![\w-])text-\[(?:(?:[0-9]|1[01])(?:\.\d+)?px|0?\.(?:[0-6]\d*|7[0-4]\d*)r?em)\]/g, ka = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} smaller than small print — on a phone nobody reads it, and it fails every accessibility check the owner's clients may run. The smallest words on any page are text-xs (12px), and anything somebody has to READ — a name, a price, a note, a status — is text-sm or larger. Change every one, and where that makes a card too full, say less on the card and the rest in its <Popup> — never shrink the words to fit.${K(t.length)} (rule 30)`;
2026
+ function Aa(e, t) {
2005
2027
  let n = /* @__PURE__ */ new Set();
2006
2028
  for (let { end: r } of $(e, "Button|button")) {
2007
2029
  if (e[r - 1] === "/") continue;
@@ -2010,8 +2032,8 @@ function Ea(e, t) {
2010
2032
  }
2011
2033
  return [...n];
2012
2034
  }
2013
- var Da = /^(?:submit|ok|okay|confirm|proceed|click here)$/i, Oa = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are buttons that do" : "is a button that does"} not say what ${t.length > 1 ? "they do" : "it does"}. Somebody deciding whether to press it has to work out from the rest of the page what will happen — and "Submit" is the word that makes a page feel like a form to be got through. A button says the thing itself, in the person's own words: "Add product", "Send request", "Save changes", "Book this time", "Pay" with the amount. Change only the words — the button, its onClick and its place stay exactly as they are. A yes-or-no before a delete is <ConfirmModal confirmText="Delete" …>, which already names it (rule 20).${K(t.length)} (rule 32)`, ka = /^(?:apply|apply filters?|apply changes|update results|refresh|reload|refresh list)$/i, Aa = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are presses" : "is a press"} nobody should have to make — added to EVERY use of the page. Filters and a search narrow the list AS somebody picks and types: keep what they chose in state and filter the list from it on every render, with no button to apply it. And a list is never refreshed by hand: read it live — onSnapshot on its collection, or the page's own hook — so it is up to date the moment anything changes, for everybody looking at it. Delete the button, and whatever state only existed to hold the choices until it was pressed.${K(t.length)} (rule 32)`;
2014
- function ja(e) {
2035
+ var ja = /^(?:submit|ok|okay|confirm|proceed|click here)$/i, Ma = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are buttons that do" : "is a button that does"} not say what ${t.length > 1 ? "they do" : "it does"}. Somebody deciding whether to press it has to work out from the rest of the page what will happen — and "Submit" is the word that makes a page feel like a form to be got through. A button says the thing itself, in the person's own words: "Add product", "Send request", "Save changes", "Book this time", "Pay" with the amount. Change only the words — the button, its onClick and its place stay exactly as they are. A yes-or-no before a delete is <ConfirmModal confirmText="Delete" …>, which already names it (rule 20).${K(t.length)} (rule 32)`, Na = /^(?:apply|apply filters?|apply changes|update results|refresh|reload|refresh list)$/i, Pa = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are presses" : "is a press"} nobody should have to make — added to EVERY use of the page. Filters and a search narrow the list AS somebody picks and types: keep what they chose in state and filter the list from it on every render, with no button to apply it. And a list is never refreshed by hand: read it live — onSnapshot on its collection, or the page's own hook — so it is up to date the moment anything changes, for everybody looking at it. Delete the button, and whatever state only existed to hold the choices until it was pressed.${K(t.length)} (rule 32)`;
2036
+ function Fa(e) {
2015
2037
  let t = [];
2016
2038
  for (let n of e.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"]lucide-react['"]/g)) for (let e of n[1].split(",")) {
2017
2039
  let n = e.trim().split(/\s+as\s+/).pop()?.trim();
@@ -2019,8 +2041,8 @@ function ja(e) {
2019
2041
  }
2020
2042
  return t;
2021
2043
  }
2022
- function Ma(e) {
2023
- let t = ja(e);
2044
+ function Ia(e) {
2045
+ let t = Fa(e);
2024
2046
  if (!t.length) return [];
2025
2047
  let n = `<(${t.join("|")})\\b[^>]*\\/?>`, r = /* @__PURE__ */ new Set(), i = (e) => e.replace(/<[^>]*>/g, " ").replace(/\{[^}]*\}/g, " ").replace(/\s+/g, " ").trim().slice(0, 32);
2026
2048
  for (let t of e.matchAll(/<(h[1-3]|SectionHeading)\b[^>]*>([\s\S]*?)<\/\1>/g)) {
@@ -2031,25 +2053,25 @@ function Ma(e) {
2031
2053
  for (let t of e.matchAll(RegExp(`<(h[1-3]|SectionHeading)\\b[^>]*>([\\s\\S]*?)<\\/\\1>\\s*${n}`, "g"))) r.add(`<${t[3]}> beside "${i(t[2])}"`);
2032
2054
  return [...r];
2033
2055
  }
2034
- var Na = (e, t) => `${e}: ${G(t)} — an icon nobody asked for, on a heading. A little picture beside a title is the first thing that makes a page look generated: it says nothing the words do not, and every page gets a different one. A heading is its words, set in the theme's type — take the icon out (and the flex wrapper that only existed to hold it) and leave the words exactly as they are. An icon stays only where the owner's own words asked for one there.${K(t.length)} (rule 27)`, Pa = /(?:^|>)\s*(?:No|Nothing|None|Not any)\b[^<{]{0,80}?\b(?:yet|so far|to show|found|here)\b|\b(?:is|are) empty\b|\bEmpty\b[^<{]{0,20}?(?:list|basket|bag|cart|inbox)/i, Fa = /\bw-(?:8|9|1\d|2\d|3\d)\b|\bsize=\{(?:3[2-9]|[4-9]\d|\d{3})\}/;
2035
- function Ia(e) {
2036
- let t = ja(e);
2056
+ var La = (e, t) => `${e}: ${G(t)} — an icon nobody asked for, on a heading. A little picture beside a title is the first thing that makes a page look generated: it says nothing the words do not, and every page gets a different one. A heading is its words, set in the theme's type — take the icon out (and the flex wrapper that only existed to hold it) and leave the words exactly as they are. An icon stays only where the owner's own words asked for one there.${K(t.length)} (rule 27)`, Ra = /(?:^|>)\s*(?:No|Nothing|None|Not any)\b[^<{]{0,80}?\b(?:yet|so far|to show|found|here)\b|\b(?:is|are) empty\b|\bEmpty\b[^<{]{0,20}?(?:list|basket|bag|cart|inbox)/i, za = /\bw-(?:8|9|1\d|2\d|3\d)\b|\bsize=\{(?:3[2-9]|[4-9]\d|\d{3})\}/;
2057
+ function Ba(e) {
2058
+ let t = Fa(e);
2037
2059
  if (!t.length) return [];
2038
2060
  let n = /* @__PURE__ */ new Set();
2039
2061
  for (let r of e.matchAll(RegExp(`<(${t.join("|")})\\b([^>]*)\\/?>`, "g"))) {
2040
- if (!Fa.test(r[2])) continue;
2041
- let t = e.slice(r.index + r[0].length, r.index + r[0].length + 500).match(Pa);
2062
+ if (!za.test(r[2])) continue;
2063
+ let t = e.slice(r.index + r[0].length, r.index + r[0].length + 500).match(Ra);
2042
2064
  t && n.add(`<${r[1]}> over "${t[0].replace(/^>\s*/, "").trim().slice(0, 40)}"`);
2043
2065
  }
2044
2066
  return [...n];
2045
2067
  }
2046
- var La = (e, t) => `${e}: ${G(t)} — an empty list drawn as a poster: a big icon, a bold line, an explanation. Nobody asked for the picture, and a page with nothing in it yet should look calm, not announced. An empty state is ONE short, quiet line of words in the quieter text colour (text-foreground/60, text-sm), with room around it — no icon, no circle or dashed box round it, no second line — plus the button that adds the first thing where there is one. Take the icon and the wrapper out and keep the words.${K(t.length)} (rule 27)`, Ra = (e, t) => {
2068
+ var Va = (e, t) => `${e}: ${G(t)} — an empty list drawn as a poster: a big icon, a bold line, an explanation. Nobody asked for the picture, and a page with nothing in it yet should look calm, not announced. An empty state is ONE short, quiet line of words in the quieter text colour (text-foreground/60, text-sm), with room around it — no icon, no circle or dashed box round it, no second line — plus the button that adds the first thing where there is one. Take the icon and the wrapper out and keep the words.${K(t.length)} (rule 27)`, Ha = (e, t) => {
2047
2069
  let n = 0;
2048
2070
  for (let r = t; r < e.length; r++) if (e[r] === "(") n++;
2049
2071
  else if (e[r] === ")" && --n === 0) return r;
2050
2072
  return e.length;
2051
- }, za = /\buseCases\s*[<(]/;
2052
- function Ba(e) {
2073
+ }, Ua = /\buseCases\s*[<(]/;
2074
+ function Wa(e) {
2053
2075
  let t = /* @__PURE__ */ new Set();
2054
2076
  for (let n of e.matchAll(/\{([^{}]*)\}\s*=\s*useCases\s*[<(]/g)) {
2055
2077
  let e = n[1].match(/\brecords\b(?:\s*:\s*([A-Za-z_$][\w$]*))?/);
@@ -2061,99 +2083,99 @@ function Ba(e) {
2061
2083
  }
2062
2084
  return [...t];
2063
2085
  }
2064
- var Va = /<(?:CustomSelect|Input|TextArea|textarea|input|select)\b/g;
2065
- function Ha(e) {
2066
- if (!za.test(e)) return [];
2086
+ var Ga = /<(?:CustomSelect|Input|TextArea|textarea|input|select)\b/g;
2087
+ function Ka(e) {
2088
+ if (!Ua.test(e)) return [];
2067
2089
  let t = /* @__PURE__ */ new Set();
2068
- for (let n of Ba(e)) for (let r of e.matchAll(RegExp(`(?:^|[^\\w$.])(${n})\\s*(?:\\?\\.|\\.)\\s*map\\s*\\(`, "g"))) {
2090
+ for (let n of Wa(e)) for (let r of e.matchAll(RegExp(`(?:^|[^\\w$.])(${n})\\s*(?:\\?\\.|\\.)\\s*map\\s*\\(`, "g"))) {
2069
2091
  let i = (r.index ?? 0) + r[0].length - 1;
2070
- (e.slice(i, Ra(e, i)).match(Va) || []).length >= 2 && t.add(`${n}.map(…)`);
2092
+ (e.slice(i, Ha(e, i)).match(Ga) || []).length >= 2 && t.add(`${n}.map(…)`);
2071
2093
  }
2072
2094
  return [...t];
2073
2095
  }
2074
- var Ua = (e, t) => `${e}: every entry here is drawn OPEN, one under another, each with its own boxes and dropdowns (inside ${G(t)}) — a dozen orders is a long scroll, one at a time, and nobody can see at a glance what is waiting. Draw each one as a compact card or row saying only what tells it apart — its number or name, who it is for, its stage as a small pill, when, how much, who holds it — and move everything done to one into the one that is OPENED: keep its id, const [openId, setOpenId] = useState<string | null>(null), a press on a card sets it, and const open = records.find((r) => r.id === openId) is drawn whole in <Popup open={!!open} onClose={() => setOpenId(null)} title={…} side="right">, laid out as an even grid — each label above its value, one spacing throughout, fields and buttons side by side the same height. Where entries move through stages, the people who work on them see <Pipeline stages={…} items={records} stageOf={(o) => o.status} onMove={(o, stage) => …the save the page already has…} onOpen={(o) => setOpenId(o.id)} card={(o) => <>…</>} />, imported from 'your-ai-workflow-firebase-os'; a person who only follows their own sees theirs as the short list. Keep every save, sendTo, takeBack and notify exactly as it is — only where it is drawn moves (rules 6 and 31).`, Wa = /[\w$]*items\s*(?:(?:\|\||\?\?)\s*\[\s*\])?\s*\)?\s*\??$/i;
2075
- function Ga(e) {
2076
- if (!za.test(e)) return [];
2096
+ var qa = (e, t) => `${e}: every entry here is drawn OPEN, one under another, each with its own boxes and dropdowns (inside ${G(t)}) — a dozen orders is a long scroll, one at a time, and nobody can see at a glance what is waiting. Draw each one as a compact card or row saying only what tells it apart — its number or name, who it is for, its stage as a small pill, when, how much, who holds it — and move everything done to one into the one that is OPENED: keep its id, const [openId, setOpenId] = useState<string | null>(null), a press on a card sets it, and const open = records.find((r) => r.id === openId) is drawn whole in <Popup open={!!open} onClose={() => setOpenId(null)} title={…} side="right">, laid out as an even grid — each label above its value, one spacing throughout, fields and buttons side by side the same height. Where entries move through stages, the people who work on them see <Pipeline stages={…} items={records} stageOf={(o) => o.status} onMove={(o, stage) => …the save the page already has…} onOpen={(o) => setOpenId(o.id)} card={(o) => <>…</>} />, imported from 'your-ai-workflow-firebase-os'; a person who only follows their own sees theirs as the short list. Keep every save, sendTo, takeBack and notify exactly as it is — only where it is drawn moves (rules 6 and 31).`, Ja = /[\w$]*items\s*(?:(?:\|\||\?\?)\s*\[\s*\])?\s*\)?\s*\??$/i;
2097
+ function Ya(e) {
2098
+ if (!Ua.test(e)) return [];
2077
2099
  let t = /* @__PURE__ */ new Set();
2078
2100
  for (let n of e.matchAll(/\.map\s*\(\s*\(?\s*([A-Za-z_$][\w$]*)/g)) {
2079
2101
  let r = n.index ?? 0, i = e.slice(Math.max(0, r - 60), r);
2080
- if (!Wa.test(i)) continue;
2102
+ if (!Ja.test(i)) continue;
2081
2103
  let [, a, o] = /(\.\s*)?([A-Za-z_$][\w$]*)\s*(?:(?:\|\||\?\?)\s*\[\s*\])?\s*\)?\s*\??$/.exec(i) || [];
2082
2104
  if (o && !a && new RegExp(String.raw`\b(?:const|let|var)\s+${o.replace(/\$/g, "\\$")}\s*(?::[^=]*)?=\s*\[`).test(e)) continue;
2083
- let s = e.indexOf("(", r), c = e.slice(s, Ra(e, s)).match(RegExp(`\\bkey\\s*=\\s*\\{\\s*(${n[1]}\\s*(?:\\?\\.|\\.)\\s*(?:id|productId)\\b[^}]*)\\}`));
2105
+ let s = e.indexOf("(", r), c = e.slice(s, Ha(e, s)).match(RegExp(`\\bkey\\s*=\\s*\\{\\s*(${n[1]}\\s*(?:\\?\\.|\\.)\\s*(?:id|productId)\\b[^}]*)\\}`));
2084
2106
  c && t.add(`key={${c[1].replace(/\s+/g, " ").trim()}}`);
2085
2107
  }
2086
2108
  return [...t];
2087
2109
  }
2088
- var Ka = (e, t) => `${e}: ${G(t)} — the lines of an order are keyed by their id, and an item's id is its PRODUCT's: the same thing bought in two colours or sizes is two lines with ONE key, so React draws one of them twice and drops the other, and the console fills with "Encountered two children with the same key". Key each line by its place in the list instead — items.map((item, i) => <div key={i}>…) — the lines of an order never move (rule 9).`, qa = /function TutorialPopup\(\{ onClose \}[\s\S]*?\n\}\n/;
2089
- function Ja(e, t) {
2090
- let n = [], r = W(t, Zi, 1);
2091
- (r.length || Xi.test(t)) && n.push(["own-drag", Qi(e, r)]);
2092
- let i = $i(t);
2093
- i && n.push(["mapped-table", ea(e, i)]);
2094
- let a = na(t);
2095
- a.length && n.push(["step-state", ra(e, a)]);
2096
- let o = W(t, ya);
2097
- o.length && n.push(["own-draft", xa(e, o)]);
2098
- let s = W(t, ia, 1);
2099
- s.length && n.push(["raw-html", aa(e, s)]);
2100
- let c = oa(t);
2101
- c.length && n.push(["own-money", sa(e, c)]);
2102
- let l = la(t);
2103
- l.length && n.push(["dead-click", ua(e, l)]);
2104
- let u = _a(t);
2105
- u.length && n.push(["glyph-icon", va(e, u)]), Sa.test(t) && n.push(["own-spinner", Ca(e)]);
2106
- let d = W(t, wa);
2107
- d.length && n.push(["tiny-text", Ta(e, d)]);
2108
- let f = Ea(t, Da);
2109
- f.length && n.push(["vague-button", Oa(e, f)]);
2110
- let p = Ma(t);
2111
- p.length && n.push(["icon-in-heading", Na(e, p)]);
2112
- let m = Ia(t);
2113
- m.length && n.push(["decorated-empty", La(e, m)]);
2114
- let h = Ea(t, ka);
2115
- h.length && n.push(["extra-press", Aa(e, h)]);
2116
- let g = Ha(t);
2117
- g.length && n.push(["whole-records", Ua(e, g)]);
2118
- let _ = Ga(t);
2119
- _.length && n.push(["item-key", Ka(e, _)]);
2120
- let v = Gs(t);
2121
- return v && n.push(["order-currency", Ks(e, v)]), n;
2122
- }
2123
- var Ya = (e) => `${e}: a bare <img> holds no space while it loads, pops in when it lands, always fetches the full-size file, and goes BLANK the moment its src changes — which is what makes a product's colour swatches flash. Swap every one for <Picture src={…} alt="…" className="…" /> imported from 'your-ai-workflow-firebase-os': same props, and it holds the box open, stands a small version in until the real one lands, and crosses over to a new picture instead of emptying. Add preload={[…other photos]} where a click changes the picture, and priority on the one at the very top of the page.`, Xa = /(?:\b(?:const|let|var)\s+[A-Za-z_$]*(?:sample|mock|dummy|demo|fake)[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*[{'"`])|(?:\b(?:const|let|var)\s+[A-Za-z_$]*(?:seed|placeholder)[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{)|(?:\b(?:const|let|var)\s+[A-Za-z_$]*fallback[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{)|lorem ipsum|\b(?:John|Jane) Doe\b/i, Za = (e) => e.replace(/placeholder\s*[=:]\s*(?:\{\s*)?['"`][^'"`]*['"`]/gi, ""), Qa = /https?:\/\/(?:images\.unsplash\.com|source\.unsplash\.com|picsum\.photos|placehold\.co|via\.placeholder\.com|placeholder\.com|dummyimage\.com|placekitten\.com|randomuser\.me|i\.pravatar\.cc|loremflickr\.com|images\.pexels\.com)[^'"`\s)]*/g, $a = /\b(?:const|let|var)\s+[A-Za-z_$]*(?:initial|default|example|starter|test)_?(?:products|orders|customers|reviews|testimonials|bookings|posts|listings|clients|members|entries|records|services|events|appointments|invoices|users)\b[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{/gi, eo = String.raw`(?=[^}]*\b(?:name|title|price)\s*:\s*['"\x60\d])`, to = new RegExp(String.raw`\b[\w$.?]*(?:records|products|orders|entries|listings|posts|reviews)\s*(?:\|\||\?\?)\s*\[\s*\{${eo}|\b([\w$.?]+)\.length\s*(?:>\s*0\s*)?\?\s*\1\s*:\s*\[\s*\{${eo}`, "g"), no = /\buseState\s*(?:<[^>]*>\s*)?\(\s*\[\s*\{[^}]*\b(?:name|title)\s*:\s*['"`][^'"`]+['"`][^}]*\b(?:price|amount|total|cost)\s*:\s*\d/g, ro = (e) => {
2124
- let t = Za(e), n = [
2125
- ...W(t, Xa),
2126
- ...W(t, Qa),
2127
- ...W(t, $a),
2128
- ...W(t, to),
2129
- ...W(t, no)
2110
+ var Xa = (e, t) => `${e}: ${G(t)} — the lines of an order are keyed by their id, and an item's id is its PRODUCT's: the same thing bought in two colours or sizes is two lines with ONE key, so React draws one of them twice and drops the other, and the console fills with "Encountered two children with the same key". Key each line by its place in the list instead — items.map((item, i) => <div key={i}>…) — the lines of an order never move (rule 9).`, Za = /function TutorialPopup\(\{ onClose \}[\s\S]*?\n\}\n/;
2111
+ function Qa(e, t) {
2112
+ let n = [], r = W(t, ta, 1);
2113
+ (r.length || ea.test(t)) && n.push(["own-drag", na(e, r)]);
2114
+ let i = ra(t);
2115
+ i && n.push(["mapped-table", ia(e, i)]);
2116
+ let a = oa(t);
2117
+ a.length && n.push(["step-state", sa(e, a)]);
2118
+ let o = W(t, Ca);
2119
+ o.length && n.push(["own-draft", Ta(e, o)]);
2120
+ let s = W(t, ca, 1);
2121
+ s.length && n.push(["raw-html", la(e, s)]);
2122
+ let c = ua(t);
2123
+ c.length && n.push(["own-money", da(e, c)]);
2124
+ let l = pa(t);
2125
+ l.length && n.push(["dead-click", ma(e, l)]);
2126
+ let u = xa(t);
2127
+ u.length && n.push(["glyph-icon", Sa(e, u)]), Ea.test(t) && n.push(["own-spinner", Da(e)]);
2128
+ let d = W(t, Oa);
2129
+ d.length && n.push(["tiny-text", ka(e, d)]);
2130
+ let f = Aa(t, ja);
2131
+ f.length && n.push(["vague-button", Ma(e, f)]);
2132
+ let p = Ia(t);
2133
+ p.length && n.push(["icon-in-heading", La(e, p)]);
2134
+ let m = Ba(t);
2135
+ m.length && n.push(["decorated-empty", Va(e, m)]);
2136
+ let h = Aa(t, Na);
2137
+ h.length && n.push(["extra-press", Pa(e, h)]);
2138
+ let g = Ka(t);
2139
+ g.length && n.push(["whole-records", qa(e, g)]);
2140
+ let _ = Ya(t);
2141
+ _.length && n.push(["item-key", Xa(e, _)]);
2142
+ let v = Ys(t);
2143
+ return v && n.push(["order-currency", Xs(e, v)]), n;
2144
+ }
2145
+ var $a = (e) => `${e}: a bare <img> holds no space while it loads, pops in when it lands, always fetches the full-size file, and goes BLANK the moment its src changes — which is what makes a product's colour swatches flash. Swap every one for <Picture src={…} alt="…" className="…" /> imported from 'your-ai-workflow-firebase-os': same props, and it holds the box open, stands a small version in until the real one lands, and crosses over to a new picture instead of emptying. Add preload={[…other photos]} where a click changes the picture, and priority on the one at the very top of the page.`, eo = /(?:\b(?:const|let|var)\s+[A-Za-z_$]*(?:sample|mock|dummy|demo|fake)[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*[{'"`])|(?:\b(?:const|let|var)\s+[A-Za-z_$]*(?:seed|placeholder)[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{)|(?:\b(?:const|let|var)\s+[A-Za-z_$]*fallback[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{)|lorem ipsum|\b(?:John|Jane) Doe\b/i, to = (e) => e.replace(/placeholder\s*[=:]\s*(?:\{\s*)?['"`][^'"`]*['"`]/gi, ""), no = /https?:\/\/(?:images\.unsplash\.com|source\.unsplash\.com|picsum\.photos|placehold\.co|via\.placeholder\.com|placeholder\.com|dummyimage\.com|placekitten\.com|randomuser\.me|i\.pravatar\.cc|loremflickr\.com|images\.pexels\.com)[^'"`\s)]*/g, ro = /\b(?:const|let|var)\s+[A-Za-z_$]*(?:initial|default|example|starter|test)_?(?:products|orders|customers|reviews|testimonials|bookings|posts|listings|clients|members|entries|records|services|events|appointments|invoices|users)\b[A-Za-z_$]*\s*(?::[^=\n]*)?=\s*\[\s*\{/gi, io = String.raw`(?=[^}]*\b(?:name|title|price)\s*:\s*['"\x60\d])`, ao = new RegExp(String.raw`\b[\w$.?]*(?:records|products|orders|entries|listings|posts|reviews)\s*(?:\|\||\?\?)\s*\[\s*\{${io}|\b([\w$.?]+)\.length\s*(?:>\s*0\s*)?\?\s*\1\s*:\s*\[\s*\{${io}`, "g"), oo = /\buseState\s*(?:<[^>]*>\s*)?\(\s*\[\s*\{[^}]*\b(?:name|title)\s*:\s*['"`][^'"`]+['"`][^}]*\b(?:price|amount|total|cost)\s*:\s*\d/g, so = (e) => {
2146
+ let t = to(e), n = [
2147
+ ...W(t, eo),
2148
+ ...W(t, no),
2149
+ ...W(t, ro),
2150
+ ...W(t, ao),
2151
+ ...W(t, oo)
2130
2152
  ];
2131
2153
  return [...new Set(n.map((e) => e.slice(0, 48).trim()))];
2132
- }, io = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are" : "is"} made-up data standing in for the real thing — this is the owner's live app, not a demo. Delete ${t.length > 1 ? "every one of them" : "it"}: a list reads the records it is for and shows a short empty line you designed until there are some, and every name, price and picture on the page comes from the owner, not from you.`, ao = new RegExp(String.raw`\b(?:bg-(?:white|black)\b|(?:bg|text|border|from|via|to|ring|fill|stroke|decoration|outline|shadow|accent|caret|divide)-(?:${"gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose"})-\d+\b|(?:bg|text|border|from|via|to|ring|fill|stroke)-\[#[0-9a-fA-F]{3,8}\])`), oo = /-(?:red|orange|amber|yellow|lime|green|emerald|rose)-\d+\b/, so = (e, t) => {
2154
+ }, co = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are" : "is"} made-up data standing in for the real thing — this is the owner's live app, not a demo. Delete ${t.length > 1 ? "every one of them" : "it"}: a list reads the records it is for and shows a short empty line you designed until there are some, and every name, price and picture on the page comes from the owner, not from you.`, lo = new RegExp(String.raw`\b(?:bg-(?:white|black)\b|(?:bg|text|border|from|via|to|ring|fill|stroke|decoration|outline|shadow|accent|caret|divide)-(?:${"gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose"})-\d+\b|(?:bg|text|border|from|via|to|ring|fill|stroke)-\[#[0-9a-fA-F]{3,8}\])`), uo = /-(?:red|orange|amber|yellow|lime|green|emerald|rose)-\d+\b/, fo = (e, t) => {
2133
2155
  let n = t.length > 1, r = n ? " Replace every one of them, everywhere in the file." : "";
2134
- return t.some((e) => oo.test(e)) ? `${e}: ${G(t)} ${n ? "are hard-coded colours, traffic-light ones among them, and they are" : "is a traffic-light colour, and it is"} nobody's theme — a green "in stock", a red "sold out", an amber "pending" all look cheap over the design the owner chose. Say the state in WORDS and tell it apart with the theme's own range: text-accent and bg-accent/10 for the one that matters, text-foreground for the ordinary one, text-foreground/50 for the quiet one, border-[var(--panel-border)] around a pill. The red of a real error or a delete is the app's own (ConfirmModal, a form's error line, troubleSentence) and is never written by hand on a page.${r}` : `${e}: ${G(t)} ${n ? "are hard-coded colours that ignore the owner's theme and stay" : "is a hard-coded colour that ignores the owner's theme and stays"} wrong in light or dark mode — use bg-background, text-foreground, text-accent, border-[var(--panel-border)], glass-panel or btn-primary.${r}`;
2135
- }, co = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are" : "is"} wording off the owner's admin screens — the page is for its visitors, not for the owner or for you. Delete ${t.length > 1 ? "every one" : "it"} and any prompt or copy-this box on the page.`, lo = /(?:(?:copy|paste) (?:this|the sentence|it) (?:in)?to (?:your |the )?(?:ai\b|assistant|chat\b|gemini|ai studio)|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i, uo = /(?:return|export\s+default\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>)\s*\(?\s*(?:<>\s*)?<[A-Za-z][\w.]*\s[^>]*?className=\{?\s*["'`]([^"'`]*)["'`]/g, fo = (e) => {
2136
- for (let t of e.matchAll(uo)) {
2156
+ return t.some((e) => uo.test(e)) ? `${e}: ${G(t)} ${n ? "are hard-coded colours, traffic-light ones among them, and they are" : "is a traffic-light colour, and it is"} nobody's theme — a green "in stock", a red "sold out", an amber "pending" all look cheap over the design the owner chose. Say the state in WORDS and tell it apart with the theme's own range: text-accent and bg-accent/10 for the one that matters, text-foreground for the ordinary one, text-foreground/50 for the quiet one, border-[var(--panel-border)] around a pill. The red of a real error or a delete is the app's own (ConfirmModal, a form's error line, troubleSentence) and is never written by hand on a page.${r}` : `${e}: ${G(t)} ${n ? "are hard-coded colours that ignore the owner's theme and stay" : "is a hard-coded colour that ignores the owner's theme and stays"} wrong in light or dark mode — use bg-background, text-foreground, text-accent, border-[var(--panel-border)], glass-panel or btn-primary.${r}`;
2157
+ }, po = (e, t) => `${e}: ${G(t.map((e) => `"${e}"`))} ${t.length > 1 ? "are" : "is"} wording off the owner's admin screens — the page is for its visitors, not for the owner or for you. Delete ${t.length > 1 ? "every one" : "it"} and any prompt or copy-this box on the page.`, mo = /(?:(?:copy|paste) (?:this|the sentence|it) (?:in)?to (?:your |the )?(?:ai\b|assistant|chat\b|gemini|ai studio)|copy for (?:your |the )?(?:ai|assistant)|ask your assistant|your assistant.{0,3}s chat|copy this to change)[^\n]{0,40}/i, ho = /(?:return|export\s+default\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>)\s*\(?\s*(?:<>\s*)?<[A-Za-z][\w.]*\s[^>]*?className=\{?\s*["'`]([^"'`]*)["'`]/g, go = (e) => {
2158
+ for (let t of e.matchAll(ho)) {
2137
2159
  let e = t[1];
2138
2160
  if (!/(?<![\w-])mx-auto(?![\w-])/.test(e)) continue;
2139
2161
  let n = e.match(/(?<![\w-])max-w-(?:[2-6]xl|screen-(?:sm|md|lg|xl)|prose)(?![\w-])/);
2140
2162
  if (n) return n[0];
2141
2163
  }
2142
2164
  return null;
2143
- }, po = (e, t) => `${e}: the whole page is wrapped in ${t} with mx-auto, which squeezes it into a narrow strip down the middle of a screen it was handed the full width of — its left and right edges no longer line up with the menu above it. Take the cap off the page's outermost element and let it fill that width, with the content inside one container like the app's other screens (glass-panel border border-[var(--panel-border)] rounded-3xl); a readable measure inside a section is fine, a cap around the page is not.`, mo = /(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/g, ho = /(?<![\w-])overflow-(?:y-)?(?:auto|scroll)(?![\w-])/, go = /\[(?:9\d|8\d)(?:vh|dvh|svh|lvh)\]$/, _o = (e) => {
2144
- for (let t of e.matchAll(mo)) {
2165
+ }, _o = (e, t) => `${e}: the whole page is wrapped in ${t} with mx-auto, which squeezes it into a narrow strip down the middle of a screen it was handed the full width of — its left and right edges no longer line up with the menu above it. Take the cap off the page's outermost element and let it fill that width, with the content inside one container like the app's other screens (glass-panel border border-[var(--panel-border)] rounded-3xl); a readable measure inside a section is fine, a cap around the page is not.`, vo = /(?<![\w-])(?:h|max-h)-(?:screen|dvh|svh|lvh|\[(?:100|9\d|8\d)(?:vh|dvh|svh|lvh)\])/g, yo = /(?<![\w-])overflow-(?:y-)?(?:auto|scroll)(?![\w-])/, bo = /\[(?:9\d|8\d)(?:vh|dvh|svh|lvh)\]$/, xo = (e) => {
2166
+ for (let t of e.matchAll(vo)) {
2145
2167
  let n = t.index ?? 0, r = Math.max(e.lastIndexOf("\"", n), e.lastIndexOf("'", n), e.lastIndexOf("`", n)), i = e.slice(n).search(/["'`]/), a = e.slice(r === -1 ? n : r + 1, i === -1 ? e.length : n + i);
2146
- if (!(go.test(t[0]) && ho.test(a))) return t[0];
2168
+ if (!(bo.test(t[0]) && yo.test(a))) return t[0];
2147
2169
  }
2148
2170
  return null;
2149
- }, vo = /(?<![\w-])(?:px|pl|pr|p)-(?:\d|\[|px\b)/, yo = /(?<![\w-])(?:px|pl|pr|p)-0(?![\w.])/g, bo = /className=/g, xo = 3, So = (e) => (e.match(bo) || []).length < xo ? !1 : !vo.test(e.replace(yo, "")), Co = (e) => `${e}: nothing on this page has any side padding, so its words sit hard against the edge of the window — on a phone they touch the glass. The menu bar above runs the full width with px-4 md:px-8, so give every section that same side padding and the page lines up with the logo: px-4 md:px-8 as the floor, px-6 md:px-12 where a section wants more air. A background, a picture or a band of colour may still run right to the edges — the words, buttons and boxes inside it keep the padding.`, wo = /\bimport\s*\(\s*['"](\.{1,2}\/[^'"]+)['"]\s*\)(?:\s*\.then\s*\(([^\n]*))?/g, To = /\b(?:import\s+(?!type\s)(?:([A-Za-z_$][\w$]*)\s*,?\s*)?(\{[^}]*\}|\*\s*as\s+[A-Za-z_$][\w$]*)?\s*from|export\s*(?:\{[^}]*\}|\*)\s*from)\s*['"](\.{1,2}\/[^'"]+)['"]/g, Eo = (e) => {
2171
+ }, So = /(?<![\w-])(?:px|pl|pr|p)-(?:\d|\[|px\b)/, Co = /(?<![\w-])(?:px|pl|pr|p)-0(?![\w.])/g, wo = /className=/g, To = 3, Eo = (e) => (e.match(wo) || []).length < To ? !1 : !So.test(e.replace(Co, "")), Do = (e) => `${e}: nothing on this page has any side padding, so its words sit hard against the edge of the window — on a phone they touch the glass. The menu bar above runs the full width with px-4 md:px-8, so give every section that same side padding and the page lines up with the logo: px-4 md:px-8 as the floor, px-6 md:px-12 where a section wants more air. A background, a picture or a band of colour may still run right to the edges — the words, buttons and boxes inside it keep the padding.`, Oo = /\bimport\s*\(\s*['"](\.{1,2}\/[^'"]+)['"]\s*\)(?:\s*\.then\s*\(([^\n]*))?/g, ko = /\b(?:import\s+(?!type\s)(?:([A-Za-z_$][\w$]*)\s*,?\s*)?(\{[^}]*\}|\*\s*as\s+[A-Za-z_$][\w$]*)?\s*from|export\s*(?:\{[^}]*\}|\*)\s*from)\s*['"](\.{1,2}\/[^'"]+)['"]/g, Ao = (e) => {
2150
2172
  let t = X(e, "route");
2151
2173
  return {
2152
2174
  first: t[0],
2153
2175
  count: t.length
2154
2176
  };
2155
2177
  };
2156
- function Do(e, t = [], n = {}) {
2178
+ function jo(e, t = [], n = {}) {
2157
2179
  let r = [], i = [], a = [], u = typeof n.accountCurrency == "string" && /^[a-z]{3}$/i.test(n.accountCurrency.trim()) ? n.accountCurrency.trim().toLowerCase() : null, d = (e) => e.slice(0, Math.max(0, e.indexOf(": "))), h = (e, t) => {
2158
2180
  r.push(t), a.push({
2159
2181
  check: e,
@@ -2170,7 +2192,7 @@ function Do(e, t = [], n = {}) {
2170
2192
  });
2171
2193
  }, b = /* @__PURE__ */ new Map(), x = /* @__PURE__ */ new Set();
2172
2194
  for (let { file: t } of e) if (J(t)) for (let e of g(q(t))) x.add(e);
2173
- let ee = Kl(e), S = /* @__PURE__ */ new Map(), te = /* @__PURE__ */ new Set(), C = /* @__PURE__ */ new Set();
2195
+ let ee = Xl(e), S = /* @__PURE__ */ new Map(), te = /* @__PURE__ */ new Set(), C = /* @__PURE__ */ new Set();
2174
2196
  for (let { file: t, source: n } of e) {
2175
2197
  if (!hn(t)) continue;
2176
2198
  let e = q(t), r = v(e);
@@ -2189,20 +2211,20 @@ function Do(e, t = [], n = {}) {
2189
2211
  source: Y(e.source)
2190
2212
  }));
2191
2213
  for (let e of [...C]) for (let t of w) t.keys.includes(e) && T.add(t.file);
2192
- let D = /* @__PURE__ */ new Set(), O = /* @__PURE__ */ new Set(), ne = /* @__PURE__ */ new Map();
2214
+ let ne = /* @__PURE__ */ new Set(), D = /* @__PURE__ */ new Set(), re = /* @__PURE__ */ new Map();
2193
2215
  for (let e of t) {
2194
2216
  let t = It(e, w, c, T);
2195
2217
  if (!t) continue;
2196
- (Ft(e) ? D : O).add(t);
2218
+ (Ft(e) ? ne : D).add(t);
2197
2219
  let n = e.config?.template;
2198
- typeof n == "string" && kn.includes(n) && ne.set(t, n);
2220
+ typeof n == "string" && kn.includes(n) && re.set(t, n);
2199
2221
  }
2200
- let k = /* @__PURE__ */ new Map();
2222
+ let O = /* @__PURE__ */ new Map();
2201
2223
  for (let e of t) {
2202
2224
  let t = It(e, w, c, T);
2203
- t && k.set(t, (k.get(t) ?? !1) || Pt(e.config));
2225
+ t && O.set(t, (O.get(t) ?? !1) || Pt(e.config));
2204
2226
  }
2205
- let re = /* @__PURE__ */ new Set(), ie = /* @__PURE__ */ new Set();
2227
+ let ie = /* @__PURE__ */ new Set(), ae = /* @__PURE__ */ new Set();
2206
2228
  for (let e of t) {
2207
2229
  let t = It(e, w, c, T);
2208
2230
  if (!t) continue;
@@ -2211,39 +2233,39 @@ function Do(e, t = [], n = {}) {
2211
2233
  "internal",
2212
2234
  "admin",
2213
2235
  "shared"
2214
- ].includes(n) || r.some((e) => e === "internal" || e === "admin")) && re.add(t);
2236
+ ].includes(n) || r.some((e) => e === "internal" || e === "admin")) && ie.add(t);
2215
2237
  let i = e.config?.formEmails;
2216
- i && (i.owner === !0 || i.confirm === !0) && ie.add(t);
2238
+ i && (i.owner === !0 || i.confirm === !0) && ae.add(t);
2217
2239
  }
2218
- let ae = (e, t, n) => {
2219
- let r = zs(t);
2240
+ let oe = (e, t, n) => {
2241
+ let r = Us(t);
2220
2242
  if (!r.length) return;
2221
2243
  let i = n ? [] : Yt(e, E).files.filter((t) => t !== e).map((e) => E.find((t) => t.file === e)?.source || "");
2222
2244
  if (r = r.filter((e) => !i.some((t) => new RegExp(String.raw`<${e}[\s/>]`).test(t))), !r.length) return;
2223
- let a = n ? null : k.get(e) ?? null, o = a === !0 ? Ls.filter((e) => [t, ...i].some((t) => new RegExp(String.raw`<${e}[\s/>]`).test(t))) : [];
2224
- a === !0 && !o.length ? y("unused-pay", Xs(e, r, !0)) : y("unused-pay-import", Xs(e, r, a, o));
2225
- }, oe = [...new Set(e.flatMap(({ source: e }) => {
2226
- let { baskets: t, reads: n } = jl(Y(e));
2245
+ let a = n ? null : O.get(e) ?? null, o = a === !0 ? Vs.filter((e) => [t, ...i].some((t) => new RegExp(String.raw`<${e}[\s/>]`).test(t))) : [];
2246
+ a === !0 && !o.length ? y("unused-pay", ec(e, r, !0)) : y("unused-pay-import", ec(e, r, a, o));
2247
+ }, se = [...new Set(e.flatMap(({ source: e }) => {
2248
+ let { baskets: t, reads: n } = Fl(Y(e));
2227
2249
  return t.length ? [] : n;
2228
- }))], se = [...new Set(e.flatMap(({ source: e }) => {
2250
+ }))], ce = [...new Set(e.flatMap(({ source: e }) => {
2229
2251
  let t = Y(e);
2230
- return /\buseBasket\s*\(/.test(t) && /\.add\s*\(/.test(t) ? jl(t).reads : [];
2231
- }))], A = /* @__PURE__ */ new Set(), j = /* @__PURE__ */ new Set();
2252
+ return /\buseBasket\s*\(/.test(t) && /\.add\s*\(/.test(t) ? Fl(t).reads : [];
2253
+ }))], k = /* @__PURE__ */ new Set(), A = /* @__PURE__ */ new Set();
2232
2254
  for (let { file: t, source: n } of e) {
2233
2255
  let e = t.split("/").slice(0, -1).join("/"), r = (t) => `${Kt(`${e}/${t}`).replace(/\.(?:tsx|ts|jsx|js)$/, "")}.tsx`, i = Y(n);
2234
- for (let e of i.matchAll(To)) (e[1] ? j : A).add(r(e[3]));
2235
- for (let e of i.matchAll(wo)) {
2256
+ for (let e of i.matchAll(ko)) (e[1] ? A : k).add(r(e[3]));
2257
+ for (let e of i.matchAll(Oo)) {
2236
2258
  let t = e.index ?? 0, n = i.lastIndexOf("\n", t - 1) + 1;
2237
- z(i.slice(n, t + 6)).slice(-6) === "import" && (e[2] ? (/\.default\b/.test(e[2]) ? j : A).add(r(e[1])) : /\blazy\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?$/.test(i.slice(Math.max(0, t - 80), t)) && j.add(r(e[1])));
2259
+ R(i.slice(n, t + 6)).slice(-6) === "import" && (e[2] ? (/\.default\b/.test(e[2]) ? A : k).add(r(e[1])) : /\blazy\s*\(\s*(?:async\s*)?\(\s*\)\s*=>\s*(?:\{\s*return\s+)?$/.test(i.slice(Math.max(0, t - 80), t)) && A.add(r(e[1])));
2238
2260
  }
2239
2261
  }
2240
- let M = new Set([...A].filter((e) => !j.has(e)));
2262
+ let le = new Set([...k].filter((e) => !A.has(e)));
2241
2263
  for (let { file: t, source: r, helper: i } of e) {
2242
2264
  let a = q(t), o = Y(r);
2243
2265
  if (hn(t)) {
2244
2266
  let e = v(a);
2245
2267
  if (e === "shipped") {
2246
- if (a === "theme.config.ts") for (let e of Ms(r)) y("brand-gone", Ns(t, e));
2268
+ if (a === "theme.config.ts") for (let e of Is(r)) y("brand-gone", Ls(t, e));
2247
2269
  continue;
2248
2270
  }
2249
2271
  if (e === "other") {
@@ -2251,7 +2273,7 @@ function Do(e, t = [], n = {}) {
2251
2273
  continue;
2252
2274
  }
2253
2275
  if (!Cn(o) && !Tn(o) && h("config-export", `${t}: export one const named ${_(a)}Config.`), e === "tab" || e === "page") {
2254
- let { first: n, count: r } = Eo(o), i = n === void 0 ? null : Z(n);
2276
+ let { first: n, count: r } = Ao(o), i = n === void 0 ? null : Z(n);
2255
2277
  if (r === 0 || i === null || !i.startsWith("/")) h("route", `${t}: route must be one string starting with /, e.g. route: '/onboarding'. A page has exactly one web address.`);
2256
2278
  else if (i.includes(":") || i.includes("*")) {
2257
2279
  let e = i.slice(0, i.search(/[:*]/)).replace(/\/+$/, "") || "/page";
@@ -2299,8 +2321,8 @@ function Do(e, t = [], n = {}) {
2299
2321
  continue;
2300
2322
  }
2301
2323
  if (J(t) || i) {
2302
- !i && (/Content\.tsx$/.test(a) || !M.has(t)) && !Tn(o) && h("default-export", `${t}: add export default.`);
2303
- let r = g(a).map((e) => S.get(e)).find(Boolean) || ne.get(t), s = /<form\b/.test(o) || /\bonSubmit\b/.test(o);
2324
+ !i && (/Content\.tsx$/.test(a) || !le.has(t)) && !Tn(o) && h("default-export", `${t}: add export default.`);
2325
+ let r = g(a).map((e) => S.get(e)).find(Boolean) || re.get(t), s = /<form\b/.test(o) || /\bonSubmit\b/.test(o);
2304
2326
  if (s && r) {
2305
2327
  let e = tr[r];
2306
2328
  o.includes(e.token) || y("preset-form", `${t}: this page is a ${r} and already has its own add and edit dialog, themed for light and dark — open it with ${e.call} instead of building a form here.`);
@@ -2309,268 +2331,272 @@ function Do(e, t = [], n = {}) {
2309
2331
  c && h("bell-collection", `${t}: ${c[0]} is the bell's own collection and pages never touch it — delete that code and send the notification with notify({ toUid | toRole, title, body, link }) imported from 'your-ai-workflow-firebase-os'. If this page is about notices of its own, keep them somewhere else (user_page_notices).`);
2310
2332
  let l = o.match(/['"`](?:chat_threads|chat_files|mem_chat_notes|mem_chat_state)['"`]/);
2311
2333
  l && h("chat-collection", `${t}: ${l[0]} is not a collection any page may touch — delete that code. This app has no chat: where people should be able to write to the owner, use the built-in form, <ContactPopup formId="…" />, and what they send arrives in the Inbox. If this page keeps messages of its own, keep them somewhere else (mem_page_messages).`);
2312
- let d = o.match(_c);
2334
+ let d = o.match(xc);
2313
2335
  d && h("chat-call", `${t}: ${d[0]} is not part of this app — the chat is gone, and nothing is exported under that name. Delete it, and where people should be able to write to the owner use the built-in form, <ContactPopup formId="…" />; what they send arrives in the Inbox.`), /['"`]user_phoneclaims['"`]/.test(o) && h("phone-claims", `${t}: user_phoneclaims is how the app keeps a two-step phone number to one account, and pages never touch it — keep this page's own records somewhere else (user_page_phones).`), /<style[\s>]/.test(o) && h("style-tag", `${t}: a <style> tag is global CSS and restyles every page of the app — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`);
2314
- let f = _o(o);
2336
+ let f = xo(o);
2315
2337
  f && y("screen-cap", `${t}: ${f} makes the page exactly one screen tall and strands everything below it on a phone — the page already scrolls, so let it be as long as it needs and drop the height and any overflow-hidden around it.`);
2316
- let p = fo(o);
2317
- p && y("narrow-page", po(t, p));
2338
+ let p = go(o);
2339
+ p && y("narrow-page", _o(t, p));
2318
2340
  let m = Vn.test(o), _ = Xn(o), v = _ ? null : Pn(o);
2319
2341
  _ ? y("address-box", (m ? In : Fn)(t, _)) : v && y("own-address", (m ? In : Fn)(t, v));
2320
2342
  let b = Zn(o);
2321
2343
  b && y("own-phone", (m ? $n : Qn)(t, b));
2322
2344
  let x = Hn(o);
2323
2345
  x && y("buyer-twice", Un(t, x));
2324
- let C = W(o, lo);
2325
- C.length && y("owner-words", co(t, C));
2326
- let w = ro(o);
2327
- w.length && y("demo-data", io(t, w));
2328
- let T = W(o, ao);
2329
- T.length && y("colour", so(t, T)), /<select[\s/>]/.test(o) && y("native-select", `${t}: a native <select> is drawn by the browser in its own chrome and no theme class can restyle it — replace it with <CustomSelect value={…} options={[{ value, label }]} onChange={(v) => …} /> imported from 'your-ai-workflow-firebase-os' (onChange receives the chosen value string, not an event).`);
2330
- let E = nc(o);
2331
- E.length && h("unknown-import", rc(t, E));
2332
- let k = Ct(o, n.appIcons);
2333
- k.length && h("bad-import", wt(t, k));
2334
- let A = Fe(o);
2335
- A.length && y("not-imported", Ie(t, A));
2336
- let j = Be(o);
2337
- j.length && y("hook-order", He(t, j));
2338
- let N = We(o);
2339
- N.length && y("inner-component", Ge(t, N));
2340
- let P = Ke(o);
2341
- P.length && y("stamp-as-timestamp", Je(t, P));
2342
- let F = Ye(o);
2343
- F.length && y("amount-by-hand", at(t, F));
2344
- let ce = ot(o);
2345
- ce.length && y("needs-index", st(t, ce));
2346
- let le = ut(o);
2347
- if (le.length && y("no-prefix", dt(t, le)), Tt.test(o) && y("own-firebase", Et(t, o)), ae(t, o, !!i), u) {
2348
- let e = qs(o, u);
2349
- e.length && y("currency-mismatch", Ys(t, e, u));
2346
+ let C = W(o, mo);
2347
+ C.length && y("owner-words", po(t, C));
2348
+ let w = so(o);
2349
+ w.length && y("demo-data", co(t, w));
2350
+ let T = W(o, lo);
2351
+ T.length && y("colour", fo(t, T)), /<select[\s/>]/.test(o) && y("native-select", `${t}: a native <select> is drawn by the browser in its own chrome and no theme class can restyle it — replace it with <CustomSelect value={…} options={[{ value, label }]} onChange={(v) => …} /> imported from 'your-ai-workflow-firebase-os' (onChange receives the chosen value string, not an event).`);
2352
+ let E = oc(o);
2353
+ E.length && h("unknown-import", sc(t, E));
2354
+ let O = Ct(o, n.appIcons);
2355
+ O.length && h("bad-import", wt(t, O));
2356
+ let k = Ie(o);
2357
+ k.length && y("not-imported", Le(t, k));
2358
+ let A = Ve(o);
2359
+ A.length && y("hook-order", Ue(t, A));
2360
+ let j = Ge(o);
2361
+ j.length && y("inner-component", Ke(t, j));
2362
+ let M = qe(o);
2363
+ M.length && y("stamp-as-timestamp", Ye(t, M));
2364
+ let N = Xe(o);
2365
+ N.length && y("amount-by-hand", ot(t, N));
2366
+ let P = st(o);
2367
+ P.length && y("needs-index", ct(t, P));
2368
+ let F = dt(o);
2369
+ if (F.length && y("no-prefix", ft(t, F)), Tt.test(o) && y("own-firebase", Et(t, o)), oe(t, o, !!i), u) {
2370
+ let e = Zs(o, u);
2371
+ e.length && y("currency-mismatch", $s(t, e, u));
2350
2372
  }
2351
- let ue = pt(o, /Content\.tsx$/.test(t));
2352
- ue.length && y("unknown-action", _t(t, ue));
2353
- let de = ac(o);
2354
- de && y("notify-as-toast", gc(t, de));
2355
- let fe = oc(o);
2356
- fe && y("notify-body", sc(t, fe));
2357
- let pe = cc(o);
2358
- pe !== null && y("popup-open", lc(t, pe));
2359
- let me = fc(o);
2360
- me && y("hand-by-field", pc(t, me));
2361
- let he = mc(o);
2362
- he && y("own-hand-over", hc(t, he, /<HandOver\b/.test(o))), pr.test(o) && y("bare-textarea", mr(t));
2373
+ let ue = mt(o, /Content\.tsx$/.test(t));
2374
+ ue.length && y("unknown-action", vt(t, ue));
2375
+ let de = lc(o);
2376
+ de && y("notify-as-toast", bc(t, de));
2377
+ let fe = uc(o);
2378
+ fe && y("notify-body", dc(t, fe));
2379
+ let pe = fc(o);
2380
+ pe !== null && y("popup-open", pc(t, pe));
2381
+ let me = gc(o);
2382
+ me && y("hand-by-field", _c(t, me));
2383
+ let he = vc(o);
2384
+ he && y("own-hand-over", yc(t, he, /<HandOver\b/.test(o))), pr.test(o) && y("bare-textarea", mr(t));
2363
2385
  let I = _r(o);
2364
- I.length && y("own-swatch", Lr(t, I));
2386
+ I.length && y("own-swatch", Vr(t, I));
2365
2387
  let ge = yr(o);
2366
- ge && y("own-picture-set", br(t, ge));
2367
- let L = Qo(o) ? null : Tr(o);
2368
- L && y("own-stepper", Er(t, L));
2369
- let _e = kr(o);
2370
- _e && y("own-chart", Ar(t, _e));
2371
- let ve = o.match(jr);
2372
- ve && y("full-reload", Mr(t, ve[0]));
2373
- let R = Fr(o);
2374
- R.length && y("typed-id", Ir(t, R));
2375
- let z = W(o, nr);
2376
- z.length && y("media-tag", rr(t, z)), ir.test(o) && h("bare-img", Ya(t));
2377
- let xe = $r(o), B = xe.filter((e) => !e.held);
2378
- B.length && y("boxed-picture", ti(t, B));
2379
- let Se = xe.filter((e) => e.held);
2380
- Se.length && y("boxed-upload", ti(t, Se));
2381
- let Ce = pi(o);
2382
- Ce.length && y("repeated-items", Si(t, Ce));
2383
- let we = bi(o);
2384
- we.length && y("picture-twice", xi(t, we));
2385
- let Te = Di(o);
2386
- Te && y("own-popup", Oi(t, Te));
2387
- let Ee = [...new Set(W(Ai(o), ki))];
2388
- Ee.length && y("own-toast", ji(t, Ee));
2389
- for (let [e, n] of Ja(t, o)) y(e, n);
2390
- let De = Zl(t, o, ee);
2391
- De.length && y("leads-nowhere", Ql(t, De)), ar.test(o) && h("background-picture", or(t));
2392
- let Oe = dr(o);
2393
- Oe.length && h("stuck-zero", fr(t, Oe));
2394
- let ke = W(o, Dc);
2395
- ke.length && y("billing-code", Oc(t, ke));
2396
- let Ae = W(mn(o, Dc), iu);
2397
- Ae.length && y("payment-code", au(t, Ae));
2398
- let je = W(o, ou);
2399
- je.length && y("email-code", _u(t, je));
2400
- let Me = $t(o);
2401
- (Me.unknown.length || Me.wrongWay.length) && y("email-template", en(t, Me.unknown, Me.wrongWay));
2402
- let Ne = un(o);
2403
- Ne.length && y("email-in-effect", dn(t, Ne));
2404
- let Pe = ie.has(t) ? an(o) : [];
2405
- Pe.length && y("email-twice", on(t, Pe));
2406
- let Le = W(o, su);
2407
- Le.length && h("storage-code", cu(t, Le)), hu.test(o) && y("file-input", gu(t));
2408
- let Re = uu(o);
2409
- Re.length && h("browser-dialog", mu(t, Re));
2410
- let ze = be(o);
2411
- ze && h("ai-code", ye(t, ze)), Ro(o) && y("case-query", Ps(t));
2412
- for (let e of Ts(o)) y("hook-answer", Es(t, e));
2413
- let Ve = ss(o);
2414
- Ve.length && y("not-a-case", cs(t, Ve));
2415
- let Ue = ds(o);
2416
- Ue.length && y("save-args", fs(t, Ue));
2417
- let qe = Os(o);
2418
- qe && y("own-publish", ks(t, qe));
2419
- let Xe = W(o, kc);
2420
- Xe.length && y(Xe.some((e) => !e.startsWith("runTransaction")) ? "booking-code" : "own-transaction", Ac(t, Xe));
2421
- let Ze = Mc(o);
2422
- Ze.length && y("stale-times", Nc(t, Ze));
2423
- let V = g(a).some((e) => te.has(e)) || D.has(t) || !O.has(t) && xc(o);
2424
- if (tn.test(o) && (V || O.has(t) && !re.has(t)) && y("email-anyone", nn(t, V)), V) {
2425
- So(o) && y("no-gutter", Co(t)), Gi(o) && y("no-page-title", Ki(t));
2426
- let e = Ji(o);
2427
- e.length && y("one-per-row", Yi(t, e));
2428
- let n = g(a).some((e) => S.has(e)) || ne.has(t) ? null : o.match(Ri);
2429
- n && y("no-actions", zi(t, n[0], o));
2430
- let r = mn(o, kc), i = W(r, Po);
2431
- i.length && y("private-collection", Fo(t, i));
2432
- let s = wc.test(r);
2433
- s && y("own-tally", Ec(t));
2434
- let c = Tc(W(r, Sc, 1), s);
2435
- c.length && y("public-write", Cc(t, c));
2388
+ ge && y("own-picture-set", wr(t, ge));
2389
+ let _e = Sr(o);
2390
+ _e.length && y("swapped-upload", Cr(t, _e));
2391
+ let ve = ns(o) ? null : kr(o);
2392
+ ve && y("own-stepper", Ar(t, ve));
2393
+ let ye = Nr(o);
2394
+ ye && y("own-chart", Pr(t, ye));
2395
+ let L = o.match(Fr);
2396
+ L && y("full-reload", Ir(t, L[0]));
2397
+ let R = zr(o);
2398
+ R.length && y("typed-id", Br(t, R));
2399
+ let Se = W(o, nr);
2400
+ Se.length && y("media-tag", rr(t, Se)), ir.test(o) && h("bare-img", $a(t));
2401
+ let z = ri(o), Ce = z.filter((e) => !e.held);
2402
+ Ce.length && y("boxed-picture", ai(t, Ce));
2403
+ let we = z.filter((e) => e.held);
2404
+ we.length && y("boxed-upload", ai(t, we));
2405
+ let Te = _i(o);
2406
+ Te.length && y("repeated-items", Ei(t, Te));
2407
+ let Ee = wi(o);
2408
+ Ee.length && y("picture-twice", Ti(t, Ee));
2409
+ let De = ji(o);
2410
+ De && y("own-popup", Mi(t, De));
2411
+ let Oe = [...new Set(W(Pi(o), Ni))];
2412
+ Oe.length && y("own-toast", Fi(t, Oe));
2413
+ for (let [e, n] of Qa(t, o)) y(e, n);
2414
+ let ke = tu(t, o, ee);
2415
+ ke.length && y("leads-nowhere", nu(t, ke)), ar.test(o) && h("background-picture", or(t));
2416
+ let Ae = dr(o);
2417
+ Ae.length && h("stuck-zero", fr(t, Ae));
2418
+ let je = W(o, jc);
2419
+ je.length && y("billing-code", Mc(t, je));
2420
+ let Me = W(mn(o, jc), cu);
2421
+ Me.length && y("payment-code", lu(t, Me));
2422
+ let Ne = W(o, uu);
2423
+ Ne.length && y("email-code", xu(t, Ne));
2424
+ let Pe = $t(o);
2425
+ (Pe.unknown.length || Pe.wrongWay.length) && y("email-template", en(t, Pe.unknown, Pe.wrongWay));
2426
+ let Fe = un(o);
2427
+ Fe.length && y("email-in-effect", dn(t, Fe));
2428
+ let Re = ae.has(t) ? an(o) : [];
2429
+ Re.length && y("email-twice", on(t, Re));
2430
+ let ze = W(o, du);
2431
+ ze.length && h("storage-code", fu(t, ze)), yu.test(o) && y("file-input", bu(t));
2432
+ let Be = mu(o);
2433
+ Be.length && h("browser-dialog", vu(t, Be));
2434
+ let He = xe(o);
2435
+ He && h("ai-code", be(t, He)), Ho(o) && y("case-query", Rs(t));
2436
+ for (let e of ks(o)) y("hook-answer", As(t, e));
2437
+ let We = ds(o);
2438
+ We.length && y("not-a-case", fs(t, We));
2439
+ let Je = hs(o);
2440
+ Je.length && y("save-args", gs(t, Je));
2441
+ let Ze = Ms(o);
2442
+ Ze && y("own-publish", Ns(t, Ze));
2443
+ let Qe = W(o, Nc);
2444
+ Qe.length && y(Qe.some((e) => !e.startsWith("runTransaction")) ? "booking-code" : "own-transaction", Pc(t, Qe));
2445
+ let $e = Ic(o);
2446
+ $e.length && y("stale-times", Lc(t, $e));
2447
+ let B = g(a).some((e) => te.has(e)) || ne.has(t) || !D.has(t) && Tc(o);
2448
+ if (tn.test(o) && (B || D.has(t) && !ie.has(t)) && y("email-anyone", nn(t, B)), B) {
2449
+ Eo(o) && y("no-gutter", Do(t)), Yi(o) && y("no-page-title", Xi(t));
2450
+ let e = Qi(o);
2451
+ e.length && y("one-per-row", $i(t, e));
2452
+ let n = g(a).some((e) => S.has(e)) || re.has(t) ? null : o.match(Hi);
2453
+ n && y("no-actions", Ui(t, n[0], o));
2454
+ let r = mn(o, Nc), i = W(r, Ro);
2455
+ i.length && y("private-collection", zo(t, i));
2456
+ let s = Oc.test(r);
2457
+ s && y("own-tally", Ac(t));
2458
+ let c = kc(W(r, Ec, 1), s);
2459
+ c.length && y("public-write", Dc(t, c));
2436
2460
  }
2437
- let Qe = Mo(o);
2438
- if (!V && jo(o) && Qe.size === 1 && !Qe.has("case") && y("role-curtain", No(t)), !V && !r && /\bactions\b/.test(o)) {
2439
- let e = vs(o);
2440
- e.length && y("own-write", ys(t, e));
2461
+ let et = Io(o);
2462
+ if (!B && Fo(o) && et.size === 1 && !et.has("case") && y("role-curtain", Lo(t)), !B && !r && /\bactions\b/.test(o)) {
2463
+ let e = Ss(o);
2464
+ e.length && y("own-write", Cs(t, e));
2441
2465
  }
2442
- let $e = V ? null : el(o);
2443
- $e && y("whole-inbox", ru(t, $e));
2444
- let et = Vo(o);
2445
- et && y("public-copy", Ho(t, et.token));
2446
- let tt = Fi(o);
2447
- tt.length && y("own-copy", Li(t, o, tt)), !V && Bi(o) && y("no-copy", Vi(t));
2448
- let nt = Qo(o), rt = Ko(o);
2449
- if (nt ? y("own-basket", Zo.test(o) ? $o(t, nt, /<Checkout\b/.test(o), qo(o)) : es(t, nt)) : rt ? y("own-checkout", Yo(t, rt, qo(o))) : ts(o) && y("own-purchase", ns(t)), !nt && !rt) for (let n of Bl(o, e)) n.why === "flag" ? y("order-flag", tu(n.config, t)) : n.why === "asks" ? y("order-asks", Vl(n.config, t)) : y("buyer-form", nu(n.config, t));
2450
- let it = is(o);
2451
- it && y("own-return", as(t, it));
2452
- for (let n of Gl(t, o, e)) n.why === "typed" ? y("no-road-in", $l(n.target, t, n.formId)) : y("own-road", eu(n, t));
2453
- let ct = Bc(o);
2454
- ct.length && y("swallowed-write", qc(t, ct));
2455
- let lt = Gc(o);
2456
- lt.length && y("swallowed-block", Kc(t, lt));
2457
- let H = Zc(o);
2458
- H.length && y("raw-fault", Qc(t, H));
2459
- let ft = il(o);
2460
- ft.length && y("made-up-history", pl(t, ft));
2461
- let mt = !V && gl(o);
2462
- mt && y("own-stock", Rl(t)), !mt && yl(o) && y("own-left", Ll(t));
2463
- let ht = Nl(o, se);
2464
- ht && y("basket-collection", Fl(t, ht.reads, ht.elsewhere));
2465
- let gt = Pl(o, oe);
2466
- gt && y("basket-collection-name", Il(t, gt.said, gt.reads));
2467
- let vt = Tl(o);
2468
- vt.length && y("joined-variants", El(t, vt)), Dl(o) && y("zoom-is-a-gallery", Ol(t));
2469
- let yt = V || mt ? null : sl(o);
2470
- yt && y("signed-by-admin", zl(t, yt.collection, yt.signs));
2471
- let bt = dl(o).filter((e) => !yt || e.startsWith("a profile read"));
2472
- bt.length && y("own-byline", fl(t, bt));
2466
+ let tt = B ? null : il(o);
2467
+ tt && y("whole-inbox", su(t, tt));
2468
+ let nt = Go(o);
2469
+ nt && y("public-copy", Ko(t, nt.token));
2470
+ let rt = zi(o);
2471
+ rt.length && y("own-copy", Vi(t, o, rt)), !B && Wi(o) && y("no-copy", Gi(t));
2472
+ let it = ns(o), at = Xo(o);
2473
+ if (it ? y("own-basket", ts.test(o) ? rs(t, it, /<Checkout\b/.test(o), Zo(o)) : is(t, it)) : at ? y("own-checkout", $o(t, at, Zo(o))) : as(o) && y("own-purchase", os(t)), !it && !at) for (let n of Wl(o, e)) n.why === "flag" ? y("order-flag", au(n.config, t)) : n.why === "asks" ? y("order-asks", Gl(n.config, t)) : y("buyer-form", ou(n.config, t));
2474
+ let lt = cs(o);
2475
+ lt && y("own-return", ls(t, lt));
2476
+ for (let n of Yl(t, o, e)) n.why === "typed" ? y("no-road-in", ru(n.target, t, n.formId)) : y("own-road", iu(n, t));
2477
+ let ut = Wc(o);
2478
+ ut.length && y("swallowed-write", Zc(t, ut));
2479
+ let V = Yc(o);
2480
+ V.length && y("swallowed-block", Xc(t, V));
2481
+ let pt = tl(o);
2482
+ pt.length && y("raw-fault", nl(t, pt));
2483
+ let ht = cl(o);
2484
+ ht.length && y("made-up-history", _l(t, ht));
2485
+ let gt = !B && bl(o);
2486
+ gt && y("own-stock", Hl(t)), !gt && Cl(o) && y("own-left", Vl(t));
2487
+ let _t = Ll(o, ce);
2488
+ _t && y("basket-collection", zl(t, _t.reads, _t.elsewhere));
2489
+ let H = Rl(o, se);
2490
+ H && y("basket-collection-name", Bl(t, H.said, H.reads));
2491
+ let yt = kl(o);
2492
+ yt.length && y("joined-variants", Al(t, yt)), jl(o) && y("zoom-is-a-gallery", Ml(t));
2493
+ let bt = B || gt ? null : dl(o);
2494
+ bt && y("signed-by-admin", Ul(t, bt.collection, bt.signs));
2495
+ let xt = hl(o).filter((e) => !bt || e.startsWith("a profile read"));
2496
+ xt.length && y("own-byline", gl(t, xt));
2473
2497
  continue;
2474
2498
  }
2475
2499
  if (t === c || t === l) {
2476
- /<style[\s>]/.test(o) && h("style-tag", t === l ? `${t}: a <style> tag is global CSS — the menu is on every screen, so it restyles every page of the app all the time — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).` : `${t}: a <style> tag is global CSS — while the home page is open it restyles every page of the app, the titles and the menu included — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`), t === l && !En(o, "Menu") && h("menu-export", `${t}: App.tsx imports { Menu } from this file — keep the named export \`export function Menu(props)\`; with a default export or another name the owner's menu is never found and visitors get the app's standard menu instead.`), t === c && !En(o, "Home") && h("home-export", Bs(t, o));
2477
- let e = _o(o);
2500
+ /<style[\s>]/.test(o) && h("style-tag", t === l ? `${t}: a <style> tag is global CSS — the menu is on every screen, so it restyles every page of the app all the time — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).` : `${t}: a <style> tag is global CSS — while the home page is open it restyles every page of the app, the titles and the menu included — delete it and style with theme classes (bg-background, text-foreground, glass-panel, btn-primary).`), t === l && !En(o, "Menu") && h("menu-export", `${t}: App.tsx imports { Menu } from this file — keep the named export \`export function Menu(props)\`; with a default export or another name the owner's menu is never found and visitors get the app's standard menu instead.`), t === c && !En(o, "Home") && h("home-export", Ws(t, o));
2501
+ let e = xo(o);
2478
2502
  e && y("screen-cap", `${t}: ${e} makes the page exactly one screen tall and strands everything below it on a phone — let the page be as long as it needs and drop the height and any overflow-hidden around it.`);
2479
- let r = t === c ? fo(o) : null;
2480
- r && y("narrow-page", po(t, r));
2481
- let i = ro(o);
2482
- i.length && y("demo-data", io(t, i));
2483
- let a = W(o, ao);
2484
- a.length && y("colour", so(t, a)), /<select[\s/>]/.test(o) && y("native-select", `${t}: a native <select> is drawn by the browser in its own chrome and no theme class can restyle it — replace it with <CustomSelect value={…} options={[{ value, label }]} onChange={(v) => …} /> imported from 'your-ai-workflow-firebase-os' (onChange receives the chosen value string, not an event).`);
2485
- let s = nc(o);
2486
- s.length && h("unknown-import", rc(t, s));
2503
+ let r = t === c ? go(o) : null;
2504
+ r && y("narrow-page", _o(t, r));
2505
+ let i = so(o);
2506
+ i.length && y("demo-data", co(t, i));
2507
+ let a = W(o, lo);
2508
+ a.length && y("colour", fo(t, a)), /<select[\s/>]/.test(o) && y("native-select", `${t}: a native <select> is drawn by the browser in its own chrome and no theme class can restyle it — replace it with <CustomSelect value={…} options={[{ value, label }]} onChange={(v) => …} /> imported from 'your-ai-workflow-firebase-os' (onChange receives the chosen value string, not an event).`);
2509
+ let s = oc(o);
2510
+ s.length && h("unknown-import", sc(t, s));
2487
2511
  let d = Ct(o, n.appIcons);
2488
2512
  d.length && h("bad-import", wt(t, d));
2489
- let f = Fe(o);
2490
- f.length && y("not-imported", Ie(t, f));
2491
- let p = Be(o);
2492
- p.length && y("hook-order", He(t, p));
2493
- let m = We(o);
2494
- m.length && y("inner-component", Ge(t, m));
2495
- let g = Ke(o);
2496
- g.length && y("stamp-as-timestamp", Je(t, g));
2497
- let _ = Ye(o);
2498
- _.length && y("amount-by-hand", at(t, _));
2499
- let v = ot(o);
2500
- v.length && y("needs-index", st(t, v));
2501
- let b = ut(o);
2502
- if (b.length && y("no-prefix", dt(t, b)), Tt.test(o) && y("own-firebase", Et(t, o)), ae(t, o, !1), u) {
2503
- let e = qs(o, u);
2504
- e.length && y("currency-mismatch", Ys(t, e, u));
2513
+ let f = Ie(o);
2514
+ f.length && y("not-imported", Le(t, f));
2515
+ let p = Ve(o);
2516
+ p.length && y("hook-order", Ue(t, p));
2517
+ let m = Ge(o);
2518
+ m.length && y("inner-component", Ke(t, m));
2519
+ let g = qe(o);
2520
+ g.length && y("stamp-as-timestamp", Ye(t, g));
2521
+ let _ = Xe(o);
2522
+ _.length && y("amount-by-hand", ot(t, _));
2523
+ let v = st(o);
2524
+ v.length && y("needs-index", ct(t, v));
2525
+ let b = dt(o);
2526
+ if (b.length && y("no-prefix", ft(t, b)), Tt.test(o) && y("own-firebase", Et(t, o)), oe(t, o, !1), u) {
2527
+ let e = Zs(o, u);
2528
+ e.length && y("currency-mismatch", $s(t, e, u));
2505
2529
  }
2506
- let x = ac(o);
2507
- x && y("notify-as-toast", gc(t, x));
2508
- let S = oc(o);
2509
- S && y("notify-body", sc(t, S));
2510
- let te = cc(o);
2511
- te !== null && y("popup-open", lc(t, te));
2512
- let C = fc(o);
2513
- C && y("hand-by-field", pc(t, C));
2514
- let w = mc(o);
2515
- w && y("own-hand-over", hc(t, w, /<HandOver\b/.test(o))), pr.test(o) && y("bare-textarea", mr(t));
2530
+ let x = lc(o);
2531
+ x && y("notify-as-toast", bc(t, x));
2532
+ let S = uc(o);
2533
+ S && y("notify-body", dc(t, S));
2534
+ let te = fc(o);
2535
+ te !== null && y("popup-open", pc(t, te));
2536
+ let C = gc(o);
2537
+ C && y("hand-by-field", _c(t, C));
2538
+ let w = vc(o);
2539
+ w && y("own-hand-over", yc(t, w, /<HandOver\b/.test(o))), pr.test(o) && y("bare-textarea", mr(t));
2516
2540
  let T = _r(o);
2517
- T.length && y("own-swatch", Lr(t, T));
2541
+ T.length && y("own-swatch", Vr(t, T));
2518
2542
  let E = yr(o);
2519
- E && y("own-picture-set", br(t, E));
2520
- let D = Qo(o) ? null : Tr(o);
2521
- D && y("own-stepper", Er(t, D));
2522
- let O = kr(o);
2523
- O && y("own-chart", Ar(t, O));
2524
- let ne = o.match(jr);
2525
- ne && y("full-reload", Mr(t, ne[0]));
2526
- let k = Fr(o);
2527
- k.length && y("typed-id", Ir(t, k));
2528
- let re = W(o, nr);
2529
- re.length && y("media-tag", rr(t, re)), ir.test(o) && h("bare-img", Ya(t));
2530
- let ie = $r(o), oe = ie.filter((e) => !e.held);
2531
- oe.length && y("boxed-picture", ti(t, oe));
2532
- let se = ie.filter((e) => e.held);
2533
- se.length && y("boxed-upload", ti(t, se));
2534
- let A = pi(o);
2535
- A.length && y("repeated-items", Si(t, A));
2536
- let j = bi(o);
2537
- j.length && y("picture-twice", xi(t, j));
2538
- let M = mn(o, qa), N = Di(M);
2539
- N && y("own-popup", Oi(t, N));
2540
- let P = [...new Set(W(Ai(o), ki))];
2541
- P.length && y("own-toast", ji(t, P));
2542
- let F = Ji(o);
2543
- F.length && y("one-per-row", Yi(t, F));
2544
- for (let [e, n] of Ja(t, M)) y(e, n);
2545
- let ce = Zl(t, M, ee);
2546
- ce.length && y("leads-nowhere", Ql(t, ce)), ar.test(o) && h("background-picture", or(t));
2547
- let le = dr(o);
2548
- le.length && h("stuck-zero", fr(t, le));
2549
- let ue = W(o, Dc);
2550
- ue.length && y("billing-code", Oc(t, ue));
2551
- let de = W(mn(o, Dc), iu);
2552
- de.length && y("payment-code", au(t, de));
2553
- let fe = W(o, ou);
2554
- fe.length && y("email-code", _u(t, fe));
2555
- let pe = W(o, su);
2556
- pe.length && h("storage-code", cu(t, pe)), hu.test(o) && y("file-input", gu(t));
2557
- let me = uu(o);
2558
- me.length && h("browser-dialog", mu(t, me));
2559
- let he = be(o);
2560
- he && h("ai-code", ye(t, he));
2561
- let I = W(o, kc);
2562
- I.length && y(I.some((e) => !e.startsWith("runTransaction")) ? "booking-code" : "own-transaction", Ac(t, I));
2563
- let ge = Mc(o);
2564
- ge.length && y("stale-times", Nc(t, ge));
2565
- let L = mn(o, kc), _e = W(L, Po);
2566
- _e.length && y("private-collection", Fo(t, _e));
2567
- let ve = wc.test(L);
2568
- ve && y("own-tally", Ec(t));
2569
- let R = Tc(W(L, Sc, 1), ve);
2570
- R.length && y("public-write", Cc(t, R));
2571
- }
2572
- }
2573
- let N = /* @__PURE__ */ new Map();
2543
+ E && y("own-picture-set", wr(t, E));
2544
+ let ne = Sr(o);
2545
+ ne.length && y("swapped-upload", Cr(t, ne));
2546
+ let D = ns(o) ? null : kr(o);
2547
+ D && y("own-stepper", Ar(t, D));
2548
+ let re = Nr(o);
2549
+ re && y("own-chart", Pr(t, re));
2550
+ let O = o.match(Fr);
2551
+ O && y("full-reload", Ir(t, O[0]));
2552
+ let ie = zr(o);
2553
+ ie.length && y("typed-id", Br(t, ie));
2554
+ let ae = W(o, nr);
2555
+ ae.length && y("media-tag", rr(t, ae)), ir.test(o) && h("bare-img", $a(t));
2556
+ let se = ri(o), ce = se.filter((e) => !e.held);
2557
+ ce.length && y("boxed-picture", ai(t, ce));
2558
+ let k = se.filter((e) => e.held);
2559
+ k.length && y("boxed-upload", ai(t, k));
2560
+ let A = _i(o);
2561
+ A.length && y("repeated-items", Ei(t, A));
2562
+ let le = wi(o);
2563
+ le.length && y("picture-twice", Ti(t, le));
2564
+ let j = mn(o, Za), M = ji(j);
2565
+ M && y("own-popup", Mi(t, M));
2566
+ let N = [...new Set(W(Pi(o), Ni))];
2567
+ N.length && y("own-toast", Fi(t, N));
2568
+ let P = Qi(o);
2569
+ P.length && y("one-per-row", $i(t, P));
2570
+ for (let [e, n] of Qa(t, j)) y(e, n);
2571
+ let F = tu(t, j, ee);
2572
+ F.length && y("leads-nowhere", nu(t, F)), ar.test(o) && h("background-picture", or(t));
2573
+ let ue = dr(o);
2574
+ ue.length && h("stuck-zero", fr(t, ue));
2575
+ let de = W(o, jc);
2576
+ de.length && y("billing-code", Mc(t, de));
2577
+ let fe = W(mn(o, jc), cu);
2578
+ fe.length && y("payment-code", lu(t, fe));
2579
+ let pe = W(o, uu);
2580
+ pe.length && y("email-code", xu(t, pe));
2581
+ let me = W(o, du);
2582
+ me.length && h("storage-code", fu(t, me)), yu.test(o) && y("file-input", bu(t));
2583
+ let he = mu(o);
2584
+ he.length && h("browser-dialog", vu(t, he));
2585
+ let I = xe(o);
2586
+ I && h("ai-code", be(t, I));
2587
+ let ge = W(o, Nc);
2588
+ ge.length && y(ge.some((e) => !e.startsWith("runTransaction")) ? "booking-code" : "own-transaction", Pc(t, ge));
2589
+ let _e = Ic(o);
2590
+ _e.length && y("stale-times", Lc(t, _e));
2591
+ let ve = mn(o, Nc), ye = W(ve, Ro);
2592
+ ye.length && y("private-collection", zo(t, ye));
2593
+ let L = Oc.test(ve);
2594
+ L && y("own-tally", Ac(t));
2595
+ let R = kc(W(ve, Ec, 1), L);
2596
+ R.length && y("public-write", Dc(t, R));
2597
+ }
2598
+ }
2599
+ let j = /* @__PURE__ */ new Map();
2574
2600
  for (let n of t) {
2575
2601
  let t = It(n, w, c, T);
2576
2602
  if (!t || !e.some((e) => e.file === t)) continue;
@@ -2578,19 +2604,19 @@ function Do(e, t = [], n = {}) {
2578
2604
  if (!r.length) continue;
2579
2605
  let i = Yt(t, E);
2580
2606
  if (!i.whole) continue;
2581
- let a = i.files.map((e) => E.find((t) => t.file === e)?.source || ""), o = N.get(t) || [];
2607
+ let a = i.files.map((e) => E.find((t) => t.file === e)?.source || ""), o = j.get(t) || [];
2582
2608
  for (let e of Vt(r, a)) o.some((t) => t.url === e.url) || o.push(e);
2583
- o.length && N.set(t, o);
2609
+ o.length && j.set(t, o);
2584
2610
  }
2585
- for (let [e, t] of N) y("picture-dropped", Gt(e, t));
2586
- let P = e.find((e) => e.file === s);
2587
- if (P) {
2611
+ for (let [e, t] of j) y("picture-dropped", Gt(e, t));
2612
+ let M = e.find((e) => e.file === s);
2613
+ if (M) {
2588
2614
  let t = e.filter((e) => J(e.file) && !e.helper && e.file.startsWith(`${o}/`)).map((e) => ({
2589
2615
  file: e.file,
2590
2616
  name: q(e.file),
2591
2617
  source: Y(e.source),
2592
2618
  keys: g(q(e.file))
2593
- })), { broken: n, unregistered: r } = Ot(Y(P.source), t, o);
2619
+ })), { broken: n, unregistered: r } = Ot(Y(M.source), t, o);
2594
2620
  n.length && h("registry-import", kt(s, n)), r.length && y("unregistered", At(s, r));
2595
2621
  }
2596
2622
  return a.sort((e, t) => Number(t.level === "error") - Number(e.level === "error")), {
@@ -2599,51 +2625,51 @@ function Do(e, t = [], n = {}) {
2599
2625
  findings: a
2600
2626
  };
2601
2627
  }
2602
- var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHeading|div|section|>)`), ko = String.raw`\buserRole\s*(?:===|!==|==|!=)\s*['"\`](?:admin|internal)['"\`]`, Ao = (e) => {
2603
- let t = [], n = RegExp(`(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${ko}`, "g");
2628
+ var Mo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHeading|div|section|>)`), No = String.raw`\buserRole\s*(?:===|!==|==|!=)\s*['"\`](?:admin|internal)['"\`]`, Po = (e) => {
2629
+ let t = [], n = RegExp(`(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${No}`, "g");
2604
2630
  for (let r of e.matchAll(n)) t.push(r[1]);
2605
2631
  return t;
2606
- }, jo = (e) => Oo(ko).test(e) || Ao(e).some((t) => Oo(`\\b${t}\\b`).test(e)), Mo = (e) => {
2632
+ }, Fo = (e) => Mo(No).test(e) || Po(e).some((t) => Mo(`\\b${t}\\b`).test(e)), Io = (e) => {
2607
2633
  let t = /* @__PURE__ */ new Set();
2608
2634
  for (let n of e.matchAll(/['"`](mem|admin|user|role|case)_[A-Za-z0-9_]+['"`]/g)) t.add(n[1]);
2609
2635
  return t;
2610
- }, No = (e) => `${e}: a section here is hidden from people by their role, but everything this page reads sits in one collection they may all read anyway — so it is hidden on screen and kept from nobody, and a browser console reads it straight out of the database. Whatever only some of them may see belongs in a collection whose audience matches: admin_ for the admins alone, user_ with that person's own uid for their own records, role_<role>_ for one of the owner's roles, mem_ for the team, case_ for an entry that names who it was sent to. Keep the check as well, so nobody meets a permission error. If what is hidden is only a BUTTON on records everybody here may read, that is right as it is — nothing to change.`, Po = /['"`](?:mem|admin|user|role|case)_(?!notifications['"`]|chat_notes['"`]|chat_state['"`])[A-Za-z0-9_]+['"`]/, Fo = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are private collections" : "is a private collection"} and this page is open to anyone, so every visitor gets a permission error instead of the data — read a pub_ copy here instead, kept in sync by the page that owns the data (it writes the same change to the pub_ copy on every add, edit and delete, under the same document id). Never name a mem_, admin_, user_, role_ or case_ collection on a public page.`, Io = /(addDoc\s*\(\s*)?collection\s*\(\s*db\s*,\s*['"`]case_[A-Za-z0-9_]+['"`]/g, Lo = /\b(?:getDocs|onSnapshot|query)\s*\(/, Ro = (e) => [...e.matchAll(Io)].some((e) => !e[1]) && Lo.test(e), zo = /\b(?:setDoc|updateDoc|addDoc|deleteDoc)\s*\([^;]{0,200}?['"`]pub_[A-Za-z0-9_]+['"`]/, Bo = /['"`](user|case)_(?!profiles['"`]|submissions['"`]|requests['"`]|notifications['"`])[A-Za-z0-9_]+['"`]/, Vo = (e) => {
2611
- let t = e.match(zo);
2636
+ }, Lo = (e) => `${e}: a section here is hidden from people by their role, but everything this page reads sits in one collection they may all read anyway — so it is hidden on screen and kept from nobody, and a browser console reads it straight out of the database. Whatever only some of them may see belongs in a collection whose audience matches: admin_ for the admins alone, user_ with that person's own uid for their own records, role_<role>_ for one of the owner's roles, mem_ for the team, case_ for an entry that names who it was sent to. Keep the check as well, so nobody meets a permission error. If what is hidden is only a BUTTON on records everybody here may read, that is right as it is — nothing to change.`, Ro = /['"`](?:mem|admin|user|role|case)_(?!notifications['"`]|chat_notes['"`]|chat_state['"`])[A-Za-z0-9_]+['"`]/, zo = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are private collections" : "is a private collection"} and this page is open to anyone, so every visitor gets a permission error instead of the data — read a pub_ copy here instead, kept in sync by the page that owns the data (it writes the same change to the pub_ copy on every add, edit and delete, under the same document id). Never name a mem_, admin_, user_, role_ or case_ collection on a public page.`, Bo = /(addDoc\s*\(\s*)?collection\s*\(\s*db\s*,\s*['"`]case_[A-Za-z0-9_]+['"`]/g, Vo = /\b(?:getDocs|onSnapshot|query)\s*\(/, Ho = (e) => [...e.matchAll(Bo)].some((e) => !e[1]) && Vo.test(e), Uo = /\b(?:setDoc|updateDoc|addDoc|deleteDoc)\s*\([^;]{0,200}?['"`]pub_[A-Za-z0-9_]+['"`]/, Wo = /['"`](user|case)_(?!profiles['"`]|submissions['"`]|requests['"`]|notifications['"`])[A-Za-z0-9_]+['"`]/, Go = (e) => {
2637
+ let t = e.match(Uo);
2612
2638
  if (!t) return null;
2613
- let n = e.match(Bo) || (/\buseCases\s*\(/.test(e) ? ["a case_ collection"] : null);
2639
+ let n = e.match(Wo) || (/\buseCases\s*\(/.test(e) ? ["a case_ collection"] : null);
2614
2640
  return n ? {
2615
2641
  write: t[0],
2616
2642
  token: n[0]
2617
2643
  } : null;
2618
- }, Ho = (e, t) => `${e}: this page copies ${t} into a pub_ collection, and a pub_ collection is the open internet — the pasted rules let anybody read one, signed in or not. ${t.startsWith("'case") || t === "a case_ collection" ? "An entry that names who it was sent to" : "One person's own records"} must never go there: the copy hands it to every visitor at once. Take the pub_ write out. There is nothing to copy — the rules read a collection's NAME and never which page asked, so two pages behind sign-in read the SAME collection and each person gets their own on both; and where the other page is one anybody can open, a visitor is not that person, so it shows none of it and links to the signed-in page instead.`, Uo = /<PayButton\b|\bstartCheckout\s*\(/, Wo = /\b(orderEntryId|orderId|entryId|submissionId)\s*:/, Go = /<ContactPopup\b[\s\S]{0,600}?\bextra\s*=/, Ko = (e) => {
2619
- if (!Uo.test(e) || /<(?:Checkout|Basket)\b/.test(e)) return null;
2620
- let t = e.match(Wo);
2621
- return t ? `${t[1]} carried onto the payment by hand` : Go.test(e) ? "an order form beside the Pay button" : null;
2622
- }, qo = (e) => /<(?:Basket|BuyButton|PayButton|Checkout)\b[^>]*?\bcurrency\s*=\s*\{?\s*['"`]([A-Za-z]{3})['"`]/.exec(e)?.[1].toLowerCase() ?? /\bmoney\s*\([^()]*(?:\([^()]*\)[^()]*)*,\s*['"`]([A-Za-z]{3})['"`]\s*\)/.exec(e)?.[1].toLowerCase() ?? null, Jo = (e) => e ? ` currency="${e}"` : "", Yo = (e, t, n = null) => `${e}: this page pays for an order with pieces it wired together itself (${t}) — that hand-over is where a buyer pays and no order is ever saved, or the sale is refused. Replace the <BuyerDetails>, the order form and the <PayButton> of that last step with ONE <Checkout formId="…" items={…} extras={…}${Jo(n)} phone address /> from 'your-ai-workflow-firebase-os', keeping the same formId: give it the basket (each item's id, name, options, quantity, price for one, picture) and it asks who is paying, saves the order whole and only then starts the payment. Delete the order's *Form.config.ts if it asked nothing. Change nothing else on the page.`, Xo = /\buseVisitorState\s*(?:<[^()]*?>)?\s*\(\s*['"`]([^'"`]*(?:basket|cart|bag)[^'"`]*)['"`]/i, Zo = /<(?:PayButton|BuyButton|Checkout|BuyerDetails|CheckoutReturn)\b|\bstartCheckout\s*\(/, Qo = (e) => /<Basket\b/.test(e) || !(Zo.test(e) || Go.test(e)) ? null : e.match(Xo)?.[1] ?? null, $o = (e, t, n, r = null) => `${e}: this page keeps its basket in useVisitorState('${t}') and draws the bag, its lines and its steps itself — so the count on the bag is a second copy that stays a press behind, and the drawer is the page's own guess at where the one button goes. Hand all of it to the app's pieces from 'your-ai-workflow-firebase-os': const basket = useBasket('${t}') wherever something is added or counted — basket.add({ id, name, options, price, picture, max }) (price is for ONE, options the choices in words, max what is in stock; it opens the drawer itself), basket.count, basket.lines; <BasketButton name="${t}" /> where the bag goes; and ONE <Basket name="${t}" formId="…"${Jo(r)} phone address extras={…} /> near the top of the page for the drawer — the lines with their steppers, then who is paying and the Pay button. ${n ? "Move the props of the <Checkout> this page has onto that <Basket> (it has the checkout in it) and delete the <Checkout>" : "It has the checkout in it, so there is no <Checkout>, <BuyerDetails> or <PayButton> of your own"}, the useVisitorState, the drawer or window, the quantity buttons and every basket step. Keep <CheckoutReturn basket="${t}" … />, the product cards and everything else on the page exactly as they are.`, es = (e, t) => `${e}: this page keeps a list in useVisitorState('${t}'), draws it itself and hands it to a form it wired up — so the count on its button is a second copy that stays a press behind, and the drawer is the page's own guess at where the one button goes. Hand all of it to the app's pieces from 'your-ai-workflow-firebase-os': const basket = useBasket('${t}') wherever something is added or counted — basket.add({ id, name, options, picture }) (a price only if the thing has one; it opens the drawer itself), basket.count, basket.lines; <BasketButton name="${t}" /> where the list is opened; and ONE <Basket send name="${t}" formId="…" /> near the top of the page, with the formId the <ContactPopup> had. Its last step IS that form, drawn in the drawer, and the list rides on the entry as items — so delete the <ContactPopup … extra=…>, the useVisitorState, the drawer or window and the quantity buttons. Keep the form's *Form.config.ts, the cards and everything else on the page exactly as they are.`, ts = (e) => /<BuyerDetails\b/.test(e) && /<PayButton\b/.test(e) && !/<(?:BuyButton|Checkout|Basket)\b/.test(e), ns = (e) => `${e}: this page asks who is paying with <BuyerDetails> and then shows a <PayButton> it wired to the answer itself — two presses where there should be one, boxes standing open on the page, and a button that pays without who they are the day one spread is forgotten. Replace the pair with ONE <BuyButton> from 'your-ai-workflow-firebase-os' carrying the same amount / productName (or priceId / mode), currency, metadata and successPath the <PayButton> had, plus phone / address if the <BuyerDetails> had them: its press opens one small window that says what is being bought, asks who is paying and goes straight on to Stripe. A subscription's plans are <Plans product="…" /> instead, which reads the plans, prices and ids from the owner's Payments screen. Delete the buyer state, the <BuyerDetails> and the <PayButton>; keep the button's words, look and place, and change nothing else on the page.`, rs = /['"`]session_id['"`]|\bconfirmPayment\s*\(/, is = (e) => {
2644
+ }, Ko = (e, t) => `${e}: this page copies ${t} into a pub_ collection, and a pub_ collection is the open internet — the pasted rules let anybody read one, signed in or not. ${t.startsWith("'case") || t === "a case_ collection" ? "An entry that names who it was sent to" : "One person's own records"} must never go there: the copy hands it to every visitor at once. Take the pub_ write out. There is nothing to copy — the rules read a collection's NAME and never which page asked, so two pages behind sign-in read the SAME collection and each person gets their own on both; and where the other page is one anybody can open, a visitor is not that person, so it shows none of it and links to the signed-in page instead.`, qo = /<PayButton\b|\bstartCheckout\s*\(/, Jo = /\b(orderEntryId|orderId|entryId|submissionId)\s*:/, Yo = /<ContactPopup\b[\s\S]{0,600}?\bextra\s*=/, Xo = (e) => {
2645
+ if (!qo.test(e) || /<(?:Checkout|Basket)\b/.test(e)) return null;
2646
+ let t = e.match(Jo);
2647
+ return t ? `${t[1]} carried onto the payment by hand` : Yo.test(e) ? "an order form beside the Pay button" : null;
2648
+ }, Zo = (e) => /<(?:Basket|BuyButton|PayButton|Checkout)\b[^>]*?\bcurrency\s*=\s*\{?\s*['"`]([A-Za-z]{3})['"`]/.exec(e)?.[1].toLowerCase() ?? /\bmoney\s*\([^()]*(?:\([^()]*\)[^()]*)*,\s*['"`]([A-Za-z]{3})['"`]\s*\)/.exec(e)?.[1].toLowerCase() ?? null, Qo = (e) => e ? ` currency="${e}"` : "", $o = (e, t, n = null) => `${e}: this page pays for an order with pieces it wired together itself (${t}) — that hand-over is where a buyer pays and no order is ever saved, or the sale is refused. Replace the <BuyerDetails>, the order form and the <PayButton> of that last step with ONE <Checkout formId="…" items={…} extras={…}${Qo(n)} phone address /> from 'your-ai-workflow-firebase-os', keeping the same formId: give it the basket (each item's id, name, options, quantity, price for one, picture) and it asks who is paying, saves the order whole and only then starts the payment. Delete the order's *Form.config.ts if it asked nothing. Change nothing else on the page.`, es = /\buseVisitorState\s*(?:<[^()]*?>)?\s*\(\s*['"`]([^'"`]*(?:basket|cart|bag)[^'"`]*)['"`]/i, ts = /<(?:PayButton|BuyButton|Checkout|BuyerDetails|CheckoutReturn)\b|\bstartCheckout\s*\(/, ns = (e) => /<Basket\b/.test(e) || !(ts.test(e) || Yo.test(e)) ? null : e.match(es)?.[1] ?? null, rs = (e, t, n, r = null) => `${e}: this page keeps its basket in useVisitorState('${t}') and draws the bag, its lines and its steps itself — so the count on the bag is a second copy that stays a press behind, and the drawer is the page's own guess at where the one button goes. Hand all of it to the app's pieces from 'your-ai-workflow-firebase-os': const basket = useBasket('${t}') wherever something is added or counted — basket.add({ id, name, options, price, picture, max }) (price is for ONE, options the choices in words, max what is in stock; it opens the drawer itself), basket.count, basket.lines; <BasketButton name="${t}" /> where the bag goes; and ONE <Basket name="${t}" formId="…"${Qo(r)} phone address extras={…} /> near the top of the page for the drawer — the lines with their steppers, then who is paying and the Pay button. ${n ? "Move the props of the <Checkout> this page has onto that <Basket> (it has the checkout in it) and delete the <Checkout>" : "It has the checkout in it, so there is no <Checkout>, <BuyerDetails> or <PayButton> of your own"}, the useVisitorState, the drawer or window, the quantity buttons and every basket step. Keep <CheckoutReturn basket="${t}" … />, the product cards and everything else on the page exactly as they are.`, is = (e, t) => `${e}: this page keeps a list in useVisitorState('${t}'), draws it itself and hands it to a form it wired up — so the count on its button is a second copy that stays a press behind, and the drawer is the page's own guess at where the one button goes. Hand all of it to the app's pieces from 'your-ai-workflow-firebase-os': const basket = useBasket('${t}') wherever something is added or counted — basket.add({ id, name, options, picture }) (a price only if the thing has one; it opens the drawer itself), basket.count, basket.lines; <BasketButton name="${t}" /> where the list is opened; and ONE <Basket send name="${t}" formId="…" /> near the top of the page, with the formId the <ContactPopup> had. Its last step IS that form, drawn in the drawer, and the list rides on the entry as items — so delete the <ContactPopup … extra=…>, the useVisitorState, the drawer or window and the quantity buttons. Keep the form's *Form.config.ts, the cards and everything else on the page exactly as they are.`, as = (e) => /<BuyerDetails\b/.test(e) && /<PayButton\b/.test(e) && !/<(?:BuyButton|Checkout|Basket)\b/.test(e), os = (e) => `${e}: this page asks who is paying with <BuyerDetails> and then shows a <PayButton> it wired to the answer itself — two presses where there should be one, boxes standing open on the page, and a button that pays without who they are the day one spread is forgotten. Replace the pair with ONE <BuyButton> from 'your-ai-workflow-firebase-os' carrying the same amount / productName (or priceId / mode), currency, metadata and successPath the <PayButton> had, plus phone / address if the <BuyerDetails> had them: its press opens one small window that says what is being bought, asks who is paying and goes straight on to Stripe. A subscription's plans are <Plans product="…" /> instead, which reads the plans, prices and ids from the owner's Payments screen. Delete the buyer state, the <BuyerDetails> and the <PayButton>; keep the button's words, look and place, and change nothing else on the page.`, ss = /['"`]session_id['"`]|\bconfirmPayment\s*\(/, cs = (e) => {
2623
2649
  if (/<CheckoutReturn\b/.test(e)) return null;
2624
- let t = e.match(rs);
2650
+ let t = e.match(ss);
2625
2651
  return t ? t[0].replace(/\s*\($/, "()") : null;
2626
- }, as = (e, t) => `${e}: this page works out for itself what happens when a buyer comes back from paying (${t}) — which is how a basket is emptied before the money moved, or somebody is thanked for a payment that failed. Delete that code and its thank-you, and write <CheckoutReturn basket="…" orderPath="…" /> from 'your-ai-workflow-firebase-os' ONCE near the top of the page, outside any basket window or step: basket is the useVisitorState key the basket is kept under, orderPath the page their order is on. It asks the server, and only then empties the basket and thanks them. Change nothing else on the page.`, os = /\b(useCases|usePublished|useCase)\s*(?:<[^<>()]*>)?\s*\(\s*['"`]([A-Za-z0-9_]+)['"`]/g, ss = (e) => [...e.matchAll(os)].filter((e) => !e[2].startsWith("case_")).map((e) => ({
2652
+ }, ls = (e, t) => `${e}: this page works out for itself what happens when a buyer comes back from paying (${t}) — which is how a basket is emptied before the money moved, or somebody is thanked for a payment that failed. Delete that code and its thank-you, and write <CheckoutReturn basket="…" orderPath="…" /> from 'your-ai-workflow-firebase-os' ONCE near the top of the page, outside any basket window or step: basket is the useVisitorState key the basket is kept under, orderPath the page their order is on. It asks the server, and only then empties the basket and thanks them. Change nothing else on the page.`, us = /\b(useCases|usePublished|useCase)\s*(?:<[^<>()]*>)?\s*\(\s*['"`]([A-Za-z0-9_]+)['"`]/g, ds = (e) => [...e.matchAll(us)].filter((e) => !e[2].startsWith("case_")).map((e) => ({
2627
2653
  hook: e[1],
2628
2654
  name: e[2]
2629
- })), cs = (e, t) => {
2655
+ })), fs = (e, t) => {
2630
2656
  let n = t[0];
2631
2657
  return `${e}: ${n.hook}('${n.name}') reads NOTHING — ${n.hook}() is for a page whose entries are sent to people, a case_ collection, and given any other name it opens no listener at all and answers { loading: false, records: [] } for ever. So this page draws its empty line however many records are saved, and every save on it says "Saved" over a page that never changes. The records of the page you are filling are HANDED to you, live, already from the page's own collection: const { records, loading } = actions — never a name you write out, never a listener of your own. Save with await actions.saveRecord(record), which answers the new record's id; change with actions.updateRecord(id, change); delete with actions.deleteRecord(id); and the new row is in actions.records the moment it is written. Delete the ${n.hook}() call${t.length > 1 ? "s" : ""} and the import${t.length > 1 ? "s" : ""} with ${t.length > 1 ? "them" : "it"}.`;
2632
- }, ls = /\bsaveRecord\s*\(\s*['"`]([A-Za-z0-9_]+)['"`]/g, us = /\b(updateRecord|deleteRecord)\s*\(\s*['"`]((?:mem|admin|user|role|case|pub)_[A-Za-z0-9_]+)['"`]\s*,/g, ds = (e) => [...[...e.matchAll(ls)].map((e) => ({
2658
+ }, ps = /\bsaveRecord\s*\(\s*['"`]([A-Za-z0-9_]+)['"`]/g, ms = /\b(updateRecord|deleteRecord)\s*\(\s*['"`]((?:mem|admin|user|role|case|pub)_[A-Za-z0-9_]+)['"`]\s*,/g, hs = (e) => [...[...e.matchAll(ps)].map((e) => ({
2633
2659
  call: "saveRecord",
2634
2660
  name: e[1]
2635
- })), ...[...e.matchAll(us)].map((e) => ({
2661
+ })), ...[...e.matchAll(ms)].map((e) => ({
2636
2662
  call: e[1],
2637
2663
  name: e[2]
2638
- }))], fs = (e, t) => {
2664
+ }))], gs = (e, t) => {
2639
2665
  let n = t[0], r = n.call === "saveRecord" ? "actions.saveRecord(record)" : n.call === "updateRecord" ? "actions.updateRecord(id, change)" : "actions.deleteRecord(id)";
2640
2666
  return `${e}: actions.${n.call}('${n.name}', …) passes the collection, and none of these calls takes one — they are already bound to the page's own collection, the one you are handed as actions.collection, and a file that has actions never writes a collection name out. ${r} is the whole call. A collection passed in front of the record used to be saved INSTEAD of it — { ...'${n.name}' } is { 0: '${n.name[0]}', 1: '${n.name[1]}', … } — so the record went in with none of its own fields under a "Saved" on the screen. And the save answers the new record's id: const id = await actions.saveRecord(record), which is the id a history line (logHistory) or a link to the new record needs — never a res.id off it${t.length > 1 ? `. There are ${t.length} of these in this file — put every one right` : ""}.`;
2641
- }, ps = /^(?:user_submissions|user_requests|user_profiles|user_notifications|user_phoneclaims)$/, ms = String.raw`(?:addDoc|setDoc|updateDoc|deleteDoc)`, hs = String.raw`['"\x60]((?:mem|admin|user|role)_[A-Za-z0-9_]+)['"\x60]`, gs = new RegExp(String.raw`\b${ms}\s*\(\s*(?:doc|collection)\s*\(\s*db\s*,\s*${hs}`, "g"), _s = new RegExp(String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:doc|collection)\s*\(\s*db\s*,\s*${hs}`, "g"), vs = (e) => {
2667
+ }, _s = /^(?:user_submissions|user_requests|user_profiles|user_notifications|user_phoneclaims)$/, vs = String.raw`(?:addDoc|setDoc|updateDoc|deleteDoc)`, ys = String.raw`['"\x60]((?:mem|admin|user|role)_[A-Za-z0-9_]+)['"\x60]`, bs = new RegExp(String.raw`\b${vs}\s*\(\s*(?:doc|collection)\s*\(\s*db\s*,\s*${ys}`, "g"), xs = new RegExp(String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:doc|collection)\s*\(\s*db\s*,\s*${ys}`, "g"), Ss = (e) => {
2642
2668
  let t = /* @__PURE__ */ new Set();
2643
- for (let n of e.matchAll(gs)) ps.test(n[1]) || t.add(n[1]);
2644
- for (let n of e.matchAll(_s)) ps.test(n[2]) || new RegExp(String.raw`\b${ms}\s*\(\s*${n[1].replace(/\$/g, "\\$")}\b`).test(e) && t.add(n[2]);
2669
+ for (let n of e.matchAll(bs)) _s.test(n[1]) || t.add(n[1]);
2670
+ for (let n of e.matchAll(xs)) _s.test(n[2]) || new RegExp(String.raw`\b${vs}\s*\(\s*${n[1].replace(/\$/g, "\\$")}\b`).test(e) && t.add(n[2]);
2645
2671
  return [...t];
2646
- }, ys = (e, t) => `${e}: this page writes ${G(t)} itself, and the page you are filling already has the calls that write its records — each one bound to its OWN collection, the one it is handed as actions.collection: await actions.saveRecord(record), which answers the new record's id; actions.updateRecord(id, change); actions.deleteRecord(id), which takes the record's files down with it. A collection written out by hand is a different collection the moment it is a word off: a Products page that wrote mem_products, where its own page keeps mem_products_records, saved every product where nothing read it and deleted nothing it was asked to delete, neither of them saying a word. Even spelled right it loses what the app puts on a record as it saves: who made it and when (<MadeBy record={r} />), the lock that decides who may delete it, the date under a stock count that makes what is LEFT right, the row on the page before the server answers, and the public copy a page anyone can open reads. Write through those three, delete the hand-written reads and writes, and with them the doc/collection/setDoc/addDoc/deleteDoc imports the page no longer needs.`, bs = {
2672
+ }, Cs = (e, t) => `${e}: this page writes ${G(t)} itself, and the page you are filling already has the calls that write its records — each one bound to its OWN collection, the one it is handed as actions.collection: await actions.saveRecord(record), which answers the new record's id; actions.updateRecord(id, change); actions.deleteRecord(id), which takes the record's files down with it. A collection written out by hand is a different collection the moment it is a word off: a Products page that wrote mem_products, where its own page keeps mem_products_records, saved every product where nothing read it and deleted nothing it was asked to delete, neither of them saying a word. Even spelled right it loses what the app puts on a record as it saves: who made it and when (<MadeBy record={r} />), the lock that decides who may delete it, the date under a stock count that makes what is LEFT right, the row on the page before the server answers, and the public copy a page anyone can open reads. Write through those three, delete the hand-written reads and writes, and with them the doc/collection/setDoc/addDoc/deleteDoc imports the page no longer needs.`, ws = {
2647
2673
  useCases: [
2648
2674
  "loading",
2649
2675
  "records",
@@ -2695,38 +2721,38 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2695
2721
  "createWorkspace",
2696
2722
  "createOrg"
2697
2723
  ]
2698
- }, xs = {
2724
+ }, Ts = {
2699
2725
  useVisitorState: "Its answer comes apart both ways — const { value, setValue, clear } = useVisitorState('draft', {}) or const [draft, setDraft] = useVisitorState('draft', {}) — and there is no state, setState or data on it: read under those names it is undefined, so what was typed is never kept and never shown.",
2700
2726
  useAuth: "Who is signed in is user — const { user, userRole } = useAuth(), then user?.uid and user?.email — and there is no uid, email or name on the answer itself: read under those names it is undefined, so every record is saved with no owner and every notify({ toUid }) reaches nobody."
2701
- }, Ss = "(useCases|usePublished|useCase|usePeople|useVisitorState|useAuth)", Cs = new RegExp(String.raw`\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*${Ss}\b`, "g"), ws = new RegExp(String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*${Ss}\b`, "g"), Ts = (e) => {
2727
+ }, Es = "(useCases|usePublished|useCase|usePeople|useVisitorState|useAuth)", Ds = new RegExp(String.raw`\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*${Es}\b`, "g"), Os = new RegExp(String.raw`\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*${Es}\b`, "g"), ks = (e) => {
2702
2728
  let t = /* @__PURE__ */ new Map(), n = (e, n) => {
2703
- !n || bs[e].includes(n) || t.set(e, (t.get(e) || /* @__PURE__ */ new Set()).add(n));
2729
+ !n || ws[e].includes(n) || t.set(e, (t.get(e) || /* @__PURE__ */ new Set()).add(n));
2704
2730
  };
2705
- for (let t of e.matchAll(Cs)) for (let e of t[1].split(",")) {
2731
+ for (let t of e.matchAll(Ds)) for (let e of t[1].split(",")) {
2706
2732
  let r = e.split(/[:=]/)[0].trim();
2707
2733
  r && !r.startsWith("...") && n(t[2], r);
2708
2734
  }
2709
- for (let t of e.matchAll(ws)) for (let r of e.matchAll(RegExp(`\\b${t[1].replace(/\$/g, "\\$")}\\s*\\??\\.\\s*([A-Za-z_$][\\w$]*)`, "g"))) n(t[2], r[1]);
2735
+ for (let t of e.matchAll(Os)) for (let r of e.matchAll(RegExp(`\\b${t[1].replace(/\$/g, "\\$")}\\s*\\??\\.\\s*([A-Za-z_$][\\w$]*)`, "g"))) n(t[2], r[1]);
2710
2736
  return [...t].map(([e, t]) => ({
2711
2737
  hook: e,
2712
2738
  read: [...t]
2713
2739
  }));
2714
- }, Es = (e, { hook: t, read: n }) => {
2715
- let r = bs[t], i = xs[t];
2740
+ }, As = (e, { hook: t, read: n }) => {
2741
+ let r = ws[t], i = Ts[t];
2716
2742
  if (i) return `${e}: ${t}() hands back { ${t === "useAuth" ? "user, userRole, isAdmin, loading, …" : r.join(", ")} } and nothing else — this page reads ${n.map((e) => `\`${e}\``).join(" and ")} from it, which ${n.length === 1 ? "is" : "are"} never there. ${i} Change nothing else on the page.`;
2717
2743
  let a = r[1], o = a === "people" ? "people" : a === "record" ? "entry" : "entries", s = a === "record" ? "so the page draws nothing whatever has arrived and shows its not-found line to everybody" : "so the list is empty whatever has arrived and the page shows its empty line to everybody";
2718
2744
  return `${e}: ${t}() hands back { ${r.join(", ")} } and nothing else — this page reads ${n.map((e) => `\`${e}\``).join(" and ")} from it, which ${n.length === 1 ? "is" : "are"} never there, ${s}. Write const { ${r.join(", ")} } = ${t}(…) and read the ${o} from \`${a}\`. Change nothing else on the page.`;
2719
- }, Ds = /(?:access|editors)\s*:\s*\[[^\]]*['"`]everyone['"`]|(?:access|editors)\s*:\s*arrayUnion\s*\(\s*['"`]everyone['"`]|arrayUnion\s*\(\s*['"`]everyone['"`]|arrayRemove\s*\(\s*['"`]everyone['"`]/, Os = (e) => {
2720
- let t = Ds.exec(e);
2745
+ }, js = /(?:access|editors)\s*:\s*\[[^\]]*['"`]everyone['"`]|(?:access|editors)\s*:\s*arrayUnion\s*\(\s*['"`]everyone['"`]|arrayUnion\s*\(\s*['"`]everyone['"`]|arrayRemove\s*\(\s*['"`]everyone['"`]/, Ms = (e) => {
2746
+ let t = js.exec(e);
2721
2747
  return t ? /editors/.test(t[0]) ? "editors" : "access" : "";
2722
- }, ks = (e, t) => t === "editors" ? `${e}: this page writes 'everyone' onto a record's editors list. That would mean anybody signed in may rewrite or delete anybody's — the security rules refuse the write outright, so nothing is saved and the person is told nothing. 'everyone' belongs on \`access\` and nowhere else, and it is publish('case_…', id) that puts it there. Take the word out of editors; leave the author's own key on it.` : `${e}: this page writes 'everyone' into a record's access list by hand. Putting it there as the record is created publishes it the moment it is saved — there is no draft, and the owner asked for one. Use the two calls from 'your-ai-workflow-firebase-os': save it with withAccess(data, { uid }) and nothing else, then publish('case_…', id) from the button the person presses when they are ready, unpublish(…) to take it down, isPublished(record) to know which it is.`, As = /^data:|placehold|placeholder|picsum|unsplash|dummyimage|loremflickr|example\.com|your[-_]?logo|logo[-_]?here/i, js = (e, t) => {
2748
+ }, Ns = (e, t) => t === "editors" ? `${e}: this page writes 'everyone' onto a record's editors list. That would mean anybody signed in may rewrite or delete anybody's — the security rules refuse the write outright, so nothing is saved and the person is told nothing. 'everyone' belongs on \`access\` and nowhere else, and it is publish('case_…', id) that puts it there. Take the word out of editors; leave the author's own key on it.` : `${e}: this page writes 'everyone' into a record's access list by hand. Putting it there as the record is created publishes it the moment it is saved — there is no draft, and the owner asked for one. Use the two calls from 'your-ai-workflow-firebase-os': save it with withAccess(data, { uid }) and nothing else, then publish('case_…', id) from the button the person presses when they are ready, unpublish(…) to take it down, isPublished(record) to know which it is.`, Ps = /^data:|placehold|placeholder|picsum|unsplash|dummyimage|loremflickr|example\.com|your[-_]?logo|logo[-_]?here/i, Fs = (e, t) => {
2723
2749
  let n = RegExp(`\\b${t}\\s*:\\s*(['"\`])([\\s\\S]*?)\\1`).exec(e);
2724
2750
  return n ? n[2].trim() : null;
2725
- }, Ms = (e) => {
2726
- let t = ["logoUrl", "faviconUrl"], n = new Map(t.map((t) => [t, js(e, t)])), r = /DO NOT EDIT: logoUrl/.test(e), i = [];
2751
+ }, Is = (e) => {
2752
+ let t = ["logoUrl", "faviconUrl"], n = new Map(t.map((t) => [t, Fs(e, t)])), r = /DO NOT EDIT: logoUrl/.test(e), i = [];
2727
2753
  for (let e of t) {
2728
2754
  let t = n.get(e) ?? null, a = n.get(e === "logoUrl" ? "faviconUrl" : "logoUrl");
2729
- t ? As.test(t) && i.push({
2755
+ t ? Ps.test(t) && i.push({
2730
2756
  key: e,
2731
2757
  why: "stand-in"
2732
2758
  }) : (r || a) && i.push({
@@ -2735,40 +2761,40 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2735
2761
  });
2736
2762
  }
2737
2763
  return i;
2738
- }, Ns = (e, { key: t, why: n }) => {
2764
+ }, Ls = (e, { key: t, why: n }) => {
2739
2765
  let r = t === "logoUrl" ? "logo, the picture in the top bar" : "favicon, the picture in the browser tab";
2740
2766
  return n === "stand-in" ? `${e}: \`${t}\` points at a stand-in rather than at my ${r}. That value is my brand and is never yours to choose: put back the link that was there. If you do not have it, say so and stop — I upload it in the "Design it with your assistant" box on my /theme screen, and the sentence that box gives me carries the link for you to paste in. Never a placeholder, never a picture you generated, never a link I did not give you.` : `${e}: \`${t}\` is empty — my ${r}, is gone from my app. This file ships with it set and there is exactly one thing that may ever change it: a link in a message of mine. Put the old value back, character for character (its own history in this file is where it is). If it cannot be recovered, say so plainly and change nothing: I upload it again in the "Design it with your assistant" box on my /theme screen, and the sentence that box gives me carries the link. Never fill it with a placeholder, a picture you generated, or any link I did not give you.`;
2741
- }, Ps = (e) => `${e}: a plain query on a case_ collection is refused for everybody but an admin — the security rules prove a list by the key it names, and only useCases() asks for it that way. Read it with useCases('case_tickets') from 'your-ai-workflow-firebase-os' instead ({ loading, records, error }); addDoc(collection(db, '…'), withAccess(data, { uid })) is still how an entry is created.`, Fs = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?\{([^}]*)\}\s*from\s*['"]your-ai-workflow-firebase-os['"]/g, Is = new Set([
2767
+ }, Rs = (e) => `${e}: a plain query on a case_ collection is refused for everybody but an admin — the security rules prove a list by the key it names, and only useCases() asks for it that way. Read it with useCases('case_tickets') from 'your-ai-workflow-firebase-os' instead ({ loading, records, error }); addDoc(collection(db, '…'), withAccess(data, { uid })) is still how an entry is created.`, zs = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?\{([^}]*)\}\s*from\s*['"]your-ai-workflow-firebase-os['"]/g, Bs = new Set([
2742
2768
  "BuyButton",
2743
2769
  "PayButton",
2744
2770
  "Basket",
2745
2771
  "Checkout",
2746
2772
  "BasketButton",
2747
2773
  "CheckoutReturn"
2748
- ]), Ls = [
2774
+ ]), Vs = [
2749
2775
  "BuyButton",
2750
2776
  "PayButton",
2751
2777
  "Basket",
2752
2778
  "Checkout",
2753
2779
  "Plans"
2754
- ], Rs = /\bimport\s+(?!type\s)(?:[A-Za-z_$][\w$]*\s*,\s*)?\{([^}]*)\}\s*from\s*['"]your-ai-workflow-firebase-os['"]/g, zs = (e) => {
2780
+ ], Hs = /\bimport\s+(?!type\s)(?:[A-Za-z_$][\w$]*\s*,\s*)?\{([^}]*)\}\s*from\s*['"]your-ai-workflow-firebase-os['"]/g, Us = (e) => {
2755
2781
  let t = [];
2756
- for (let n of e.matchAll(Rs)) for (let e of n[1].split(",")) {
2782
+ for (let n of e.matchAll(Hs)) for (let e of n[1].split(",")) {
2757
2783
  if (/^\s*type\s/.test(e)) continue;
2758
2784
  let [n, r] = e.trim().split(/\s+as\s+/).map((e) => e.trim());
2759
- Is.has(n) && t.push([n, r || n]);
2785
+ Bs.has(n) && t.push([n, r || n]);
2760
2786
  }
2761
2787
  if (!t.length) return [];
2762
- let n = Ai(e.replace(/\bimport\s[^;]*?\bfrom\s*['"][^'"]+['"]/g, ""));
2788
+ let n = Pi(e.replace(/\bimport\s[^;]*?\bfrom\s*['"][^'"]+['"]/g, ""));
2763
2789
  return [...new Set(t.filter(([, e]) => !new RegExp(String.raw`(?<![\w$.])${e.replace(/\$/g, "\\$")}(?![\w$])`).test(n)).map(([e]) => e))];
2764
- }, Bs = (e, t) => {
2790
+ }, Ws = (e, t) => {
2765
2791
  let n = Dn(t), r = t.match(/\bexport\s+default\s+(?:React\.)?(?:memo|forwardRef)\s*\(\s*([A-Za-z_$][\w$]*)\s*[,)]/)?.[1];
2766
2792
  return `${e}: App.tsx imports { Home } from this file — ${n && n !== "default" ? `keep the default export as it is and add \`export { ${n === "Home" ? "Home" : `${n} as Home`} };\` on a line of its own below it` : r ? `keep the default export as it is and add \`export { ${r === "Home" ? "Home" : `${r} as Home`} };\` on a line of its own below it` : "give the page its name: write it as `export function Home() { … }` in place of the default export, and put `export default Home;` below it"}; with only a default export or another name the owner's home page is never found and visitors get the starter page instead. Add that and change nothing else.`;
2767
- }, Vs = "Basket|BuyButton|Checkout|PayButton|Plans", Hs = (e) => {
2793
+ }, Gs = "Basket|BuyButton|Checkout|PayButton|Plans", Ks = (e) => {
2768
2794
  if (e === void 0 || e === !0) return null;
2769
2795
  let t = e.startsWith("{") ? /^\{\s*(['"`])([^'"`$]*)\1\s*\}$/.exec(e)?.[2] : e;
2770
2796
  return t && /^[A-Za-z]{3}$/.test(t.trim()) ? t.trim().toLowerCase() : null;
2771
- }, Us = (e) => {
2797
+ }, qs = (e) => {
2772
2798
  let t = [];
2773
2799
  for (let n of e.matchAll(/(?<![\w$.])money\s*\(/g)) {
2774
2800
  let r = [], i = 0, a = "", o = (n.index ?? 0) + n[0].length;
@@ -2788,11 +2814,11 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2788
2814
  t.push(r);
2789
2815
  }
2790
2816
  return t;
2791
- }, Ws = /(?<![\w$.])([A-Za-z_$][\w$]*)\s*\??\.\s*currency\b/g, Gs = (e) => {
2792
- let t = Y(e), n = [...new Set([...t.matchAll(Ws)].map((e) => e[1]))];
2817
+ }, Js = /(?<![\w$.])([A-Za-z_$][\w$]*)\s*\??\.\s*currency\b/g, Ys = (e) => {
2818
+ let t = Y(e), n = [...new Set([...t.matchAll(Js)].map((e) => e[1]))];
2793
2819
  if (!n.length) return null;
2794
2820
  let r = (e) => n.find((t) => RegExp(`(?<![\\w$.])${t.replace(/\$/g, "\\$")}\\s*\\??\\.`).test(e)), i = "", a = [];
2795
- for (let e of Us(t)) {
2821
+ for (let e of qs(t)) {
2796
2822
  let t = e.length === 1 ? r(e[0]) : void 0;
2797
2823
  t && (i ||= t, a.push(`money(${e[0].replace(/\s+/g, " ")})`));
2798
2824
  }
@@ -2800,16 +2826,16 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2800
2826
  owner: i,
2801
2827
  calls: [...new Set(a)]
2802
2828
  } : null;
2803
- }, Ks = (e, t) => `${e}: ${G(t.calls.map((e) => `"${e}"`))} ${t.calls.length > 1 ? "draw amounts" : "draws an amount"} of ${t.owner}, which carries its own currency — this page reads ${t.owner}.currency — without it. money(amount) with nothing after it is the owner's account currency, so an order paid in euros shows "$" in an account kept in dollars, beside its own "EUR". Every amount on it — its total, its subtotal, each item's price and each extra — is money(amount, ${t.owner}.currency), in the currency it was paid in: pass that second word to each of them, and change nothing else.`, qs = (e, t) => {
2829
+ }, Xs = (e, t) => `${e}: ${G(t.calls.map((e) => `"${e}"`))} ${t.calls.length > 1 ? "draw amounts" : "draws an amount"} of ${t.owner}, which carries its own currency — this page reads ${t.owner}.currency — without it. money(amount) with nothing after it is the owner's account currency, so an order paid in euros shows "$" in an account kept in dollars, beside its own "EUR". Every amount on it — its total, its subtotal, each item's price and each extra — is money(amount, ${t.owner}.currency), in the currency it was paid in: pass that second word to each of them, and change nothing else.`, Zs = (e, t) => {
2804
2830
  let n = /* @__PURE__ */ new Set();
2805
- for (let t of e.matchAll(Rs)) for (let e of t[1].split(",")) {
2831
+ for (let t of e.matchAll(Hs)) for (let e of t[1].split(",")) {
2806
2832
  let [t, r] = e.trim().split(/\s+as\s+/).map((e) => e.trim());
2807
2833
  t && !/^type\s/.test(e.trim()) && n.add(r || t);
2808
2834
  }
2809
2835
  let r = [];
2810
- for (let { tag: i, attrs: a } of $(e, Vs)) {
2836
+ for (let { tag: i, attrs: a } of $(e, Gs)) {
2811
2837
  if (!n.has(i)) continue;
2812
- let o = kl(a), s = Hs(o.get("currency"));
2838
+ let o = Nl(a), s = Ks(o.get("currency"));
2813
2839
  if (!s || s === t || (i === "BuyButton" || i === "PayButton") && o.has("priceId")) continue;
2814
2840
  let c = i === "Basket" && o.has("collection") || i === "Checkout" && (o.has("collection") || /\bcollection\s*:/.test(e));
2815
2841
  r.push({
@@ -2818,8 +2844,8 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2818
2844
  shape: i === "Plans" ? "words" : c ? "records" : "typed"
2819
2845
  });
2820
2846
  }
2821
- if (n.has("money")) for (let n of Us(e)) {
2822
- let e = n.length === 2 ? Hs(/^(['"`])[^'"`$]*\1$/.test(n[1]) ? n[1].slice(1, -1) : void 0) : null;
2847
+ if (n.has("money")) for (let n of qs(e)) {
2848
+ let e = n.length === 2 ? Ks(/^(['"`])[^'"`$]*\1$/.test(n[1]) ? n[1].slice(1, -1) : void 0) : null;
2823
2849
  e && e !== t && r.push({
2824
2850
  piece: "money()",
2825
2851
  said: e,
@@ -2827,7 +2853,7 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2827
2853
  });
2828
2854
  }
2829
2855
  return r;
2830
- }, Js = (e) => {
2856
+ }, Qs = (e) => {
2831
2857
  try {
2832
2858
  return new Intl.NumberFormat("en", {
2833
2859
  style: "currency",
@@ -2836,10 +2862,10 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2836
2862
  } catch {
2837
2863
  return e;
2838
2864
  }
2839
- }, Ys = (e, t, n) => {
2840
- let r = G([...new Set(t.map((e) => e.piece))]), i = [...new Set(t.map((e) => e.said))], a = t.find((e) => e.shape === "records") || t.find((e) => e.shape === "typed") || t[0], o = a.shape === "records" ? `the owner's Stripe account charges in ${n}, and a basket checked against her records is refused in any other currency` : a.shape === "typed" ? `its visitors are charged in ${a.said} while her account and every other price in the app are in ${n}` : `the words say ${Js(a.said)} while the charge is in ${n}`, s = [...new Set(t.filter((e) => e.piece !== "money()").map((e) => e.said))], c = [...new Set(t.filter((e) => e.piece === "money()").map((e) => e.said))], l = [s.length ? `each ${s.map((e) => `currency='${e}'`).join(" and ")} on this page to currency='${n}'` : "", c.length ? `each ${c.map((e) => `money(…, '${e}')`).join(" and ")} to money(…, '${n}')` : ""].filter(Boolean).join(", and ");
2865
+ }, $s = (e, t, n) => {
2866
+ let r = G([...new Set(t.map((e) => e.piece))]), i = [...new Set(t.map((e) => e.said))], a = t.find((e) => e.shape === "records") || t.find((e) => e.shape === "typed") || t[0], o = a.shape === "records" ? `the owner's Stripe account charges in ${n}, and a basket checked against her records is refused in any other currency` : a.shape === "typed" ? `its visitors are charged in ${a.said} while her account and every other price in the app are in ${n}` : `the words say ${Qs(a.said)} while the charge is in ${n}`, s = [...new Set(t.filter((e) => e.piece !== "money()").map((e) => e.said))], c = [...new Set(t.filter((e) => e.piece === "money()").map((e) => e.said))], l = [s.length ? `each ${s.map((e) => `currency='${e}'`).join(" and ")} on this page to currency='${n}'` : "", c.length ? `each ${c.map((e) => `money(…, '${e}')`).join(" and ")} to money(…, '${n}')` : ""].filter(Boolean).join(", and ");
2841
2867
  return `${e}: ${r} ${t.length > 1 ? "name" : "names"} ${i.join(" and ")} — ${o}. Change ${l} — or leave the currency out of the piece and out of money(), and the app uses the account's own.`;
2842
- }, Xs = (e, t, n, r = []) => {
2868
+ }, ec = (e, t, n, r = []) => {
2843
2869
  let i = G(t.map((e) => `<${e}>`)), a = t.length > 1 ? "are" : "is";
2844
2870
  if (r.length) return `${e}: ${i} ${a} imported and never drawn, and this page takes its payment with ${G(r.map((e) => `<${e}>`))} — delete ${t.length > 1 ? "those names" : "that name"} from the import line, and change nothing else.`;
2845
2871
  if (n === !0) {
@@ -2847,7 +2873,7 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2847
2873
  return `${e}: ${i} ${a} imported and never drawn, and this page's sentence takes money here — as it stands nobody can pay: whatever the page books or sends goes through for nothing. Draw ${t.length > 1 ? "them" : "it"} where the page's sentence asked${n}, and change nothing else.`;
2848
2874
  }
2849
2875
  return n === !1 ? `${e}: ${i} ${a} imported and never drawn, and the owner has no payment on this page — delete ${t.length > 1 ? "those names" : "that name"} from the import line, and change nothing else.` : `${e}: ${i} ${a} imported and never drawn — if the page's sentence asked for a payment here, draw ${t.length > 1 ? "them" : "it"} where it asked; if it did not, delete ${t.length > 1 ? "those names" : "that name"} from the import line.`;
2850
- }, Zs = {
2876
+ }, tc = {
2851
2877
  useFirestore: "db — import { db } from 'your-ai-workflow-firebase-os', with collection(db, '…') from 'firebase/firestore'",
2852
2878
  useDb: "db — import { db } from 'your-ai-workflow-firebase-os'",
2853
2879
  firestore: "db — import { db } from 'your-ai-workflow-firebase-os'",
@@ -2869,7 +2895,7 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2869
2895
  formatCurrency: "money(amount)",
2870
2896
  Image: "<Picture>",
2871
2897
  Img: "<Picture>"
2872
- }, Qs = new Set([
2898
+ }, nc = new Set([
2873
2899
  "collection",
2874
2900
  "onSnapshot",
2875
2901
  "doc",
@@ -2891,47 +2917,47 @@ var Oo = (e) => RegExp(`(?:${e})\\s*(?:&&|\\?)\\s*\\(?\\s*<\\s*(?:Card|SectionHe
2891
2917
  "Timestamp",
2892
2918
  "writeBatch",
2893
2919
  "runTransaction"
2894
- ]), $s = (e) => Zs[e] || (Qs.has(e) ? `${e} from 'firebase/firestore' — import { ${e} } from 'firebase/firestore'; only db comes from the package` : null) || ec(e), ec = (e) => {
2920
+ ]), rc = (e) => tc[e] || (nc.has(e) ? `${e} from 'firebase/firestore' — import { ${e} } from 'firebase/firestore'; only db comes from the package` : null) || ic(e), ic = (e) => {
2895
2921
  let t = e.toLowerCase();
2896
- return R.find((e) => e.toLowerCase() === t) || R.filter((e) => e.length > 3 && (t.includes(e.toLowerCase()) || e.toLowerCase().includes(t))).sort((t, n) => Math.abs(t.length - e.length) - Math.abs(n.length - e.length))[0] || null;
2897
- }, tc = /^(?:ChatThread|openChat|useAiChat|askAi|useAiEnabled|useAiModels)$/, nc = (e) => {
2922
+ return L.find((e) => e.toLowerCase() === t) || L.filter((e) => e.length > 3 && (t.includes(e.toLowerCase()) || e.toLowerCase().includes(t))).sort((t, n) => Math.abs(t.length - e.length) - Math.abs(n.length - e.length))[0] || null;
2923
+ }, ac = /^(?:ChatThread|openChat|useAiChat|askAi|useAiEnabled|useAiModels)$/, oc = (e) => {
2898
2924
  let t = /* @__PURE__ */ new Set();
2899
- for (let n of Y(e).matchAll(Fs)) for (let e of n[1].split(",")) {
2925
+ for (let n of Y(e).matchAll(zs)) for (let e of n[1].split(",")) {
2900
2926
  let n = e.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0].trim();
2901
- n && /^[A-Za-z_$][\w$]*$/.test(n) && !tc.test(n) && !R.includes(n) && t.add(n);
2927
+ n && /^[A-Za-z_$][\w$]*$/.test(n) && !ac.test(n) && !L.includes(n) && t.add(n);
2902
2928
  }
2903
2929
  return [...t];
2904
- }, rc = (e, t) => `${e}: ${G(t.map((e) => {
2905
- let t = $s(e);
2930
+ }, sc = (e, t) => `${e}: ${G(t.map((e) => {
2931
+ let t = rc(e);
2906
2932
  return t ? `${e} (what is there: ${t})` : e;
2907
- }))} ${t.length > 1 ? "are" : "is"} imported from 'your-ai-workflow-firebase-os', and the package has nothing by ${t.length > 1 ? "those names" : "that name"} — the import fails, and because every page is loaded through registry.ts the WHOLE app stops loading, not only this page. Never guess a name: import only what the app's instructions name, spelled as they spell it. Take ${t.length > 1 ? "each one" : "it"} out of the import and use what is there in its place; if nothing the instructions name does the job, write it in this file from React and 'firebase/firestore' rather than importing a piece that does not exist. (If the instructions DO name it, the installed package is older than they are: update it.)`, ic = /\bnotify\s*\(\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*\)/g, ac = (e) => {
2908
- for (let t of Y(e).matchAll(ic)) if (!/\bto(?:Uid|Role)\b|\.\.\./.test(t[1])) return t[0].replace(/\s+/g, " ").slice(0, 80);
2933
+ }))} ${t.length > 1 ? "are" : "is"} imported from 'your-ai-workflow-firebase-os', and the package has nothing by ${t.length > 1 ? "those names" : "that name"} — the import fails, and because every page is loaded through registry.ts the WHOLE app stops loading, not only this page. Never guess a name: import only what the app's instructions name, spelled as they spell it. Take ${t.length > 1 ? "each one" : "it"} out of the import and use what is there in its place; if nothing the instructions name does the job, write it in this file from React and 'firebase/firestore' rather than importing a piece that does not exist. (If the instructions DO name it, the installed package is older than they are: update it.)`, cc = /\bnotify\s*\(\s*\{((?:[^{}]|\{[^{}]*\})*)\}\s*\)/g, lc = (e) => {
2934
+ for (let t of Y(e).matchAll(cc)) if (!/\bto(?:Uid|Role)\b|\.\.\./.test(t[1])) return t[0].replace(/\s+/g, " ").slice(0, 80);
2909
2935
  return null;
2910
- }, oc = (e) => {
2911
- for (let t of Y(e).matchAll(ic)) if (/\bto(?:Uid|Role)\b|\.\.\./.test(t[1]) && /\bmessage\s*:/.test(t[1]) && !/\bbody\s*:/.test(t[1])) return t[0].replace(/\s+/g, " ").slice(0, 80);
2936
+ }, uc = (e) => {
2937
+ for (let t of Y(e).matchAll(cc)) if (/\bto(?:Uid|Role)\b|\.\.\./.test(t[1]) && /\bmessage\s*:/.test(t[1]) && !/\bbody\s*:/.test(t[1])) return t[0].replace(/\s+/g, " ").slice(0, 80);
2912
2938
  return null;
2913
- }, sc = (e, t) => `${e}: "${t}" — notify() takes { toUid or toRole, title, body, link }, and message is not one of its words: the bell shows title in bold with body under it, so this notification arrives with nothing to read. Put the one-line headline in title and the rest in body, and change nothing else about it.`, cc = (e) => {
2939
+ }, dc = (e, t) => `${e}: "${t}" — notify() takes { toUid or toRole, title, body, link }, and message is not one of its words: the bell shows title in bold with body under it, so this notification arrives with nothing to read. Put the one-line headline in title and the rest in body, and change nothing else about it.`, fc = (e) => {
2914
2940
  for (let t of $(Y(e), "Popup")) if (!(/\.\.\./.test(t.attrs) || /\bopen\b/.test(t.attrs))) return t.attrs.replace(/\s+/g, " ").trim().slice(0, 60);
2915
2941
  return null;
2916
- }, lc = (e, t) => `${e}: a <Popup${t ? ` ${t}…` : ""}> here has no open prop — a window is drawn only while open is true, so this one never appears, and nothing says why. Give it the same state that decides whether it is on screen — <Popup open={!!chosen} onClose={() => setChosen(null)} title="…"> — and keep it rendered rather than mounted only while that state is set, so it can ease in and out (rule 30).`, uc = /\b(?:assign(?:ed|ee)(?:To|Uid|Id)|assignedUserId|handedTo|heldBy|holderUid)\s*:/, dc = (e) => {
2942
+ }, pc = (e, t) => `${e}: a <Popup${t ? ` ${t}…` : ""}> here has no open prop — a window is drawn only while open is true, so this one never appears, and nothing says why. Give it the same state that decides whether it is on screen — <Popup open={!!chosen} onClose={() => setChosen(null)} title="…"> — and keep it rendered rather than mounted only while that state is set, so it can ease in and out (rule 30).`, mc = /\b(?:assign(?:ed|ee)(?:To|Uid|Id)|assignedUserId|handedTo|heldBy|holderUid)\s*:/, hc = (e) => {
2917
2943
  let t = e;
2918
2944
  for (;;) {
2919
2945
  let e = /\b(?:export\s+)?(?:interface\s+[A-Za-z_$][\w$]*[^{]*|type\s+[A-Za-z_$][\w$]*(?:<[^>]*>)?\s*=\s*)\{/.exec(t);
2920
2946
  if (!e) return t;
2921
- let n = (e.index ?? 0) + e[0].length - 1, r = Hc(t, n);
2947
+ let n = (e.index ?? 0) + e[0].length - 1, r = Kc(t, n);
2922
2948
  if (r < 0) return t;
2923
2949
  t = `${t.slice(0, e.index)}${t.slice(r + 1)}`;
2924
2950
  }
2925
- }, fc = (e) => {
2926
- let t = dc(Y(e)), n = t.match(/\buseCases\s*(?:<[^>]*>)?\s*\(\s*['"]([^'"]+)['"]/);
2927
- return !n || /\bsendTo\s*\(/.test(t) || /\bactions\s*\.\s*sendTo\b/.test(t) ? null : uc.test(t) ? n[1] : null;
2928
- }, pc = (e, t) => `${e}: an entry here is handed to somebody by writing who holds it onto the record, and sendTo() is never called — the entry's own lists do not change, so the person it was handed to is never sent it and sees nothing, whatever the record says. Handing one over is the app's own piece: <HandOver entry={entry} collection='${t}' name={…} /> imported from 'your-ai-workflow-firebase-os' — the collection this page reads with useCases — on each entry's card (compact) and inside the opened entry (label='Assigned to'). It shows who holds the entry, is the picker for whoever may hand it over, sends it, takes it off whoever had it and rings the person. Replace the picker, its button and the fields written by hand with it, and change nothing else on the page.`, mc = (e) => {
2929
- let t = dc(Y(e)), n = t.match(/\buseCases\s*(?:<[^>]*>)?\s*\(\s*['"]([^'"]+)['"]/);
2930
- return n && /\bsendTo\s*\([^;]*?\bpeople\s*:/.test(t) && uc.test(t) ? n[1] : null;
2931
- }, hc = (e, t, n = !1) => `${e}: handing an entry to a person is built by hand here${n ? " — beside the <HandOver> the page already draws" : ""} — a picker, a button, sendTo(), takeBack() and who holds it written onto the entry. That is the app's own piece: <HandOver entry={entry} collection='${t}' name={…} /> imported from 'your-ai-workflow-firebase-os' — name is what the entry is called, for the bell. It shows who holds the entry by their picture and their name; for whoever may hand it over it is the picker, and picking a person hands it over at once, takes whoever had it off, keeps who holds it on the entry and rings them — and it draws that picker for nobody the owner's database would refuse. Replace the picker, its Assign and Unassign buttons, the usePeople() behind them, the sendTo/takeBack calls to one person, the notify() that told them and every field that wrote who holds it with <HandOver>: on each entry's card (compact) and inside the opened entry (label='Assigned to'). Where the page drew who holds an entry as words, that is <HandOver> too. Change nothing else on the page.`, gc = (e, t) => `${e}: "${t}" — notify() is the BELL: a notification sent to somebody else, and it needs who it is for (toUid for one person, toRole for an audience) and a title. Called like this it sends nothing and shows nothing, so nobody is ever told their save went through. To answer the person who just pressed something it is say('Saved') imported from 'your-ai-workflow-firebase-os' — one brief line, no options needed. Change every notify({ … }) that has no toUid or toRole into say('the same words'), and keep notify() only where somebody ELSE has to hear about it.`, _c = /<ChatThread\b|\bopenChat\s*\(/, vc = /\buseVisitorState\b/, yc = /\btally\s*\(/, bc = /\b(?:saveRecord|updateRecord|deleteRecord|usePublicCopy|useStockFromOrders|useTallies|logHistory|useCases|userRole)\b/, xc = (e) => (vc.test(e) || yc.test(e)) && !bc.test(e), Sc = /\b(updateDoc|setDoc|addDoc|deleteDoc|writeBatch)\s*\(/, Cc = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "write" : "writes"} the database from a page anyone can open, and a visitor is not signed in — the rules refuse every write from here with "Missing or insufficient permissions", a pub_ copy included: the page that owns the data writes the copy, this page only reads it. Delete ${t.length > 1 ? "every one of those writes" : "the write"}. What a visitor does goes through the app's own form — <ContactPopup formId="…" isInline bare extra={{ … }} onSubmitted={…} /> from a *Form.config.ts, with what the page worked out (the items, their options, the total) in extra — a <PayButton>, or a schedule question; the page that owns the data reads the entry while an admin has it open and applies it once, to its own record and the pub_ copy together.`, wc = /\bincrement\s*\(/, Tc = (e, t) => t ? e.filter((e) => e !== "updateDoc" && e !== "setDoc") : e, Ec = (e) => `${e}: increment( counts what visitors do by writing the database from a page anyone can open, and a visitor is not signed in — the rules refuse that write for every real visitor, so the number only ever moves for the owner testing their own page while signed in. Delete the write and the increment import. Count with the app's own piece, imported from 'your-ai-workflow-firebase-os': tally('products', product.id, 'views') where the thing is opened — the word for what is counted, the record's own id, and one of views, added, clicks, saves, shares or downloads. It counts a visitor once a visit, leaves the team's own looks out, never throws and never holds the press up, so it is called and not awaited. A shop's <Basket tally="products"> counts every add itself. The page for the team reads them with useTallies('products') — counts.of(product.id).views. Never keep a count on the record, and never in a pub_ collection.`, Dc = /fetch\(\s*['"`]\/api\/payments\/portal|cancel_at_period_end|cancelSubscription|subscriptions?\.(?:cancel|del)\(/, Oc = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} a hand-rolled billing flow — the app already has one. Cancel, resume, plan switches, card changes and invoices are ALL Stripe's own portal: render <ManagePlanButton /> imported from 'your-ai-workflow-firebase-os' (buyers also reach it on the built-in /billing page) and delete this code.`, kc = /['"`](?:mem_bookings|pub_bookings|sys_schedules|sys_bookingpages)['"`]|runTransaction\s*\(/, Ac = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled booking code, and booking a time is not part of this version of the app. Delete it: people ask for a time on the page's form instead — a date question and a short text question in its *Form.config.ts, shown with <ContactPopup formId="…" isInline /> — and each request arrives in the owner's Inbox like any other entry. Say in your reply that booking a time is not in this version.`, jc = /\b(availableSlots|availableSlotsFor|togetherSlots|slotsFor)\s*\(/g;
2932
- function Mc(e) {
2951
+ }, gc = (e) => {
2952
+ let t = hc(Y(e)), n = t.match(/\buseCases\s*(?:<[^>]*>)?\s*\(\s*['"]([^'"]+)['"]/);
2953
+ return !n || /\bsendTo\s*\(/.test(t) || /\bactions\s*\.\s*sendTo\b/.test(t) ? null : mc.test(t) ? n[1] : null;
2954
+ }, _c = (e, t) => `${e}: an entry here is handed to somebody by writing who holds it onto the record, and sendTo() is never called — the entry's own lists do not change, so the person it was handed to is never sent it and sees nothing, whatever the record says. Handing one over is the app's own piece: <HandOver entry={entry} collection='${t}' name={…} /> imported from 'your-ai-workflow-firebase-os' — the collection this page reads with useCases — on each entry's card (compact) and inside the opened entry (label='Assigned to'). It shows who holds the entry, is the picker for whoever may hand it over, sends it, takes it off whoever had it and rings the person. Replace the picker, its button and the fields written by hand with it, and change nothing else on the page.`, vc = (e) => {
2955
+ let t = hc(Y(e)), n = t.match(/\buseCases\s*(?:<[^>]*>)?\s*\(\s*['"]([^'"]+)['"]/);
2956
+ return n && /\bsendTo\s*\([^;]*?\bpeople\s*:/.test(t) && mc.test(t) ? n[1] : null;
2957
+ }, yc = (e, t, n = !1) => `${e}: handing an entry to a person is built by hand here${n ? " — beside the <HandOver> the page already draws" : ""} — a picker, a button, sendTo(), takeBack() and who holds it written onto the entry. That is the app's own piece: <HandOver entry={entry} collection='${t}' name={…} /> imported from 'your-ai-workflow-firebase-os' — name is what the entry is called, for the bell. It shows who holds the entry by their picture and their name; for whoever may hand it over it is the picker, and picking a person hands it over at once, takes whoever had it off, keeps who holds it on the entry and rings them — and it draws that picker for nobody the owner's database would refuse. Replace the picker, its Assign and Unassign buttons, the usePeople() behind them, the sendTo/takeBack calls to one person, the notify() that told them and every field that wrote who holds it with <HandOver>: on each entry's card (compact) and inside the opened entry (label='Assigned to'). Where the page drew who holds an entry as words, that is <HandOver> too. Change nothing else on the page.`, bc = (e, t) => `${e}: "${t}" — notify() is the BELL: a notification sent to somebody else, and it needs who it is for (toUid for one person, toRole for an audience) and a title. Called like this it sends nothing and shows nothing, so nobody is ever told their save went through. To answer the person who just pressed something it is say('Saved') imported from 'your-ai-workflow-firebase-os' — one brief line, no options needed. Change every notify({ … }) that has no toUid or toRole into say('the same words'), and keep notify() only where somebody ELSE has to hear about it.`, xc = /<ChatThread\b|\bopenChat\s*\(/, Sc = /\buseVisitorState\b/, Cc = /\btally\s*\(/, wc = /\b(?:saveRecord|updateRecord|deleteRecord|usePublicCopy|useStockFromOrders|useTallies|logHistory|useCases|userRole)\b/, Tc = (e) => (Sc.test(e) || Cc.test(e)) && !wc.test(e), Ec = /\b(updateDoc|setDoc|addDoc|deleteDoc|writeBatch)\s*\(/, Dc = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "write" : "writes"} the database from a page anyone can open, and a visitor is not signed in — the rules refuse every write from here with "Missing or insufficient permissions", a pub_ copy included: the page that owns the data writes the copy, this page only reads it. Delete ${t.length > 1 ? "every one of those writes" : "the write"}. What a visitor does goes through the app's own form — <ContactPopup formId="…" isInline bare extra={{ … }} onSubmitted={…} /> from a *Form.config.ts, with what the page worked out (the items, their options, the total) in extra — a <PayButton>, or a schedule question; the page that owns the data reads the entry while an admin has it open and applies it once, to its own record and the pub_ copy together.`, Oc = /\bincrement\s*\(/, kc = (e, t) => t ? e.filter((e) => e !== "updateDoc" && e !== "setDoc") : e, Ac = (e) => `${e}: increment( counts what visitors do by writing the database from a page anyone can open, and a visitor is not signed in — the rules refuse that write for every real visitor, so the number only ever moves for the owner testing their own page while signed in. Delete the write and the increment import. Count with the app's own piece, imported from 'your-ai-workflow-firebase-os': tally('products', product.id, 'views') where the thing is opened — the word for what is counted, the record's own id, and one of views, added, clicks, saves, shares or downloads. It counts a visitor once a visit, leaves the team's own looks out, never throws and never holds the press up, so it is called and not awaited. A shop's <Basket tally="products"> counts every add itself. The page for the team reads them with useTallies('products') — counts.of(product.id).views. Never keep a count on the record, and never in a pub_ collection.`, jc = /fetch\(\s*['"`]\/api\/payments\/portal|cancel_at_period_end|cancelSubscription|subscriptions?\.(?:cancel|del)\(/, Mc = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} a hand-rolled billing flow — the app already has one. Cancel, resume, plan switches, card changes and invoices are ALL Stripe's own portal: render <ManagePlanButton /> imported from 'your-ai-workflow-firebase-os' (buyers also reach it on the built-in /billing page) and delete this code.`, Nc = /['"`](?:mem_bookings|pub_bookings|sys_schedules|sys_bookingpages)['"`]|runTransaction\s*\(/, Pc = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled booking code, and booking a time is not part of this version of the app. Delete it: people ask for a time on the page's form instead — a date question and a short text question in its *Form.config.ts, shown with <ContactPopup formId="…" isInline /> — and each request arrives in the owner's Inbox like any other entry. Say in your reply that booking a time is not in this version.`, Fc = /\b(availableSlots|availableSlotsFor|togetherSlots|slotsFor)\s*\(/g;
2958
+ function Ic(e) {
2933
2959
  let t = [];
2934
- for (let n of e.matchAll(jc)) {
2960
+ for (let n of e.matchAll(Fc)) {
2935
2961
  let r = (n.index ?? 0) + n[0].length - 1, i = 0, a = -1;
2936
2962
  for (let t = r; t < e.length; t++) if (e[t] === "(") i++;
2937
2963
  else if (e[t] === ")" && --i === 0) {
@@ -2944,42 +2970,42 @@ function Mc(e) {
2944
2970
  }
2945
2971
  return t;
2946
2972
  }
2947
- var Nc = (e, t) => {
2973
+ var Lc = (e, t) => {
2948
2974
  let n = t[0].trim().replace(/\s+/g, " ");
2949
2975
  return `${e}: ${n.length > 80 ? `${n.slice(0, 80)}…` : n} works out free times without the booked ones, so the page offers hours somebody has already booked. Read the booked times with useTakenSlots(memberIds, from) from 'your-ai-workflow-firebase-os' — memberIds the people the page shows, from ONE Date kept in state — and pass them in: availableSlotsFor(schedules, { now: new Date(), taken }). A time booked on ANY booking link, page or schedule of that person is taken on all of them.${K(t.length)}`;
2950
- }, Pc = /\b(setDoc|updateDoc|addDoc|deleteDoc)\s*\(/g, Fc = String.raw`\b(?:console|logger|log|Sentry)\s*\.\s*\w+`, Ic = /^(?:noop|nop|ignore|ignoreErrors?|swallow|doNothing|_\.noop)$/, Lc = (e, t) => new RegExp(String.raw`\b(?:const|let|var)\s+${t.replace(/\$/g, "\\$")}\s*=\s*(?:async\s*)?(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{\s*\}|null\b|undefined\b|void\s*0)|\bfunction\s+${t.replace(/\$/g, "\\$")}\s*\([^()]*\)\s*\{\s*\}`).test(e), Rc = (e, t = "", n = !1) => {
2976
+ }, Rc = /\b(setDoc|updateDoc|addDoc|deleteDoc)\s*\(/g, zc = String.raw`\b(?:console|logger|log|Sentry)\s*\.\s*\w+`, Bc = /^(?:noop|nop|ignore|ignoreErrors?|swallow|doNothing|_\.noop)$/, Vc = (e, t) => new RegExp(String.raw`\b(?:const|let|var)\s+${t.replace(/\$/g, "\\$")}\s*=\s*(?:async\s*)?(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{\s*\}|null\b|undefined\b|void\s*0)|\bfunction\s+${t.replace(/\$/g, "\\$")}\s*\([^()]*\)\s*\{\s*\}`).test(e), Hc = (e, t = "", n = !1) => {
2951
2977
  let r = e.trim().replace(/^async\s+/, "").replace(/^(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>\s*/, "").replace(/^function\s*[\w$]*\s*\([^()]*\)\s*/, "");
2952
- if (RegExp(`^${Fc}$`).test(r)) return !0;
2978
+ if (RegExp(`^${zc}$`).test(r)) return !0;
2953
2979
  if (((e) => /^\(?\s*(?:null|undefined|void\s*0|false|true|-?\d+(?:\.\d+)?|''|""|\[\s*\])\s*\)?$/.test(e.trim()))(r)) return !n;
2954
- if (/^[A-Za-z_$][\w$.]*$/.test(r)) return Ic.test(r) || Lc(t, r);
2955
- let i = RegExp(`${Fc}\\s*\\(`);
2980
+ if (/^[A-Za-z_$][\w$.]*$/.test(r)) return Bc.test(r) || Vc(t, r);
2981
+ let i = RegExp(`${zc}\\s*\\(`);
2956
2982
  for (let e = r.search(i); e !== -1; e = r.search(i)) {
2957
- let t = zc(r, r.indexOf("(", e));
2983
+ let t = Uc(r, r.indexOf("(", e));
2958
2984
  if (t === -1) break;
2959
2985
  r = `${r.slice(0, e)}${r.slice(t + 1)}`;
2960
2986
  }
2961
2987
  return r.replace(/\b(?:return|null|undefined|void\s*0)\b|[\s;{}()]/g, "") === "";
2962
- }, zc = (e, t) => {
2988
+ }, Uc = (e, t) => {
2963
2989
  let n = 0;
2964
2990
  for (let r = t; r < e.length; r++) if (e[r] === "(") n++;
2965
2991
  else if (e[r] === ")" && --n === 0) return r;
2966
2992
  return -1;
2967
2993
  };
2968
- function Bc(e) {
2994
+ function Wc(e) {
2969
2995
  let t = [];
2970
- for (let n of e.matchAll(Pc)) {
2971
- let r = zc(e, (n.index ?? 0) + n[0].length - 1);
2996
+ for (let n of e.matchAll(Rc)) {
2997
+ let r = Uc(e, (n.index ?? 0) + n[0].length - 1);
2972
2998
  if (r === -1) continue;
2973
2999
  let i = e.slice(r + 1).match(/^\s*\.catch\s*\(/);
2974
3000
  if (!i) continue;
2975
- let a = r + 1 + i[0].length - 1, o = zc(e, a);
3001
+ let a = r + 1 + i[0].length - 1, o = Uc(e, a);
2976
3002
  if (o === -1) continue;
2977
3003
  let s = e.slice(a + 1, o), c = e.slice(0, n.index ?? 0).replace(/\s+$/, "").replace(/\b(?:await|void)$/, "").replace(/\s+$/, "");
2978
- Rc(s, e, !(c === "" || /[;{}]$/.test(c) || /\bon[A-Z]\w*\s*=\s*\{\s*(?:async\s*)?(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>$/.test(c))) && t.push(`${n[1]}(…).catch(${s.replace(/\s+/g, " ").trim()})`);
3004
+ Hc(s, e, !(c === "" || /[;{}]$/.test(c) || /\bon[A-Z]\w*\s*=\s*\{\s*(?:async\s*)?(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>$/.test(c))) && t.push(`${n[1]}(…).catch(${s.replace(/\s+/g, " ").trim()})`);
2979
3005
  }
2980
3006
  return t;
2981
3007
  }
2982
- var Vc = (e) => e.replace(/console\s*\.\s*\w+\s*\([^()]*\)/g, "").replace(/[\s;]/g, "") === "", Hc = (e, t) => {
3008
+ var Gc = (e) => e.replace(/console\s*\.\s*\w+\s*\([^()]*\)/g, "").replace(/[\s;]/g, "") === "", Kc = (e, t) => {
2983
3009
  let n = 0;
2984
3010
  for (let r = t; r < e.length; r++) {
2985
3011
  let t = e[r];
@@ -3001,16 +3027,16 @@ var Vc = (e) => e.replace(/console\s*\.\s*\w+\s*\([^()]*\)/g, "").replace(/[\s;]
3001
3027
  else if (t === "}" && --n === 0) return r;
3002
3028
  }
3003
3029
  return -1;
3004
- }, Uc = /\btry\s*\{/g, Wc = /^\s*catch\s*(?:\([^()]*\)\s*)?\{/;
3005
- function Gc(e) {
3030
+ }, qc = /\btry\s*\{/g, Jc = /^\s*catch\s*(?:\([^()]*\)\s*)?\{/;
3031
+ function Yc(e) {
3006
3032
  let t = [];
3007
- for (let n of e.matchAll(Uc)) {
3008
- let r = (n.index ?? 0) + n[0].length - 1, i = Hc(e, r);
3033
+ for (let n of e.matchAll(qc)) {
3034
+ let r = (n.index ?? 0) + n[0].length - 1, i = Kc(e, r);
3009
3035
  if (i === -1) continue;
3010
- let a = e.slice(i + 1).match(Wc);
3036
+ let a = e.slice(i + 1).match(Jc);
3011
3037
  if (!a) continue;
3012
- let o = i + 1 + a[0].length - 1, s = Hc(e, o);
3013
- if (s === -1 || !Vc(e.slice(o + 1, s))) continue;
3038
+ let o = i + 1 + a[0].length - 1, s = Kc(e, o);
3039
+ if (s === -1 || !Gc(e.slice(o + 1, s))) continue;
3014
3040
  let c = e.slice(r + 1, i);
3015
3041
  if (/\btry\s*\{/.test(c)) continue;
3016
3042
  let l = c.match(/\b(setDoc|updateDoc|addDoc|deleteDoc)\s*\(/);
@@ -3018,33 +3044,33 @@ function Gc(e) {
3018
3044
  }
3019
3045
  return t;
3020
3046
  }
3021
- var Kc = (e, t) => `${e}: ${t[0]} throws the failure away — the write is awaited inside a try whose catch tells nobody, so the page carries on as though it landed and whatever it says next ("Saved", a tick, a row it adds to a list) it says whether Firestore took the write or refused it. A refusal is the ordinary case here, not a freak one: the rules refuse a write whose collection prefix is not that person's, so a trail the TEAM appends to in an admin_ collection records the admins and silently records nobody else (contract rule 11). In the catch, say plainly that it did not save and leave what they typed on the screen so they can send it again (contract rule 19).${K(t.length)}`, qc = (e, t) => `${e}: ${t[0].length > 72 ? `${t[0].slice(0, 72)}…` : t[0]} throws the failure away, so the page carries on as though the write landed — and whatever it says next ("Saved", a tick, a box it clears) it says whether Firestore took the write or refused it. That is the one thing a page must never do: it teaches the owner to trust a number that is not there. Await the write with nothing chained to it, say "Saved" only after it resolves, and in the catch say plainly that it did not save and leave what they typed on the screen so they can send it again (contract rule 19).${K(t.length)}`, Jc = String.raw`(?:(?:e|err|error|ex|caught)\s*\??\.\s*(?:message|code)\b|String\(\s*(?:e|err|error)\s*\))`, Yc = new RegExp(String.raw`\bset[A-Z]\w*\(\s*[^;)]{0,90}?` + Jc, ""), Xc = new RegExp(String.raw`[^=]>\s*\{[^{}]{0,90}?` + Jc + String.raw`[^{}]{0,90}?\}`, ""), Zc = (e) => {
3047
+ var Xc = (e, t) => `${e}: ${t[0]} throws the failure away — the write is awaited inside a try whose catch tells nobody, so the page carries on as though it landed and whatever it says next ("Saved", a tick, a row it adds to a list) it says whether Firestore took the write or refused it. A refusal is the ordinary case here, not a freak one: the rules refuse a write whose collection prefix is not that person's, so a trail the TEAM appends to in an admin_ collection records the admins and silently records nobody else (contract rule 11). In the catch, say plainly that it did not save and leave what they typed on the screen so they can send it again (contract rule 19).${K(t.length)}`, Zc = (e, t) => `${e}: ${t[0].length > 72 ? `${t[0].slice(0, 72)}…` : t[0]} throws the failure away, so the page carries on as though the write landed — and whatever it says next ("Saved", a tick, a box it clears) it says whether Firestore took the write or refused it. That is the one thing a page must never do: it teaches the owner to trust a number that is not there. Await the write with nothing chained to it, say "Saved" only after it resolves, and in the catch say plainly that it did not save and leave what they typed on the screen so they can send it again (contract rule 19).${K(t.length)}`, Qc = String.raw`(?:(?:e|err|error|ex|caught)\s*\??\.\s*(?:message|code)\b|String\(\s*(?:e|err|error)\s*\))`, $c = new RegExp(String.raw`\bset[A-Z]\w*\(\s*[^;)]{0,90}?` + Qc, ""), el = new RegExp(String.raw`[^=]>\s*\{[^{}]{0,90}?` + Qc + String.raw`[^{}]{0,90}?\}`, ""), tl = (e) => {
3022
3048
  let t = [];
3023
- for (let n of [Yc, Xc]) for (let r of e.matchAll(new RegExp(n.source, "g"))) {
3049
+ for (let n of [$c, el]) for (let r of e.matchAll(new RegExp(n.source, "g"))) {
3024
3050
  let n = r.index ?? 0, i = e.slice(Math.max(0, n - 60), n);
3025
3051
  /console\.\w+\($/.test(i.trimEnd()) || /console\.\w+\([^)]*$/.test(i) || t.push(r[0].trim());
3026
3052
  }
3027
3053
  return t;
3028
- }, Qc = (e, t) => `${e}: ${t[0].length > 72 ? `${t[0].slice(0, 72)}…` : t[0]} puts what you CAUGHT on the screen. Those words are the library talking to a developer — "Missing or insufficient permissions.", "FirebaseError: unavailable", permission-denied — and to the person using the app they mean nothing, read as though they broke something, and leave them with nowhere to go. Say what it means and what to do next instead: troubleSentence(e, 'That could not be saved.'), imported from 'your-ai-workflow-firebase-os', which words a lost connection, a sign-in that ran out, a refusal and a busy app each in their own sentence and falls back to yours for anything else. Put it where the work was, leave what they typed on screen, and give them the button again (contract rule 25).${K(t.length)}`, $c = /['"`](user_submissions|user_requests)['"`]/, el = (e) => {
3029
- let t = e.match($c);
3054
+ }, nl = (e, t) => `${e}: ${t[0].length > 72 ? `${t[0].slice(0, 72)}…` : t[0]} puts what you CAUGHT on the screen. Those words are the library talking to a developer — "Missing or insufficient permissions.", "FirebaseError: unavailable", permission-denied — and to the person using the app they mean nothing, read as though they broke something, and leave them with nowhere to go. Say what it means and what to do next instead: troubleSentence(e, 'That could not be saved.'), imported from 'your-ai-workflow-firebase-os', which words a lost connection, a sign-in that ran out, a refusal and a busy app each in their own sentence and falls back to yours for anything else. Put it where the work was, leave what they typed on screen, and give them the button again (contract rule 25).${K(t.length)}`, rl = /['"`](user_submissions|user_requests)['"`]/, il = (e) => {
3055
+ let t = e.match(rl);
3030
3056
  return !t || /\bformId\b/.test(e) ? null : t[1];
3031
- }, tl = /\b(?:user|currentUser)\s*[?!]?\s*\.\s*(?:email|displayName|photoURL)\b/, nl = /\b([A-Za-z_$][\w$]*)\s*:\s*(?:new\s+Date\s*\(\s*)?Date\.now\(\)\s*-(?!=)/g, rl = (e, t) => {
3057
+ }, al = /\b(?:user|currentUser)\s*[?!]?\s*\.\s*(?:email|displayName|photoURL)\b/, ol = /\b([A-Za-z_$][\w$]*)\s*:\s*(?:new\s+Date\s*\(\s*)?Date\.now\(\)\s*-(?!=)/g, sl = (e, t) => {
3032
3058
  let n = 0;
3033
3059
  for (let r = t; r >= 0; r--) if (e[r] === "}") n++;
3034
3060
  else if (e[r] === "{" && n-- === 0) return r;
3035
3061
  return -1;
3036
3062
  };
3037
- function il(e) {
3063
+ function cl(e) {
3038
3064
  let t = /* @__PURE__ */ new Set();
3039
- for (let n of e.matchAll(nl)) {
3040
- let r = rl(e, n.index ?? 0);
3065
+ for (let n of e.matchAll(ol)) {
3066
+ let r = sl(e, n.index ?? 0);
3041
3067
  if (r === -1) continue;
3042
- let i = Hc(e, r), a = e.slice(r, i === -1 ? void 0 : i + 1).match(tl);
3068
+ let i = Kc(e, r), a = e.slice(r, i === -1 ? void 0 : i + 1).match(al);
3043
3069
  a && t.add(`${n[1]}: Date.now() - … beside ${a[0].replace(/\s+/g, "")}`);
3044
3070
  }
3045
3071
  return [...t];
3046
3072
  }
3047
- var al = (e, t, n) => {
3073
+ var ll = (e, t, n) => {
3048
3074
  let r = 0;
3049
3075
  for (let i = t + 1; i < n; i++) {
3050
3076
  let t = e[i];
@@ -3053,29 +3079,29 @@ var al = (e, t, n) => {
3053
3079
  else if (t === "," && r === 0) return i;
3054
3080
  }
3055
3081
  return n;
3056
- }, ol = (e, t) => {
3082
+ }, ul = (e, t) => {
3057
3083
  let n = t.replace(/\$/g, "\\$"), r = new RegExp(String.raw`\b(?:(?:const|let|var)\s+${n}\s*=\s*(?:useCallback\s*\(\s*)?(?:async\s+)?(?:function\b[^{]*|(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*(?::\s*[^={]+)?=>\s*\(?\s*)|function\s+${n}\s*\([^()]*\)[^{]*)\{`).exec(e);
3058
3084
  if (!r) return "";
3059
- let i = Hc(e, r.index + r[0].length - 1);
3085
+ let i = Kc(e, r.index + r[0].length - 1);
3060
3086
  return i === -1 ? "" : e.slice(r.index, i + 1);
3061
3087
  };
3062
- function sl(e) {
3088
+ function dl(e) {
3063
3089
  for (let t of e.matchAll(/\bonSnapshot\s*\(/g)) {
3064
- let n = (t.index ?? 0) + t[0].length - 1, r = zc(e, n);
3090
+ let n = (t.index ?? 0) + t[0].length - 1, r = Uc(e, n);
3065
3091
  if (r === -1) continue;
3066
- let i = al(e, n, r), a = e.slice(n + 1, i).trim();
3092
+ let i = ll(e, n, r), a = e.slice(n + 1, i).trim();
3067
3093
  if (/^[A-Za-z_$][\w$]*$/.test(a)) {
3068
3094
  let t = new RegExp(String.raw`\b(?:const|let|var)\s+${a.replace(/\$/g, "\\$")}\s*=\s*([^;]{0,300})`).exec(e);
3069
3095
  a = t ? t[1] : "";
3070
3096
  }
3071
- let o = a.match($c);
3097
+ let o = a.match(rl);
3072
3098
  if (!o) continue;
3073
3099
  let s = e.slice(i, r);
3074
3100
  if (/\bsubmitter(?:Name|Email)\b/.test(s)) continue;
3075
- let c = /* @__PURE__ */ new Set(), l = s.match(tl);
3101
+ let c = /* @__PURE__ */ new Set(), l = s.match(al);
3076
3102
  l && c.add(l[0].replace(/\s+/g, ""));
3077
3103
  for (let [, t] of s.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
3078
- let n = ol(e, t).match(tl);
3104
+ let n = ul(e, t).match(al);
3079
3105
  n && c.add(`${n[0].replace(/\s+/g, "")} (inside ${t})`);
3080
3106
  }
3081
3107
  if (c.size) return {
@@ -3085,28 +3111,28 @@ function sl(e) {
3085
3111
  }
3086
3112
  return null;
3087
3113
  }
3088
- var cl = /\bdoc\s*\(\s*[\w.]+\s*,\s*['"`]user_?(?:profiles|s)['"`]\s*,\s*([^,)]+)\)/g, ll = /^(?:auth\s*\.\s*)?(?:user|currentUser|me|viewer)\s*[?!]?\s*\.\s*uid$|^uid$/, ul = /\b(createdBy|updatedBy|addedBy|editedBy|modifiedBy|lastEditedBy|lastModifiedBy|createdByName|updatedByName)\s*:\s*[^,\n}]*\b(?:user|currentUser)\s*[?!]?\s*\.\s*(?:email|displayName|uid)\b/g;
3089
- function dl(e) {
3114
+ var fl = /\bdoc\s*\(\s*[\w.]+\s*,\s*['"`]user_?(?:profiles|s)['"`]\s*,\s*([^,)]+)\)/g, pl = /^(?:auth\s*\.\s*)?(?:user|currentUser|me|viewer)\s*[?!]?\s*\.\s*uid$|^uid$/, ml = /\b(createdBy|updatedBy|addedBy|editedBy|modifiedBy|lastEditedBy|lastModifiedBy|createdByName|updatedByName)\s*:\s*[^,\n}]*\b(?:user|currentUser)\s*[?!]?\s*\.\s*(?:email|displayName|uid)\b/g;
3115
+ function hl(e) {
3090
3116
  let t = /* @__PURE__ */ new Set();
3091
- for (let n of e.matchAll(cl)) {
3117
+ for (let n of e.matchAll(fl)) {
3092
3118
  let e = n[1].replace(/\s+/g, "");
3093
- ll.test(e) || t.add(`a profile read for ${e}`);
3119
+ pl.test(e) || t.add(`a profile read for ${e}`);
3094
3120
  }
3095
- for (let n of e.matchAll(ul)) t.add(`${n[1]} signed by hand`);
3121
+ for (let n of e.matchAll(ml)) t.add(`${n[1]} signed by hand`);
3096
3122
  return [...t];
3097
3123
  }
3098
- var fl = (e, t) => `${e}: ${G(t)} — the page works out WHO MADE a record for itself. It is already on the record: actions.saveRecord stamps creatorName, creatorAvatar, creatorEmail and createdAt on everything it saves, and actions.updateRecord stamps editedName, editedAvatar and editedAt on every change. Draw them with <MadeBy record={item} /> and <MadeBy record={item} edited /> from 'your-ai-workflow-firebase-os', and delete the rest: reading another person's user_profiles document is "Missing or insufficient permissions" for a role the owner invented and for every member — the card draws for the admin who built the page and for nobody it was built for — and on a card made from an Inbox entry it names the admin instead of the sender; a createdBy: user.email written by hand is an address where a name was wanted. There is no users collection. A person somebody PICKED (an assignee) is saved as a uid AND a name on the record, and the name is what the page draws. The one name a page passes itself is a change made for somebody else: actions.updateRecord(id, { stock, editedName: order.submitterName }) (contract rules 29 and 31).`, pl = (e, t) => `${e}: ${G(t)} — a history line signed with whoever is LOOKING at the page, at a time made up by counting back from now. Lines like that are invented after the fact: every one of them says the owner, or whoever opens the page next, did something they never did, at a time nobody did it. Delete the code that makes them, and any copy of them the page kept in the browser. A history holds only the lines written at the moment something is done, by the save that does it, each signed by the person who did it — and while there are none, it says so (contract rule 29). On a page for the team that is two pieces of the app: logHistory('<the records' collection>', record.id, 'what was done', { from, to }) straight after the save — it signs the line itself — and <History collection="…" of={record.id} /> to draw them.${K(t.length)}`, ml = /['"`]user_(?:submissions|requests)['"`]/, hl = /\bstock\b[^\n;]{0,120}\b(?:quantity|qty)\b/i, gl = (e) => ml.test(e) && hl.test(e) && !/\buseStock(?:FromOrders)?\b/.test(e), _l = /<Basket\b|<Checkout\b|<BuyButton\b|usePublicCopy\(|useTallies\(/, vl = /\b(?:[A-Za-z_$][\w$]*(?:\??\.?\[[^\]\n]*\])*\??\.)+stock\b(?!SetAt)/, yl = (e) => _l.test(e) && vl.test(e) && !/\buseStock(?:FromOrders)?\s*\(/.test(e), bl = String.raw`(?:\b\w*[vV]ariants\s*\??|\(\s*[\w.?]*[vV]ariants\s*(?:\|\||\?\?)\s*\[\]\s*\))\s*\.\s*`, xl = String.raw`\s*\(\s*\(?\s*\w+\s*(?::\s*[\w<>[\]|. ]+)?,\s*(\w+)\s*(?::\s*[\w<>[\]|. ]+)?\)?\s*=>`, Sl = RegExp(`${bl}reduce${xl}[^;]{0,200}?(?:\\b\\1\\s*\\??\\.\\s*stock\\b|\\.left\\s*\\([^)]*\\b\\1\\s*\\??\\.\\s*id\\b)`, "g"), Cl = RegExp(`${bl}flatMap\\s*\\(\\s*\\(?\\s*(\\w+)\\s*(?::\\s*[\\w<>[\\]|. ]+)?\\)?\\s*=>\\s*\\(?\\s*\\1\\s*\\??\\.\\s*(?:shades|colou?rs|swatch(?:es)?)\\b`, "g"), wl = (e, t) => {
3124
+ var gl = (e, t) => `${e}: ${G(t)} — the page works out WHO MADE a record for itself. It is already on the record: actions.saveRecord stamps creatorName, creatorAvatar, creatorEmail and createdAt on everything it saves, and actions.updateRecord stamps editedName, editedAvatar and editedAt on every change. Draw them with <MadeBy record={item} /> and <MadeBy record={item} edited /> from 'your-ai-workflow-firebase-os', and delete the rest: reading another person's user_profiles document is "Missing or insufficient permissions" for a role the owner invented and for every member — the card draws for the admin who built the page and for nobody it was built for — and on a card made from an Inbox entry it names the admin instead of the sender; a createdBy: user.email written by hand is an address where a name was wanted. There is no users collection. A person somebody PICKED (an assignee) is saved as a uid AND a name on the record, and the name is what the page draws. The one name a page passes itself is a change made for somebody else: actions.updateRecord(id, { stock, editedName: order.submitterName }) (contract rules 29 and 31).`, _l = (e, t) => `${e}: ${G(t)} — a history line signed with whoever is LOOKING at the page, at a time made up by counting back from now. Lines like that are invented after the fact: every one of them says the owner, or whoever opens the page next, did something they never did, at a time nobody did it. Delete the code that makes them, and any copy of them the page kept in the browser. A history holds only the lines written at the moment something is done, by the save that does it, each signed by the person who did it — and while there are none, it says so (contract rule 29). On a page for the team that is two pieces of the app: logHistory('<the records' collection>', record.id, 'what was done', { from, to }) straight after the save — it signs the line itself — and <History collection="…" of={record.id} /> to draw them.${K(t.length)}`, vl = /['"`]user_(?:submissions|requests)['"`]/, yl = /\bstock\b[^\n;]{0,120}\b(?:quantity|qty)\b/i, bl = (e) => vl.test(e) && yl.test(e) && !/\buseStock(?:FromOrders)?\b/.test(e), xl = /<Basket\b|<Checkout\b|<BuyButton\b|usePublicCopy\(|useTallies\(/, Sl = /\b(?:[A-Za-z_$][\w$]*(?:\??\.?\[[^\]\n]*\])*\??\.)+stock\b(?!SetAt)/, Cl = (e) => xl.test(e) && Sl.test(e) && !/\buseStock(?:FromOrders)?\s*\(/.test(e), wl = String.raw`(?:\b\w*[vV]ariants\s*\??|\(\s*[\w.?]*[vV]ariants\s*(?:\|\||\?\?)\s*\[\]\s*\))\s*\.\s*`, Tl = String.raw`\s*\(\s*\(?\s*\w+\s*(?::\s*[\w<>[\]|. ]+)?,\s*(\w+)\s*(?::\s*[\w<>[\]|. ]+)?\)?\s*=>`, El = RegExp(`${wl}reduce${Tl}[^;]{0,200}?(?:\\b\\1\\s*\\??\\.\\s*stock\\b|\\.left\\s*\\([^)]*\\b\\1\\s*\\??\\.\\s*id\\b)`, "g"), Dl = RegExp(`${wl}flatMap\\s*\\(\\s*\\(?\\s*(\\w+)\\s*(?::\\s*[\\w<>[\\]|. ]+)?\\)?\\s*=>\\s*\\(?\\s*\\1\\s*\\??\\.\\s*(?:shades|colou?rs|swatch(?:es)?)\\b`, "g"), Ol = (e, t) => {
3099
3125
  for (let n of e.matchAll(t)) {
3100
3126
  let t = e.slice(Math.max(0, (n.index ?? 0) - 160), n.index);
3101
3127
  if (!/\.(?:reduce|flatMap)\s*\(|\bfor\s*\(/.test(t)) return !0;
3102
3128
  }
3103
3129
  return !1;
3104
- }, Tl = (e) => [...wl(e, Sl) ? ["stock"] : [], ...wl(e, Cl) ? ["shades"] : []], El = (e, t) => `${e}: this page draws a thing's variants as one — it ${[...t.includes("stock") ? ["adds its variants' stock up into one number (variants.reduce(… .stock …)), to show on its card or to save as the whole product's stock"] : [], ...t.includes("shades") ? ["runs every variant's shades together into one row of swatches (variants.flatMap((v) => v.shades)) that nobody can press"] : []].join(", and ")}. A thing with variants is shown as ONE variant at a time, the chosen one, its card included: every card keeps its own chosen variant (its first until another is pressed), each variant is one <Swatch color={variant.shades[0]} name={variant.name} selected={variant.id === chosen.id} onClick={() => choose(variant)} /> ON the card (its press never opens the card too), and pressing one swaps that card's picture and stock for that variant's right there, without opening anything. The stock shown is the chosen variant's own — stock.left(product, variant.id) where the page uses useStock — never the variants added up, and nothing keeps a product-wide stock beside theirs. A total over the whole shop is fine. Change nothing else on the page.`, Dl = (e) => {
3130
+ }, kl = (e) => [...Ol(e, El) ? ["stock"] : [], ...Ol(e, Dl) ? ["shades"] : []], Al = (e, t) => `${e}: this page draws a thing's variants as one — it ${[...t.includes("stock") ? ["adds its variants' stock up into one number (variants.reduce(… .stock …)), to show on its card or to save as the whole product's stock"] : [], ...t.includes("shades") ? ["runs every variant's shades together into one row of swatches (variants.flatMap((v) => v.shades)) that nobody can press"] : []].join(", and ")}. A thing with variants is shown as ONE variant at a time, the chosen one, its card included: every card keeps its own chosen variant (its first until another is pressed), each variant is one <Swatch color={variant.shades[0]} name={variant.name} selected={variant.id === chosen.id} onClick={() => choose(variant)} /> ON the card (its press never opens the card too), and pressing one swaps that card's picture and stock for that variant's right there, without opening anything. The stock shown is the chosen variant's own — stock.left(product, variant.id) where the page uses useStock — never the variants added up, and nothing keeps a product-wide stock beside theirs. A total over the whole shop is fine. Change nothing else on the page.`, jl = (e) => {
3105
3131
  if (!/\bcursor-zoom-in\b|<ZoomIn\b/.test(e)) return !1;
3106
3132
  let t = $(e, "Lightbox");
3107
3133
  return t.length > 0 && t.every((e) => !/(?:^|\s)zoom(?:\s|=|\/|$)/.test(e.attrs));
3108
- }, Ol = (e) => `${e}: this page shows a picture as something to ZOOM (a zoom-in cursor or a magnifier) and then opens it in <Lightbox> without zoom — a gallery: the same picture a little bigger with arrows through the others. Viewing and zooming are two things. Viewing is on the page: the small pictures swap the big one IN PLACE (never open a window). Zooming is pressing the big one: <Lightbox pictures={pictures} index={at} onIndex={setAt} onClose={() => setAt(null)} zoom /> opens it whole and magnifies the spot pressed in it — the close-up then follows the mouse, a finger drags and pinches it, one more press shows it whole again. Add zoom to the <Lightbox>, make the small pictures swap the big one, and change nothing else on the page.`;
3109
- function kl(e) {
3134
+ }, Ml = (e) => `${e}: this page shows a picture as something to ZOOM (a zoom-in cursor or a magnifier) and then opens it in <Lightbox> without zoom — a gallery: the same picture a little bigger with arrows through the others. Viewing and zooming are two things. Viewing is on the page: the small pictures swap the big one IN PLACE (never open a window). Zooming is pressing the big one: <Lightbox pictures={pictures} index={at} onIndex={setAt} onClose={() => setAt(null)} zoom /> opens it whole and magnifies the spot pressed in it — the close-up then follows the mouse, a finger drags and pinches it, one more press shows it whole again. Add zoom to the <Lightbox>, make the small pictures swap the big one, and change nothing else on the page.`;
3135
+ function Nl(e) {
3110
3136
  let t = /* @__PURE__ */ new Map(), n = (t) => {
3111
3137
  let n = 0, r = "";
3112
3138
  for (let i = t; i < e.length; i += 1) {
@@ -3153,10 +3179,10 @@ function kl(e) {
3153
3179
  }
3154
3180
  return t;
3155
3181
  }
3156
- var Al = /^pub_[A-Za-z0-9_]{1,120}$/;
3157
- function jl(e) {
3182
+ var Pl = /^pub_[A-Za-z0-9_]{1,120}$/;
3183
+ function Fl(e) {
3158
3184
  let t = $(e, "Basket"), n = t.map(({ attrs: e }) => {
3159
- let t = kl(e);
3185
+ let t = Nl(e);
3160
3186
  return /\{\s*\.\.\./.test(e) && t.set("...", !0), t;
3161
3187
  }).filter((e) => {
3162
3188
  let t = e.get("send");
@@ -3168,9 +3194,9 @@ function jl(e) {
3168
3194
  reads: [...new Set([...r.matchAll(/['"`](pub_[A-Za-z0-9_]+)['"`]/g)].map((e) => e[1]))]
3169
3195
  };
3170
3196
  }
3171
- var Ml = (e) => e === !0 ? null : e.startsWith("{") ? /^\{\s*(['"`])([^'"`$]*)\1\s*\}$/.exec(e)?.[2] ?? null : e;
3172
- function Nl(e, t = []) {
3173
- let { baskets: n, reads: r } = jl(e);
3197
+ var Il = (e) => e === !0 ? null : e.startsWith("{") ? /^\{\s*(['"`])([^'"`$]*)\1\s*\}$/.exec(e)?.[2] ?? null : e;
3198
+ function Ll(e, t = []) {
3199
+ let { baskets: n, reads: r } = Fl(e);
3174
3200
  return !n.length || n.every((e) => e.has("collection") || e.has("...")) ? null : r.length ? {
3175
3201
  reads: r,
3176
3202
  elsewhere: !1
@@ -3179,23 +3205,23 @@ function Nl(e, t = []) {
3179
3205
  elsewhere: !0
3180
3206
  } : null;
3181
3207
  }
3182
- function Pl(e, t = []) {
3183
- let { baskets: n, reads: r } = jl(e);
3208
+ function Rl(e, t = []) {
3209
+ let { baskets: n, reads: r } = Fl(e);
3184
3210
  for (let e of n) {
3185
3211
  let n = e.get("collection");
3186
3212
  if (n === void 0) continue;
3187
- let i = Ml(n);
3188
- if (i !== null && (!Al.test(i) || r.length && !r.includes(i) && !t.includes(i))) return {
3213
+ let i = Il(n);
3214
+ if (i !== null && (!Pl.test(i) || r.length && !r.includes(i) && !t.includes(i))) return {
3189
3215
  said: i,
3190
3216
  reads: r
3191
3217
  };
3192
3218
  }
3193
3219
  return null;
3194
3220
  }
3195
- var Fl = (e, t, n = !1) => {
3221
+ var zl = (e, t, n = !1) => {
3196
3222
  let r = n ? "the page that puts things in the basket reads" : "this page reads";
3197
3223
  return `${e}: its <Basket> does not say which collection its lines come from, so the server charges what the page says without checking — anybody can send a basket of their own at any price, and nothing bought through it comes off what is left in stock. ${t.length === 1 ? `Add collection="${t[0]}" to it (the public collection ${r})` : `Add collection="…" to it, naming the public collection the things put in the basket are read from — ${r} ${G(t.map((e) => `"${e}"`))}, and every line of the basket comes from one of them`}: the server then reads each line's price off the record before anything is charged. A line's price is the record's price, or its variant's, never a number the page works out; a discount is a Stripe promotion code, typed at checkout.`;
3198
- }, Il = (e, t, n) => `${e}: its <Basket> says collection="${t}", but ${n.length ? `this page reads ${G(n.map((e) => `"${e}"`))}` : "that is not a public collection, and the server reads prices only from the pub_ copy the page reads"} — the server would read each line's price from a collection that is not there and refuse every checkout, for every buyer. ${n.length === 1 ? `Use collection="${n[0]}"` : n.length ? "Use the one its lines come from" : "Use the pub_ name this page reads, as the page's sentence names it"}, letter for letter, and change nothing else.`, Ll = (e) => `${e}: this page reads a stock number straight off a record — product.stock, or a variant's — to show it or to decide whether one can still be bought. That number is the count my team last SET, not what is left: it is right until the first sale and wrong for every visitor from then on, because a sale writes nothing to the record. What is left is the app's to work out: const stock = useStock(records) from 'your-ai-workflow-firebase-os' over the records this page shows, and stock.left(product) or stock.left(product, variant.id) is the number to draw and the number that decides — null where a thing keeps no count. Never product.stock or variants[i].stock in what is shown or compared.`, Rl = (e) => `${e}: this page takes the stock of what was sold by itself — it reads the shop's orders out of user_submissions and counts each line's quantity off a stock number. Written by hand that goes wrong silently: stock taken twice after a reload, taken for a basket nobody paid for, or "Missing or insufficient permissions" for everybody who is not an admin. Nothing is taken off the record at all any more: the record keeps the count my team last set (stock, or variants[i].stock, each variant with an id of its own), the app dates it when it is saved, and what is LEFT is that count minus what Stripe says was paid for since — const stock = useStock(records) from 'your-ai-workflow-firebase-os', and stock.left(product) or stock.left(product, variant.id) is the number, on this page and on the shop alike, for everybody, with nobody signed in. Delete the listener, the useSales() join, the decrement and the list of applied entries.`, zl = (e, t, n) => `${e}: it applies entries from ${t} — things somebody else SENT — and writes them up signed with ${G(n)}. That code runs in the browser of the admin who has the page open, so every line names that admin, whoever actually placed the order or sent the entry. Take the person off the entry instead: submitterName and submitterEmail when they were signed in, else the name and email they typed into the form, else "A visitor" — and the time off the entry's submittedAt, not the moment the page got round to it. A helper that writes a line takes the person as an argument rather than reading user itself (contract rule 29) — the app's own logHistory(collection, id, what, { by: { name }, at }) does exactly that.`, Bl = (e, t) => {
3224
+ }, Bl = (e, t, n) => `${e}: its <Basket> says collection="${t}", but ${n.length ? `this page reads ${G(n.map((e) => `"${e}"`))}` : "that is not a public collection, and the server reads prices only from the pub_ copy the page reads"} — the server would read each line's price from a collection that is not there and refuse every checkout, for every buyer. ${n.length === 1 ? `Use collection="${n[0]}"` : n.length ? "Use the one its lines come from" : "Use the pub_ name this page reads, as the page's sentence names it"}, letter for letter, and change nothing else.`, Vl = (e) => `${e}: this page reads a stock number straight off a record — product.stock, or a variant's — to show it or to decide whether one can still be bought. That number is the count my team last SET, not what is left: it is right until the first sale and wrong for every visitor from then on, because a sale writes nothing to the record. What is left is the app's to work out: const stock = useStock(records) from 'your-ai-workflow-firebase-os' over the records this page shows, and stock.left(product) or stock.left(product, variant.id) is the number to draw and the number that decides — null where a thing keeps no count. Never product.stock or variants[i].stock in what is shown or compared.`, Hl = (e) => `${e}: this page takes the stock of what was sold by itself — it reads the shop's orders out of user_submissions and counts each line's quantity off a stock number. Written by hand that goes wrong silently: stock taken twice after a reload, taken for a basket nobody paid for, or "Missing or insufficient permissions" for everybody who is not an admin. Nothing is taken off the record at all any more: the record keeps the count my team last set (stock, or variants[i].stock, each variant with an id of its own), the app dates it when it is saved, and what is LEFT is that count minus what Stripe says was paid for since — const stock = useStock(records) from 'your-ai-workflow-firebase-os', and stock.left(product) or stock.left(product, variant.id) is the number, on this page and on the shop alike, for everybody, with nobody signed in. Delete the listener, the useSales() join, the decrement and the list of applied entries.`, Ul = (e, t, n) => `${e}: it applies entries from ${t} — things somebody else SENT — and writes them up signed with ${G(n)}. That code runs in the browser of the admin who has the page open, so every line names that admin, whoever actually placed the order or sent the entry. Take the person off the entry instead: submitterName and submitterEmail when they were signed in, else the name and email they typed into the form, else "A visitor" — and the time off the entry's submittedAt, not the moment the page got round to it. A helper that writes a line takes the person as an argument rather than reading user itself (contract rule 29) — the app's own logHistory(collection, id, what, { by: { name }, at }) does exactly that.`, Wl = (e, t) => {
3199
3225
  let n = /<(?:PayButton|BuyButton)\b/.test(e), r = /<PayButton\b/.test(e) && !/<(?:BuyButton|Basket|Checkout|Plans)\b/.test(e), i = [], a = /* @__PURE__ */ new Set();
3200
3226
  for (let o of e.matchAll(/<ContactPopup\b/g)) {
3201
3227
  let s = o.index ?? 0, c = e.indexOf("/>", s), l = e.slice(s, c === -1 ? s + 800 : c + 2), u = l.match(/formId=\{?\s*['"`]([^'"`]+)['"`]/)?.[1];
@@ -3222,18 +3248,18 @@ var Fl = (e, t, n = !1) => {
3222
3248
  }));
3223
3249
  }
3224
3250
  return i;
3225
- }, Vl = (e, t) => `${e}: this is the order ${t} sends back beside its payment, and an order asks the buyer NOTHING (fields: []) — who is paying is their ACCOUNT, and the app's own checkout asks it. Replace that last step — the form, the Pay or Buy button and any <BuyerDetails> — with ONE <Checkout formId="…" items={…} phone address /> from 'your-ai-workflow-firebase-os', keeping this formId (a basket of things is <Basket formId="…" /> instead): it asks who is paying, saves the order whole and only then starts the payment. Take every name, email, phone and address question out of the form's fields. Asked through the form, the buyer has no account: their order can never be shown back to them, and a page built on these entries — an Orders page each buyer sees their own on — has nobody to send it to (contract rule 9).`, Hl = /(?:\bto|\bhref|\borderPath)=\{?\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]|\bnavigate\(\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]/g, Ul = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, ""), Wl = /\baddDoc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_|\bsetDoc\s*\(\s*doc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_/, Gl = (e, t, n) => {
3251
+ }, Gl = (e, t) => `${e}: this is the order ${t} sends back beside its payment, and an order asks the buyer NOTHING (fields: []) — who is paying is their ACCOUNT, and the app's own checkout asks it. Replace that last step — the form, the Pay or Buy button and any <BuyerDetails> — with ONE <Checkout formId="…" items={…} phone address /> from 'your-ai-workflow-firebase-os', keeping this formId (a basket of things is <Basket formId="…" /> instead): it asks who is paying, saves the order whole and only then starts the payment. Take every name, email, phone and address question out of the form's fields. Asked through the form, the buyer has no account: their order can never be shown back to them, and a page built on these entries — an Orders page each buyer sees their own on — has nobody to send it to (contract rule 9).`, Kl = /(?:\bto|\bhref|\borderPath)=\{?\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]|\bnavigate\(\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]/g, ql = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, ""), Jl = /\baddDoc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_|\bsetDoc\s*\(\s*doc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_/, Yl = (e, t, n) => {
3226
3252
  let r = (e) => [...t.matchAll(RegExp(`<${e}\\b`, "g"))].map((e) => {
3227
3253
  let n = e.index ?? 0, r = t.indexOf("/>", n);
3228
3254
  return t.slice(n, r === -1 ? n + 800 : r + 2);
3229
3255
  }), i = [...r("Basket"), ...r("Checkout")].find((e) => /\bformId=/.test(e)), a = (i ?? r("ContactPopup").find((e) => /\bextra=/.test(e)))?.match(/formId=\{?\s*['"`]([^'"`]+)['"`]/)?.[1];
3230
3256
  if (!a) return [];
3231
3257
  let o = [], s = /* @__PURE__ */ new Set(), c = [];
3232
- for (let e of t.matchAll(Hl)) {
3233
- let t = Ul(e[1] || e[2] || "");
3258
+ for (let e of t.matchAll(Kl)) {
3259
+ let t = ql(e[1] || e[2] || "");
3234
3260
  if (!t || s.has(t)) continue;
3235
3261
  s.add(t);
3236
- let r = n.find((e) => J(e.file) && Ul(q(e.file).replace(/Content\.tsx$/, "")) === t);
3262
+ let r = n.find((e) => J(e.file) && ql(q(e.file).replace(/Content\.tsx$/, "")) === t);
3237
3263
  r && c.push(r);
3238
3264
  }
3239
3265
  let l = (e) => {
@@ -3248,12 +3274,12 @@ var Fl = (e, t, n = !1) => {
3248
3274
  }
3249
3275
  };
3250
3276
  }, u = (e) => {
3251
- let t = e.match($c)?.[1], n = RegExp(`['"\`]${a.replace(/[^A-Za-z0-9_-]/g, "")}['"\`]`).test(e);
3277
+ let t = e.match(rl)?.[1], n = RegExp(`['"\`]${a.replace(/[^A-Za-z0-9_-]/g, "")}['"\`]`).test(e);
3252
3278
  return t && n && !/\bappliedEntries\b/.test(e) ? t : void 0;
3253
3279
  };
3254
3280
  for (let e of c) {
3255
3281
  let { theirs: t, gap: n } = l(e);
3256
- !/\b(?:usePublished|publish)\s*\(/.test(t) && /\buseCases\s*\(/.test(t) && Wl.test(t) && o.push({
3282
+ !/\b(?:usePublished|publish)\s*\(/.test(t) && /\buseCases\s*\(/.test(t) && Jl.test(t) && o.push({
3257
3283
  ...n,
3258
3284
  why: "typed",
3259
3285
  inbox: ""
@@ -3275,9 +3301,9 @@ var Fl = (e, t, n = !1) => {
3275
3301
  });
3276
3302
  }
3277
3303
  return o;
3278
- }, Kl = (e) => {
3304
+ }, Xl = (e) => {
3279
3305
  let t = [], n = /* @__PURE__ */ new Set(), r = (e, r, i) => {
3280
- let a = `${Ul(e)}|${r}`;
3306
+ let a = `${ql(e)}|${r}`;
3281
3307
  !e || n.has(a) || (n.add(a), t.push({
3282
3308
  words: e.toLowerCase(),
3283
3309
  route: r,
@@ -3290,7 +3316,7 @@ var Fl = (e, t, n = !1) => {
3290
3316
  if (i !== "tab" && i !== "page") continue;
3291
3317
  let a = Y(n), o = Z(X(a, "route")[0] ?? "");
3292
3318
  if (!o || !o.startsWith("/")) continue;
3293
- let s = Z(X(a, i === "tab" ? "tabName" : "pageName")[0] ?? ""), c = e.find((e) => J(e.file) && Ul(q(e.file).replace(/Content\.tsx$/, "")) === Ul(o.slice(1)))?.file ?? "";
3319
+ let s = Z(X(a, i === "tab" ? "tabName" : "pageName")[0] ?? ""), c = e.find((e) => J(e.file) && ql(q(e.file).replace(/Content\.tsx$/, "")) === ql(o.slice(1)))?.file ?? "";
3294
3320
  s && r(s, o, c), r(o.slice(1).replace(/-/g, " "), o, c);
3295
3321
  }
3296
3322
  for (let { file: t } of e) {
@@ -3299,17 +3325,17 @@ var Fl = (e, t, n = !1) => {
3299
3325
  !e || e === "Dashboard" || r(e.replace(/([a-z0-9])([A-Z])/g, "$1 $2"), `/${e.toLowerCase()}`, t);
3300
3326
  }
3301
3327
  return t;
3302
- }, ql = (e) => e.replace(/<[^>]*>/g, " ").replace(/\{[^{}]*\}/g, " ").replace(/&\w+;/g, " ").replace(/\s+/g, " ").trim(), Jl = (e, t) => {
3328
+ }, Zl = (e) => e.replace(/<[^>]*>/g, " ").replace(/\{[^{}]*\}/g, " ").replace(/&\w+;/g, " ").replace(/\s+/g, " ").trim(), Ql = (e, t) => {
3303
3329
  let n = e.slice(0, t);
3304
3330
  return (n.match(/<Link\b/g) || []).length > (n.match(/<\/Link>/g) || []).length;
3305
- }, Yl = /\bhref\s*=\s*(?:\{\s*)?['"`]\s*#?\s*['"`]/, Xl = /\bonClick\s*=\s*\{\s*\(\s*\)\s*=>\s*\{\s*\}\s*\}/;
3306
- function Zl(e, t, n) {
3331
+ }, $l = /\bhref\s*=\s*(?:\{\s*)?['"`]\s*#?\s*['"`]/, eu = /\bonClick\s*=\s*\{\s*\(\s*\)\s*=>\s*\{\s*\}\s*\}/;
3332
+ function tu(e, t, n) {
3307
3333
  let r = [], i = /* @__PURE__ */ new Set();
3308
3334
  for (let { tag: a, attrs: o, at: s, end: c } of $(t, "button|Button|a")) {
3309
- if (/\bonClick\s*=/.test(o) && !Xl.test(o) || /\bdisabled\b|\{\.\.\./.test(o) || a !== "a" && /type\s*=\s*['"`]submit['"`]/.test(o) || a === "a" && /\bhref\s*=/.test(o) && !Yl.test(o) || Jl(t, s)) continue;
3335
+ if (/\bonClick\s*=/.test(o) && !eu.test(o) || /\bdisabled\b|\{\.\.\./.test(o) || a !== "a" && /type\s*=\s*['"`]submit['"`]/.test(o) || a === "a" && /\bhref\s*=/.test(o) && !$l.test(o) || Ql(t, s)) continue;
3310
3336
  let l = t.indexOf(`</${a}>`, c);
3311
3337
  if (l === -1 || /<Link\b|<a\b[^>]*\bhref\s*=|\bnavigate\s*\(/.test(t.slice(c + 1, l)) || a !== "a" && !/\btype\s*=/.test(o) && t.lastIndexOf("<form", s) > t.lastIndexOf("</form>", s)) continue;
3312
- let u = ql(t.slice(c + 1, l));
3338
+ let u = Zl(t.slice(c + 1, l));
3313
3339
  if (!u || u.length > 40) continue;
3314
3340
  let d = ` ${u.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `, f = n.find((t) => t.file !== e && t.words.length > 2 && d.includes(` ${t.words} `));
3315
3341
  !f || i.has(u) || (i.add(u), r.push({
@@ -3319,15 +3345,15 @@ function Zl(e, t, n) {
3319
3345
  }
3320
3346
  return r;
3321
3347
  }
3322
- var Ql = (e, t) => `${e}: ${G(t.map((e) => `"${e.label}"`))} ${t.length > 1 ? "name pages" : "names a page"} this app HAS — ${G([...new Set(t.map((e) => e.route))])} — and ${t.length > 1 ? "none of them leads anywhere" : "it leads nowhere"}: no link, no handler, nothing happens when it is pressed. This is what a page built BEFORE that one looks like — the button was written when there was nowhere to send it. Wrap exactly the words and classes that are there in <Link to="${t[0].route}" className="…"> (or navigate('${t[0].route}') where a press has to do something first), and change nothing else about it: not its words, not its look, not where it sits.${K(t.length)} (rule 7)`, $l = (e, t, n) => `${e}: ${t} sends what is made there as a form entry ('${n}') and links here — and the app itself lets each of those entries into this page's case_ collection, sent to the person who made it and to the team. So this page only READS them with useCases(): delete the form, button or function that writes a case_ entry by hand. An entry typed in here was paid for by nobody and belongs to nobody, and one sent to a whole role ('role:client') is shown to every person marked with it. While there are no entries yet, show a short empty line — never made-up ones (contract rule 11).`, eu = (e, t) => `${e.target}: it fetches the '${e.formId}' entries out of ${e.inbox} itself — what ${t} sends — and what is in there is not this page's to read. ${e.paid ? "<Checkout> saves each one BEFORE the money is taken, so that list holds every basket somebody walked away from, and this page draws each of them as an order. " : ""}An entry there carries uid and submittedAt — never userId, customerName or createdAt — so a list filtered on a guessed key is empty for the very person who ordered, and nobody but an admin may read the rest of it at all. The app already lets each ${e.paid ? "PAID " : ""}one into this page's own collection, sent to the person who made it and to the team: read them with const { loading, records, error } = useCases('${e.reads}') — the name this page's own sentence gave, if it gave another — and with nothing else. Delete the ${e.inbox} query, and any COPY of the entries kept in a collection of its own for somebody to work from: a stage, a note, who it was handed to are saved ON the entry (updateDoc on it), and handing one to a single person is sendTo('${e.reads}', id, { people: [uid] }) with the person picked from usePeople('internal') — a copy is a second order the person who made it never sees change. Never keep the list in localStorage either. Change nothing about how the page looks (contract rule 11).`, tu = (e, t) => `${e}: this form is given extra={…} — an order going back to the page that keeps the record (on ${t}) — it is a payment's, and its entries are half of a transaction, not mail. Add transaction: true to its config, and they land on the owner's Transactions screen beside the payment instead of in the Inbox.`, nu = (e, t) => `${e}: this form stands on a page with a <PayButton> (on ${t}) — the questions asked before paying. Who is paying is not a form, it is their ACCOUNT, and the app's own button asks it: replace the <PayButton> with ONE <BuyButton> from 'your-ai-workflow-firebase-os' carrying the same amount / productName (or priceId / mode), currency, metadata and successPath, plus phone / address when the form asked for them — its press says what is being bought, asks who is paying and goes on to Stripe — then delete the form, its <ContactPopup> and its config. A form here lands every buyer in the Inbox as mail and rings the owner's bell with "New form submission" instead of "New payment" (contract rule 9).`, ru = (e, t) => `${e}: it reads ${t} without ever looking at formId, so it takes in every form in the app — the contact form, a newsletter box, a form on somebody else's page — not just this page's own. Read the entry's formId and skip the ones that are not this page's form (const FORM_ID = 'quote'; if (s.formId !== FORM_ID) return;), unless the owner asked for the whole Inbox in so many words. A booking is in neither collection: a time somebody takes is on the calendar, and useBookings() is the only way to read it.`, iu = /loadStripe|@stripe\/stripe-js|new Stripe\(|fetch\(\s*['"`]\/api\/payments/, au = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled payment code that is not connected to the owner's Stripe account — delete it and take money with <PayButton amount={25} productName="…" /> for a one-off or <PayButton priceId="price_…" mode="subscription" /> for a subscription, imported from 'your-ai-workflow-firebase-os' (read purchases with useMyPayments(); an admins' page reads every sale with useSales()).`, ou = /api\.resend\.com|from\s+['"]resend['"]|fetch\(\s*['"`]\/api\/email/, su = /from\s+['"]firebase\/storage['"]|\buploadBytes(?:Resumable)?\s*\(|\bgetDownloadURL\s*\(|\bgetStorage\s*\(|\bref\s*\(\s*storage\b/, cu = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} upload code written by hand, and the file it writes is guarded by a different rule than the record that links to it — delete it and take the file with <UploadBox value={photo} onChange={setPhoto} label="Product photo" /> imported from 'your-ai-workflow-firebase-os' (add \`shared\` when the picture is also shown on a page this one's people are not the audience for; a file attached while filling in a form is the form's own \`file\` question instead).`, lu = /\b(?:window|globalThis|self)\s*\.\s*(?:confirm|alert|prompt)\b|(?<![\w$.]|function\s)(?:confirm|alert|prompt)\((?![^()]*\)\s*\{)/, uu = (e) => {
3323
- let t = z(e), n = /* @__PURE__ */ new Set();
3324
- for (let r of e.matchAll(new RegExp(lu.source, "g"))) {
3348
+ var nu = (e, t) => `${e}: ${G(t.map((e) => `"${e.label}"`))} ${t.length > 1 ? "name pages" : "names a page"} this app HAS — ${G([...new Set(t.map((e) => e.route))])} — and ${t.length > 1 ? "none of them leads anywhere" : "it leads nowhere"}: no link, no handler, nothing happens when it is pressed. This is what a page built BEFORE that one looks like — the button was written when there was nowhere to send it. Wrap exactly the words and classes that are there in <Link to="${t[0].route}" className="…"> (or navigate('${t[0].route}') where a press has to do something first), and change nothing else about it: not its words, not its look, not where it sits.${K(t.length)} (rule 7)`, ru = (e, t, n) => `${e}: ${t} sends what is made there as a form entry ('${n}') and links here — and the app itself lets each of those entries into this page's case_ collection, sent to the person who made it and to the team. So this page only READS them with useCases(): delete the form, button or function that writes a case_ entry by hand. An entry typed in here was paid for by nobody and belongs to nobody, and one sent to a whole role ('role:client') is shown to every person marked with it. While there are no entries yet, show a short empty line — never made-up ones (contract rule 11).`, iu = (e, t) => `${e.target}: it fetches the '${e.formId}' entries out of ${e.inbox} itself — what ${t} sends — and what is in there is not this page's to read. ${e.paid ? "<Checkout> saves each one BEFORE the money is taken, so that list holds every basket somebody walked away from, and this page draws each of them as an order. " : ""}An entry there carries uid and submittedAt — never userId, customerName or createdAt — so a list filtered on a guessed key is empty for the very person who ordered, and nobody but an admin may read the rest of it at all. The app already lets each ${e.paid ? "PAID " : ""}one into this page's own collection, sent to the person who made it and to the team: read them with const { loading, records, error } = useCases('${e.reads}') — the name this page's own sentence gave, if it gave another — and with nothing else. Delete the ${e.inbox} query, and any COPY of the entries kept in a collection of its own for somebody to work from: a stage, a note, who it was handed to are saved ON the entry (updateDoc on it), and handing one to a single person is sendTo('${e.reads}', id, { people: [uid] }) with the person picked from usePeople('internal') — a copy is a second order the person who made it never sees change. Never keep the list in localStorage either. Change nothing about how the page looks (contract rule 11).`, au = (e, t) => `${e}: this form is given extra={…} — an order going back to the page that keeps the record (on ${t}) — it is a payment's, and its entries are half of a transaction, not mail. Add transaction: true to its config, and they land on the owner's Transactions screen beside the payment instead of in the Inbox.`, ou = (e, t) => `${e}: this form stands on a page with a <PayButton> (on ${t}) — the questions asked before paying. Who is paying is not a form, it is their ACCOUNT, and the app's own button asks it: replace the <PayButton> with ONE <BuyButton> from 'your-ai-workflow-firebase-os' carrying the same amount / productName (or priceId / mode), currency, metadata and successPath, plus phone / address when the form asked for them — its press says what is being bought, asks who is paying and goes on to Stripe — then delete the form, its <ContactPopup> and its config. A form here lands every buyer in the Inbox as mail and rings the owner's bell with "New form submission" instead of "New payment" (contract rule 9).`, su = (e, t) => `${e}: it reads ${t} without ever looking at formId, so it takes in every form in the app — the contact form, a newsletter box, a form on somebody else's page — not just this page's own. Read the entry's formId and skip the ones that are not this page's form (const FORM_ID = 'quote'; if (s.formId !== FORM_ID) return;), unless the owner asked for the whole Inbox in so many words. A booking is in neither collection: a time somebody takes is on the calendar, and useBookings() is the only way to read it.`, cu = /loadStripe|@stripe\/stripe-js|new Stripe\(|fetch\(\s*['"`]\/api\/payments/, lu = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled payment code that is not connected to the owner's Stripe account — delete it and take money with <PayButton amount={25} productName="…" /> for a one-off or <PayButton priceId="price_…" mode="subscription" /> for a subscription, imported from 'your-ai-workflow-firebase-os' (read purchases with useMyPayments(); an admins' page reads every sale with useSales()).`, uu = /api\.resend\.com|from\s+['"]resend['"]|fetch\(\s*['"`]\/api\/email/, du = /from\s+['"]firebase\/storage['"]|\buploadBytes(?:Resumable)?\s*\(|\bgetDownloadURL\s*\(|\bgetStorage\s*\(|\bref\s*\(\s*storage\b/, fu = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} upload code written by hand, and the file it writes is guarded by a different rule than the record that links to it — delete it and take the file with <UploadBox value={photo} onChange={setPhoto} label="Product photo" /> imported from 'your-ai-workflow-firebase-os' (add \`shared\` when the picture is also shown on a page this one's people are not the audience for; a file attached while filling in a form is the form's own \`file\` question instead).`, pu = /\b(?:window|globalThis|self)\s*\.\s*(?:confirm|alert|prompt)\b|(?<![\w$.]|function\s)(?:confirm|alert|prompt)\((?![^()]*\)\s*\{)/, mu = (e) => {
3349
+ let t = R(e), n = /* @__PURE__ */ new Set();
3350
+ for (let r of e.matchAll(new RegExp(pu.source, "g"))) {
3325
3351
  let i = r[0].replace(/\s+/g, " ").trim(), a = /^(confirm|alert|prompt)\($/.exec(i);
3326
- a && (new RegExp(String.raw`\b(?:const|let|var|function)\s+${a[1]}\b|\{[^{}]*\b${a[1]}\b[^{}]*\}\s*=`).test(e) || pu(e, t, a[1]).some(([e, t]) => (r.index ?? 0) >= e && (r.index ?? 0) < t)) || n.add(i);
3352
+ a && (new RegExp(String.raw`\b(?:const|let|var|function)\s+${a[1]}\b|\{[^{}]*\b${a[1]}\b[^{}]*\}\s*=`).test(e) || _u(e, t, a[1]).some(([e, t]) => (r.index ?? 0) >= e && (r.index ?? 0) < t)) || n.add(i);
3327
3353
  }
3328
3354
  return [...n];
3329
- }, du = /\bfunction\s*\*?\s*(?:[A-Za-z_$][\w$]*)?\s*(?:<[^()]*?>)?\s*\(((?:[^()]|\([^()]*\))*)\)|\(((?:[^()]|\([^()]*\))*)\)\s*(?::[^=;{}()]*)?=>|(?<![\w$.])([A-Za-z_$][\w$]*)\s*=>/g;
3330
- function fu(e, t) {
3355
+ }, hu = /\bfunction\s*\*?\s*(?:[A-Za-z_$][\w$]*)?\s*(?:<[^()]*?>)?\s*\(((?:[^()]|\([^()]*\))*)\)|\(((?:[^()]|\([^()]*\))*)\)\s*(?::[^=;{}()]*)?=>|(?<![\w$.])([A-Za-z_$][\w$]*)\s*=>/g;
3356
+ function gu(e, t) {
3331
3357
  if (e[3] !== void 0) return e[3] === t;
3332
3358
  let n = e[1] ?? e[2] ?? "", r = 0;
3333
3359
  for (let e of n.matchAll(new RegExp(String.raw`[{}]|(?<![\w$.])${t}(?![\w$])`, "g"))) {
@@ -3344,10 +3370,10 @@ function fu(e, t) {
3344
3370
  }
3345
3371
  return !1;
3346
3372
  }
3347
- function pu(e, t, n) {
3373
+ function _u(e, t, n) {
3348
3374
  let r = [[0, e.length]], i = [];
3349
- for (let a of e.matchAll(du)) {
3350
- if (!fu(a, n)) continue;
3375
+ for (let a of e.matchAll(hu)) {
3376
+ if (!gu(a, n)) continue;
3351
3377
  let e = a.index ?? 0, o = e + a[0].length;
3352
3378
  for (; /\s/.test(t[o] || "");) o++;
3353
3379
  if (!a[0].endsWith("=>") && t[o] !== "{") return r;
@@ -3380,7 +3406,7 @@ function pu(e, t, n) {
3380
3406
  }
3381
3407
  return i;
3382
3408
  }
3383
- var mu = (e, t) => `${e}: ${G(t.map((e) => e.replace(/\($/, "()")))} ${t.length > 1 ? "open" : "opens"} the browser's own pop-up, drawn by the browser and not the app, over the owner's themed page — delete ${t.length > 1 ? "every one" : "it"}. A delete asks first in <ConfirmModal isOpen={!!toDelete} title="Delete this?" message="It cannot be undone." confirmText="Delete" onConfirm={removeIt} onCancel={() => setToDelete(null)} /> imported from 'your-ai-workflow-firebase-os' (on a from-scratch page actions.confirmDelete(id) opens it for you, and a drive's deleteFile(file) already asks); anything to tell somebody is words you styled on the page.`, hu = /<input[^>]*type\s*=\s*\{?['"]file['"]\}?/i, gu = (e) => `${e}: an <input type="file"> uploads nothing on its own — draw the app's own box instead, <UploadBox value={photo} onChange={setPhoto} label="Product photo" /> imported from 'your-ai-workflow-firebase-os', which uploads as the file is picked or dropped and hands back the link to save on the record (on a form it is the form's own \`file\` question, and on a drive page it is openUploadModal()).`, _u = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled email code — delete it. A form sent on a page is emailed by the app itself, as the owner switched it on the page's row; to tell somebody something by email it is notify({ …, email: true }), and the app finds the address; a payment's receipt and sale alert are the app's own; and only on a page the owner's team opens, emailAnyone(to, subject, message) writes to an address — all imported from 'your-ai-workflow-firebase-os'. A page never writes an email body or names a recipient for anybody else (contract rule 10).`, vu = [
3409
+ var vu = (e, t) => `${e}: ${G(t.map((e) => e.replace(/\($/, "()")))} ${t.length > 1 ? "open" : "opens"} the browser's own pop-up, drawn by the browser and not the app, over the owner's themed page — delete ${t.length > 1 ? "every one" : "it"}. A delete asks first in <ConfirmModal isOpen={!!toDelete} title="Delete this?" message="It cannot be undone." confirmText="Delete" onConfirm={removeIt} onCancel={() => setToDelete(null)} /> imported from 'your-ai-workflow-firebase-os' (on a from-scratch page actions.confirmDelete(id) opens it for you, and a drive's deleteFile(file) already asks); anything to tell somebody is words you styled on the page.`, yu = /<input[^>]*type\s*=\s*\{?['"]file['"]\}?/i, bu = (e) => `${e}: an <input type="file"> uploads nothing on its own — draw the app's own box instead, <UploadBox value={photo} onChange={setPhoto} label="Product photo" /> imported from 'your-ai-workflow-firebase-os', which uploads as the file is picked or dropped and hands back the link to save on the record (on a form it is the form's own \`file\` question, and on a drive page it is openUploadModal()).`, xu = (e, t) => `${e}: ${G(t)} ${t.length > 1 ? "are" : "is"} hand-rolled email code — delete it. A form sent on a page is emailed by the app itself, as the owner switched it on the page's row; to tell somebody something by email it is notify({ …, email: true }), and the app finds the address; a payment's receipt and sale alert are the app's own; and only on a page the owner's team opens, emailAnyone(to, subject, message) writes to an address — all imported from 'your-ai-workflow-firebase-os'. A page never writes an email body or names a recipient for anybody else (contract rule 10).`, Su = [
3384
3410
  "--bg-color",
3385
3411
  "--bg-secondary-color",
3386
3412
  "--fg-color",
@@ -3398,10 +3424,10 @@ var mu = (e, t) => `${e}: ${G(t.map((e) => e.replace(/\($/, "()")))} ${t.length
3398
3424
  "--notification-icon-color",
3399
3425
  "--selection-bg",
3400
3426
  "--selection-text"
3401
- ], yu = (e, t) => {
3427
+ ], Cu = (e, t) => {
3402
3428
  let n = [], r = t.replace(/\/\*[\s\S]*?\*\//g, "");
3403
3429
  for (let [, t, i] of r.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
3404
- let r = t.trim(), a = vu.filter((e) => RegExp(`${e}\\s*:[^;}]*!important`).test(i));
3430
+ let r = t.trim(), a = Su.filter((e) => RegExp(`${e}\\s*:[^;}]*!important`).test(i));
3405
3431
  if (a.length) {
3406
3432
  n.push(`${e}: \`${a[0]}\` is set with \`!important\` here, and the app writes that colour itself from the owner's theme — forcing it freezes it, so their Theme screen stops working. Delete the \`!important\` and set the colour in configs/theme.config.ts instead, in background, backgroundSecondary, foreground, accent or buttonText; every other colour is derived from those five.`);
3407
3433
  continue;
@@ -3421,8 +3447,8 @@ var mu = (e, t) => `${e}: ${G(t.map((e) => e.replace(/\($/, "()")))} ${t.length
3421
3447
  }
3422
3448
  return n;
3423
3449
  };
3424
- function bu(e, t, n = [], r = {}) {
3425
- return Do(e.map((e) => ({
3450
+ function wu(e, t, n = [], r = {}) {
3451
+ return jo(e.map((e) => ({
3426
3452
  file: e.file,
3427
3453
  source: t(e.abs),
3428
3454
  helper: e.kind === "helper"
@@ -3430,8 +3456,8 @@ function bu(e, t, n = [], r = {}) {
3430
3456
  }
3431
3457
  //#endregion
3432
3458
  //#region src/vite/appIcons.ts
3433
- var xu = (e) => e.split("-").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(""), Su = /* @__PURE__ */ new Map();
3434
- function Cu(e) {
3459
+ var Tu = (e) => e.split("-").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(""), Eu = /* @__PURE__ */ new Map();
3460
+ function Du(e) {
3435
3461
  let t = r.resolve(e);
3436
3462
  for (;;) {
3437
3463
  let e = r.join(t, "node_modules", "lucide-react");
@@ -3441,22 +3467,22 @@ function Cu(e) {
3441
3467
  t = i;
3442
3468
  }
3443
3469
  }
3444
- function wu(e) {
3470
+ function Ou(e) {
3445
3471
  try {
3446
- let t = Cu(e);
3472
+ let t = Du(e);
3447
3473
  if (!t) return null;
3448
- let i = String(JSON.parse(n.readFileSync(r.join(t, "package.json"), "utf8")).version || ""), a = `${r.resolve(e)}\n${t}`, o = Su.get(a);
3474
+ let i = String(JSON.parse(n.readFileSync(r.join(t, "package.json"), "utf8")).version || ""), a = `${r.resolve(e)}\n${t}`, o = Eu.get(a);
3449
3475
  if (o && o.version === i) return o.names;
3450
3476
  let s = r.join(t, "dist", "esm", "icons"), c = null;
3451
3477
  if (n.existsSync(s)) {
3452
3478
  c = /* @__PURE__ */ new Set();
3453
3479
  for (let e of n.readdirSync(s)) {
3454
3480
  let t = /^([a-z0-9]+(?:-[a-z0-9]+)*)\.m?js$/.exec(e);
3455
- t && c.add(xu(t[1]));
3481
+ t && c.add(Tu(t[1]));
3456
3482
  }
3457
3483
  c.size || (c = null);
3458
3484
  }
3459
- return Su.set(a, {
3485
+ return Eu.set(a, {
3460
3486
  version: i,
3461
3487
  names: c
3462
3488
  }), c;
@@ -3465,4 +3491,4 @@ function wu(e) {
3465
3491
  }
3466
3492
  }
3467
3493
  //#endregion
3468
- export { p as C, L as D, _e as E, g as S, f as T, m as _, bu as a, x as b, i as c, o as d, u as f, me as g, _ as h, Do as i, c as l, ee as m, Y as n, pn as o, v as p, yu as r, a as s, wu as t, l as u, A as v, S as w, he as x, b as y };
3494
+ export { p as C, _e as D, ve as E, g as S, f as T, m as _, wu as a, x as b, i as c, o as d, u as f, me as g, _ as h, jo as i, c as l, ee as m, Y as n, pn as o, v as p, Cu as r, a as s, Ou as t, l as u, k as v, S as w, he as x, b as y };