@tokenoftrust/storefront-runner 2.0.1 → 2.0.3

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 (120) hide show
  1. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +19 -1
  2. package/apps/storefront/dev-plugins/tenant-validate-on-save.mjs +78 -0
  3. package/apps/storefront/src/middleware/index.ts +17 -1
  4. package/package.json +1 -1
  5. package/scripts/lib/tenant-dirs.mjs +80 -0
  6. package/scripts/tenant/lint-embeds.mjs +180 -0
  7. package/scripts/tenant/materialize-tenant-content.mjs +533 -0
  8. package/scripts/tenant/validate.mjs +837 -0
  9. package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
  10. package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
  11. package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
  12. package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
  13. package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
  14. package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
  15. package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
  16. package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
  17. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
  18. package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
  19. package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
  20. package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
  21. package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
  22. package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
  23. package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
  24. package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
  25. package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
  26. package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
  27. package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
  28. package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
  29. package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
  30. package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
  31. package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
  32. package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
  33. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
  34. package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
  35. package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
  36. package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
  37. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
  38. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
  39. package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
  40. package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
  41. package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2978
  42. package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
  43. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
  44. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
  45. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
  46. package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
  47. package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
  48. package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
  49. package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
  50. package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
  51. package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
  52. package/apps/storefront/src/lib/publish/domainState.ts +0 -360
  53. package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
  54. package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
  55. package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
  56. package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
  57. package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
  58. package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
  59. package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
  60. package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
  61. package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
  62. package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
  63. package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
  64. package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
  65. package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
  66. package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
  67. package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
  68. package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
  69. package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
  70. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
  71. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
  72. package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
  73. package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
  74. package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
  75. package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
  76. package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
  77. package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
  78. package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
  79. package/apps/storefront/src/pages/admin/review-release.astro +0 -265
  80. package/apps/storefront/src/pages/admin/select.astro +0 -96
  81. package/apps/storefront/src/pages/admin.astro +0 -568
  82. package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
  83. package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
  84. package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
  85. package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
  86. package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
  87. package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
  88. package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
  89. package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
  90. package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
  91. package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
  92. package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
  93. package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
  94. package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
  95. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
  96. package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
  97. package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
  98. package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
  99. package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
  100. package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
  101. package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
  102. package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
  103. package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
  104. package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
  105. package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
  106. package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
  107. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
  108. package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
  109. package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
  110. package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
  111. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
  112. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
  113. package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
  114. package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
  115. package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
  116. package/apps/storefront/src/pages/cockpit.astro +0 -1548
  117. package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
  118. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
  119. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
  120. package/apps/storefront/src/pages/dashboard/index.astro +0 -160
