@aglyn/plugins-commerce 1.0.0-beta.218 → 1.0.0-beta.220

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 (148) hide show
  1. package/README.md +6 -0
  2. package/package.json +14 -14
  3. package/src/index.d.ts +1 -0
  4. package/src/index.js +1 -19
  5. package/src/index.js.map +1 -1
  6. package/src/lib/components/cart.js +2 -1
  7. package/src/lib/components/cart.js.map +1 -1
  8. package/src/lib/components/console/catalog-organization-card.component.js +665 -459
  9. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  10. package/src/lib/components/console/host-orders-card.component.js +2 -1
  11. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  12. package/src/lib/components/console/member-posts-card.component.js +1 -1
  13. package/src/lib/components/console/member-posts-card.component.js.map +1 -1
  14. package/src/lib/components/console/order-detail-dialog.component.js +8 -7
  15. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  16. package/src/lib/components/console/payments-settings-card.component.js +2 -2
  17. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  18. package/src/lib/components/console/product-editor-dialog.component.js +1221 -889
  19. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  20. package/src/lib/components/console/product-zones.d.ts +184 -0
  21. package/src/lib/components/console/product-zones.js +54 -0
  22. package/src/lib/components/console/product-zones.js.map +1 -0
  23. package/src/lib/components/console/products-hub-card.component.js +3 -2
  24. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  25. package/src/lib/components/console/products-hub-zone.component.d.ts +1 -1
  26. package/src/lib/components/console/products-hub-zone.component.js +2 -1
  27. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  28. package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
  29. package/src/lib/components/console/site-member-purchases.component.js +339 -0
  30. package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
  31. package/src/lib/components/console/store-settings-card.component.js +2 -2
  32. package/src/lib/components/console/store-settings-card.component.js.map +1 -1
  33. package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -1
  34. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
  35. package/src/lib/components/member-signup.js +2 -2
  36. package/src/lib/components/member-signup.js.map +1 -1
  37. package/src/lib/components/newsletter-signup.js +2 -2
  38. package/src/lib/components/newsletter-signup.js.map +1 -1
  39. package/src/lib/components/product-detail.js +2 -1
  40. package/src/lib/components/product-detail.js.map +1 -1
  41. package/src/lib/components/product-grid.js +1 -1
  42. package/src/lib/components/product-grid.js.map +1 -1
  43. package/src/lib/constants/operator-alerts.js +5 -3
  44. package/src/lib/constants/operator-alerts.js.map +1 -1
  45. package/src/lib/constants/storefront-payments.d.ts +33 -0
  46. package/src/lib/constants/storefront-payments.js +35 -0
  47. package/src/lib/constants/storefront-payments.js.map +1 -0
  48. package/src/lib/declarations.server.d.ts +3 -1
  49. package/src/lib/declarations.server.js +19 -1
  50. package/src/lib/declarations.server.js.map +1 -1
  51. package/src/lib/model/commerce-io.js +1 -1
  52. package/src/lib/model/commerce-io.js.map +1 -1
  53. package/src/lib/model/commerce-record-lists.d.ts +31 -0
  54. package/src/lib/model/commerce-record-lists.js +45 -0
  55. package/src/lib/model/commerce-record-lists.js.map +1 -0
  56. package/src/lib/model/commerce-record-routes.d.ts +45 -0
  57. package/src/lib/model/commerce-record-routes.js +72 -0
  58. package/src/lib/model/commerce-record-routes.js.map +1 -0
  59. package/src/lib/model/commerce-search-sources.d.ts +21 -0
  60. package/src/lib/model/commerce-search-sources.js +42 -0
  61. package/src/lib/model/commerce-search-sources.js.map +1 -0
  62. package/src/lib/model/commerce-topics.d.ts +20 -0
  63. package/src/lib/model/commerce-topics.js +33 -0
  64. package/src/lib/model/commerce-topics.js.map +1 -0
  65. package/src/lib/model/index.d.ts +1 -0
  66. package/src/lib/model/index.js +1 -0
  67. package/src/lib/model/index.js.map +1 -1
  68. package/src/lib/model/product-record.d.ts +32 -0
  69. package/src/lib/model/product-record.js +82 -0
  70. package/src/lib/model/product-record.js.map +1 -0
  71. package/src/lib/model/site-member-purchases.d.ts +48 -0
  72. package/src/lib/model/site-member-purchases.js +50 -0
  73. package/src/lib/model/site-member-purchases.js.map +1 -0
  74. package/src/lib/model/storefront-tax-summary.d.ts +121 -0
  75. package/src/lib/model/storefront-tax-summary.js +148 -0
  76. package/src/lib/model/storefront-tax-summary.js.map +1 -0
  77. package/src/lib/plan-entitlements.d.ts +35 -0
  78. package/src/lib/plan-entitlements.js +54 -0
  79. package/src/lib/plan-entitlements.js.map +1 -0
  80. package/src/lib/plugin.js +21 -0
  81. package/src/lib/plugin.js.map +1 -1
  82. package/src/lib/server/billing-webhook.js +387 -248
  83. package/src/lib/server/billing-webhook.js.map +1 -1
  84. package/src/lib/server/cart-checkout.js +3 -3
  85. package/src/lib/server/cart-checkout.js.map +1 -1
  86. package/src/lib/server/checkout.js +2 -2
  87. package/src/lib/server/checkout.js.map +1 -1
  88. package/src/lib/server/connect.js +5 -5
  89. package/src/lib/server/connect.js.map +1 -1
  90. package/src/lib/server/draft-order.js +2 -2
  91. package/src/lib/server/draft-order.js.map +1 -1
  92. package/src/lib/server/gift-cards.js +7 -3
  93. package/src/lib/server/gift-cards.js.map +1 -1
  94. package/src/lib/server/low-stock.js +3 -1
  95. package/src/lib/server/low-stock.js.map +1 -1
  96. package/src/lib/server/member-post.js +2 -2
  97. package/src/lib/server/member-post.js.map +1 -1
  98. package/src/lib/server/membership-admin-password.js +4 -1
  99. package/src/lib/server/membership-admin-password.js.map +1 -1
  100. package/src/lib/server/membership-content.js +1 -1
  101. package/src/lib/server/membership-content.js.map +1 -1
  102. package/src/lib/server/membership-register.js +13 -2
  103. package/src/lib/server/membership-register.js.map +1 -1
  104. package/src/lib/server/newsletter.js +32 -19
  105. package/src/lib/server/newsletter.js.map +1 -1
  106. package/src/lib/server/pos-order.js +3 -3
  107. package/src/lib/server/pos-order.js.map +1 -1
  108. package/src/lib/server/process-abandoned.js +4 -2
  109. package/src/lib/server/process-abandoned.js.map +1 -1
  110. package/src/lib/server/process-restock.js +7 -5
  111. package/src/lib/server/process-restock.js.map +1 -1
  112. package/src/lib/server/product-record-index.d.ts +19 -0
  113. package/src/lib/server/product-record-index.js +83 -0
  114. package/src/lib/server/product-record-index.js.map +1 -0
  115. package/src/lib/server/reconcile-stock.js +1 -1
  116. package/src/lib/server/reconcile-stock.js.map +1 -1
  117. package/src/lib/server/reserve-stock.js +6 -1
  118. package/src/lib/server/reserve-stock.js.map +1 -1
  119. package/src/lib/server/reserve.js +2 -2
  120. package/src/lib/server/reserve.js.map +1 -1
  121. package/src/lib/server/revenue-source.d.ts +130 -0
  122. package/src/lib/server/revenue-source.js +286 -0
  123. package/src/lib/server/revenue-source.js.map +1 -0
  124. package/src/lib/server/supplier-outbox.js +16 -1
  125. package/src/lib/server/supplier-outbox.js.map +1 -1
  126. package/src/lib/server/supplier-update.js +19 -9
  127. package/src/lib/server/supplier-update.js.map +1 -1
  128. package/src/lib/server/tax-profile.d.ts +8 -5
  129. package/src/lib/server/tax-profile.js +22 -5
  130. package/src/lib/server/tax-profile.js.map +1 -1
  131. package/src/lib/server/tax-return-source.d.ts +125 -0
  132. package/src/lib/server/tax-return-source.js +524 -0
  133. package/src/lib/server/tax-return-source.js.map +1 -0
  134. package/src/lib/server/tax-summary.d.ts +92 -0
  135. package/src/lib/server/tax-summary.js +258 -0
  136. package/src/lib/server/tax-summary.js.map +1 -0
  137. package/src/lib/server.d.ts +1 -0
  138. package/src/lib/server.js +23 -0
  139. package/src/lib/server.js.map +1 -1
  140. package/src/lib/starter-templates.d.ts +19 -0
  141. package/src/lib/starter-templates.js +157 -0
  142. package/src/lib/starter-templates.js.map +1 -0
  143. package/src/lib/tenant-emails.d.ts +33 -0
  144. package/src/lib/tenant-emails.js +551 -0
  145. package/src/lib/tenant-emails.js.map +1 -0
  146. package/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
  147. package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
  148. package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The tax storefront sales collected, by who is liable for it (AGL-1904).
