your-ai-workflow-firebase-os 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,10 +57,17 @@ export declare function useViewingAs({ on, everyone }: {
57
57
  everyone?: boolean;
58
58
  }): ViewingAs;
59
59
  /**
60
- * The switch with its word beside it, for a toolbar that wants one line.
60
+ * The switch with its word BESIDE it, for a toolbar that wants one line.
61
+ *
62
+ * The word is drawn here and not by the select, which stacks its label above
63
+ * the trigger the way a form field does: in a toolbar that made the bar two
64
+ * rows tall, so "Viewing" floated over the row while the eye, the switch and
65
+ * every button beside it centred against the taller box. One line, one
66
+ * baseline — the eye, the word, the switch.
61
67
  *
62
68
  * The eye is the same glyph the console uses for looking at something without
63
- * changing it, and the word goes on a phone the way every other button's does.
69
+ * changing it, and it stays on a phone while the word goes, the way every
70
+ * other row up here is icons on a phone and words on a screen.
64
71
  */
65
72
  export declare function ViewingAsBar({ viewing }: {
66
73
  viewing: ViewingAs;
@@ -0,0 +1,15 @@
1
+ export interface AttachmentsProps {
2
+ /** The answer as it sits on the entry — one link, or several. */
3
+ value: unknown;
4
+ /** The file to show large. The screen keeps the viewer. */
5
+ onOpen: (file: {
6
+ src: string;
7
+ name: string;
8
+ }) => void;
9
+ /** The tile's size, where a screen wants them smaller than the Inbox's. */
10
+ className?: string;
11
+ }
12
+ export declare function Attachments({ value, onOpen, className }: AttachmentsProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare namespace Attachments {
14
+ var displayName: string;
15
+ }
@@ -102,7 +102,7 @@ export declare const PIECES_LINE = "And the hard parts are the app\u2019s own pi
102
102
  * that keeps many things — and names no piece: rule 6 and rule 31 do, and
103
103
  * `whole-records` is the build's half.
104
104
  */
105
- export declare const WORKING_VIEW_LINE = "If this page keeps many things \u2014 orders, requests, clients \u2014 show them the way a professional tool does, so a dozen can be seen at once without my asking for it: each one a compact card or a single row saying only what tells it apart \u2014 its number or name, who it is for, its stage as a small label, when, how much, who has it \u2014 and pressing one opens the whole of it in the app\u2019s own pop-up, where everything done to it is: its stage, its notes, handing it over. Where things move through stages, the people who work on them see a board of those stages; somebody who only follows their own sees theirs as a short list. Never every one drawn open, one big box under another. Inside the opened one, keep it tidy and even: a clear grid, each label above its value, the same spacing throughout, and boxes and buttons that sit side by side the same height. Do not add a gallery to a page I did not ask it for.";
105
+ export declare const WORKING_VIEW_LINE = "If this page keeps many things \u2014 orders, requests, clients \u2014 show them the way a professional tool does, so a dozen can be seen at once without my asking for it: each one a compact card or a single row saying only what tells it apart \u2014 its number or name, who it is for, its stage as a small label, when, how much, who has it \u2014 and pressing one opens the whole of it in the app\u2019s own pop-up, where everything done to it is: its stage, its notes, handing it over. Where things move through stages, the people who work on them see a board of those stages; somebody who only follows their own sees theirs as a short list. Never every one drawn open, one big box under another. Inside the opened one, keep it tidy and even: the pop-up is its one frame, so its parts are set apart by space or a thin rule and never each in a bordered box of its own, nothing boxed inside a box; a clear grid, each label in small print above its value at the page\u2019s ordinary size, one spacing throughout, boxes and buttons that sit side by side the same height, and what can be done to it \u2014 its stage, who holds it, a note \u2014 together in one place at the end rather than scattered through it; nothing on it pulses, and every word on it is mine or the entry\u2019s, never a made-up place, carrier or name standing in for one that is missing. Do not add a gallery to a page I did not ask it for.";
106
106
  export declare const finishLine: (website: boolean) => string;
107
107
  /**
108
108
  * A change sentence's first rule: what the owner's words are about is the
@@ -164,13 +164,34 @@ export declare function openBillingPortal(): Promise<string | null>;
164
164
  * that URL. Only `paid: true` from here means money moved.
165
165
  */
166
166
  export declare function confirmPayment(sessionId: string): Promise<PaymentConfirmation>;
167
- /** Forget the cached /payments/status answer — the Payments switch, and tests. */
167
+ export interface PaymentsStatus {
168
+ configured: boolean;
169
+ mode: 'live' | 'test' | null;
170
+ }
171
+ /** How often a watching screen asks again while the key is still not in. */
172
+ export declare const STATUS_WATCH_MS = 5000;
173
+ /**
174
+ * Forget the cached /payments/status answer and ask again — the Payments
175
+ * switch, and tests. Anything on screen holding the old answer is told.
176
+ */
168
177
  export declare function resetPaymentsStatus(): void;
178
+ /**
179
+ * An answer the status route gave SOMEWHERE ELSE — the polled chips on the
180
+ * Setup, Payments and Go live screens (lib/serviceStatus.ts). The screen that
181
+ * tells the owner their key is in is the screen that tells the app, so walking
182
+ * from there to the Pages screen finds the Payments section already there.
183
+ */
184
+ export declare function notePaymentsStatus(data: any): void;
169
185
  /**
170
186
  * Whether the owner has connected Stripe. A page uses this to avoid drawing a
171
187
  * Pay button that cannot work on an app whose owner is still mid-setup.
188
+ *
189
+ * `watch` is for the screens an owner sets payments up FROM — they ask again
190
+ * every few seconds until the key is in, so a section appears where the owner
191
+ * is standing rather than after a reload. It stops itself the moment the
192
+ * answer turns true, so an app that is set up polls nothing.
172
193
  */
173
- export declare function usePaymentsEnabled(): {
194
+ export declare function usePaymentsEnabled(watch?: boolean): {
174
195
  loading: boolean;
175
196
  configured: boolean;
176
197
  mode: 'live' | 'test' | null;
@@ -6,6 +6,15 @@ export interface Person {
6
6
  name: string;
7
7
  /** Their picture, or '' — most installs keep none on the profile. */
8
8
  photo: string;
9
+ /**
10
+ * Their address, or '' — beside the name in a picker, so two people who
11
+ * share a name are told apart and a name that IS an address is not the
12
+ * only thing known about them. Only the team and admins ever list people,
13
+ * so it reaches nobody a profile does not already reach.
14
+ */
15
+ email: string;
16
+ /** The number they signed in with, or ''. */
17
+ phone: string;
9
18
  /** The word they are marked with, which is the word that was asked for. */
10
19
  role: string;
11
20
  }
@@ -28,3 +28,27 @@ export declare function pageRecordsCollection(config: any, prefix: string, id?:
28
28
  * no preset draws no record list of its own).
29
29
  */
30
30
  export declare function recordsKeyInUse(config: any, id: string, isPublic: boolean): string;
31
+ /**
32
+ * The word a page of SENT entries names its collection after: its tab name
33
+ * as the sentence has always folded it — lower case, one `_` between words,
34
+ * none at either end — behind the key the page keeps once it has been
35
+ * renamed or moved, so a rename never sends the road to an empty list.
36
+ */
37
+ export declare const casePageName: (config: any) => string;
38
+ /**
39
+ * The ONE collection a page whose entries are sent keeps them in —
40
+ * `case_orders_entries` for a page called Orders.
41
+ *
42
+ * Three things have to agree on it to the letter, and until 2026-09-24 only
43
+ * two did. The page's sentence hands the assistant this name
44
+ * (`useCases('case_orders_entries')`), and the arrival road writes a shop's
45
+ * orders into it (lib/arrivalRoads.ts). But the page templates handed the
46
+ * content file its `actions` — `records`, `saveRecord`, `updateRecord`,
47
+ * `deleteRecord`, `collection` — bound to `case_<name>_records`, the shape
48
+ * every other page has, and nothing else ever read or wrote that name. So
49
+ * `actions.updateRecord(order.id, …)` on an order that had arrived answered
50
+ * "No document to update: …/case_orders_records/<id>": the order sat one
51
+ * collection over under the same id, and the first anybody heard of it was
52
+ * the owner pressing Assign. Named here, once, and read by all three.
53
+ */
54
+ export declare const caseEntriesCollection: (config: any, prefix?: string) => string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Was that click a PRESS on the row, or the tail of a text selection?
3
+ *
4
+ * The admin lists — Pages, Forms — are cards that open when you click them
5
+ * and shut when you click them again, and the handler sits on the whole
6
+ * card, so the open settings are inside the very element that folds them.
7
+ * Everything drawn in there stops the click from reaching it, which works
8
+ * for an ordinary click and not at all for a SELECTION: dragging across a
9
+ * hint, a sentence or a label starts on one element and ends on another, so
10
+ * the browser fires the click on the nearest thing that holds them both —
11
+ * the card — above every guard inside it. The owner's words, 2026-09-24:
12
+ * "if you select text inside a page settings anywhere it folds page
13
+ * settings which is super annoying".
14
+ *
15
+ * So the card asks this instead. A press is a press: down and up in the same
16
+ * spot, with no text left selected. A drag is not, wherever it landed, and
17
+ * neither is a click that ends with words highlighted — a double-click on a
18
+ * word, a triple-click on a line, a drag across a paragraph.
19
+ *
20
+ * Clicking to CLOSE a row still works after a selection: the mouse going
21
+ * down clears it, so by the time the click lands there is nothing selected.
22
+ */
23
+ /** How far the mouse may travel between down and up and still be a press. */
24
+ export declare const PRESS_SLOP = 4;
25
+ export interface PressPoint {
26
+ x: number;
27
+ y: number;
28
+ }
29
+ /**
30
+ * `from` is where the mouse went down on the card, or null when the click
31
+ * came from somewhere that never pressed it — a keyboard, or a test.
32
+ */
33
+ export declare const isRowPress: (at: {
34
+ clientX: number;
35
+ clientY: number;
36
+ }, from: PressPoint | null) => boolean;
@@ -16,6 +16,13 @@ export interface StepsState<T extends string = string> {
16
16
  goTo: (id: T) => void;
17
17
  /** Back to the first step with the question taken off the address — for when the thing is sent. */
18
18
  reset: () => void;
19
+ /**
20
+ * A file somewhere in the app is still going up, so the step is held: every
21
+ * way off it does nothing but say so. For a page that wants its own button
22
+ * to show it — `<Button disabled={flow.waiting}>` — rather than let somebody
23
+ * press a button that will not move.
24
+ */
25
+ waiting: boolean;
19
26
  /** Everything `<StepBar>` needs: `<StepBar {...flow.bar} />`. */
20
27
  bar: {
21
28
  steps: StepInfo<T>[];
@@ -118,6 +118,12 @@ export declare function splitCollected(collected: [string, unknown][]): {
118
118
  };
119
119
  /** A metadata value is always a string; one that holds JSON is read as what it holds. */
120
120
  export declare function readValue(value: unknown): unknown;
121
+ /**
122
+ * A key that holds a PICTURE rather than something to read. Anywhere an
123
+ * entry's own keys are printed — a receipt line, a sent list in the Inbox —
124
+ * this one is either drawn or left out, never said as an address.
125
+ */
126
+ export declare const isPictureKey: (key: string) => boolean;
121
127
  /**
122
128
  * One thing bought, as a line: "2 × Blazer — Noir Black, M · £240". The
123
129
  * page named the keys itself, so each part is found by the names pages
@@ -125,10 +131,14 @@ export declare function readValue(value: unknown): unknown;
125
131
  * follows as "label: value".
126
132
  */
127
133
  export declare function itemLine(item: Record<string, unknown>, currency?: string | null): string;
128
- /** The same thing with its price apart — the words for the left of a receipt line, the sum for the right. */
134
+ /**
135
+ * The same thing with its price apart — the words for the left of a receipt
136
+ * line, the sum for the right, and the picture for the line to draw.
137
+ */
129
138
  export declare function itemParts(item: Record<string, unknown>, currency?: string | null): {
130
139
  words: string;
131
140
  price: string;
141
+ picture: string;
132
142
  };
133
143
  /**
134
144
  * A collected value as the lines it reads as. A list of things bought is
@@ -142,6 +152,8 @@ export interface ReceiptLine {
142
152
  right: string;
143
153
  /** A thing bought; a number that made up the amount or a choice (delivery, a discount, the plan, the seats); the total. */
144
154
  kind: 'item' | 'extra' | 'total';
155
+ /** The thing's own picture, where the page put one on the line — drawn beside its words, never printed as an address. */
156
+ picture?: string;
145
157
  }
146
158
  /**
147
159
  * What was bought, as the lines of a receipt: each thing on a line of its
@@ -102,6 +102,12 @@ export declare class UploadQueue<T = unknown> {
102
102
  private pump;
103
103
  private run;
104
104
  }
105
+ /** How many files are going up that walking away from would stop. */
106
+ export declare const uploadsStoppedByLeaving: () => number;
107
+ /** Hear when that number changes. Hands back the way to stop hearing. */
108
+ export declare function watchUploads(heard: () => void): () => void;
109
+ /** The same number, for a component — it redraws as each file lands. */
110
+ export declare const useUploadsStoppedByLeaving: () => number;
105
111
  /** The list in numbers — what a button or a status line says. */
106
112
  export declare function uploadSummary(items: UploadItem[]): {
107
113
  total: number;
@@ -72,13 +72,6 @@ export declare const audienceRoleOptions: (roles?: CustomRole[]) => {
72
72
  value: string;
73
73
  label: string;
74
74
  }[];
75
- /**
76
- * The collection a page of sent entries keeps them in — the name its sentence
77
- * hands the assistant (`useCases('case_orders_entries')`), and so the name the
78
- * app lets a linked page's entries into (arrivalRoads). ONE place, because the
79
- * two have to agree to the letter.
80
- */
81
- export declare const caseEntriesCollection: (config: any, prefix?: string) => string;
82
75
  export declare const audienceLines: (config: any) => string[];
83
76
  /** Which groups a piece of the owner's words names, each counted once. */
84
77
  export declare const groupsNamed: (text: string, roles?: CustomRole[]) => string[];
@@ -584,6 +577,7 @@ export declare const pageAddress: (page: {
584
577
  /** What the owner still has to fill in before the sentence is worth pasting. */
585
578
  export declare const promptBlocker: (page: {
586
579
  path: string;
580
+ id?: string;
587
581
  }, config: any, linkTargets?: WiringPageRow[]) => string;
588
582
  /**
589
583
  * One thing the owner has handed over for the page — a picture, a video, a
@@ -1496,6 +1490,54 @@ export declare const arrivalsTo: (config: any) => ArrivalsTo | "";
1496
1490
  * that is not the receiving end of a road.
1497
1491
  */
1498
1492
  export declare const arrivalSources: (config: any, targets: WiringPageRow[]) => WiringPageRow[];
1493
+ /**
1494
+ * The pages anybody can open that are linked to THIS page and make entries,
1495
+ * when this page is one person's own records (`user_`) — where nothing can
1496
+ * ever land.
1497
+ *
1498
+ * The app carries what a visitor makes onto a page whose entries are SENT
1499
+ * (`case_`, arrivalRoads) and onto no other kind: a `user_` record is read by
1500
+ * the one uid on it, so an order could carry the buyer OR the person it is
1501
+ * handed to, never both, and a guest's order nobody at all. There is no road
1502
+ * into `user_`, and there cannot be one.
1503
+ *
1504
+ * The owner's Orders rehearsal of 2026-09-24 was this. Members had to see
1505
+ * their own orders, so she answered "Only the person who added it" — the
1506
+ * reading of an order that anybody would make — and the sentence told the
1507
+ * assistant the shop and the page had "nothing to share". Every assistant
1508
+ * given it built the same page: actions.records on a collection nothing ever
1509
+ * writes, empty for the buyer, the team and the admins, and not a fault
1510
+ * anywhere, because nothing was refused. Regenerated with the context
1511
+ * cleared it came back the same, which is how she knew it was not Gemini.
1512
+ *
1513
+ * So the row says it where she is standing, and the sentence waits: a page
1514
+ * that can never work is not one to hand over. Looked for from both ends,
1515
+ * like arrivalSources, because a link is kept on one of its two rows.
1516
+ */
1517
+ export declare const deadRoadSources: (config: any, targets: WiringPageRow[]) => WiringPageRow[];
1518
+ /**
1519
+ * The same road seen from the page that MAKES the entries: the pages of one
1520
+ * person's own records this page links to, when this page is one anybody can
1521
+ * open and makes something. Empty for a page that makes nothing.
1522
+ */
1523
+ export declare const deadRoadEnds: (config: any, targets: WiringPageRow[]) => WiringPageRow[];
1524
+ /**
1525
+ * The line under "Who sees what somebody adds here?" on a page nothing can
1526
+ * land on — in place of the answer's own hint, because the answer is wrong
1527
+ * for this page and saying what it does would only confirm it.
1528
+ */
1529
+ export declare const deadRoadHint: (config: any, targets: WiringPageRow[]) => string;
1530
+ /**
1531
+ * The same, under the link on the row that MAKES the entries — the other
1532
+ * page's setting is not on this row, so the line says where it is.
1533
+ */
1534
+ export declare const deadRoadEndWords: (target: WiringPageRow) => string;
1535
+ /**
1536
+ * What holds a sentence back on either end of a road with nowhere to end.
1537
+ * A page that can never show what it is for is not one to hand over: the
1538
+ * assistant would build it faithfully, empty, and nothing would say why.
1539
+ */
1540
+ export declare const roadBlocker: (config: any, targets: WiringPageRow[]) => string;
1499
1541
  /**
1500
1542
  * What "only the person it is handed to" asks of the page that draws the
1501
1543
  * entries. The app keeps a new one from the team by itself; without a way to
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunk-CiTBaPhY.cjs`);const e=require(`./ContactPopup-MalER7E7.cjs`);exports.ContactPopup=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./chunk-CiTBaPhY.cjs`);const e=require(`./ContactPopup-IwBm-eqz.cjs`);exports.ContactPopup=e.t;
@@ -1,2 +1,2 @@
1
- import { t as e } from "./ContactPopup-Du_AVUgk.js";
1
+ import { t as e } from "./ContactPopup-Dsg2ntq3.js";
2
2
  export { e as ContactPopup };
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunk-CiTBaPhY.cjs`),t=require(`./verifiedAccount-D3OeaOBo.cjs`),n=require(`./aiModels-BIhoTNH0.cjs`),r=require(`./validate-L8QWFfBP.cjs`);let i=require(`node:fs`);i=e.a(i,1);let a=require(`express`);a=e.a(a,1);let o=require(`node:crypto`),s=require(`stripe`);s=e.a(s,1);var c=`https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com`,l=60;function u(e,t,n,r=Math.floor(Date.now()/1e3)){return n?e.alg===`RS256`?e.kid?t.aud===n?t.iss===`https://securetoken.google.com/${n}`?t.sub?t.sub.length>128?`Token subject is not a valid uid.`:typeof t.exp!=`number`||t.exp<=r?`Token has expired.`:typeof t.iat!=`number`||t.iat>r+l?`Token was issued in the future.`:null:`Token has no subject.`:`Token has the wrong issuer.`:`Token was issued for a different Firebase project.`:`Token has no key id.`:`Token is not signed with RS256.`:`The server does not know its Firebase project id.`}function d(e){return Buffer.from(e.replace(/-/g,`+`).replace(/_/g,`/`),`base64`)}function f(e){let t=String(e||``).split(`.`);if(t.length!==3)return null;try{return{header:JSON.parse(d(t[0]).toString(`utf8`)),claims:JSON.parse(d(t[1]).toString(`utf8`)),signed:`${t[0]}.${t[1]}`,signature:d(t[2])}}catch{return null}}var p=null;async function m(){if(p&&p.expiresAt>Date.now())return p.keys;let e=await fetch(c);if(!e.ok)throw Error(`Could not fetch Google signing keys (${e.status}).`);let t=await e.json(),n=/max-age=(\d+)/.exec(e.headers.get(`cache-control`)||``);return p={keys:t,expiresAt:Date.now()+(n?Number(n[1]):3600)*1e3},t}async function h(e,t){let n=f(e);if(!n)throw Error(`That is not a valid sign-in token.`);let r=u(n.header,n.claims,t);if(r)throw Error(r);let i=(await m())[n.header.kid];if(!i)throw Error(`Token was signed with a key Google no longer publishes.`);let a=(0,o.createVerify)(`RSA-SHA256`);if(a.update(n.signed),a.end(),!a.verify(i,n.signature))throw Error(`Token signature does not check out.`);return{uid:n.claims.sub,email:n.claims.email||null,emailVerified:!!n.claims.email_verified,signInProvider:n.claims.firebase?.sign_in_provider||null}}function g(){return(process.env.VITE_FIREBASE_PROJECT_ID||process.env.FIREBASE_PROJECT_ID||``).trim()}var _=`https://firestore.googleapis.com/v1`;function v(e){return e==null?null:`stringValue`in e?e.stringValue:`integerValue`in e?Number(e.integerValue):`doubleValue`in e?e.doubleValue:`booleanValue`in e?e.booleanValue:`timestampValue`in e?e.timestampValue:`nullValue`in e?null:`mapValue`in e?y(e.mapValue?.fields||{}):`arrayValue`in e?(e.arrayValue?.values||[]).map(v):null}function y(e){let t={};for(let[n,r]of Object.entries(e||{}))t[n]=v(r);return t}function ee(e,t){let n=`${e}/${t}`.split(`/`);if(n.length%2!=0)return`That is not a document path.`;for(let e of n)if(!e||e===`.`||e===`..`||!/^[A-Za-z0-9_.@~+-]+$/.test(e))return`That is not a document path.`;return null}async function b(e,t,n,r){let i=ee(t,n);if(i)throw Error(i);let a=`${_}/projects/${e}/databases/(default)/documents/${t}/${n}`,o=await fetch(a,{headers:r?{Authorization:`Bearer ${r}`}:{}});if(o.status===404)return null;if(o.status===403)throw Error(`You do not have permission to read that record.`);if(!o.ok)throw Error(`Firestore refused the read (${o.status}).`);return y((await o.json()).fields||{})}async function te(e,t,n){if(!/^[A-Za-z0-9_.@~+-]+$/.test(t)||t===`.`||t===`..`)throw Error(`That is not a collection.`);let r=`${_}/projects/${e}/databases/(default)/documents/${t}?pageSize=300`,i=await fetch(r,{headers:n?{Authorization:`Bearer ${n}`}:{}});if(i.status===403)throw Error(`You do not have permission to read those records.`);if(!i.ok)throw Error(`Firestore refused the read (${i.status}).`);return((await i.json()).documents||[]).map(e=>({id:String(e.name||``).split(`/`).pop()||``,data:y(e.fields||{})}))}var ne=[`email`,`Email`,`emailAddress`,`email_address`,`userEmail`,`contactEmail`,`submitterEmail`];function re(e){for(let t of ne){let n=e[t];if(typeof n==`string`&&n.includes(`@`))return n.trim()}return null}var ie=4320*60*1e3,ae=new Set([`user`,`users`,`free`,`guest`,`guests`,`internal`,`admin`,`admins`,`public`,`owner`,`team`,`member`,`members`,`none`,`role`,`roles`]),oe=e=>typeof e==`string`&&/^[a-z][a-z0-9]{1,23}$/.test(e)&&!ae.has(e),se=e=>(0,o.createHmac)(`sha256`,e).update(`your-ai-firebase plan roles v1`).digest(`hex`),ce=(e,t,n,r)=>(0,o.createHash)(`sha256`).update(`${e}:${t}:${n}:${r}`).digest(`hex`);function le(e){let t=new Map;for(let n of e){let e=Array.isArray(n?.plans)?n.plans:[];for(let n of e){let e=String(n?.roleId||``);if(oe(e))for(let r of[n?.monthly,n?.yearly])for(let n of[r?.priceId,r?.livePriceId]){let r=String(n||``).trim();r&&t.set(r,e)}}}return t}var ue=e=>le(e).size>0;function de(e,t,n){let r=null;for(let i of e){if(!i.active||!i.priceId)continue;let e=t.get(i.priceId);if(!e)continue;let a=typeof i.currentPeriodEnd==`number`?i.currentPeriodEnd*1e3:n,o=Math.round(Math.max(a,n)+ie);(!r||o>r.until)&&(r={role:e,until:o})}return r}var fe=`https://api.resend.com/emails`;function pe(){return(process.env.RESEND_API_KEY||``).trim()}function x(){return(process.env.EMAIL_FROM||``).trim()||`onboarding@resend.dev`}function me(){return(process.env.EMAIL_REPLY_TO||``).trim()||void 0}function S(){return(process.env.VITE_ADMIN_EMAILS||process.env.ADMIN_EMAILS||``).split(/[,\s]+/).map(e=>e.trim().toLowerCase()).filter(e=>e.includes(`@`))}var he={appName:``,logoUrl:``,appUrl:``,accent:`#7c3aed`,foreground:`#1e1b4b`,buttonText:``,buttonGradient:[],panelBg:``,panelBorder:``};function C(e,t){let n=String(e??``).trim();return/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(n)?n:t}function ge(e){let t=String(e??``).trim();return t.length>2e3||/[\s"'<>]/.test(t)?``:/^https?:\/\/[^/]+/i.test(t)?t:``}function _e(e){return String(e??``).replace(/[\r\n\t<>]/g,` `).replace(/\s+/g,` `).trim().slice(0,60)}function ve(e){let t=e&&typeof e==`object`?e:{},n=Array.isArray(t.buttonGradient)?t.buttonGradient.map(e=>C(e,``)).filter(Boolean).slice(0,2):[];return{appName:_e(t.appName),logoUrl:ge(t.logoUrl),appUrl:ge(t.appUrl),accent:C(t.accent,he.accent),foreground:C(t.foreground,he.foreground),buttonText:C(t.buttonText,``),buttonGradient:n,panelBg:C(t.panelBg,``),panelBorder:C(t.panelBorder,``)}}function ye(e){let t=e.replace(`#`,``);if(t.length===3&&(t=t.split(``).map(e=>e+e).join(``)),t.length<6)return null;let n=parseInt(t.slice(0,6),16);return Number.isNaN(n)?null:[n>>16&255,n>>8&255,n&255]}function be(e,t,n=[255,255,255]){let r=ye(e);return r?`#`+r.map((e,r)=>Math.round(e+(n[r]-e)*t)).map(e=>e.toString(16).padStart(2,`0`)).join(``):e}function xe(e){let t=ye(e);return t?(.299*t[0]+.587*t[1]+.114*t[2])/255:1}function Se(e,t=`#1f2937`,n=`#ffffff`){return xe(e)>.6?t:n}function Ce(e){let t=e.accent,n=xe(e.foreground)>.55?he.foreground:e.foreground,r=e.buttonGradient[0]||be(t,.22,[0,0,0]),i=e.buttonGradient[1]||(e.buttonGradient[0]?e.buttonGradient[0]:t),a=e.panelBg||be(t,.94);return{accent:t,text:n,muted:`#6b7280`,faint:`#9ca3af`,g0:r,g1:i,btnFg:e.buttonText||Se(r),panelBg:a,panelBorder:e.panelBorder||(e.panelBg?be(e.panelBg,.12,[0,0,0]):be(t,.84)),hr:be(t,.88)}}var w={test:{audience:`admins`,look:`notification`,kicker:`Test`,subject:`Test email from {{appName}}`,title:`{{appName}} can send email`,opener:`{{appName}} can send email`,body:`This is a test. If you are reading it, the connection works and every email your app sends will look like this one.
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./chunk-CiTBaPhY.cjs`),t=require(`./verifiedAccount-D3OeaOBo.cjs`),n=require(`./aiModels-BIhoTNH0.cjs`),r=require(`./validate-e48WXJdP.cjs`);let i=require(`node:fs`);i=e.a(i,1);let a=require(`express`);a=e.a(a,1);let o=require(`node:crypto`),s=require(`stripe`);s=e.a(s,1);var c=`https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com`,l=60;function u(e,t,n,r=Math.floor(Date.now()/1e3)){return n?e.alg===`RS256`?e.kid?t.aud===n?t.iss===`https://securetoken.google.com/${n}`?t.sub?t.sub.length>128?`Token subject is not a valid uid.`:typeof t.exp!=`number`||t.exp<=r?`Token has expired.`:typeof t.iat!=`number`||t.iat>r+l?`Token was issued in the future.`:null:`Token has no subject.`:`Token has the wrong issuer.`:`Token was issued for a different Firebase project.`:`Token has no key id.`:`Token is not signed with RS256.`:`The server does not know its Firebase project id.`}function d(e){return Buffer.from(e.replace(/-/g,`+`).replace(/_/g,`/`),`base64`)}function f(e){let t=String(e||``).split(`.`);if(t.length!==3)return null;try{return{header:JSON.parse(d(t[0]).toString(`utf8`)),claims:JSON.parse(d(t[1]).toString(`utf8`)),signed:`${t[0]}.${t[1]}`,signature:d(t[2])}}catch{return null}}var p=null;async function m(){if(p&&p.expiresAt>Date.now())return p.keys;let e=await fetch(c);if(!e.ok)throw Error(`Could not fetch Google signing keys (${e.status}).`);let t=await e.json(),n=/max-age=(\d+)/.exec(e.headers.get(`cache-control`)||``);return p={keys:t,expiresAt:Date.now()+(n?Number(n[1]):3600)*1e3},t}async function h(e,t){let n=f(e);if(!n)throw Error(`That is not a valid sign-in token.`);let r=u(n.header,n.claims,t);if(r)throw Error(r);let i=(await m())[n.header.kid];if(!i)throw Error(`Token was signed with a key Google no longer publishes.`);let a=(0,o.createVerify)(`RSA-SHA256`);if(a.update(n.signed),a.end(),!a.verify(i,n.signature))throw Error(`Token signature does not check out.`);return{uid:n.claims.sub,email:n.claims.email||null,emailVerified:!!n.claims.email_verified,signInProvider:n.claims.firebase?.sign_in_provider||null}}function g(){return(process.env.VITE_FIREBASE_PROJECT_ID||process.env.FIREBASE_PROJECT_ID||``).trim()}var _=`https://firestore.googleapis.com/v1`;function v(e){return e==null?null:`stringValue`in e?e.stringValue:`integerValue`in e?Number(e.integerValue):`doubleValue`in e?e.doubleValue:`booleanValue`in e?e.booleanValue:`timestampValue`in e?e.timestampValue:`nullValue`in e?null:`mapValue`in e?y(e.mapValue?.fields||{}):`arrayValue`in e?(e.arrayValue?.values||[]).map(v):null}function y(e){let t={};for(let[n,r]of Object.entries(e||{}))t[n]=v(r);return t}function ee(e,t){let n=`${e}/${t}`.split(`/`);if(n.length%2!=0)return`That is not a document path.`;for(let e of n)if(!e||e===`.`||e===`..`||!/^[A-Za-z0-9_.@~+-]+$/.test(e))return`That is not a document path.`;return null}async function b(e,t,n,r){let i=ee(t,n);if(i)throw Error(i);let a=`${_}/projects/${e}/databases/(default)/documents/${t}/${n}`,o=await fetch(a,{headers:r?{Authorization:`Bearer ${r}`}:{}});if(o.status===404)return null;if(o.status===403)throw Error(`You do not have permission to read that record.`);if(!o.ok)throw Error(`Firestore refused the read (${o.status}).`);return y((await o.json()).fields||{})}async function te(e,t,n){if(!/^[A-Za-z0-9_.@~+-]+$/.test(t)||t===`.`||t===`..`)throw Error(`That is not a collection.`);let r=`${_}/projects/${e}/databases/(default)/documents/${t}?pageSize=300`,i=await fetch(r,{headers:n?{Authorization:`Bearer ${n}`}:{}});if(i.status===403)throw Error(`You do not have permission to read those records.`);if(!i.ok)throw Error(`Firestore refused the read (${i.status}).`);return((await i.json()).documents||[]).map(e=>({id:String(e.name||``).split(`/`).pop()||``,data:y(e.fields||{})}))}var ne=[`email`,`Email`,`emailAddress`,`email_address`,`userEmail`,`contactEmail`,`submitterEmail`];function re(e){for(let t of ne){let n=e[t];if(typeof n==`string`&&n.includes(`@`))return n.trim()}return null}var ie=4320*60*1e3,ae=new Set([`user`,`users`,`free`,`guest`,`guests`,`internal`,`admin`,`admins`,`public`,`owner`,`team`,`member`,`members`,`none`,`role`,`roles`]),oe=e=>typeof e==`string`&&/^[a-z][a-z0-9]{1,23}$/.test(e)&&!ae.has(e),se=e=>(0,o.createHmac)(`sha256`,e).update(`your-ai-firebase plan roles v1`).digest(`hex`),ce=(e,t,n,r)=>(0,o.createHash)(`sha256`).update(`${e}:${t}:${n}:${r}`).digest(`hex`);function le(e){let t=new Map;for(let n of e){let e=Array.isArray(n?.plans)?n.plans:[];for(let n of e){let e=String(n?.roleId||``);if(oe(e))for(let r of[n?.monthly,n?.yearly])for(let n of[r?.priceId,r?.livePriceId]){let r=String(n||``).trim();r&&t.set(r,e)}}}return t}var ue=e=>le(e).size>0;function de(e,t,n){let r=null;for(let i of e){if(!i.active||!i.priceId)continue;let e=t.get(i.priceId);if(!e)continue;let a=typeof i.currentPeriodEnd==`number`?i.currentPeriodEnd*1e3:n,o=Math.round(Math.max(a,n)+ie);(!r||o>r.until)&&(r={role:e,until:o})}return r}var fe=`https://api.resend.com/emails`;function pe(){return(process.env.RESEND_API_KEY||``).trim()}function x(){return(process.env.EMAIL_FROM||``).trim()||`onboarding@resend.dev`}function me(){return(process.env.EMAIL_REPLY_TO||``).trim()||void 0}function S(){return(process.env.VITE_ADMIN_EMAILS||process.env.ADMIN_EMAILS||``).split(/[,\s]+/).map(e=>e.trim().toLowerCase()).filter(e=>e.includes(`@`))}var he={appName:``,logoUrl:``,appUrl:``,accent:`#7c3aed`,foreground:`#1e1b4b`,buttonText:``,buttonGradient:[],panelBg:``,panelBorder:``};function C(e,t){let n=String(e??``).trim();return/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(n)?n:t}function ge(e){let t=String(e??``).trim();return t.length>2e3||/[\s"'<>]/.test(t)?``:/^https?:\/\/[^/]+/i.test(t)?t:``}function _e(e){return String(e??``).replace(/[\r\n\t<>]/g,` `).replace(/\s+/g,` `).trim().slice(0,60)}function ve(e){let t=e&&typeof e==`object`?e:{},n=Array.isArray(t.buttonGradient)?t.buttonGradient.map(e=>C(e,``)).filter(Boolean).slice(0,2):[];return{appName:_e(t.appName),logoUrl:ge(t.logoUrl),appUrl:ge(t.appUrl),accent:C(t.accent,he.accent),foreground:C(t.foreground,he.foreground),buttonText:C(t.buttonText,``),buttonGradient:n,panelBg:C(t.panelBg,``),panelBorder:C(t.panelBorder,``)}}function ye(e){let t=e.replace(`#`,``);if(t.length===3&&(t=t.split(``).map(e=>e+e).join(``)),t.length<6)return null;let n=parseInt(t.slice(0,6),16);return Number.isNaN(n)?null:[n>>16&255,n>>8&255,n&255]}function be(e,t,n=[255,255,255]){let r=ye(e);return r?`#`+r.map((e,r)=>Math.round(e+(n[r]-e)*t)).map(e=>e.toString(16).padStart(2,`0`)).join(``):e}function xe(e){let t=ye(e);return t?(.299*t[0]+.587*t[1]+.114*t[2])/255:1}function Se(e,t=`#1f2937`,n=`#ffffff`){return xe(e)>.6?t:n}function Ce(e){let t=e.accent,n=xe(e.foreground)>.55?he.foreground:e.foreground,r=e.buttonGradient[0]||be(t,.22,[0,0,0]),i=e.buttonGradient[1]||(e.buttonGradient[0]?e.buttonGradient[0]:t),a=e.panelBg||be(t,.94);return{accent:t,text:n,muted:`#6b7280`,faint:`#9ca3af`,g0:r,g1:i,btnFg:e.buttonText||Se(r),panelBg:a,panelBorder:e.panelBorder||(e.panelBg?be(e.panelBg,.12,[0,0,0]):be(t,.84)),hr:be(t,.88)}}var w={test:{audience:`admins`,look:`notification`,kicker:`Test`,subject:`Test email from {{appName}}`,title:`{{appName}} can send email`,opener:`{{appName}} can send email`,body:`This is a test. If you are reading it, the connection works and every email your app sends will look like this one.
2
2
 
3
3
  Sent {{now}}.`,button:{label:`Open the app`},description:`Proves the connection works. Goes to the admin addresses.`},new_submission:{audience:`admins`,look:`notification`,kicker:`New form entry`,subject:`New {{formName}} submission`,title:`Someone filled in {{formName}}`,opener:`Someone filled in {{formName}}`,body:`{{summary}}`,facts:[[`Name`,`{{name}}`],[`Email`,`{{email}}`]],button:{label:`Open the inbox`,path:`/inbox`},description:`Lets you know someone filled in a form.`},submission_received:{audience:`record`,look:`text`,kicker:`Received`,subject:`We got your message`,title:`We got your message`,greeting:`Hi {{name}},`,body:`Thanks for getting in touch — we have your message and will come back to you shortly.
4
4
 
package/dist/server.es.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { S as e, c as t, h as n, t as r, u as i, w as a } from "./verifiedAccount-C1iCxopN.js";
2
2
  import { a as o, c as s, i as c, l, m as u, n as d, o as f, r as p, s as m, t as h, u as g } from "./aiModels-BHlEfuRJ.js";
3
- import { _, a as v, i as ee, p as te } from "./validate-C_iJKXDU.js";
3
+ import { _, a as v, i as ee, p as te } from "./validate-DzSJWuyL.js";
4
4
  import * as ne from "node:fs";
5
5
  import y from "express";
6
6
  import { createHash as re, createHmac as ie, createVerify as ae } from "node:crypto";
@@ -308,13 +308,13 @@ var D = (e) => E(y(e)), O = /\b(?:useAiChat|askAi|useAiEnabled|useAiModels)\b|ge
308
308
  "raw-html": "breaks",
309
309
  "own-money": "breaks",
310
310
  "dead-click": "breaks",
311
- "glyph-icon": "breaks",
312
- "own-spinner": "breaks",
313
- "tiny-text": "breaks",
311
+ "glyph-icon": "looks",
312
+ "own-spinner": "looks",
313
+ "tiny-text": "looks",
314
314
  "vague-button": "breaks",
315
315
  "extra-press": "breaks",
316
- "icon-in-heading": "breaks",
317
- "decorated-empty": "breaks",
316
+ "icon-in-heading": "looks",
317
+ "decorated-empty": "looks",
318
318
  "item-key": "breaks",
319
319
  "whole-records": "breaks"
320
320
  };
@@ -1014,7 +1014,7 @@ function _r(e) {
1014
1014
  if (J ? c("own-basket", zr.test(i) ? Vr(t, J, /<Checkout\b/.test(i)) : Hr(t, J)) : Y ? c("own-checkout", Lr(t, Y)) : Ur(i) && c("own-purchase", Wr(t)), !J && !Y) for (let n of Wa(i, e)) n.why === "flag" ? c("order-flag", io(n.config, t)) : n.why === "asks" ? c("order-asks", Ga(n.config, t)) : c("buyer-form", ao(n.config, t));
1015
1015
  let Ae = Kr(i);
1016
1016
  Ae && c("own-return", qr(t, Ae));
1017
- for (let n of Ja(i, e)) n.why === "typed" ? c("no-road-in", no(n.target, t, n.formId)) : c("own-road", ro(n, t));
1017
+ for (let n of Ja(t, i, e)) n.why === "typed" ? c("no-road-in", no(n.target, t, n.formId)) : c("own-road", ro(n, t));
1018
1018
  let Fe = Qi(i);
1019
1019
  Fe.length && c("swallowed-write", aa(t, Fe));
1020
1020
  let Ie = ra(i);
@@ -1440,38 +1440,59 @@ var wa = (e, t) => `${e}: ${P(t)} — the page works out WHO MADE a record for i
1440
1440
  }));
1441
1441
  }
1442
1442
  return r;
1443
- }, Ga = (e, t) => `${e}: this is the order ${t} sends back beside its <PayButton>, and an order asks the buyer NOTHING (fields: []) — who is paying is their ACCOUNT. Put <BuyerDetails onReady={setBuyer} phone address /> from 'your-ai-workflow-firebase-os' before the form, take every name, email, phone and address question out of the form, spread { ...buyer.metadata } into the button's metadata, and carry buyer.name, buyer.email, buyer.phone and buyer.address in the form's extra with the items. 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).`, Ka = /(?:\bto|\bhref|\borderPath)=\{?\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]|\bnavigate\(\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]/g, $ = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, ""), qa = /\baddDoc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_|\bsetDoc\s*\(\s*doc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_/, Ja = (e, t) => {
1444
- let n = (t) => [...e.matchAll(RegExp(`<${t}\\b`, "g"))].map((t) => {
1445
- let n = t.index ?? 0, r = e.indexOf("/>", n);
1446
- return e.slice(n, r === -1 ? n + 800 : r + 2);
1447
- }), r = [...n("Basket"), ...n("Checkout")].find((e) => /\bformId=/.test(e)), i = (r ?? n("ContactPopup").find((e) => /\bextra=/.test(e)))?.match(/formId=\{?\s*['"`]([^'"`]+)['"`]/)?.[1];
1448
- if (!i) return [];
1449
- let a = [], o = /* @__PURE__ */ new Set();
1450
- for (let n of e.matchAll(Ka)) {
1451
- let e = $(n[1] || n[2] || "");
1452
- if (!e || o.has(e)) continue;
1453
- o.add(e);
1454
- let s = t.find((t) => R(t.file) && $(I(t.file).replace(/Content\.tsx$/, "")) === e);
1455
- if (!s) continue;
1456
- let c = z(s.source), l = /\b(?:usePublished|publish)\s*\(/.test(c), u = c.match(/\buseCases(?:<[^>]*>)?\s*\(\s*['"`](case_[A-Za-z0-9_]+)['"`]/)?.[1] ?? `case_${I(s.file).replace(/Content\.tsx$/, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_entries`, d = {
1457
- target: s.file,
1458
- formId: i,
1459
- paid: !!r,
1460
- reads: u
1443
+ }, Ga = (e, t) => `${e}: this is the order ${t} sends back beside its <PayButton>, and an order asks the buyer NOTHING (fields: []) — who is paying is their ACCOUNT. Put <BuyerDetails onReady={setBuyer} phone address /> from 'your-ai-workflow-firebase-os' before the form, take every name, email, phone and address question out of the form, spread { ...buyer.metadata } into the button's metadata, and carry buyer.name, buyer.email, buyer.phone and buyer.address in the form's extra with the items. 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).`, Ka = /(?:\bto|\bhref|\borderPath)=\{?\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]|\bnavigate\(\s*['"`]\/([A-Za-z0-9_-]+)[?'"`]/g, $ = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, ""), qa = /\baddDoc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_|\bsetDoc\s*\(\s*doc\s*\(\s*collection\s*\(\s*db\s*,\s*['"`]case_/, Ja = (e, t, n) => {
1444
+ let r = (e) => [...t.matchAll(RegExp(`<${e}\\b`, "g"))].map((e) => {
1445
+ let n = e.index ?? 0, r = t.indexOf("/>", n);
1446
+ return t.slice(n, r === -1 ? n + 800 : r + 2);
1447
+ }), i = [...r("Basket"), ...r("Checkout")].find((e) => /\bformId=/.test(e)), a = (i ?? r("ContactPopup").find((e) => /\bextra=/.test(e)))?.match(/formId=\{?\s*['"`]([^'"`]+)['"`]/)?.[1];
1448
+ if (!a) return [];
1449
+ let o = [], s = /* @__PURE__ */ new Set(), c = [];
1450
+ for (let e of t.matchAll(Ka)) {
1451
+ let t = $(e[1] || e[2] || "");
1452
+ if (!t || s.has(t)) continue;
1453
+ s.add(t);
1454
+ let r = n.find((e) => R(e.file) && $(I(e.file).replace(/Content\.tsx$/, "")) === t);
1455
+ r && c.push(r);
1456
+ }
1457
+ let l = (e) => {
1458
+ let t = z(e.source), n = t.match(/\buseCases(?:<[^>]*>)?\s*\(\s*['"`](case_[A-Za-z0-9_]+)['"`]/)?.[1] ?? `case_${I(e.file).replace(/Content\.tsx$/, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_entries`;
1459
+ return {
1460
+ theirs: t,
1461
+ gap: {
1462
+ target: e.file,
1463
+ formId: a,
1464
+ paid: !!i,
1465
+ reads: n
1466
+ }
1461
1467
  };
1462
- !l && /\buseCases\s*\(/.test(c) && qa.test(c) && a.push({
1463
- ...d,
1468
+ }, u = (e) => {
1469
+ let t = e.match(da)?.[1], n = RegExp(`['"\`]${a.replace(/[^A-Za-z0-9_-]/g, "")}['"\`]`).test(e);
1470
+ return t && n && !/\bappliedEntries\b/.test(e) ? t : void 0;
1471
+ };
1472
+ for (let e of c) {
1473
+ let { theirs: t, gap: n } = l(e);
1474
+ !/\b(?:usePublished|publish)\s*\(/.test(t) && /\buseCases\s*\(/.test(t) && qa.test(t) && o.push({
1475
+ ...n,
1464
1476
  why: "typed",
1465
1477
  inbox: ""
1466
1478
  });
1467
- let f = c.match(da)?.[1], p = RegExp(`['"\`]${i.replace(/[^A-Za-z0-9_-]/g, "")}['"\`]`).test(c);
1468
- f && p && !/\bappliedEntries\b/.test(c) && a.push({
1469
- ...d,
1479
+ let r = u(t);
1480
+ r && o.push({
1481
+ ...n,
1482
+ why: "fetched",
1483
+ inbox: r
1484
+ });
1485
+ }
1486
+ for (let t of n) {
1487
+ if (t.file === e || !R(t.file) || c.includes(t)) continue;
1488
+ let { theirs: n, gap: r } = l(t), i = u(n);
1489
+ i && o.push({
1490
+ ...r,
1470
1491
  why: "fetched",
1471
- inbox: f
1492
+ inbox: i
1472
1493
  });
1473
1494
  }
1474
- return a;
1495
+ return o;
1475
1496
  }, Ya = (e) => {
1476
1497
  let t = [], n = /* @__PURE__ */ new Set(), r = (e, r, i) => {
1477
1498
  let a = `${$(e)}|${r}`;