@@ -1,465 +0,0 @@
1
- ---
2
- import { getFulfillmentViewModel, getSettingsViewModel } from "@/lib/admin/services/mock";
3
-
4
- const { envelope } = Astro.props;
5
- const fulfillment = getFulfillmentViewModel(envelope);
6
- const settings = getSettingsViewModel(envelope);
7
-
8
- const QUEUE_TONE: Record<string, string> = {
9
- ready_to_ship: "green",
10
- holds: "amber",
11
- picked: "blue",
12
- packed: "blue",
13
- label_needed: "amber",
14
- shipped: "teal",
15
- exceptions: "red",
16
- };
17
-
18
- const readyToShipCount = fulfillment.queues.find((q) => q.key === "ready_to_ship")?.count ?? 0;
19
- const holdsCount = fulfillment.queues.find((q) => q.key === "holds")?.count ?? 0;
20
- const exceptionsCount = fulfillment.queues.find((q) => q.key === "exceptions")?.count ?? 0;
21
- const labelsLockedCount = fulfillment.shipments.filter((s) => s.labelLocked).length;
22
- const oldestSlaHours = fulfillment.shipments.reduce((max, s) => Math.max(max, s.slaAgeHours), 0);
23
-
24
- const selectedShipment = fulfillment.shipments.find((s) => s.labelLocked) ?? fulfillment.shipments[0] ?? null;
25
- const selectedAssistDraft = selectedShipment
26
- ? fulfillment.assistDrafts.find((d) => d.orderId === selectedShipment.orderId) ?? null
27
- : null;
28
-
29
- function queueLabel(key: string): string {
30
- return fulfillment.queues.find((q) => q.key === key)?.label ?? key;
31
- }
32
- ---
33
-
34
- <!-- agent-hook:fulfillment-tab -->
35
- <section id="fulfillment" class="band fulfillment-tab" aria-labelledby="admin-tab-fulfillment fulfillment-title" data-admin-panel data-agent-scope="fulfillment" role="tabpanel">
36
- <div class="section-title">
37
- <div>
38
- <h2 id="fulfillment-title">Fulfillment</h2>
39
- <p>Ship paid orders, clear exceptions, print labels, and keep fulfillment moving.</p>
40
- </div>
41
- <div class="actions">
42
- <button class="btn" type="button"><svg><use href="#i-export"></use></svg>Export queue</button>
43
- <button class="btn primary" type="button"><svg><use href="#i-truck"></use></svg>Create batch</button>
44
- </div>
45
- </div>
46
-
47
- {holdsCount + exceptionsCount > 0 && (
48
- <div class="ship-gate amber fulfillment-gate" aria-labelledby="fulfillment-gate-title">
49
- <div class="ship-gate-main">
50
- <span class="seal"><svg><use href="#i-alert"></use></svg></span>
51
- <div>
52
- <b id="fulfillment-gate-title">Hold: Review Required</b>
53
- <span>{holdsCount + exceptionsCount} shipment{holdsCount + exceptionsCount === 1 ? "" : "s"} need merchant action. Ready-to-ship orders stay visible and fast to label.</span>
54
- </div>
55
- </div>
56
- <div class="actions">
57
- <a class="btn" href="#fulfillment-blockers">Open blockers</a>
58
- <a class="btn primary" href="#fulfillment-ready">Ship ready orders</a>
59
- </div>
60
- </div>
61
- )}
62
-
63
- <div class="grid cols-4">
64
- <div class="metric">
65
- <span>Ready to ship</span>
66
- <strong>{readyToShipCount}</strong>
67
- <small>Paid, allocated, and checked</small>
68
- </div>
69
- <div class="metric">
70
- <span>Labels locked</span>
71
- <strong>{labelsLockedCount}</strong>
72
- <small>Blocked until the stated reason clears</small>
73
- </div>
74
- <div class="metric">
75
- <span>Holds</span>
76
- <strong>{holdsCount}</strong>
77
- <small>Action required before label creation</small>
78
- </div>
79
- <div class="metric">
80
- <span>Oldest SLA age</span>
81
- <strong>{oldestSlaHours}h</strong>
82
- <small>Since payment was captured</small>
83
- </div>
84
- </div>
85
-
86
- <div class="panel fulfillment-assist-panel">
87
- <div class="panel-head fulfillment-panel-head">
88
- <h3><svg><use href="#i-ask"></use></svg>Fulfillment Assist</h3>
89
- <span class="chip blue">Operator support, not autopilot — draft-only, evidence-linked</span>
90
- </div>
91
- <div class="panel-body grid">
92
- {fulfillment.assistDrafts.length === 0 ? (
93
- <p class="note">No exceptions or label locks right now — nothing for Assist to explain.</p>
94
- ) : (
95
- <div class="assist-signal-grid">
96
- {fulfillment.assistDrafts.map((draft) => (
97
- <div class="assist-signal">
98
- <span class="chip amber">{draft.orderId}</span>
99
- <p>{draft.summary}</p>
100
- </div>
101
- ))}
102
- </div>
103
- )}
104
- <p class="note">Assist never automates a shipping decision — every output here is a draft or explanation an operator reviews before acting.</p>
105
- </div>
106
- </div>
107
-
108
- <div class="fulfillment-workspace">
109
- <div class="panel fulfillment-queue-panel">
110
- <div class="panel-head fulfillment-panel-head">
111
- <h3><svg><use href="#i-truck"></use></svg>Fulfillment Queue</h3>
112
- <span class="chip teal">{fulfillment.shipments.length} shipments</span>
113
- </div>
114
- <div class="panel-body grid">
115
- <div class="tabs" role="tablist" aria-label="Fulfillment queues">
116
- {fulfillment.queues.map((queue, index) => (
117
- <button class:list={["tab", { active: index === 0 }]} type="button">{queue.label} ({queue.count})</button>
118
- ))}
119
- </div>
120
-
121
- <div class="table-wrap fulfillment-table-wrap" id="fulfillment-ready">
122
- <table class="fulfillment-table">
123
- <thead>
124
- <tr>
125
- <th>Order</th>
126
- <th>Destination</th>
127
- <th>Queue</th>
128
- <th>SLA age</th>
129
- <th>Carrier</th>
130
- <th>Label</th>
131
- <th class="action">Action</th>
132
- </tr>
133
- </thead>
134
- <tbody>
135
- {fulfillment.shipments.map((shipment) => (
136
- <tr class:list={[{ selected: shipment.orderId === selectedShipment?.orderId }]}>
137
- <td><strong>{shipment.orderId}</strong></td>
138
- <td><strong>{shipment.destination}</strong><span class="subtle">{shipment.destinationReady ? "Serviceable" : "Needs review"}</span></td>
139
- <td><span class:list={["chip", QUEUE_TONE[shipment.queue] ?? ""]}>{queueLabel(shipment.queue)}</span></td>
140
- <td><strong>{shipment.slaAgeHours}h</strong></td>
141
- <td><span class:list={["chip", shipment.carrierReady ? "green" : "amber"]}>{shipment.carrier}</span></td>
142
- <td><span class:list={["chip", shipment.labelLocked ? "amber" : "green"]}>{shipment.labelLocked ? "Locked" : "Unlocked"}</span></td>
143
- <td class="action"><a class="btn" href={shipment.labelLocked ? "#fulfillment-blockers" : "#fulfillment-detail"}>{shipment.labelLocked ? "Resolve" : "Create label"}</a></td>
144
- </tr>
145
- ))}
146
- </tbody>
147
- </table>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <aside class="panel fulfillment-detail-panel" id="fulfillment-detail" aria-labelledby="fulfillment-detail-title">
153
- {selectedShipment ? (
154
- <>
155
- <div class="panel-head fulfillment-panel-head">
156
- <h3 id="fulfillment-detail-title"><svg><use href="#i-file"></use></svg>{selectedShipment.orderId}</h3>
157
- <span class:list={["chip", selectedShipment.labelLocked ? "amber" : "green"]}>
158
- {selectedShipment.labelLocked ? "Shipping Label Locked" : "Shipping Label Unlocked"}
159
- </span>
160
- </div>
161
- <div class="panel-body grid">
162
- <div class="detail-summary">
163
- <div>
164
- <span class="eyebrow">Destination</span>
165
- <strong>{selectedShipment.destination}</strong>
166
- <span class="subtle">{selectedShipment.destinationReady ? "Serviceable" : "Needs review"}</span>
167
- </div>
168
- <div>
169
- <span class="eyebrow">Carrier</span>
170
- <strong>{selectedShipment.carrier}</strong>
171
- <span class="subtle">{selectedShipment.carrierReady ? "Eligible" : "Not eligible yet"}</span>
172
- </div>
173
- <div>
174
- <span class="eyebrow">SLA age</span>
175
- <strong>{selectedShipment.slaAgeHours}h</strong>
176
- <span class="subtle">Since payment captured</span>
177
- </div>
178
- </div>
179
-
180
- <div class:list={["label-card", selectedShipment.labelLocked ? "locked" : "unlocked"]}>
181
- <div>
182
- <span class="eyebrow">Shipping label</span>
183
- <strong>{selectedShipment.labelLocked ? "Locked until the blocker clears" : "Ready to create"}</strong>
184
- <p>
185
- {selectedShipment.labelLocked
186
- ? selectedShipment.labelLockReason ?? "Resolve the outstanding blocker to unlock this label."
187
- : "Carrier and destination checks are clear — create the label whenever you're ready."}
188
- </p>
189
- </div>
190
- <button class="btn primary" type="button" disabled={selectedShipment.labelLocked}>
191
- {selectedShipment.labelLocked ? "Label locked" : "Create label"}
192
- </button>
193
- </div>
194
-
195
- {selectedShipment.labelLocked && (
196
- <div class="action-strip" id="fulfillment-blockers">
197
- <a class="btn primary" href="#fulfillment-readiness">Resolve blocker</a>
198
- <button class="btn" type="button">Contact customer</button>
199
- </div>
200
- )}
201
-
202
- <div class="detail-section" id="fulfillment-readiness">
203
- <div class="detail-section-head">
204
- <h4>Readiness Checks</h4>
205
- <span class:list={["chip", selectedShipment.carrierReady && selectedShipment.destinationReady ? "green" : "amber"]}>
206
- {[!selectedShipment.carrierReady && "carrier", !selectedShipment.destinationReady && "destination"].filter(Boolean).length || "0"} blocker(s)
207
- </span>
208
- </div>
209
- <div class:list={["check-row", { blocking: !selectedShipment.carrierReady }]}>
210
- <div><b>Carrier eligibility</b><p>Carrier options unlock once the shipment's carrier check passes.</p></div>
211
- <strong>{selectedShipment.carrier}</strong>
212
- <span class:list={["chip", selectedShipment.carrierReady ? "green" : "amber"]}>{selectedShipment.carrierReady ? "OK" : "Waiting"}</span>
213
- </div>
214
- <div class:list={["check-row", { blocking: !selectedShipment.destinationReady }]}>
215
- <div><b>Address and jurisdiction</b><p>Destination is serviceable once any jurisdiction or address concerns clear.</p></div>
216
- <strong>{selectedShipment.destination}</strong>
217
- <span class:list={["chip", selectedShipment.destinationReady ? "green" : "amber"]}>{selectedShipment.destinationReady ? "Ready" : "Review"}</span>
218
- </div>
219
- </div>
220
-
221
- {selectedAssistDraft && (
222
- <div class="detail-section">
223
- <div class="detail-section-head">
224
- <h4>Fulfillment Assist</h4>
225
- <span class="chip blue">Draft</span>
226
- </div>
227
- <p class="note">{selectedAssistDraft.summary}</p>
228
- </div>
229
- )}
230
- </div>
231
- </>
232
- ) : (
233
- <div class="panel-body">
234
- <p class="note">No shipments yet for this tenant.</p>
235
- </div>
236
- )}
237
- </aside>
238
- </div>
239
-
240
- <div class="panel fulfillment-policy-panel">
241
- <div class="panel-head fulfillment-panel-head">
242
- <h3><svg><use href="#i-shield"></use></svg>Shipping Policy Context</h3>
243
- <span class:list={["chip", settings.shippingRestrictions.length > 0 ? "amber" : "green"]}>
244
- {settings.shippingRestrictions.length > 0 ? `${settings.shippingRestrictions.length} region(s) restricted` : "No active restrictions"}
245
- </span>
246
- </div>
247
- <div class="panel-body grid">
248
- <p class="note">Per-shipment destination readiness (above) reflects that shipment's own carrier/jurisdiction check. This panel surfaces the tenant's current shipping-restriction policy from Settings for context — see the Settings tab to manage it.</p>
249
- {settings.shippingRestrictions.length > 0 && (
250
- <div class="readiness-list" aria-label="Restricted regions">
251
- {settings.shippingRestrictions.map((restriction) => (
252
- <div class="check-row">
253
- <div><b>{restriction.region}</b><p>{restriction.reason}</p></div>
254
- <span class="chip amber">Restricted</span>
255
- </div>
256
- ))}
257
- </div>
258
- )}
259
- </div>
260
- </div>
261
- </section>
262
-
263
- <style>
264
- .fulfillment-tab {
265
- --fulfillment-split: minmax(0, 1.2fr) minmax(360px, 0.8fr);
266
- }
267
-
268
- .fulfillment-gate {
269
- scroll-margin-top: 92px;
270
- }
271
-
272
- .fulfillment-assist-panel {
273
- border-color: #b9c9ec;
274
- background: #fbfcff;
275
- }
276
-
277
- .assist-signal-grid {
278
- display: grid;
279
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
280
- gap: 10px;
281
- }
282
-
283
- .assist-signal {
284
- display: grid;
285
- gap: 7px;
286
- min-width: 0;
287
- padding: 12px;
288
- border: 1px solid var(--admin-line);
289
- border-radius: var(--admin-radius-sm);
290
- background: var(--admin-surface);
291
- }
292
-
293
- .assist-signal p {
294
- margin: 0;
295
- color: var(--admin-muted);
296
- font-size: 12px;
297
- }
298
-
299
- .fulfillment-workspace {
300
- display: grid;
301
- grid-template-columns: var(--fulfillment-split);
302
- gap: 14px;
303
- align-items: start;
304
- min-width: 0;
305
- }
306
-
307
- .fulfillment-queue-panel,
308
- .fulfillment-detail-panel {
309
- min-width: 0;
310
- }
311
-
312
- .fulfillment-detail-panel {
313
- position: sticky;
314
- top: 86px;
315
- box-shadow: var(--admin-shadow);
316
- }
317
-
318
- .fulfillment-panel-head {
319
- min-width: 0;
320
- }
321
-
322
- .fulfillment-panel-head h3 {
323
- min-width: 0;
324
- overflow-wrap: anywhere;
325
- }
326
-
327
- .fulfillment-table {
328
- min-width: 900px;
329
- }
330
-
331
- .fulfillment-table td {
332
- height: 58px;
333
- }
334
-
335
- .fulfillment-table tr.selected {
336
- background: #fff8ec;
337
- box-shadow: inset 3px 0 0 var(--admin-amber);
338
- }
339
-
340
- .detail-summary {
341
- display: grid;
342
- grid-template-columns: repeat(3, minmax(0, 1fr));
343
- gap: 10px;
344
- }
345
-
346
- .detail-summary > div {
347
- min-width: 0;
348
- padding-bottom: 10px;
349
- border-bottom: 1px solid var(--admin-line);
350
- }
351
-
352
- .detail-summary strong {
353
- display: block;
354
- margin-top: 3px;
355
- overflow-wrap: anywhere;
356
- }
357
-
358
- .eyebrow {
359
- color: var(--admin-muted);
360
- font-size: 11px;
361
- font-weight: 760;
362
- text-transform: uppercase;
363
- }
364
-
365
- .label-card {
366
- display: flex;
367
- align-items: center;
368
- justify-content: space-between;
369
- gap: 12px;
370
- padding: 12px;
371
- border: 1px solid var(--admin-line);
372
- border-radius: var(--admin-radius);
373
- background: var(--admin-surface-soft);
374
- }
375
-
376
- .label-card.locked {
377
- border-color: #f1c883;
378
- background: #fff8ec;
379
- }
380
-
381
- .label-card.unlocked {
382
- border-color: #a6d8c0;
383
- background: #eefaf3;
384
- }
385
-
386
- .label-card p {
387
- margin: 4px 0 0;
388
- color: var(--admin-muted);
389
- font-size: 12px;
390
- }
391
-
392
- .label-card .btn[disabled] {
393
- border-color: #d9b978;
394
- background: #f5e7c8;
395
- color: var(--admin-amber);
396
- cursor: not-allowed;
397
- }
398
-
399
- .action-strip {
400
- display: flex;
401
- flex-wrap: wrap;
402
- gap: 8px;
403
- scroll-margin-top: 92px;
404
- }
405
-
406
- .detail-section {
407
- display: grid;
408
- gap: 10px;
409
- padding-top: 2px;
410
- scroll-margin-top: 92px;
411
- }
412
-
413
- .detail-section + .detail-section {
414
- padding-top: 12px;
415
- border-top: 1px solid var(--admin-line);
416
- }
417
-
418
- .detail-section-head {
419
- display: flex;
420
- align-items: center;
421
- justify-content: space-between;
422
- gap: 10px;
423
- }
424
-
425
- .detail-section-head h4 {
426
- margin: 0;
427
- font-size: 13px;
428
- }
429
-
430
- .fulfillment-policy-panel .readiness-list {
431
- display: grid;
432
- gap: 0;
433
- }
434
-
435
- @media (max-width: 1280px) {
436
- .fulfillment-workspace {
437
- grid-template-columns: minmax(0, 1fr);
438
- }
439
-
440
- .fulfillment-detail-panel {
441
- position: static;
442
- }
443
- }
444
-
445
- @media (max-width: 920px) {
446
- .detail-summary {
447
- grid-template-columns: repeat(2, minmax(0, 1fr));
448
- }
449
-
450
- .label-card {
451
- align-items: flex-start;
452
- flex-direction: column;
453
- }
454
- }
455
-
456
- @media (max-width: 640px) {
457
- .detail-summary {
458
- grid-template-columns: 1fr;
459
- }
460
-
461
- .label-card .btn {
462
- width: 100%;
463
- }
464
- }
465
- </style>
@@ -1,105 +0,0 @@
1
- ---
2
- /** Managed rows use only the frozen ManagedAnalyticsApiV1 transport. */
3
- import { withBase } from "@/lib/basePath";
4
- const { envelope } = Astro.props;
5
- const { tenant, basePath } = Astro.locals;
6
- const viewerCapability = Astro.locals.viewer?.capability ?? "none";
7
- const canManage = viewerCapability === "owner" || viewerCapability === "admin";
8
- const managedEndpoint = withBase(basePath || "", "/api/integrations/v1");
9
- const localReadModelEndpoint = withBase(basePath || "", "/api/apps/admin/integrations");
10
- const gtmMigrationEndpoint = withBase(basePath || "", "/api/apps/admin/gtm-migration");
11
- const targetTenant = envelope.targetTenant ?? tenant.appDomain;
12
- ---
13
-
14
- <!-- agent-hook:integrations-tab -->
15
- <section id="integrations" class="band integrations-tab" aria-labelledby="admin-tab-integrations integrations-title" data-admin-panel data-agent-scope="integrations" role="tabpanel" data-integrations-panel data-managed-endpoint={managedEndpoint} data-local-readmodel-endpoint={localReadModelEndpoint} data-target-tenant={targetTenant} data-stage="staged" data-can-manage={canManage ? "true" : "false"}>
16
- <div class="section-title">
17
- <div><h2 id="integrations-title">Managed analytics</h2><p>Configure reviewed, native analytics adapters. New and revised configurations remain disabled until you explicitly publish them.</p></div>
18
- <div class="actions"><button class="btn" type="button" data-integrations-refresh><svg><use href="#i-refresh"></use></svg>Refresh</button></div>
19
- </div>
20
- {!canManage && <div class="ship-gate amber"><div class="ship-gate-main"><span class="seal"><svg><use href="#i-shield"></use></svg></span><div><b>Read-only view</b><span>Only an owner or admin can change managed analytics.</span></div></div></div>}
21
- <p class="chip" data-integrations-status role="status" aria-live="polite" tabindex="-1">Loading managed analytics…</p>
22
-
23
- <div class="panel" id="managed-analytics-inventory">
24
- <div class="panel-head"><h3><svg><use href="#i-workflows"></use></svg>Managed installs</h3><span class="chip teal">Native adapters</span></div>
25
- <div class="panel-body"><div class="table-wrap"><table class="integrations-table"><thead><tr><th>Integration</th><th>Status</th><th>Revisions</th><th>Configuration</th><th class="action">Actions</th></tr></thead><tbody data-managed-integrations-body><tr><td colspan="5" class="subtle">Loading installs…</td></tr></tbody></table></div></div>
26
- </div>
27
-
28
- <div class="panel" id="integrations-catalog">
29
- <div class="panel-head"><h3><svg><use href="#i-box"></use></svg>Add managed analytics</h3><span class="chip">Reviewed catalog</span></div>
30
- <div class="panel-body"><p class="note">Collection origins, consent purposes, and manual event types are fixed by the reviewed adapter. Nothing is enabled until Publish.</p><div class="integrations-catalog-grid" data-managed-integrations-catalog><p class="subtle">Loading catalog…</p></div></div>
31
- </div>
32
-
33
- <!-- READ-ONLY diagnostic: the uploaded GTM export is parsed only as inert data;
34
- analysis never publishes, activates, renders, or executes imported code. -->
35
- <div class="panel" id="integrations-gtm-migration" data-gtm-migration-panel data-gtm-endpoint={gtmMigrationEndpoint} data-target-tenant={targetTenant}>
36
- <div class="panel-head"><h3><svg><use href="#i-file"></use></svg>Migrate from GTM</h3><span class="chip teal">Read-only report</span></div>
37
- <div class="panel-body grid">
38
- <p class="note">Upload a Google Tag Manager container export. It is parsed as inert data to produce an inventory, dependency map, and migration dispositions. Nothing is published or activated; the file is never executed.</p>
39
- <div class="gtm-upload">
40
- <label class="gtm-upload-label" for="gtm-migration-file">GTM container export (JSON)</label>
41
- <input id="gtm-migration-file" type="file" accept="application/json,.json" data-gtm-file />
42
- <div class="actions">
43
- <button class="btn primary" type="button" data-gtm-analyze><svg><use href="#i-file"></use></svg>Analyze</button>
44
- <button class="btn" type="button" data-gtm-download disabled><svg><use href="#i-export"></use></svg>Download support bundle</button>
45
- </div>
46
- <p class="chip" data-gtm-status role="status" aria-live="polite">Choose a GTM export file to begin.</p>
47
- </div>
48
- <div class="gtm-results" data-gtm-results hidden>
49
- <section class="gtm-section" aria-labelledby="gtm-inventory-title"><h4 id="gtm-inventory-title">Inventory</h4><div data-gtm-inventory></div></section>
50
- <section class="gtm-section" aria-labelledby="gtm-dispositions-title"><h4 id="gtm-dispositions-title">Dispositions</h4><div data-gtm-dispositions></div></section>
51
- <section class="gtm-section" aria-labelledby="gtm-graph-title"><h4 id="gtm-graph-title">Dependency map</h4><div data-gtm-graph></div></section>
52
- <section class="gtm-section" aria-labelledby="gtm-findings-title"><h4 id="gtm-findings-title">Unresolved findings</h4><div data-gtm-findings></div></section>
53
- <section class="gtm-section" aria-labelledby="gtm-recommendations-title"><h4 id="gtm-recommendations-title">Recommendations</h4><div data-gtm-recommendations></div></section>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- <details class="panel" id="tenant-local-integrations">
59
- <summary class="panel-head"><h3>Other tenant-local integrations</h3><span class="chip">Read only</span></summary>
60
- <div class="panel-body"><div class="table-wrap"><table class="integrations-table local"><thead><tr><th>Integration</th><th>Status</th><th>Authority</th><th>State</th><th>Consent</th></tr></thead><tbody data-local-integrations-body><tr><td colspan="5" class="subtle">Loading tenant-local integrations…</td></tr></tbody></table></div></div>
61
- </details>
62
- </section>
63
-
64
- <script>
65
- import { initIntegrationsTab } from "@/lib/apps/integrationsTab";
66
- import { initGtmMigrationTab } from "@/lib/apps/gtmMigrationTab";
67
- initIntegrationsTab();
68
- initGtmMigrationTab();
69
- </script>
70
-
71
- <style>
72
- .integrations-table { min-width: 760px; width: 100%; border-collapse: collapse; }
73
- .integrations-table th, .integrations-table td { text-align: left; vertical-align: top; padding: 8px 10px; border-bottom: 1px solid var(--admin-line); }
74
- .integrations-table td .subtle { display: block; color: var(--admin-muted); font-size: 12px; }
75
- .integrations-actions, .integrations-inline { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
76
- .integrations-inline input, .integrations-inline select { min-width: 0; max-width: 210px; padding: 7px 9px; border: 1px solid var(--admin-line); border-radius: var(--admin-radius-sm); }
77
- [data-integrations-status][data-tone="error"] { background: var(--admin-red); color: #fff; }
78
- .integrations-catalog-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 12px; }
79
- .integrations-adapter-card { display: grid; gap: 8px; min-width: 0; padding: 12px; border: 1px solid var(--admin-line); border-radius: var(--admin-radius); background: var(--admin-surface); }
80
- .integrations-adapter-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
81
- .integrations-adapter-meta { display: grid; grid-template-columns: minmax(90px, auto) minmax(0, 1fr); gap: 2px 10px; margin: 0; font-size: 12px; }
82
- .integrations-adapter-meta dt { color: var(--admin-muted); }
83
- .integrations-adapter-meta dd { margin: 0; overflow-wrap: anywhere; }
84
- .integrations-adapter-config { display: flex; flex-wrap: wrap; gap: 8px; }
85
- .integrations-adapter-config input { flex: 1 1 160px; min-width: 0; padding: 8px 10px; border: 1px solid var(--admin-line); border-radius: var(--admin-radius-sm); }
86
- .integrations-adapter-result[data-tone="error"] { color: var(--admin-red); }
87
- .gtm-upload { display: grid; gap: 8px; }
88
- .gtm-upload-label { font-size: 12px; color: var(--admin-muted); }
89
- .gtm-upload input[type="file"] { max-width: 100%; }
90
- [data-gtm-status][data-tone="error"] { background: var(--admin-red); color: #fff; }
91
- .gtm-results { display: grid; gap: 16px; margin-top: 8px; }
92
- .gtm-section h4 { margin: 0 0 6px; }
93
- .gtm-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
94
- .gtm-stat { display: grid; gap: 2px; padding: 8px 10px; border: 1px solid var(--admin-line); border-radius: var(--admin-radius-sm); background: var(--admin-surface); }
95
- .gtm-stat strong { font-size: 18px; }
96
- .gtm-stat span { font-size: 12px; color: var(--admin-muted); overflow-wrap: anywhere; }
97
- .gtm-disposition-group { margin-bottom: 12px; }
98
- .gtm-disposition-group h4 { display: flex; align-items: center; gap: 8px; }
99
- .gtm-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
100
- .gtm-list li { padding: 6px 8px; border-bottom: 1px solid var(--admin-line); overflow-wrap: anywhere; }
101
- .gtm-list li .note { margin: 2px 0 0; color: var(--admin-muted); font-size: 12px; }
102
- details.panel > summary { cursor: pointer; list-style-position: inside; }
103
- details.panel > summary h3 { display: inline-flex; }
104
- @media (max-width: 760px) { .integrations-adapter-config, .integrations-inline { flex-direction: column; align-items: stretch; } .integrations-inline input, .integrations-inline select { max-width: none; } .table-wrap { max-width: 100%; overflow-x: auto; } }
105
- </style>