19
+ *
20
+ * A storefront sale is a merchant's sale — but on a `mode: 'stripe'` store
21
+ * the tax charged to the shopper is computed against the PLATFORM's
22
+ * registrations on a Checkout Session created on the platform's own account
23
+ * (measured, not inferred: with the platform unregistered and the destination
24
+ * connected account registered in Texas, Stripe answered `amount_tax: 0` /
25
+ * `not_collecting`; with the platform registered and nothing else changed,
26
+ * 8.25% / `standard_rated`; both sessions reported
27
+ * `automatic_tax.liability: { type: "self" }`). That tax lands in the
28
+ * platform's balance, and the operator's return reads it through this fold.
29
+ *
30
+ * ## THREE BUCKETS, AND THEY MUST NEVER BE SUMMED
31
+ *
32
+ * There is deliberately no grand total on this summary, and adding one would
33
+ * be the bug — the three are answers to three different questions:
34
+ *
35
+ * - **`aglynLiable`** — Stripe Tax computed it against the platform's own
36
+ * registrations (`taxMode: 'stripe-automatic'`,
37
+ * `taxLiability: 'platform'`). This is money the platform is holding.
38
+ * - **`merchantManual`** — the merchant's own configured rate, added as an
39
+ * ordinary line item Stripe is never told is tax. The platform's
40
+ * registrations played no part; it is not the platform's to remit, and
41
+ * counting it would have the operator filing and paying another
42
+ * company's tax.
43
+ * - **`connectedAccountLiable`** — Stripe Tax that named a CONNECTED
44
+ * account as the liable party. None exists today (no storefront path sets
45
+ * `on_behalf_of`), and the bucket is here so that if one ever does, the
46
+ * money moves out of `aglynLiable` visibly rather than silently.
47
+ *
48
+ * `aglynLiable` is a wire name the merchant's own tax report and the staff
49
+ * return have both read since AGL-1904; it means "the platform operator".
50
+ *
51
+ * The classification comes from the stored `taxMode` / `taxLiability`, which
52
+ * `storefront-tax.ts` derives from `automatic_tax.enabled` and never from the
53
+ * presence of tax lines — a manual-mode subscription renewal carries genuine
54
+ * Stripe Tax Rates (AGL-1751) and is otherwise indistinguishable.
55
+ *
56
+ * **This says nothing about marketplace-facilitator status.** It reports which
57
+ * registration computed which tax and where the money is. Which line of a
58
+ * filed return each bucket belongs on is a question for the preparer and for
59
+ * counsel, and this module deliberately declines to answer it — which is
60
+ * exactly why there is no merged total to mistake for one.
61
+ *
62
+ * Rows that cannot be classified or read are counted in `attention`, never
63
+ * dropped and never zeroed.
64
+ *
65
+ * Pure and total: no Firestore, no clock. Read by the staff return's source
66
+ * (`server/tax-return-source.ts`) and by the merchant's own tax report, so
67
+ * both read the same rows through the same classifier.
68
+ */
69
+ import { type TaxLineInput, type TaxReturnJurisdiction } from '@aglyn/aglyn/app-utils/tax-jurisdiction-figures';
70
+ /** The subset of a `storefrontTaxCollected/{stripeId}` row this reads. */
71
+ export interface StorefrontTaxReturnRowInput {
72
+ id: string;
73
+ hostId?: unknown;
74
+ orgId?: string | null;
75
+ taxMode?: unknown;
76
+ taxLiability?: unknown;
77
+ grossCents?: unknown;
78
+ taxCents?: unknown;
79
+ currency?: unknown;
80
+ customerAddress?: {
81
+ country?: unknown;
82
+ state?: unknown;
83
+ } | null;
84
+ taxLines?: TaxLineInput[] | null;
85
+ paidAt?: unknown;
86
+ }
87
+ export interface StorefrontTaxBucket {
88
+ transactionCount: number;
89
+ /** What shoppers paid, tax included. NOT the platform's revenue. */
90
+ grossCents: number;
91
+ /** Stripe's `taxable_amount` summed; 0 for buckets that state no base. */
92
+ taxableSalesCents: number;
93
+ taxCollectedCents: number;
94
+ /** Keyed `COUNTRY-STATE`, or `unknown`. */
95
+ byJurisdiction: Record<string, TaxReturnJurisdiction>;
96
+ }
97
+ export interface StorefrontTaxSummary {
98
+ periodStart: string;
99
+ periodEnd: string;
100
+ transactionCount: number;
101
+ /** Tax computed against the PLATFORM's registrations. See the module note. */
102
+ aglynLiable: StorefrontTaxBucket;
103
+ /** The merchant's own configured tax. Never the platform's to remit. */
104
+ merchantManual: StorefrontTaxBucket;
105
+ /** Stripe Tax that named a connected account liable. Empty today. */
106
+ connectedAccountLiable: StorefrontTaxBucket;
107
+ attention: {
108
+ /** Tax collected but no line states its base — see the module note. */
109
+ rowsMissingTaxableBase: number;
110
+ rowsMissingAddress: number;
111
+ nonUsdRows: number;
112
+ rowsMissingPaidAt: number;
113
+ /** A `taxMode` this code does not recognize — never silently bucketed. */
114
+ rowsUnclassified: number;
115
+ };
116
+ }
117
+ /** The storefront figures from one period's rows. See the module note. */
118
+ export declare function storefrontTaxSummary(rows: readonly StorefrontTaxReturnRowInput[], period: {
119
+ start: Date;
120
+ end: Date;
121
+ }): StorefrontTaxSummary;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The tax storefront sales collected, by who is liable for it (AGL-1904).
18
+ *
19
+ * A storefront sale is a merchant's sale — but on a `mode: 'stripe'` store
20
+ * the tax charged to the shopper is computed against the PLATFORM's
21
+ * registrations on a Checkout Session created on the platform's own account
22
+ * (measured, not inferred: with the platform unregistered and the destination
23
+ * connected account registered in Texas, Stripe answered `amount_tax: 0` /
24
+ * `not_collecting`; with the platform registered and nothing else changed,
25
+ * 8.25% / `standard_rated`; both sessions reported
26
+ * `automatic_tax.liability: { type: "self" }`). That tax lands in the
27
+ * platform's balance, and the operator's return reads it through this fold.
28
+ *
29
+ * ## THREE BUCKETS, AND THEY MUST NEVER BE SUMMED
30
+ *
31
+ * There is deliberately no grand total on this summary, and adding one would
32
+ * be the bug — the three are answers to three different questions:
33
+ *
34
+ * - **`aglynLiable`** — Stripe Tax computed it against the platform's own
35
+ * registrations (`taxMode: 'stripe-automatic'`,
36
+ * `taxLiability: 'platform'`). This is money the platform is holding.
37
+ * - **`merchantManual`** — the merchant's own configured rate, added as an
38
+ * ordinary line item Stripe is never told is tax. The platform's
39
+ * registrations played no part; it is not the platform's to remit, and
40
+ * counting it would have the operator filing and paying another
41
+ * company's tax.
42
+ * - **`connectedAccountLiable`** — Stripe Tax that named a CONNECTED
43
+ * account as the liable party. None exists today (no storefront path sets
44
+ * `on_behalf_of`), and the bucket is here so that if one ever does, the
45
+ * money moves out of `aglynLiable` visibly rather than silently.
46
+ *
47
+ * `aglynLiable` is a wire name the merchant's own tax report and the staff
48
+ * return have both read since AGL-1904; it means "the platform operator".
49
+ *
50
+ * The classification comes from the stored `taxMode` / `taxLiability`, which
51
+ * `storefront-tax.ts` derives from `automatic_tax.enabled` and never from the
52
+ * presence of tax lines — a manual-mode subscription renewal carries genuine
53
+ * Stripe Tax Rates (AGL-1751) and is otherwise indistinguishable.
54
+ *
55
+ * **This says nothing about marketplace-facilitator status.** It reports which
56
+ * registration computed which tax and where the money is. Which line of a
57
+ * filed return each bucket belongs on is a question for the preparer and for
58
+ * counsel, and this module deliberately declines to answer it — which is
59
+ * exactly why there is no merged total to mistake for one.
60
+ *
61
+ * Rows that cannot be classified or read are counted in `attention`, never
62
+ * dropped and never zeroed.
63
+ *
64
+ * Pure and total: no Firestore, no clock. Read by the staff return's source
65
+ * (`server/tax-return-source.ts`) and by the merchant's own tax report, so
66
+ * both read the same rows through the same classifier.
67
+ */ import { accumulateTaxWorkingPapers, asRowDate, emptyTaxJurisdiction, taxJurisdictionKey } from "@aglyn/aglyn/app-utils/tax-jurisdiction-figures";
68
+ function cents(value) {
69
+ const parsed = Number(value != null ? value : 0);
70
+ return Number.isFinite(parsed) ? Math.round(parsed) : 0;
71
+ }
72
+ function emptyBucket() {
73
+ return {
74
+ transactionCount: 0,
75
+ grossCents: 0,
76
+ taxableSalesCents: 0,
77
+ taxCollectedCents: 0,
78
+ byJurisdiction: {}
79
+ };
80
+ }
81
+ /** The storefront figures from one period's rows. See the module note. */ export function storefrontTaxSummary(rows, period) {
82
+ const summary = {
83
+ periodStart: period.start.toISOString(),
84
+ periodEnd: period.end.toISOString(),
85
+ transactionCount: 0,
86
+ aglynLiable: emptyBucket(),
87
+ merchantManual: emptyBucket(),
88
+ connectedAccountLiable: emptyBucket(),
89
+ attention: {
90
+ rowsMissingTaxableBase: 0,
91
+ rowsMissingAddress: 0,
92
+ nonUsdRows: 0,
93
+ rowsMissingPaidAt: 0,
94
+ rowsUnclassified: 0
95
+ }
96
+ };
97
+ for (const row of rows != null ? rows : []){
98
+ var _row_currency, _row_taxMode, _row_taxLiability, _bucket_byJurisdiction, _jurisdiction, _;
99
+ const gross = cents(row.grossCents);
100
+ const tax = cents(row.taxCents);
101
+ const lines = Array.isArray(row.taxLines) ? row.taxLines : [];
102
+ const statedBases = lines.map((line)=>line == null ? void 0 : line.taxableAmountCents).filter((base)=>base !== null && base !== undefined && Number.isFinite(Number(base)));
103
+ const taxableBase = statedBases.reduce((sum, base)=>sum + Math.round(Number(base)), 0);
104
+ const jurisdiction = taxJurisdictionKey(row.customerAddress);
105
+ if (jurisdiction === 'unknown') summary.attention.rowsMissingAddress += 1;
106
+ if (String((_row_currency = row.currency) != null ? _row_currency : 'usd').toLowerCase() !== 'usd') {
107
+ summary.attention.nonUsdRows += 1;
108
+ }
109
+ if (!asRowDate(row.paidAt)) summary.attention.rowsMissingPaidAt += 1;
110
+ // The bucket is chosen from the STORED classification, and an unfamiliar
111
+ // one falls through to `rowsUnclassified` rather than defaulting into a
112
+ // bucket — a default here would put a merchant's tax on the platform's
113
+ // return, or the platform's tax nowhere, and neither is allowed to happen
114
+ // quietly.
115
+ const mode = String((_row_taxMode = row.taxMode) != null ? _row_taxMode : '');
116
+ const liability = String((_row_taxLiability = row.taxLiability) != null ? _row_taxLiability : '');
117
+ const bucket = mode === 'stripe-automatic' && liability === 'connected-account' ? summary.connectedAccountLiable : mode === 'stripe-automatic' ? summary.aglynLiable : mode === 'manual' ? summary.merchantManual : null;
118
+ if (!bucket) {
119
+ summary.attention.rowsUnclassified += 1;
120
+ continue;
121
+ }
122
+ // A taxed row that cannot state the base the rate was applied to —
123
+ // counted ONLY for Stripe-computed tax. A manual-mode row has no Stripe
124
+ // base by construction (Stripe was never told the amount was tax), so
125
+ // flagging it here would raise a permanent alarm about a figure that can
126
+ // never exist, and an alarm that is always on is an alarm nobody reads.
127
+ if (mode === 'stripe-automatic' && tax > 0 && statedBases.length === 0) {
128
+ summary.attention.rowsMissingTaxableBase += 1;
129
+ }
130
+ summary.transactionCount += 1;
131
+ bucket.transactionCount += 1;
132
+ bucket.grossCents += gross;
133
+ bucket.taxableSalesCents += taxableBase;
134
+ bucket.taxCollectedCents += tax;
135
+ const jurisdictionBucket = (_ = (_bucket_byJurisdiction = bucket.byJurisdiction)[_jurisdiction = jurisdiction]) != null ? _ : _bucket_byJurisdiction[_jurisdiction] = emptyTaxJurisdiction();
136
+ jurisdictionBucket.transactionCount += 1;
137
+ jurisdictionBucket.totalSalesCents += gross - tax;
138
+ jurisdictionBucket.taxableSalesCents += taxableBase;
139
+ jurisdictionBucket.taxCollectedCents += tax;
140
+ // The storefront working papers (AGL-2329). Its lines carry the richer
141
+ // detail — `percentage`, `rateState`, `jurisdiction` — three fields the
142
+ // writer annotates "for the working papers".
143
+ accumulateTaxWorkingPapers(jurisdictionBucket, lines);
144
+ }
145
+ return summary;
146
+ }
147
+
148
+ //# sourceMappingURL=storefront-tax-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/storefront-tax-summary.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The tax storefront sales collected, by who is liable for it (AGL-1904).\n *\n * A storefront sale is a merchant's sale — but on a `mode: 'stripe'` store\n * the tax charged to the shopper is computed against the PLATFORM's\n * registrations on a Checkout Session created on the platform's own account\n * (measured, not inferred: with the platform unregistered and the destination\n * connected account registered in Texas, Stripe answered `amount_tax: 0` /\n * `not_collecting`; with the platform registered and nothing else changed,\n * 8.25% / `standard_rated`; both sessions reported\n * `automatic_tax.liability: { type: \"self\" }`). That tax lands in the\n * platform's balance, and the operator's return reads it through this fold.\n *\n * ## THREE BUCKETS, AND THEY MUST NEVER BE SUMMED\n *\n * There is deliberately no grand total on this summary, and adding one would\n * be the bug — the three are answers to three different questions:\n *\n * - **`aglynLiable`** — Stripe Tax computed it against the platform's own\n * registrations (`taxMode: 'stripe-automatic'`,\n * `taxLiability: 'platform'`). This is money the platform is holding.\n * - **`merchantManual`** — the merchant's own configured rate, added as an\n * ordinary line item Stripe is never told is tax. The platform's\n * registrations played no part; it is not the platform's to remit, and\n * counting it would have the operator filing and paying another\n * company's tax.\n * - **`connectedAccountLiable`** — Stripe Tax that named a CONNECTED\n * account as the liable party. None exists today (no storefront path sets\n * `on_behalf_of`), and the bucket is here so that if one ever does, the\n * money moves out of `aglynLiable` visibly rather than silently.\n *\n * `aglynLiable` is a wire name the merchant's own tax report and the staff\n * return have both read since AGL-1904; it means \"the platform operator\".\n *\n * The classification comes from the stored `taxMode` / `taxLiability`, which\n * `storefront-tax.ts` derives from `automatic_tax.enabled` and never from the\n * presence of tax lines — a manual-mode subscription renewal carries genuine\n * Stripe Tax Rates (AGL-1751) and is otherwise indistinguishable.\n *\n * **This says nothing about marketplace-facilitator status.** It reports which\n * registration computed which tax and where the money is. Which line of a\n * filed return each bucket belongs on is a question for the preparer and for\n * counsel, and this module deliberately declines to answer it — which is\n * exactly why there is no merged total to mistake for one.\n *\n * Rows that cannot be classified or read are counted in `attention`, never\n * dropped and never zeroed.\n *\n * Pure and total: no Firestore, no clock. Read by the staff return's source\n * (`server/tax-return-source.ts`) and by the merchant's own tax report, so\n * both read the same rows through the same classifier.\n */\n\nimport {\n accumulateTaxWorkingPapers,\n asRowDate,\n emptyTaxJurisdiction,\n taxJurisdictionKey,\n type TaxLineInput,\n type TaxReturnJurisdiction,\n} from '@aglyn/aglyn/app-utils/tax-jurisdiction-figures'\n\n/** The subset of a `storefrontTaxCollected/{stripeId}` row this reads. */\nexport interface StorefrontTaxReturnRowInput {\n id: string\n hostId?: unknown\n orgId?: string | null\n taxMode?: unknown\n taxLiability?: unknown\n grossCents?: unknown\n taxCents?: unknown\n currency?: unknown\n customerAddress?: {\n country?: unknown\n state?: unknown\n } | null\n taxLines?: TaxLineInput[] | null\n paidAt?: unknown\n}\n\nexport interface StorefrontTaxBucket {\n transactionCount: number\n /** What shoppers paid, tax included. NOT the platform's revenue. */\n grossCents: number\n /** Stripe's `taxable_amount` summed; 0 for buckets that state no base. */\n taxableSalesCents: number\n taxCollectedCents: number\n /** Keyed `COUNTRY-STATE`, or `unknown`. */\n byJurisdiction: Record<string, TaxReturnJurisdiction>\n}\n\nexport interface StorefrontTaxSummary {\n periodStart: string\n periodEnd: string\n transactionCount: number\n /** Tax computed against the PLATFORM's registrations. See the module note. */\n aglynLiable: StorefrontTaxBucket\n /** The merchant's own configured tax. Never the platform's to remit. */\n merchantManual: StorefrontTaxBucket\n /** Stripe Tax that named a connected account liable. Empty today. */\n connectedAccountLiable: StorefrontTaxBucket\n attention: {\n /** Tax collected but no line states its base — see the module note. */\n rowsMissingTaxableBase: number\n rowsMissingAddress: number\n nonUsdRows: number\n rowsMissingPaidAt: number\n /** A `taxMode` this code does not recognize — never silently bucketed. */\n rowsUnclassified: number\n }\n}\n\nfunction cents(value: unknown): number {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) ? Math.round(parsed) : 0\n}\n\nfunction emptyBucket(): StorefrontTaxBucket {\n return {\n transactionCount: 0,\n grossCents: 0,\n taxableSalesCents: 0,\n taxCollectedCents: 0,\n byJurisdiction: {},\n }\n}\n\n/** The storefront figures from one period's rows. See the module note. */\nexport function storefrontTaxSummary(\n rows: readonly StorefrontTaxReturnRowInput[],\n period: { start: Date; end: Date },\n): StorefrontTaxSummary {\n const summary: StorefrontTaxSummary = {\n periodStart: period.start.toISOString(),\n periodEnd: period.end.toISOString(),\n transactionCount: 0,\n aglynLiable: emptyBucket(),\n merchantManual: emptyBucket(),\n connectedAccountLiable: emptyBucket(),\n attention: {\n rowsMissingTaxableBase: 0,\n rowsMissingAddress: 0,\n nonUsdRows: 0,\n rowsMissingPaidAt: 0,\n rowsUnclassified: 0,\n },\n }\n\n for (const row of rows ?? []) {\n const gross = cents(row.grossCents)\n const tax = cents(row.taxCents)\n const lines = Array.isArray(row.taxLines) ? row.taxLines : []\n const statedBases = lines\n .map((line) => line?.taxableAmountCents)\n .filter(\n (base): base is number =>\n base !== null && base !== undefined && Number.isFinite(Number(base)),\n )\n const taxableBase = statedBases.reduce(\n (sum, base) => sum + Math.round(Number(base)),\n 0,\n )\n\n const jurisdiction = taxJurisdictionKey(row.customerAddress)\n if (jurisdiction === 'unknown') summary.attention.rowsMissingAddress += 1\n if (String(row.currency ?? 'usd').toLowerCase() !== 'usd') {\n summary.attention.nonUsdRows += 1\n }\n if (!asRowDate(row.paidAt)) summary.attention.rowsMissingPaidAt += 1\n\n // The bucket is chosen from the STORED classification, and an unfamiliar\n // one falls through to `rowsUnclassified` rather than defaulting into a\n // bucket — a default here would put a merchant's tax on the platform's\n // return, or the platform's tax nowhere, and neither is allowed to happen\n // quietly.\n const mode = String(row.taxMode ?? '')\n const liability = String(row.taxLiability ?? '')\n const bucket =\n mode === 'stripe-automatic' && liability === 'connected-account'\n ? summary.connectedAccountLiable\n : mode === 'stripe-automatic'\n ? summary.aglynLiable\n : mode === 'manual'\n ? summary.merchantManual\n : null\n if (!bucket) {\n summary.attention.rowsUnclassified += 1\n continue\n }\n\n // A taxed row that cannot state the base the rate was applied to —\n // counted ONLY for Stripe-computed tax. A manual-mode row has no Stripe\n // base by construction (Stripe was never told the amount was tax), so\n // flagging it here would raise a permanent alarm about a figure that can\n // never exist, and an alarm that is always on is an alarm nobody reads.\n if (mode === 'stripe-automatic' && tax > 0 && statedBases.length === 0) {\n summary.attention.rowsMissingTaxableBase += 1\n }\n\n summary.transactionCount += 1\n bucket.transactionCount += 1\n bucket.grossCents += gross\n bucket.taxableSalesCents += taxableBase\n bucket.taxCollectedCents += tax\n const jurisdictionBucket = (bucket.byJurisdiction[jurisdiction] ??=\n emptyTaxJurisdiction())\n jurisdictionBucket.transactionCount += 1\n jurisdictionBucket.totalSalesCents += gross - tax\n jurisdictionBucket.taxableSalesCents += taxableBase\n jurisdictionBucket.taxCollectedCents += tax\n // The storefront working papers (AGL-2329). Its lines carry the richer\n // detail — `percentage`, `rateState`, `jurisdiction` — three fields the\n // writer annotates \"for the working papers\".\n accumulateTaxWorkingPapers(jurisdictionBucket, lines)\n }\n\n return summary\n}\n"],"names":["accumulateTaxWorkingPapers","asRowDate","emptyTaxJurisdiction","taxJurisdictionKey","cents","value","parsed","Number","isFinite","Math","round","emptyBucket","transactionCount","grossCents","taxableSalesCents","taxCollectedCents","byJurisdiction","storefrontTaxSummary","rows","period","summary","periodStart","start","toISOString","periodEnd","end","aglynLiable","merchantManual","connectedAccountLiable","attention","rowsMissingTaxableBase","rowsMissingAddress","nonUsdRows","rowsMissingPaidAt","rowsUnclassified","row","bucket","jurisdiction","gross","tax","taxCents","lines","Array","isArray","taxLines","statedBases","map","line","taxableAmountCents","filter","base","undefined","taxableBase","reduce","sum","customerAddress","String","currency","toLowerCase","paidAt","mode","taxMode","liability","taxLiability","length","jurisdictionBucket","totalSalesCents"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDC,GAED,SACEA,0BAA0B,EAC1BC,SAAS,EACTC,oBAAoB,EACpBC,kBAAkB,QAGb,kDAAiD;AAoDxD,SAASC,MAAMC,KAAc;IAC3B,MAAMC,SAASC,OAAOF,gBAAAA,QAAS;IAC/B,OAAOE,OAAOC,QAAQ,CAACF,UAAUG,KAAKC,KAAK,CAACJ,UAAU;AACxD;AAEA,SAASK;IACP,OAAO;QACLC,kBAAkB;QAClBC,YAAY;QACZC,mBAAmB;QACnBC,mBAAmB;QACnBC,gBAAgB,CAAC;IACnB;AACF;AAEA,wEAAwE,GACxE,OAAO,SAASC,qBACdC,IAA4C,EAC5CC,MAAkC;IAElC,MAAMC,UAAgC;QACpCC,aAAaF,OAAOG,KAAK,CAACC,WAAW;QACrCC,WAAWL,OAAOM,GAAG,CAACF,WAAW;QACjCX,kBAAkB;QAClBc,aAAaf;QACbgB,gBAAgBhB;QAChBiB,wBAAwBjB;QACxBkB,WAAW;YACTC,wBAAwB;YACxBC,oBAAoB;YACpBC,YAAY;YACZC,mBAAmB;YACnBC,kBAAkB;QACpB;IACF;IAEA,KAAK,MAAMC,OAAOjB,eAAAA,OAAQ,EAAE,CAAE;YAiBjBiB,eAUSA,cACKA,mBA4BGC,wBAAsBC,eAAtBD;QAvD5B,MAAME,QAAQlC,MAAM+B,IAAItB,UAAU;QAClC,MAAM0B,MAAMnC,MAAM+B,IAAIK,QAAQ;QAC9B,MAAMC,QAAQC,MAAMC,OAAO,CAACR,IAAIS,QAAQ,IAAIT,IAAIS,QAAQ,GAAG,EAAE;QAC7D,MAAMC,cAAcJ,MACjBK,GAAG,CAAC,CAACC,OAASA,wBAAAA,KAAMC,kBAAkB,EACtCC,MAAM,CACL,CAACC,OACCA,SAAS,QAAQA,SAASC,aAAa5C,OAAOC,QAAQ,CAACD,OAAO2C;QAEpE,MAAME,cAAcP,YAAYQ,MAAM,CACpC,CAACC,KAAKJ,OAASI,MAAM7C,KAAKC,KAAK,CAACH,OAAO2C,QACvC;QAGF,MAAMb,eAAelC,mBAAmBgC,IAAIoB,eAAe;QAC3D,IAAIlB,iBAAiB,WAAWjB,QAAQS,SAAS,CAACE,kBAAkB,IAAI;QACxE,IAAIyB,QAAOrB,gBAAAA,IAAIsB,QAAQ,YAAZtB,gBAAgB,OAAOuB,WAAW,OAAO,OAAO;YACzDtC,QAAQS,SAAS,CAACG,UAAU,IAAI;QAClC;QACA,IAAI,CAAC/B,UAAUkC,IAAIwB,MAAM,GAAGvC,QAAQS,SAAS,CAACI,iBAAiB,IAAI;QAEnE,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,0EAA0E;QAC1E,WAAW;QACX,MAAM2B,OAAOJ,QAAOrB,eAAAA,IAAI0B,OAAO,YAAX1B,eAAe;QACnC,MAAM2B,YAAYN,QAAOrB,oBAAAA,IAAI4B,YAAY,YAAhB5B,oBAAoB;QAC7C,MAAMC,SACJwB,SAAS,sBAAsBE,cAAc,sBACzC1C,QAAQQ,sBAAsB,GAC9BgC,SAAS,qBACPxC,QAAQM,WAAW,GACnBkC,SAAS,WACPxC,QAAQO,cAAc,GACtB;QACV,IAAI,CAACS,QAAQ;YACXhB,QAAQS,SAAS,CAACK,gBAAgB,IAAI;YACtC;QACF;QAEA,mEAAmE;QACnE,wEAAwE;QACxE,sEAAsE;QACtE,yEAAyE;QACzE,wEAAwE;QACxE,IAAI0B,SAAS,sBAAsBrB,MAAM,KAAKM,YAAYmB,MAAM,KAAK,GAAG;YACtE5C,QAAQS,SAAS,CAACC,sBAAsB,IAAI;QAC9C;QAEAV,QAAQR,gBAAgB,IAAI;QAC5BwB,OAAOxB,gBAAgB,IAAI;QAC3BwB,OAAOvB,UAAU,IAAIyB;QACrBF,OAAOtB,iBAAiB,IAAIsC;QAC5BhB,OAAOrB,iBAAiB,IAAIwB;QAC5B,MAAM0B,sBAAsB7B,IAAAA,CAAAA,yBAAAA,OAAOpB,cAAc,CAAA,CAACqB,gBAAAA,aAAa,YAAnCD,IAAAA,sBAAqB,CAACC,cAAa,GAC7DnC;QACF+D,mBAAmBrD,gBAAgB,IAAI;QACvCqD,mBAAmBC,eAAe,IAAI5B,QAAQC;QAC9C0B,mBAAmBnD,iBAAiB,IAAIsC;QACxCa,mBAAmBlD,iBAAiB,IAAIwB;QACxC,uEAAuE;QACvE,wEAAwE;QACxE,6CAA6C;QAC7CvC,2BAA2BiE,oBAAoBxB;IACjD;IAEA,OAAOrB;AACT"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements';
18
+ /**
19
+ * COMMERCE'S PLAN GATES (AGL-3080): the storefront features that start above
20
+ * the entry tiers. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest
21
+ * generator (`register.planEntitlements`), so the plan comparison and every
22
+ * gate read the answers they always have.
23
+ */
24
+ declare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {
25
+ interface PluginEntitlementFeatures {
26
+ /** Recurring storefront subscription products (AGL-303). */
27
+ storefrontSubscriptions?: boolean;
28
+ /** Gift cards & store credit (AGL-322). */
29
+ giftCards?: boolean;
30
+ /** Commerce analytics dashboard (AGL-327). */
31
+ commerceAnalytics?: boolean;
32
+ }
33
+ }
34
+ /** Commerce's plan gates, for the manifest generator. */
35
+ export declare function commercePlanEntitlements(): PluginPlanEntitlementsDeclaration;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // Types only: the manifest generator loads this module on its own to compile
17
+ // the plan figures, so nothing here may reach a core module at runtime.
18
+ import { _ as _extends } from "@swc/helpers/_/_extends";
19
+ /** Business and up. */ const FROM_BUSINESS = {
20
+ free: false,
21
+ starter: false,
22
+ pro: false,
23
+ business: true,
24
+ scale: true,
25
+ advanced: true,
26
+ agency: true,
27
+ enterprise: true
28
+ };
29
+ /** Pro and up. */ const FROM_PRO = _extends({}, FROM_BUSINESS, {
30
+ pro: true
31
+ });
32
+ /** Commerce's plan gates, for the manifest generator. */ export function commercePlanEntitlements() {
33
+ return {
34
+ features: [
35
+ {
36
+ key: 'storefrontSubscriptions',
37
+ label: 'Storefront subscriptions',
38
+ byPlan: FROM_BUSINESS
39
+ },
40
+ {
41
+ key: 'giftCards',
42
+ label: 'Gift cards',
43
+ byPlan: FROM_BUSINESS
44
+ },
45
+ {
46
+ key: 'commerceAnalytics',
47
+ label: 'Commerce analytics',
48
+ byPlan: FROM_PRO
49
+ }
50
+ ]
51
+ };
52
+ }
53
+
54
+ //# sourceMappingURL=plan-entitlements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plan-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Types only: the manifest generator loads this module on its own to compile\n// the plan figures, so nothing here may reach a core module at runtime.\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport type { PluginPlanEntitlementsDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-plan-entitlements'\n\n/**\n * COMMERCE'S PLAN GATES (AGL-3080): the storefront features that start above\n * the entry tiers. Compiled into core's `PLAN_ENTITLEMENTS` by the manifest\n * generator (`register.planEntitlements`), so the plan comparison and every\n * gate read the answers they always have.\n */\ndeclare module '@aglyn/aglyn/plugin-manager/plugin-entitlement-keys' {\n interface PluginEntitlementFeatures {\n /** Recurring storefront subscription products (AGL-303). */\n storefrontSubscriptions?: boolean\n /** Gift cards & store credit (AGL-322). */\n giftCards?: boolean\n /** Commerce analytics dashboard (AGL-327). */\n commerceAnalytics?: boolean\n }\n}\n\n/** Business and up. */\nconst FROM_BUSINESS: Readonly<Record<OrgPlan, boolean>> = {\n free: false,\n starter: false,\n pro: false,\n business: true,\n scale: true,\n advanced: true,\n agency: true,\n enterprise: true,\n}\n\n/** Pro and up. */\nconst FROM_PRO: Readonly<Record<OrgPlan, boolean>> = {\n ...FROM_BUSINESS,\n pro: true,\n}\n\n/** Commerce's plan gates, for the manifest generator. */\nexport function commercePlanEntitlements(): PluginPlanEntitlementsDeclaration {\n return {\n features: [\n {\n key: 'storefrontSubscriptions',\n label: 'Storefront subscriptions',\n byPlan: FROM_BUSINESS,\n },\n { key: 'giftCards', label: 'Gift cards', byPlan: FROM_BUSINESS },\n {\n key: 'commerceAnalytics',\n label: 'Commerce analytics',\n byPlan: FROM_PRO,\n },\n ],\n }\n}\n"],"names":["FROM_BUSINESS","free","starter","pro","business","scale","advanced","agency","enterprise","FROM_PRO","commercePlanEntitlements","features","key","label","byPlan"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,6EAA6E;AAC7E,wEAAwE;;AAqBxE,qBAAqB,GACrB,MAAMA,gBAAoD;IACxDC,MAAM;IACNC,SAAS;IACTC,KAAK;IACLC,UAAU;IACVC,OAAO;IACPC,UAAU;IACVC,QAAQ;IACRC,YAAY;AACd;AAEA,gBAAgB,GAChB,MAAMC,WAA+C,aAChDT;IACHG,KAAK;;AAGP,uDAAuD,GACvD,OAAO,SAASO;IACd,OAAO;QACLC,UAAU;YACR;gBACEC,KAAK;gBACLC,OAAO;gBACPC,QAAQd;YACV;YACA;gBAAEY,KAAK;gBAAaC,OAAO;gBAAcC,QAAQd;YAAc;YAC/D;gBACEY,KAAK;gBACLC,OAAO;gBACPC,QAAQL;YACV;SACD;IACH;AACF"}
package/src/lib/plugin.js CHANGED
@@ -19,9 +19,14 @@ import { lazy } from "react";
19
19
  const PosConsolePage = lazy(()=>import("./components/console/pos-page.component.js"));
