@aglyn/aglyn 1.0.0-beta.147 → 1.0.0-beta.149
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.
- package/package.json +10 -10
- package/src/lib/app-utils/binding-token-catalog.js +9 -2
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/collection-entries.d.ts +52 -3
- package/src/lib/app-utils/collection-entries.js +98 -9
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +2 -0
- package/src/lib/app-utils/console-routes.js +14 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +3 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +1 -1
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +136 -2
- package/src/lib/app-utils/notifications.js +139 -3
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/foundation/definitions/host-content-collections.d.ts +42 -40
- package/src/lib/foundation/definitions/host-content-collections.js +47 -109
- package/src/lib/foundation/definitions/host-content-collections.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +70 -0
- package/src/lib/plugin-manager/feature-plugins.js +27 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +13 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +257 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +16 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +27 -5
- package/src/lib/plugin-manager/plugin-host-collections.js +25 -16
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-org-capacity.d.ts +57 -0
- package/src/lib/plugin-manager/plugin-org-capacity.js +68 -0
- package/src/lib/plugin-manager/plugin-org-capacity.js.map +1 -0
- package/src/lib/plugin-manager/record-captured-contact.d.ts +89 -0
- package/src/lib/plugin-manager/record-captured-contact.js +128 -0
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -0
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ /**
|
|
17
|
-
*
|
|
17
|
+
* The host subcollections CORE owns, and how the media usage scan reads them
|
|
18
|
+
* (AGL-1867, AGL-3080).
|
|
18
19
|
*
|
|
19
20
|
* ## Why this exists, and why it is shaped the way it is
|
|
20
21
|
*
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
* feared is only necessary if the default is NOT to scan. So the default here
|
|
34
35
|
* is inverted:
|
|
35
36
|
*
|
|
36
|
-
* **Every host subcollection is scanned unless it is named
|
|
37
|
-
*
|
|
37
|
+
* **Every host subcollection is scanned unless it is named with a reason it is
|
|
38
|
+
* not.**
|
|
38
39
|
*
|
|
39
40
|
* That turns the open question from "does this carry media?" — which needs a
|
|
40
41
|
* judgement about a schema nobody wrote down — into "is scanning this wasteful
|
|
@@ -43,15 +44,27 @@
|
|
|
43
44
|
* thought about is READ, so a plugin shipping a new media-bearing collection is
|
|
44
45
|
* covered the day it lands rather than the day somebody remembers it.
|
|
45
46
|
*
|
|
46
|
-
* ##
|
|
47
|
+
* ## Core holds core's, and asks the plugins for theirs (AGL-3080)
|
|
48
|
+
*
|
|
49
|
+
* This file used to hold every collection in the product: which ones commerce
|
|
50
|
+
* writes, which ones marketing writes, and which console page each deep-links
|
|
51
|
+
* to. That is the platform keeping a map of its plugins' storage, and the map
|
|
52
|
+
* went stale exactly where a stale map is invisible — three of its deep links
|
|
53
|
+
* pointed at a hub that did not hold the document, and nothing could tell.
|
|
54
|
+
*
|
|
55
|
+
* So a plugin declares its own collections in `plugins.config.json` and the
|
|
56
|
+
* readers ask `plugin-manager/plugin-host-collections`. What is left here is
|
|
57
|
+
* core's own storage, which is the only part core is entitled to name.
|
|
58
|
+
*
|
|
59
|
+
* ## What keeps these lists from rotting
|
|
47
60
|
*
|
|
48
61
|
* `host-content-media-coverage.spec.ts` sweeps `apps/**` and `libs/**` for host
|
|
49
62
|
* subcollection names — the same derived sweep
|
|
50
63
|
* `host-subcollection-write-deny-coverage.spec.ts` uses, for the same reason —
|
|
51
|
-
* and asserts
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
64
|
+
* and asserts the sweep is EXACTLY these three lists plus the plugins'
|
|
65
|
+
* declarations. A new collection fails the build with a one-line decision to
|
|
66
|
+
* make, and the default answer — declare it scanned, on the plugin that writes
|
|
67
|
+
* it — is also the safe one.
|
|
55
68
|
*
|
|
56
69
|
* ## The plugin boundary was NOT crossed to do this
|
|
57
70
|
*
|
|
@@ -89,119 +102,41 @@
|
|
|
89
102
|
'collections'
|
|
90
103
|
];
|
|
91
104
|
/**
|
|
92
|
-
*
|
|
105
|
+
* Core's own host subcollections that the scan reads generically.
|
|
106
|
+
*
|
|
107
|
+
* Generically: no per-collection field list and no per-collection decoder.
|
|
108
|
+
* Each document is flattened by `documentHaystack` — which decodes `nodes` and
|
|
109
|
+
* `elements` through `decodeStoredNodes` first — and the matching field's
|
|
110
|
+
* dotted path is recovered by walking the document. A field list here would be
|
|
111
|
+
* the same staleness trap `referencingFieldPath` was written to avoid one
|
|
112
|
+
* level down.
|
|
113
|
+
*
|
|
114
|
+
* Short because it is only core's: a blog author, the per-plugin settings
|
|
115
|
+
* document the plugin manager owns, and a site template. Everything else that
|
|
116
|
+
* is scanned generically belongs to a plugin and is declared by it.
|
|
117
|
+
*/ export const CORE_GENERIC_SCAN_COLLECTIONS = [
|
|
118
|
+
'authors',
|
|
119
|
+
'pluginSettings',
|
|
120
|
+
'templates'
|
|
121
|
+
];
|
|
122
|
+
/**
|
|
123
|
+
* Core's own host subcollections that the media scan deliberately does NOT
|
|
124
|
+
* read, and why.
|
|
93
125
|
*
|
|
94
126
|
* Every entry has to answer one of two questions: what would scanning it cost,
|
|
95
127
|
* or what would scanning it get wrong. "It probably has no images in it" is not
|
|
96
128
|
* an accepted reason — that is the guess this file is arranged to avoid making.
|
|
129
|
+
* A plugin's exclusions carry the same burden and live in its own declaration,
|
|
130
|
+
* where `mediaScanReason` is required with `mediaScan: 'none'`.
|
|
97
131
|
*/ export const MEDIA_SCAN_EXCLUDED = {
|
|
98
|
-
|
|
99
|
-
media: 'The library itself. The asset under audit IS a document in here, and its ' + 'own record holds its own url, cdnPath and storage path — every needle ' + 'the scan carries. Reading this collection would report every asset as ' + 'referencing itself, which turns a deletion-safety control into noise ' + 'that nobody reads.',
|
|
132
|
+
media: 'The library itself. The asset under audit IS a document in here, and ' + 'its own record holds its own url, cdnPath and storage path — every ' + 'needle the scan carries. Reading this collection would report every ' + 'asset as referencing itself, which turns a deletion-safety control into ' + 'noise that nobody reads.',
|
|
100
133
|
mediaFolders: 'Folder records: a name, a parent pointer and a scope. A folder never ' + 'holds an asset reference, and it is not a dependent either — deleting a ' + 'file does not break the folder it sat in.',
|
|
101
|
-
emailCampaigns: 'The campaign CONTAINER — a name, a date window, the list ids it is ' + 'aimed at and a topic. Distinct from `campaigns`, which holds the sends ' + 'and IS scanned because a send carries the copy that went out. A ' + 'container carries no copy and no asset reference: the design a send ' + 'renders is a screen, and the screen is where the picker writes.',
|
|
102
|
-
// ── Machine-written telemetry: unbounded, and no author picks into it ──
|
|
103
134
|
analytics: 'Per-day traffic rollups written by the tenant collector. One document ' + 'per day per site forever, no author-editable field, and no surface that ' + 'can pick an asset into one.',
|
|
104
135
|
screenAnalytics: 'Per-screen traffic history behind the Pro+ panel, written by the same ' + 'collector on the same unbounded per-day cadence.',
|
|
105
136
|
counters: 'Quota and usage counters, server-owned (AGL-1367) and denied to client ' + 'writes outright. Numbers, not content.',
|
|
106
137
|
activity: 'The site activity feed — an append-only audit log the console writes a ' + 'row into on every change. Unbounded by design, and a row that quotes an ' + 'asset id is a record that somebody touched it, not a place it is used.',
|
|
107
|
-
// ── Transaction and ledger rows: unbounded, and a SNAPSHOT is not a use ─
|
|
108
|
-
orders: 'Completed orders. Line items COPY a product\'s `imageUrl` at purchase ' + 'time, so these would match — and matching would be the wrong answer: an ' + 'order is an immutable record of what was sold, deleting the asset ' + 'changes nothing about it, and no author can edit one. A busy store also ' + 'holds more of these than the whole read budget.',
|
|
109
|
-
carts: 'Live and abandoned carts, with the same copied line-item image and the ' + 'same reasoning, at higher volume — one document per shopper session.',
|
|
110
|
-
checkouts: 'In-flight checkout sessions. Transient buyer state carrying the same ' + 'line-item snapshot as `carts`.',
|
|
111
|
-
formSubmissions: 'Visitor form submissions. Unbounded, PII-heavy, and a file attached to ' + 'one is the visitor\'s upload — not a library asset an author picked, and ' + 'not something deleting a library asset would break.',
|
|
112
|
-
leads: 'Captured leads, on the same footing as `formSubmissions`: visitor-' + 'submitted, unbounded, and never a place an author places an asset.',
|
|
113
|
-
bookings: 'Booking records — a customer, a time and a service pointer. The service ' + 'holds the imagery and IS scanned; a booking is the transaction against ' + 'it.',
|
|
114
|
-
campaignAttributions: 'One row per conversion — a form submission, a lead, a contact or a ' + 'booking — recording which campaign the visitor arrived from. Written ' + 'only by the Admin SDK, and the whole record is three sanitized UTM ' + 'strings capped at a hundred characters: there is no field an asset ' + 'could occupy and no surface that could pick one into it. Unbounded on ' + 'the same footing as the conversions it credits.',
|
|
115
|
-
reservations: 'POS and booking holds. Short-lived transaction rows pointing at a ' + 'resource that is itself scanned.',
|
|
116
|
-
stockHolds: 'Inventory holds taken during checkout. Machine-written, short-lived, ' + 'and a quantity rather than content.',
|
|
117
|
-
subscriptions: 'Site membership subscriptions: a plan pointer, a status and Stripe ' + 'ids. Server-written from the billing webhook.',
|
|
118
|
-
registers: 'POS register allocations — a count against the register add-on, read by ' + 'billing (AGL-1775). No content field at all.',
|
|
119
|
-
giftCards: 'Gift card balances and redemption history. Money, not content.',
|
|
120
|
-
licenseKeys: 'Digital-product license keys issued at fulfilment: a code, an order ' + 'pointer and a revocation flag.',
|
|
121
|
-
inventoryAdjustments: 'The append-only stock adjustment ledger (AGL-2269). One row per manual ' + 'stock edit and per cancellation release, unbounded over a store\'s life.',
|
|
122
|
-
inventoryReconciliation: 'Reconciliation runs comparing counted stock against recorded stock. ' + 'Machine-written totals.',
|
|
123
|
-
restockAlerts: 'Back-in-stock requests: an email address and a product pointer, written ' + 'by visitors.',
|
|
124
|
-
suppressions: 'Unsubscribes, bounces and spam complaints. Email addresses and a ' + 'reason.',
|
|
125
|
-
stripeTaxRates: 'Cached Stripe tax rate ids, written by the tax sync. Vendor ids.',
|
|
126
138
|
members: 'The ORG-facing member roster mirrored onto the site, server-owned ' + '(AGL-1367). A member\'s photo is their own account photo, uploaded ' + 'through /api/account/photo and stored outside any site\'s library, so it ' + 'is not an asset this scan can be asked about.'
|
|
127
139
|
};
|
|
128
|
-
/**
|
|
129
|
-
* Host subcollections the media scan reads generically.
|
|
130
|
-
*
|
|
131
|
-
* Generically: no per-collection field list and no per-collection decoder.
|
|
132
|
-
* Each document is flattened by `documentHaystack` — which decodes `nodes` and
|
|
133
|
-
* `elements` through `decodeStoredNodes` first, so a plugin that adopts the
|
|
134
|
-
* compressed storage form is covered without touching this file — and the
|
|
135
|
-
* matching field's dotted path is recovered by walking the document. A field
|
|
136
|
-
* list here would be the same staleness trap `referencingFieldPath` was written
|
|
137
|
-
* to avoid one level down.
|
|
138
|
-
*
|
|
139
|
-
* MUST equal the repo-wide host subcollection sweep minus
|
|
140
|
-
* {@link CORE_CONTENT_COLLECTIONS} minus {@link MEDIA_SCAN_EXCLUDED}. The guard
|
|
141
|
-
* spec asserts exactly that, in both directions, so this list can neither miss
|
|
142
|
-
* a new collection nor keep naming a deleted one.
|
|
143
|
-
*/ export const PLUGIN_CONTENT_COLLECTIONS = [
|
|
144
|
-
'actions',
|
|
145
|
-
'authors',
|
|
146
|
-
'campaigns',
|
|
147
|
-
'coupons',
|
|
148
|
-
'discounts',
|
|
149
|
-
'events',
|
|
150
|
-
'experiments',
|
|
151
|
-
'forms',
|
|
152
|
-
'functions',
|
|
153
|
-
'installs',
|
|
154
|
-
'locations',
|
|
155
|
-
'memberPosts',
|
|
156
|
-
'overlays',
|
|
157
|
-
'pluginSettings',
|
|
158
|
-
'productCategories',
|
|
159
|
-
'products',
|
|
160
|
-
'redirects',
|
|
161
|
-
'resources',
|
|
162
|
-
'reviews',
|
|
163
|
-
'services',
|
|
164
|
-
'settings',
|
|
165
|
-
'siteMembers',
|
|
166
|
-
'suppliers',
|
|
167
|
-
'templates',
|
|
168
|
-
'variables',
|
|
169
|
-
'webhooks',
|
|
170
|
-
'workflows'
|
|
171
|
-
];
|
|
172
|
-
/**
|
|
173
|
-
* Console page slug a plugin-content reference row deep-links to.
|
|
174
|
-
*
|
|
175
|
-
* Optional on purpose, and deliberately not required for a collection to be
|
|
176
|
-
* scanned. A row with no destination still renders — as text rather than a
|
|
177
|
-
* link — and that is strictly better than the row not existing, which is the
|
|
178
|
-
* bug this issue is about. Requiring a slug would have made the deep link a
|
|
179
|
-
* precondition for coverage and quietly re-created the hole for every
|
|
180
|
-
* collection nobody had got round to routing.
|
|
181
|
-
*
|
|
182
|
-
* Keyed by collection, valued with the `[pluginSlug]` segment of
|
|
183
|
-
* `Route.HOST_PLUGIN`.
|
|
184
|
-
*/ export const PLUGIN_CONTENT_ROUTE_SLUG = {
|
|
185
|
-
products: 'products',
|
|
186
|
-
productCategories: 'products',
|
|
187
|
-
suppliers: 'products',
|
|
188
|
-
locations: 'products',
|
|
189
|
-
coupons: 'products',
|
|
190
|
-
discounts: 'products',
|
|
191
|
-
reviews: 'products',
|
|
192
|
-
memberPosts: 'products',
|
|
193
|
-
events: 'events',
|
|
194
|
-
services: 'bookings',
|
|
195
|
-
resources: 'bookings',
|
|
196
|
-
redirects: 'redirects',
|
|
197
|
-
campaigns: 'marketing',
|
|
198
|
-
experiments: 'marketing',
|
|
199
|
-
workflows: 'workflows',
|
|
200
|
-
webhooks: 'workflows',
|
|
201
|
-
functions: 'logic',
|
|
202
|
-
variables: 'logic',
|
|
203
|
-
actions: 'logic'
|
|
204
|
-
};
|
|
205
140
|
/**
|
|
206
141
|
* What to call one of these in a reference row.
|
|
207
142
|
*
|
|
@@ -210,6 +145,9 @@
|
|
|
210
145
|
* `productCategories` reads as "Product category". Singularised with the two
|
|
211
146
|
* rules English spells consistently enough to be worth automating; anything
|
|
212
147
|
* else keeps its name, which is a cosmetic miss rather than a coverage one.
|
|
148
|
+
*
|
|
149
|
+
* Shared with `pluginHostCollectionLabel`, which prefers a plugin's declared
|
|
150
|
+
* label and falls back to exactly this.
|
|
213
151
|
*/ export function hostContentCollectionLabel(collection) {
|
|
214
152
|
const singular = /ies$/.test(collection) ? collection.replace(/ies$/, 'y') : /(ss|s)$/.test(collection) ? collection.replace(/s$/, '') : collection;
|
|
215
153
|
const spaced = singular.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/foundation/definitions/host-content-collections.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 * Which host subcollections the media usage scan reads (AGL-1867).\n *\n * ## Why this exists, and why it is shaped the way it is\n *\n * `scanMediaReferences` answers \"what uses this asset\" immediately before an\n * author deletes it, and every way it can be wrong points at \"unused\". AGL-1413\n * closed the screen/layout/component/host-field half. AGL-1867 closed emails.\n * What was left was plugin-owned content: a commerce product carries `imageUrl`\n * and `mediaUrls`, so a product photo used nowhere else reported as unused, and\n * the panel said so with a straight face.\n *\n * An earlier pass at this issue concluded the gap could not be closed by hand,\n * because closing it looked like classifying fifty-odd collection names as\n * media-bearing or not — \"a large list of guesses dressed as decisions, and the\n * first stale entry is where the next hole hides\". That reasoning was right\n * about the hazard and wrong about the shape of the fix. The classification it\n * feared is only necessary if the default is NOT to scan. So the default here\n * is inverted:\n *\n * **Every host subcollection is scanned unless it is named below with a\n * reason it is not.**\n *\n * That turns the open question from \"does this carry media?\" — which needs a\n * judgement about a schema nobody wrote down — into \"is scanning this wasteful\n * or wrong?\", which is answerable from the collection's own volume and who\n * writes it. It also puts the failure on the safe side: a collection nobody has\n * thought about is READ, so a plugin shipping a new media-bearing collection is\n * covered the day it lands rather than the day somebody remembers it.\n *\n * ## What keeps the list from rotting\n *\n * `host-content-media-coverage.spec.ts` sweeps `apps/**` and `libs/**` for host\n * subcollection names — the same derived sweep\n * `host-subcollection-write-deny-coverage.spec.ts` uses, for the same reason —\n * and asserts {@link PLUGIN_CONTENT_COLLECTIONS} is EXACTLY that sweep minus\n * {@link CORE_CONTENT_COLLECTIONS} minus {@link MEDIA_SCAN_EXCLUDED}. A new\n * collection fails the build with a one-line decision to make, and the default\n * answer — add it to the scanned list — is also the safe one.\n *\n * ## The plugin boundary was NOT crossed to do this\n *\n * Worth stating, because \"read plugin data into a first-party corpus\" sounds\n * like it should be. It is not, and the reason is that \"plugin\" means two\n * disjoint things:\n *\n * - a SANDBOXED marketplace plugin runs in an iframe on a separate origin and\n * has no Firestore access at all — `plugin-bridge.ts` is the whole protocol\n * and its guest verbs are `ready`, `resize`, `event`, `fetch-request` and\n * `error`, with `parseGuestMessage` rejecting anything else. It owns no\n * documents, so there is nothing here to reach into.\n * - a FIRST-PARTY feature plugin under `libs/plugins/*` is compiled into the\n * apps and writes ORDINARY host subcollections with the ordinary SDKs. Those\n * documents already sit under the same `hosts/{hostId}` rules as a screen,\n * are already read by first-party console pages, and are already covered by\n * the same scope check the scan runs before it reads anything.\n *\n * So the corpus grows to include documents the caller could already read\n * through the console, under an authorization decision that has not changed.\n * Nothing in the sandbox boundary is bypassed, because no sandboxed guest owns\n * any of this.\n */\n\n/**\n * Host subcollections the media scan reads through its own dedicated machinery.\n *\n * Not \"excluded\" — covered, by a pass that knows their shape. Screens, layouts,\n * components and email templates each hang a node tree off\n * `…/versions/{versionId}` and get the published-version and history passes;\n * `collections` is walked one level deeper, into `…/entries`.\n */\nexport const CORE_CONTENT_COLLECTIONS = [\n 'screens',\n 'layouts',\n 'components',\n 'emailTemplates',\n 'collections',\n] as const\n\n/**\n * Host subcollections deliberately NOT read by the media scan, and why.\n *\n * Every entry has to answer one of two questions: what would scanning it cost,\n * or what would scanning it get wrong. \"It probably has no images in it\" is not\n * an accepted reason — that is the guess this file is arranged to avoid making.\n */\nexport const MEDIA_SCAN_EXCLUDED: Record<string, string> = {\n // ── Scanning these would be WRONG, not merely expensive ────────────────\n media:\n 'The library itself. The asset under audit IS a document in here, and its ' +\n 'own record holds its own url, cdnPath and storage path — every needle ' +\n 'the scan carries. Reading this collection would report every asset as ' +\n 'referencing itself, which turns a deletion-safety control into noise ' +\n 'that nobody reads.',\n mediaFolders:\n 'Folder records: a name, a parent pointer and a scope. A folder never ' +\n 'holds an asset reference, and it is not a dependent either — deleting a ' +\n 'file does not break the folder it sat in.',\n emailCampaigns:\n 'The campaign CONTAINER — a name, a date window, the list ids it is ' +\n 'aimed at and a topic. Distinct from `campaigns`, which holds the sends ' +\n 'and IS scanned because a send carries the copy that went out. A ' +\n 'container carries no copy and no asset reference: the design a send ' +\n 'renders is a screen, and the screen is where the picker writes.',\n\n // ── Machine-written telemetry: unbounded, and no author picks into it ──\n analytics:\n 'Per-day traffic rollups written by the tenant collector. One document ' +\n 'per day per site forever, no author-editable field, and no surface that ' +\n 'can pick an asset into one.',\n screenAnalytics:\n 'Per-screen traffic history behind the Pro+ panel, written by the same ' +\n 'collector on the same unbounded per-day cadence.',\n counters:\n 'Quota and usage counters, server-owned (AGL-1367) and denied to client ' +\n 'writes outright. Numbers, not content.',\n activity:\n 'The site activity feed — an append-only audit log the console writes a ' +\n 'row into on every change. Unbounded by design, and a row that quotes an ' +\n 'asset id is a record that somebody touched it, not a place it is used.',\n\n // ── Transaction and ledger rows: unbounded, and a SNAPSHOT is not a use ─\n orders:\n 'Completed orders. Line items COPY a product\\'s `imageUrl` at purchase ' +\n 'time, so these would match — and matching would be the wrong answer: an ' +\n 'order is an immutable record of what was sold, deleting the asset ' +\n 'changes nothing about it, and no author can edit one. A busy store also ' +\n 'holds more of these than the whole read budget.',\n carts:\n 'Live and abandoned carts, with the same copied line-item image and the ' +\n 'same reasoning, at higher volume — one document per shopper session.',\n checkouts:\n 'In-flight checkout sessions. Transient buyer state carrying the same ' +\n 'line-item snapshot as `carts`.',\n formSubmissions:\n 'Visitor form submissions. Unbounded, PII-heavy, and a file attached to ' +\n 'one is the visitor\\'s upload — not a library asset an author picked, and ' +\n 'not something deleting a library asset would break.',\n leads:\n 'Captured leads, on the same footing as `formSubmissions`: visitor-' +\n 'submitted, unbounded, and never a place an author places an asset.',\n bookings:\n 'Booking records — a customer, a time and a service pointer. The service ' +\n 'holds the imagery and IS scanned; a booking is the transaction against ' +\n 'it.',\n campaignAttributions:\n 'One row per conversion — a form submission, a lead, a contact or a ' +\n 'booking — recording which campaign the visitor arrived from. Written ' +\n 'only by the Admin SDK, and the whole record is three sanitized UTM ' +\n 'strings capped at a hundred characters: there is no field an asset ' +\n 'could occupy and no surface that could pick one into it. Unbounded on ' +\n 'the same footing as the conversions it credits.',\n reservations:\n 'POS and booking holds. Short-lived transaction rows pointing at a ' +\n 'resource that is itself scanned.',\n stockHolds:\n 'Inventory holds taken during checkout. Machine-written, short-lived, ' +\n 'and a quantity rather than content.',\n subscriptions:\n 'Site membership subscriptions: a plan pointer, a status and Stripe ' +\n 'ids. Server-written from the billing webhook.',\n registers:\n 'POS register allocations — a count against the register add-on, read by ' +\n 'billing (AGL-1775). No content field at all.',\n giftCards:\n 'Gift card balances and redemption history. Money, not content.',\n licenseKeys:\n 'Digital-product license keys issued at fulfilment: a code, an order ' +\n 'pointer and a revocation flag.',\n inventoryAdjustments:\n 'The append-only stock adjustment ledger (AGL-2269). One row per manual ' +\n 'stock edit and per cancellation release, unbounded over a store\\'s life.',\n inventoryReconciliation:\n 'Reconciliation runs comparing counted stock against recorded stock. ' +\n 'Machine-written totals.',\n restockAlerts:\n 'Back-in-stock requests: an email address and a product pointer, written ' +\n 'by visitors.',\n suppressions:\n 'Unsubscribes, bounces and spam complaints. Email addresses and a ' +\n 'reason.',\n stripeTaxRates:\n 'Cached Stripe tax rate ids, written by the tax sync. Vendor ids.',\n members:\n 'The ORG-facing member roster mirrored onto the site, server-owned ' +\n '(AGL-1367). A member\\'s photo is their own account photo, uploaded ' +\n 'through /api/account/photo and stored outside any site\\'s library, so it ' +\n 'is not an asset this scan can be asked about.',\n}\n\n/**\n * Host subcollections the media scan reads generically.\n *\n * Generically: no per-collection field list and no per-collection decoder.\n * Each document is flattened by `documentHaystack` — which decodes `nodes` and\n * `elements` through `decodeStoredNodes` first, so a plugin that adopts the\n * compressed storage form is covered without touching this file — and the\n * matching field's dotted path is recovered by walking the document. A field\n * list here would be the same staleness trap `referencingFieldPath` was written\n * to avoid one level down.\n *\n * MUST equal the repo-wide host subcollection sweep minus\n * {@link CORE_CONTENT_COLLECTIONS} minus {@link MEDIA_SCAN_EXCLUDED}. The guard\n * spec asserts exactly that, in both directions, so this list can neither miss\n * a new collection nor keep naming a deleted one.\n */\nexport const PLUGIN_CONTENT_COLLECTIONS = [\n 'actions',\n 'authors',\n 'campaigns',\n 'coupons',\n 'discounts',\n 'events',\n 'experiments',\n 'forms',\n 'functions',\n 'installs',\n 'locations',\n 'memberPosts',\n 'overlays',\n 'pluginSettings',\n 'productCategories',\n 'products',\n 'redirects',\n 'resources',\n 'reviews',\n 'services',\n 'settings',\n 'siteMembers',\n 'suppliers',\n 'templates',\n 'variables',\n 'webhooks',\n 'workflows',\n] as const\n\nexport type PluginContentCollection =\n (typeof PLUGIN_CONTENT_COLLECTIONS)[number]\n\n/**\n * Console page slug a plugin-content reference row deep-links to.\n *\n * Optional on purpose, and deliberately not required for a collection to be\n * scanned. A row with no destination still renders — as text rather than a\n * link — and that is strictly better than the row not existing, which is the\n * bug this issue is about. Requiring a slug would have made the deep link a\n * precondition for coverage and quietly re-created the hole for every\n * collection nobody had got round to routing.\n *\n * Keyed by collection, valued with the `[pluginSlug]` segment of\n * `Route.HOST_PLUGIN`.\n */\nexport const PLUGIN_CONTENT_ROUTE_SLUG: Partial<\n Record<PluginContentCollection, string>\n> = {\n products: 'products',\n productCategories: 'products',\n suppliers: 'products',\n locations: 'products',\n coupons: 'products',\n discounts: 'products',\n reviews: 'products',\n memberPosts: 'products',\n events: 'events',\n services: 'bookings',\n resources: 'bookings',\n redirects: 'redirects',\n campaigns: 'marketing',\n experiments: 'marketing',\n workflows: 'workflows',\n webhooks: 'workflows',\n functions: 'logic',\n variables: 'logic',\n actions: 'logic',\n}\n\n/**\n * What to call one of these in a reference row.\n *\n * Derived from the collection name rather than declared, so a newly scanned\n * collection gets a readable label without a second list to keep in step —\n * `productCategories` reads as \"Product category\". Singularised with the two\n * rules English spells consistently enough to be worth automating; anything\n * else keeps its name, which is a cosmetic miss rather than a coverage one.\n */\nexport function hostContentCollectionLabel(collection: string): string {\n const singular = /ies$/.test(collection)\n ? collection.replace(/ies$/, 'y')\n : /(ss|s)$/.test(collection)\n ? collection.replace(/s$/, '')\n : collection\n const spaced = singular.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase()\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n"],"names":["CORE_CONTENT_COLLECTIONS","MEDIA_SCAN_EXCLUDED","media","mediaFolders","emailCampaigns","analytics","screenAnalytics","counters","activity","orders","carts","checkouts","formSubmissions","leads","bookings","campaignAttributions","reservations","stockHolds","subscriptions","registers","giftCards","licenseKeys","inventoryAdjustments","inventoryReconciliation","restockAlerts","suppressions","stripeTaxRates","members","PLUGIN_CONTENT_COLLECTIONS","PLUGIN_CONTENT_ROUTE_SLUG","products","productCategories","suppliers","locations","coupons","discounts","reviews","memberPosts","events","services","resources","redirects","campaigns","experiments","workflows","webhooks","functions","variables","actions","hostContentCollectionLabel","collection","singular","test","replace","spaced","toLowerCase","charAt","toUpperCase","slice"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6DC,GAED;;;;;;;CAOC,GACD,OAAO,MAAMA,2BAA2B;IACtC;IACA;IACA;IACA;IACA;CACD,CAAS;AAEV;;;;;;CAMC,GACD,OAAO,MAAMC,sBAA8C;IACzD,0EAA0E;IAC1EC,OACE,8EACA,2EACA,2EACA,0EACA;IACFC,cACE,0EACA,6EACA;IACFC,gBACE,wEACA,4EACA,qEACA,yEACA;IAEF,0EAA0E;IAC1EC,WACE,2EACA,6EACA;IACFC,iBACE,2EACA;IACFC,UACE,4EACA;IACFC,UACE,4EACA,6EACA;IAEF,2EAA2E;IAC3EC,QACE,2EACA,6EACA,uEACA,6EACA;IACFC,OACE,4EACA;IACFC,WACE,0EACA;IACFC,iBACE,4EACA,8EACA;IACFC,OACE,uEACA;IACFC,UACE,6EACA,4EACA;IACFC,sBACE,wEACA,0EACA,wEACA,wEACA,2EACA;IACFC,cACE,uEACA;IACFC,YACE,0EACA;IACFC,eACE,wEACA;IACFC,WACE,6EACA;IACFC,WACE;IACFC,aACE,yEACA;IACFC,sBACE,4EACA;IACFC,yBACE,yEACA;IACFC,eACE,6EACA;IACFC,cACE,sEACA;IACFC,gBACE;IACFC,SACE,uEACA,wEACA,8EACA;AACJ,EAAC;AAED;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,6BAA6B;IACxC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAKV;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,4BAET;IACFC,UAAU;IACVC,mBAAmB;IACnBC,WAAW;IACXC,WAAW;IACXC,SAAS;IACTC,WAAW;IACXC,SAAS;IACTC,aAAa;IACbC,QAAQ;IACRC,UAAU;IACVC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,aAAa;IACbC,WAAW;IACXC,UAAU;IACVC,WAAW;IACXC,WAAW;IACXC,SAAS;AACX,EAAC;AAED;;;;;;;;CAQC,GACD,OAAO,SAASC,2BAA2BC,UAAkB;IAC3D,MAAMC,WAAW,OAAOC,IAAI,CAACF,cACzBA,WAAWG,OAAO,CAAC,QAAQ,OAC3B,UAAUD,IAAI,CAACF,cACbA,WAAWG,OAAO,CAAC,MAAM,MACzBH;IACN,MAAMI,SAASH,SAASE,OAAO,CAAC,sBAAsB,SAASE,WAAW;IAC1E,OAAOD,OAAOE,MAAM,CAAC,GAAGC,WAAW,KAAKH,OAAOI,KAAK,CAAC;AACvD"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/aglyn/src/lib/foundation/definitions/host-content-collections.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 host subcollections CORE owns, and how the media usage scan reads them\n * (AGL-1867, AGL-3080).\n *\n * ## Why this exists, and why it is shaped the way it is\n *\n * `scanMediaReferences` answers \"what uses this asset\" immediately before an\n * author deletes it, and every way it can be wrong points at \"unused\". AGL-1413\n * closed the screen/layout/component/host-field half. AGL-1867 closed emails.\n * What was left was plugin-owned content: a commerce product carries `imageUrl`\n * and `mediaUrls`, so a product photo used nowhere else reported as unused, and\n * the panel said so with a straight face.\n *\n * An earlier pass at this issue concluded the gap could not be closed by hand,\n * because closing it looked like classifying fifty-odd collection names as\n * media-bearing or not — \"a large list of guesses dressed as decisions, and the\n * first stale entry is where the next hole hides\". That reasoning was right\n * about the hazard and wrong about the shape of the fix. The classification it\n * feared is only necessary if the default is NOT to scan. So the default here\n * is inverted:\n *\n * **Every host subcollection is scanned unless it is named with a reason it is\n * not.**\n *\n * That turns the open question from \"does this carry media?\" — which needs a\n * judgement about a schema nobody wrote down — into \"is scanning this wasteful\n * or wrong?\", which is answerable from the collection's own volume and who\n * writes it. It also puts the failure on the safe side: a collection nobody has\n * thought about is READ, so a plugin shipping a new media-bearing collection is\n * covered the day it lands rather than the day somebody remembers it.\n *\n * ## Core holds core's, and asks the plugins for theirs (AGL-3080)\n *\n * This file used to hold every collection in the product: which ones commerce\n * writes, which ones marketing writes, and which console page each deep-links\n * to. That is the platform keeping a map of its plugins' storage, and the map\n * went stale exactly where a stale map is invisible — three of its deep links\n * pointed at a hub that did not hold the document, and nothing could tell.\n *\n * So a plugin declares its own collections in `plugins.config.json` and the\n * readers ask `plugin-manager/plugin-host-collections`. What is left here is\n * core's own storage, which is the only part core is entitled to name.\n *\n * ## What keeps these lists from rotting\n *\n * `host-content-media-coverage.spec.ts` sweeps `apps/**` and `libs/**` for host\n * subcollection names — the same derived sweep\n * `host-subcollection-write-deny-coverage.spec.ts` uses, for the same reason —\n * and asserts the sweep is EXACTLY these three lists plus the plugins'\n * declarations. A new collection fails the build with a one-line decision to\n * make, and the default answer — declare it scanned, on the plugin that writes\n * it — is also the safe one.\n *\n * ## The plugin boundary was NOT crossed to do this\n *\n * Worth stating, because \"read plugin data into a first-party corpus\" sounds\n * like it should be. It is not, and the reason is that \"plugin\" means two\n * disjoint things:\n *\n * - a SANDBOXED marketplace plugin runs in an iframe on a separate origin and\n * has no Firestore access at all — `plugin-bridge.ts` is the whole protocol\n * and its guest verbs are `ready`, `resize`, `event`, `fetch-request` and\n * `error`, with `parseGuestMessage` rejecting anything else. It owns no\n * documents, so there is nothing here to reach into.\n * - a FIRST-PARTY feature plugin under `libs/plugins/*` is compiled into the\n * apps and writes ORDINARY host subcollections with the ordinary SDKs. Those\n * documents already sit under the same `hosts/{hostId}` rules as a screen,\n * are already read by first-party console pages, and are already covered by\n * the same scope check the scan runs before it reads anything.\n *\n * So the corpus grows to include documents the caller could already read\n * through the console, under an authorization decision that has not changed.\n * Nothing in the sandbox boundary is bypassed, because no sandboxed guest owns\n * any of this.\n */\n\n/**\n * Host subcollections the media scan reads through its own dedicated machinery.\n *\n * Not \"excluded\" — covered, by a pass that knows their shape. Screens, layouts,\n * components and email templates each hang a node tree off\n * `…/versions/{versionId}` and get the published-version and history passes;\n * `collections` is walked one level deeper, into `…/entries`.\n */\nexport const CORE_CONTENT_COLLECTIONS = [\n 'screens',\n 'layouts',\n 'components',\n 'emailTemplates',\n 'collections',\n] as const\n\n/**\n * Core's own host subcollections that the scan reads generically.\n *\n * Generically: no per-collection field list and no per-collection decoder.\n * Each document is flattened by `documentHaystack` — which decodes `nodes` and\n * `elements` through `decodeStoredNodes` first — and the matching field's\n * dotted path is recovered by walking the document. A field list here would be\n * the same staleness trap `referencingFieldPath` was written to avoid one\n * level down.\n *\n * Short because it is only core's: a blog author, the per-plugin settings\n * document the plugin manager owns, and a site template. Everything else that\n * is scanned generically belongs to a plugin and is declared by it.\n */\nexport const CORE_GENERIC_SCAN_COLLECTIONS = [\n 'authors',\n 'pluginSettings',\n 'templates',\n] as const\n\n/**\n * Core's own host subcollections that the media scan deliberately does NOT\n * read, and why.\n *\n * Every entry has to answer one of two questions: what would scanning it cost,\n * or what would scanning it get wrong. \"It probably has no images in it\" is not\n * an accepted reason — that is the guess this file is arranged to avoid making.\n * A plugin's exclusions carry the same burden and live in its own declaration,\n * where `mediaScanReason` is required with `mediaScan: 'none'`.\n */\nexport const MEDIA_SCAN_EXCLUDED: Record<string, string> = {\n media:\n 'The library itself. The asset under audit IS a document in here, and ' +\n 'its own record holds its own url, cdnPath and storage path — every ' +\n 'needle the scan carries. Reading this collection would report every ' +\n 'asset as referencing itself, which turns a deletion-safety control into ' +\n 'noise that nobody reads.',\n mediaFolders:\n 'Folder records: a name, a parent pointer and a scope. A folder never ' +\n 'holds an asset reference, and it is not a dependent either — deleting a ' +\n 'file does not break the folder it sat in.',\n analytics:\n 'Per-day traffic rollups written by the tenant collector. One document ' +\n 'per day per site forever, no author-editable field, and no surface that ' +\n 'can pick an asset into one.',\n screenAnalytics:\n 'Per-screen traffic history behind the Pro+ panel, written by the same ' +\n 'collector on the same unbounded per-day cadence.',\n counters:\n 'Quota and usage counters, server-owned (AGL-1367) and denied to client ' +\n 'writes outright. Numbers, not content.',\n activity:\n 'The site activity feed — an append-only audit log the console writes a ' +\n 'row into on every change. Unbounded by design, and a row that quotes an ' +\n 'asset id is a record that somebody touched it, not a place it is used.',\n members:\n 'The ORG-facing member roster mirrored onto the site, server-owned ' +\n '(AGL-1367). A member\\'s photo is their own account photo, uploaded ' +\n 'through /api/account/photo and stored outside any site\\'s library, so it ' +\n 'is not an asset this scan can be asked about.',\n}\n\n/**\n * What to call one of these in a reference row.\n *\n * Derived from the collection name rather than declared, so a newly scanned\n * collection gets a readable label without a second list to keep in step —\n * `productCategories` reads as \"Product category\". Singularised with the two\n * rules English spells consistently enough to be worth automating; anything\n * else keeps its name, which is a cosmetic miss rather than a coverage one.\n *\n * Shared with `pluginHostCollectionLabel`, which prefers a plugin's declared\n * label and falls back to exactly this.\n */\nexport function hostContentCollectionLabel(collection: string): string {\n const singular = /ies$/.test(collection)\n ? collection.replace(/ies$/, 'y')\n : /(ss|s)$/.test(collection)\n ? collection.replace(/s$/, '')\n : collection\n const spaced = singular.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase()\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n"],"names":["CORE_CONTENT_COLLECTIONS","CORE_GENERIC_SCAN_COLLECTIONS","MEDIA_SCAN_EXCLUDED","media","mediaFolders","analytics","screenAnalytics","counters","activity","members","hostContentCollectionLabel","collection","singular","test","replace","spaced","toLowerCase","charAt","toUpperCase","slice"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0EC,GAED;;;;;;;CAOC,GACD,OAAO,MAAMA,2BAA2B;IACtC;IACA;IACA;IACA;IACA;CACD,CAAS;AAEV;;;;;;;;;;;;;CAaC,GACD,OAAO,MAAMC,gCAAgC;IAC3C;IACA;IACA;CACD,CAAS;AAEV;;;;;;;;;CASC,GACD,OAAO,MAAMC,sBAA8C;IACzDC,OACE,0EACA,wEACA,yEACA,6EACA;IACFC,cACE,0EACA,6EACA;IACFC,WACE,2EACA,6EACA;IACFC,iBACE,2EACA;IACFC,UACE,4EACA;IACFC,UACE,4EACA,6EACA;IACFC,SACE,uEACA,wEACA,8EACA;AACJ,EAAC;AAED;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,2BAA2BC,UAAkB;IAC3D,MAAMC,WAAW,OAAOC,IAAI,CAACF,cACzBA,WAAWG,OAAO,CAAC,QAAQ,OAC3B,UAAUD,IAAI,CAACF,cACbA,WAAWG,OAAO,CAAC,MAAM,MACzBH;IACN,MAAMI,SAASH,SAASE,OAAO,CAAC,sBAAsB,SAASE,WAAW;IAC1E,OAAOD,OAAOE,MAAM,CAAC,GAAGC,WAAW,KAAKH,OAAOI,KAAK,CAAC;AACvD"}
|
|
@@ -497,6 +497,34 @@ export declare const CONSOLE_WIDGET_SLOTS: {
|
|
|
497
497
|
readonly besignerFunctions: "besignerFunctions";
|
|
498
498
|
/** Marketplace listing detail body. Props: hostId, listingId, permissions. */
|
|
499
499
|
readonly marketplaceListing: "marketplaceListing";
|
|
500
|
+
/**
|
|
501
|
+
* Wherever a console page offers to publish something it holds (AGL-3080).
|
|
502
|
+
* Props: {@link ConsoleArtifactPublishZoneProps}.
|
|
503
|
+
*
|
|
504
|
+
* A layouts page, a components page and the org publish panel each held a
|
|
505
|
+
* dialog that posted to `marketplace/publish-layout` and read the
|
|
506
|
+
* marketplace's price floor. The console knows it has a layout; where a
|
|
507
|
+
* layout can be PUBLISHED, what a listing costs at the least, and what to
|
|
508
|
+
* call the thing in the copy all belong to whatever sells it.
|
|
509
|
+
*
|
|
510
|
+
* ── The page says what it has, never where it goes ───────────────────────
|
|
511
|
+
*
|
|
512
|
+
* The zone is handed a {@link ConsolePublishableArtifact} in the console's
|
|
513
|
+
* own vocabulary — a kind, the site or org it belongs to, the document id —
|
|
514
|
+
* and the widget decides the endpoint, the noun and the form. A kind the
|
|
515
|
+
* widget does not publish is one it draws nothing for, which is the same
|
|
516
|
+
* answer as no widget at all.
|
|
517
|
+
*
|
|
518
|
+
* ── An offer with nowhere to go must not be made ─────────────────────────
|
|
519
|
+
*
|
|
520
|
+
* The control that OPENS this — a menu item, a button — belongs to the page
|
|
521
|
+
* and is not a widget, because it is one entry of a list the page builds.
|
|
522
|
+
* So a page offering it asks `useSlotWidgets` whether this zone has a
|
|
523
|
+
* widget at all and leaves the entry out when it does not. Drawing it
|
|
524
|
+
* anyway would be a menu item that opens nothing on a workspace with no
|
|
525
|
+
* marketplace.
|
|
526
|
+
*/
|
|
527
|
+
readonly hostArtifactPublish: "hostArtifactPublish";
|
|
500
528
|
/**
|
|
501
529
|
* Above the whole Marketplace subtree: what THIS DEPLOYMENT cannot do
|
|
502
530
|
* (AGL-2019, moved out of the console app by AGL-3080). No props — the
|
|
@@ -865,6 +893,48 @@ export interface ConsoleHostFirstRunZoneProps {
|
|
|
865
893
|
*/
|
|
866
894
|
startBlank: () => void;
|
|
867
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Something a console page holds that somebody may want to publish
|
|
898
|
+
* (AGL-3080).
|
|
899
|
+
*
|
|
900
|
+
* The console's own vocabulary and nothing else: a `kind` naming what the
|
|
901
|
+
* thing IS, the scope that holds it, and the document. Where it can be
|
|
902
|
+
* published to, what a listing of it is called and what it may cost are the
|
|
903
|
+
* publishing plugin's, which is the whole point of handing this over rather
|
|
904
|
+
* than building a request.
|
|
905
|
+
*
|
|
906
|
+
* `kind` is an open string for the reason a plugin's zone ids are: core
|
|
907
|
+
* listing every publishable noun would be core holding the catalog again. A
|
|
908
|
+
* widget that does not publish a kind draws nothing for it.
|
|
909
|
+
*/
|
|
910
|
+
export interface ConsolePublishableArtifact {
|
|
911
|
+
/** What the thing is: `layout`, `component`, `theme`, `site`, … */
|
|
912
|
+
kind: string;
|
|
913
|
+
/** The site it belongs to, where the kind belongs to one. */
|
|
914
|
+
hostId?: string | null;
|
|
915
|
+
/** The organization, for a kind held by the org rather than a site. */
|
|
916
|
+
orgId?: string | null;
|
|
917
|
+
/**
|
|
918
|
+
* The document, in the holding surface's terms. Absent for a kind that IS
|
|
919
|
+
* the site or the org — a theme, a whole site template — where the scope
|
|
920
|
+
* above already names it.
|
|
921
|
+
*/
|
|
922
|
+
artifactId?: string | null;
|
|
923
|
+
/** Seeds the listing's name; the person may change it. */
|
|
924
|
+
displayName?: string;
|
|
925
|
+
description?: string;
|
|
926
|
+
}
|
|
927
|
+
/** What the `hostArtifactPublish` zone hands each widget (AGL-3080). */
|
|
928
|
+
export interface ConsoleArtifactPublishZoneProps {
|
|
929
|
+
/**
|
|
930
|
+
* What the page asked to publish, or `null` while nothing is open. The
|
|
931
|
+
* page owns the open/closed state because the control that opens this is
|
|
932
|
+
* the page's own.
|
|
933
|
+
*/
|
|
934
|
+
artifact: ConsolePublishableArtifact | null;
|
|
935
|
+
/** Closes it, however it was closed — cancelled, published, or refused. */
|
|
936
|
+
onClose: () => void;
|
|
937
|
+
}
|
|
868
938
|
/** What the `hostTheme` zone hands each widget (AGL-2938). */
|
|
869
939
|
export interface ConsoleHostThemeZoneProps {
|
|
870
940
|
hostId: string;
|
|
@@ -121,6 +121,33 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
121
121
|
/** Besigner functions (ƒx) panel. Props: hostId. */ besignerFunctions: 'besignerFunctions',
|
|
122
122
|
/** Marketplace listing detail body. Props: hostId, listingId, permissions. */ marketplaceListing: 'marketplaceListing',
|
|
123
123
|
/**
|
|
124
|
+
* Wherever a console page offers to publish something it holds (AGL-3080).
|
|
125
|
+
* Props: {@link ConsoleArtifactPublishZoneProps}.
|
|
126
|
+
*
|
|
127
|
+
* A layouts page, a components page and the org publish panel each held a
|
|
128
|
+
* dialog that posted to `marketplace/publish-layout` and read the
|
|
129
|
+
* marketplace's price floor. The console knows it has a layout; where a
|
|
130
|
+
* layout can be PUBLISHED, what a listing costs at the least, and what to
|
|
131
|
+
* call the thing in the copy all belong to whatever sells it.
|
|
132
|
+
*
|
|
133
|
+
* ── The page says what it has, never where it goes ───────────────────────
|
|
134
|
+
*
|
|
135
|
+
* The zone is handed a {@link ConsolePublishableArtifact} in the console's
|
|
136
|
+
* own vocabulary — a kind, the site or org it belongs to, the document id —
|
|
137
|
+
* and the widget decides the endpoint, the noun and the form. A kind the
|
|
138
|
+
* widget does not publish is one it draws nothing for, which is the same
|
|
139
|
+
* answer as no widget at all.
|
|
140
|
+
*
|
|
141
|
+
* ── An offer with nowhere to go must not be made ─────────────────────────
|
|
142
|
+
*
|
|
143
|
+
* The control that OPENS this — a menu item, a button — belongs to the page
|
|
144
|
+
* and is not a widget, because it is one entry of a list the page builds.
|
|
145
|
+
* So a page offering it asks `useSlotWidgets` whether this zone has a
|
|
146
|
+
* widget at all and leaves the entry out when it does not. Drawing it
|
|
147
|
+
* anyway would be a menu item that opens nothing on a workspace with no
|
|
148
|
+
* marketplace.
|
|
149
|
+
*/ hostArtifactPublish: 'hostArtifactPublish',
|
|
150
|
+
/**
|
|
124
151
|
* Above the whole Marketplace subtree: what THIS DEPLOYMENT cannot do
|
|
125
152
|
* (AGL-2019, moved out of the console app by AGL-3080). No props — the
|
|
126
153
|
* widget reads {@link DeploymentCapabilities} from the context the org's
|