20
20
  const CommerceGlanceCard = lazy(()=>import("./components/console/commerce-glance-card.component.js"));
21
21
  const NewestSiteUsersCard = lazy(()=>import("./components/console/newest-site-users-card.component.js"));
22
+ const SiteMemberPurchases = lazy(()=>import("./components/console/site-member-purchases.component.js"));
22
23
  import { COMMERCE_CONSOLE_SECTIONS } from "./components/commerce-console-sections.js";
23
24
  import { BUNDLE_ID } from "./constants/bundle-common.js";
24
25
  import { COMMERCE_PERMISSIONS } from "./model/plugin-permissions.js";
26
+ import { registerCommerceRecordLists } from "./model/commerce-record-lists.js";
27
+ import { registerCommerceRecordRoutes } from "./model/commerce-record-routes.js";
28
+ import { registerCommerceZones } from "./components/console/product-zones.js";
29
+ import { COMMERCE_SEARCH_SOURCES } from "./model/commerce-search-sources.js";
25
30
  import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
26
31
  /** Code-split: the Products console page only loads when opened. */ const CommerceConsolePage = lazy(()=>import("./components/commerce-console-page.js"));
27
32
  /**
@@ -34,6 +39,12 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
34
39
  * tracked as follow-up. The nav item omits a `Component`, so the shell's
35
40
  * nav renders the link and the existing named route serves the page.
36
41
  */ export function registerCommerceConsole() {
42
+ // Where a product is read, for surfaces outside this plugin (AGL-3080).
43
+ registerCommerceRecordRoutes();
44
+ registerCommerceRecordLists();
45
+ // The zones its product editor, products hub and CSV import dialog host,
46
+ // with the props each hands a widget (AGL-2916, AGL-3080).
47
+ registerCommerceZones();
37
48
  // Plugin-declared permissions (AGL-435): tier defaults ride every
38
49
  // resolved role set; custom roles override key-by-key.
39
50
  Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS);
@@ -43,6 +54,7 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
43
54
  Aglyn.registerConsoleExtension({
44
55
  pluginId: BUNDLE_ID,
45
56
  displayName: 'Commerce',
57
+ searchSources: COMMERCE_SEARCH_SOURCES,
46
58
  // Dashboard/analytics glance card (AGL-419): rendered through the
47
59
  // shell's 'commerceGlance' widget slot.
48
60
  widgets: [
@@ -51,6 +63,15 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
51
63
  widgetId: 'commerce-glance',
52
64
  title: 'Commerce',
53
65
  Component: CommerceGlanceCard
66
+ },
67
+ // What a site user bought — lifetime total, orders and subscriptions —
68
+ // in their drawer on the site's Users page: the orders and
69
+ // subscriptions are this plugin's.
70
+ {
71
+ slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,
72
+ widgetId: 'commerce-site-member-purchases',
73
+ title: 'Purchases',
74
+ Component: SiteMemberPurchases
54
75
  }
55
76
  ],
56
77
  navItems: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nimport { COMMERCE_CONSOLE_SECTIONS } from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","CommerceGlanceCard","NewestSiteUsersCard","COMMERCE_CONSOLE_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","widgets","slot","widgetId","title","Component","navItems","label","href","icon","path","sections","header","docsTopic","permission","ACCOUNTS_PLUGIN_ID","CONSOLE_WIDGET_SLOTS","hostDashboard"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,oBAAoB,QAAQ,yBAAwB;AAC7D,SAASC,IAAI,QAAQ,QAAO;AAC5B,MAAMC,iBAAiBD,KAAK,IAAM,MAAM,CAAC;AACzC,MAAME,qBAAqBF,KACzB,IAAM,MAAM,CAAC;AAEf,MAAMG,sBAAsBH,KAC1B,IAAM,MAAM,CAAC;AAEf,SAASI,yBAAyB,QAAQ,4CAAwC;AAClF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,oBAAoB,QAAQ,gCAA4B;AACjE,SAASC,sBAAsB,QAAQ,qBAAiB;AAExD,kEAAkE,GAClE,MAAMC,sBAAsBR,KAAK,IAAM,MAAM,CAAC;AAE9C;;;;;;;;;CASC,GACD,OAAO,SAASS;IACd,kEAAkE;IAClE,uDAAuD;IACvDX,MAAMY,yBAAyB,CAACJ;IAChC,2EAA2E;IAC3E,kEAAkE;IAClER,MAAMa,0BAA0B,CAACJ;IACjCT,MAAMc,wBAAwB,CAAC;QAC7BC,UAAUR;QACVS,aAAa;QACb,kEAAkE;QAClE,wCAAwC;QACxCC,SAAS;YACP;gBACEC,MAAM;gBACNC,UAAU;gBACVC,OAAO;gBACPC,WAAWjB;YACb;SACD;QACDkB,UAAU;YACR;gBACEC,OAAO;gBACPC,MAAM;gBACNC,MAAM;oBAAEC,MAAMzB,qBAAqByB,IAAI;gBAAC;gBACxC,oEAAoE;gBACpE,qEAAqE;gBACrE,6CAA6C;gBAC7CC,UAAUrB;gBACVsB,QAAQ;oBACNR,OAAO;oBACPK,MAAM;wBAAEC,MAAMzB,qBAAqByB,IAAI;oBAAC;oBACxC,kEAAkE;oBAClE,0DAA0D;oBAC1DG,WAAW;gBACb;gBACAR,WAAWX;YACb;YACA;gBACE,oEAAoE;gBACpE,gEAAgE;gBAChEa,OAAO;gBACPC,MAAM;gBACN;;;;;;;;;;;;;;;;;;;;;;;SAuBC,GACDM,YAAY;gBACZL,MAAM;oBAAEC,MAAMzB,qBAAqByB,IAAI;gBAAC;gBACxCE,QAAQ;oBAAER,OAAO;oBAAiBS,WAAW;gBAAM;gBACnDR,WAAWlB;YACb;SACD;IACH;IACA;;;;;;;;;;;;;;GAcC,GACDH,MAAMc,wBAAwB,CAAC;QAC7BC,UAAUf,MAAM+B,kBAAkB;QAClCf,aAAa;QACbC,SAAS;YACP;gBACEC,MAAMlB,MAAMgC,oBAAoB,CAACC,aAAa;gBAC9Cd,UAAU;gBACVC,OAAO;gBACPC,WAAWhB;YACb;SACD;IACH;AACF;AAEA,cAAc,YAAQ"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nconst SiteMemberPurchases = lazy(\n () => import('./components/console/site-member-purchases.component'),\n)\nimport { COMMERCE_CONSOLE_SECTIONS } from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { registerCommerceRecordLists } from './model/commerce-record-lists'\nimport { registerCommerceRecordRoutes } from './model/commerce-record-routes'\nimport { registerCommerceZones } from './components/console/product-zones'\nimport { COMMERCE_SEARCH_SOURCES } from './model/commerce-search-sources'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Where a product is read, for surfaces outside this plugin (AGL-3080).\n registerCommerceRecordRoutes()\n registerCommerceRecordLists()\n // The zones its product editor, products hub and CSV import dialog host,\n // with the props each hands a widget (AGL-2916, AGL-3080).\n registerCommerceZones()\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n searchSources: COMMERCE_SEARCH_SOURCES,\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n // What a site user bought — lifetime total, orders and subscriptions —\n // in their drawer on the site's Users page: the orders and\n // subscriptions are this plugin's.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,\n widgetId: 'commerce-site-member-purchases',\n title: 'Purchases',\n Component: SiteMemberPurchases,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","CommerceGlanceCard","NewestSiteUsersCard","SiteMemberPurchases","COMMERCE_CONSOLE_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","registerCommerceRecordLists","registerCommerceRecordRoutes","registerCommerceZones","COMMERCE_SEARCH_SOURCES","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","searchSources","widgets","slot","widgetId","title","Component","CONSOLE_WIDGET_SLOTS","siteMember","navItems","label","href","icon","path","sections","header","docsTopic","permission","ACCOUNTS_PLUGIN_ID","hostDashboard"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,oBAAoB,QAAQ,yBAAwB;AAC7D,SAASC,IAAI,QAAQ,QAAO;AAC5B,MAAMC,iBAAiBD,KAAK,IAAM,MAAM,CAAC;AACzC,MAAME,qBAAqBF,KACzB,IAAM,MAAM,CAAC;AAEf,MAAMG,sBAAsBH,KAC1B,IAAM,MAAM,CAAC;AAEf,MAAMI,sBAAsBJ,KAC1B,IAAM,MAAM,CAAC;AAEf,SAASK,yBAAyB,QAAQ,4CAAwC;AAClF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,oBAAoB,QAAQ,gCAA4B;AACjE,SAASC,2BAA2B,QAAQ,mCAA+B;AAC3E,SAASC,4BAA4B,QAAQ,oCAAgC;AAC7E,SAASC,qBAAqB,QAAQ,wCAAoC;AAC1E,SAASC,uBAAuB,QAAQ,qCAAiC;AACzE,SAASC,sBAAsB,QAAQ,qBAAiB;AAExD,kEAAkE,GAClE,MAAMC,sBAAsBb,KAAK,IAAM,MAAM,CAAC;AAE9C;;;;;;;;;CASC,GACD,OAAO,SAASc;IACd,wEAAwE;IACxEL;IACAD;IACA,yEAAyE;IACzE,2DAA2D;IAC3DE;IACA,kEAAkE;IAClE,uDAAuD;IACvDZ,MAAMiB,yBAAyB,CAACR;IAChC,2EAA2E;IAC3E,kEAAkE;IAClET,MAAMkB,0BAA0B,CAACJ;IACjCd,MAAMmB,wBAAwB,CAAC;QAC7BC,UAAUZ;QACVa,aAAa;QACbC,eAAeT;QACf,kEAAkE;QAClE,wCAAwC;QACxCU,SAAS;YACP;gBACEC,MAAM;gBACNC,UAAU;gBACVC,OAAO;gBACPC,WAAWvB;YACb;YACA,uEAAuE;YACvE,2DAA2D;YAC3D,mCAAmC;YACnC;gBACEoB,MAAMxB,MAAM4B,oBAAoB,CAACC,UAAU;gBAC3CJ,UAAU;gBACVC,OAAO;gBACPC,WAAWrB;YACb;SACD;QACDwB,UAAU;YACR;gBACEC,OAAO;gBACPC,MAAM;gBACNC,MAAM;oBAAEC,MAAMjC,qBAAqBiC,IAAI;gBAAC;gBACxC,oEAAoE;gBACpE,qEAAqE;gBACrE,6CAA6C;gBAC7CC,UAAU5B;gBACV6B,QAAQ;oBACNV,OAAO;oBACPO,MAAM;wBAAEC,MAAMjC,qBAAqBiC,IAAI;oBAAC;oBACxC,kEAAkE;oBAClE,0DAA0D;oBAC1DG,WAAW;gBACb;gBACAV,WAAWZ;YACb;YACA;gBACE,oEAAoE;gBACpE,gEAAgE;gBAChEgB,OAAO;gBACPC,MAAM;gBACN;;;;;;;;;;;;;;;;;;;;;;;SAuBC,GACDM,YAAY;gBACZL,MAAM;oBAAEC,MAAMjC,qBAAqBiC,IAAI;gBAAC;gBACxCE,QAAQ;oBAAEV,OAAO;oBAAiBW,WAAW;gBAAM;gBACnDV,WAAWxB;YACb;SACD;IACH;IACA;;;;;;;;;;;;;;GAcC,GACDH,MAAMmB,wBAAwB,CAAC;QAC7BC,UAAUpB,MAAMuC,kBAAkB;QAClClB,aAAa;QACbE,SAAS;YACP;gBACEC,MAAMxB,MAAM4B,oBAAoB,CAACY,aAAa;gBAC9Cf,UAAU;gBACVC,OAAO;gBACPC,WAAWtB;YACb;SACD;IACH;AACF;AAEA,cAAc,YAAQ"}