@bleedingdev/modern-js-create 3.4.0-ultramodern.1 → 3.4.0-ultramodern.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +18 -0
  2. package/dist/cjs/ultramodern-workspace/contracts.cjs +3 -3
  3. package/dist/cjs/ultramodern-workspace/descriptors.cjs +2 -2
  4. package/dist/cjs/ultramodern-workspace/module-federation.cjs +10 -1
  5. package/dist/cjs/ultramodern-workspace/package-json.cjs +74 -13
  6. package/dist/cjs/ultramodern-workspace/versions.cjs +1 -5
  7. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
  8. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +3 -7
  9. package/dist/esm/ultramodern-workspace/add-vertical.js +19 -1
  10. package/dist/esm/ultramodern-workspace/contracts.js +4 -4
  11. package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
  12. package/dist/esm/ultramodern-workspace/module-federation.js +10 -1
  13. package/dist/esm/ultramodern-workspace/package-json.js +62 -10
  14. package/dist/esm/ultramodern-workspace/versions.js +2 -3
  15. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
  16. package/dist/esm/ultramodern-workspace/write-workspace.js +5 -9
  17. package/dist/esm-node/ultramodern-workspace/add-vertical.js +19 -1
  18. package/dist/esm-node/ultramodern-workspace/contracts.js +4 -4
  19. package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
  20. package/dist/esm-node/ultramodern-workspace/module-federation.js +10 -1
  21. package/dist/esm-node/ultramodern-workspace/package-json.js +62 -10
  22. package/dist/esm-node/ultramodern-workspace/versions.js +2 -3
  23. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
  24. package/dist/esm-node/ultramodern-workspace/write-workspace.js +5 -9
  25. package/dist/types/ultramodern-workspace/package-json.d.ts +11 -2
  26. package/dist/types/ultramodern-workspace/versions.d.ts +1 -2
  27. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
  28. package/package.json +4 -4
  29. package/template-workspace/AGENTS.md.handlebars +2 -1
  30. package/template-workspace/README.md.handlebars +7 -0
  31. package/template-workspace/oxfmt.config.ts +3 -1
  32. package/template-workspace/oxlint.config.ts +3 -1
  33. package/template-workspace/scripts/bootstrap-agent-skills.mjs +23 -8
  34. package/templates/app/ultramodern-route-head.tsx.handlebars +20 -8
  35. package/templates/packages/shared-contracts-index.ts +206 -272
  36. package/templates/workspace-scripts/ultramodern-typecheck.mjs +197 -0
  37. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +104 -2
@@ -9,11 +9,17 @@ const appName = {{appDisplayNameJson}};
9
9
  const fallbackLanguage = 'en';
10
10
  const supportedLanguages = ['en', 'cs'] as const;
11
11
  type SupportedLanguage = (typeof supportedLanguages)[number];
12
- type RouteMetadata = (typeof ultramodernRouteMetadata)[number] & {
12
+ type GeneratedRouteMetadata = (typeof ultramodernRouteMetadata)[number];
13
+ type RouteMetadata = Omit<
14
+ GeneratedRouteMetadata,
15
+ 'indexable' | 'jsonLd' | 'public'
16
+ > & {
17
+ readonly indexable: boolean;
13
18
  readonly jsonLd?: RouteJsonLd;
19
+ readonly public: boolean;
14
20
  };
15
21
 
16
- const routeMetadata = ultramodernRouteMetadata as readonly RouteMetadata[];
22
+ const routeMetadata: readonly RouteMetadata[] = ultramodernRouteMetadata;
17
23
 
18
24
  const isSupportedLanguage = (value: string): value is SupportedLanguage =>
19
25
  supportedLanguages.includes(value as SupportedLanguage);
@@ -84,6 +90,11 @@ const resolveRouteMetadata = (pathname: string) => {
84
90
  return routeMetadata[0];
85
91
  };
86
92
 
93
+ const isPublicIndexableRoute = (
94
+ route: RouteMetadata | undefined,
95
+ ): route is RouteMetadata =>
96
+ route !== undefined && route.public && route.indexable;
97
+
87
98
  const absoluteUrl = (pathname: string) => {
88
99
  const origin = ULTRAMODERN_SITE_URL.replace(/\/+$/u, '');
89
100
  return `${origin}${pathname}`;
@@ -97,11 +108,12 @@ export const UltramodernRouteHead = () => {
97
108
  const t = i18nInstance['t'].bind(i18nInstance);
98
109
  const { canonical, alternates } = useLocalizedLocation();
99
110
  const route = resolveRouteMetadata(canonical);
100
- const title = route ? t(route.titleKey) : appName;
101
- const description = route ? t(route.descriptionKey) : appName;
111
+ const title = route === undefined ? appName : t(route.titleKey);
112
+ const description =
113
+ route === undefined ? appName : t(route.descriptionKey);
102
114
  const canonicalUrl = absoluteUrl(alternates[fallbackLanguage] ?? `/${fallbackLanguage}`);
103
- const indexable = route?.public === true && route?.indexable === true;
104
- const jsonLd = indexable ? route?.jsonLd : undefined;
115
+ const indexable = isPublicIndexableRoute(route);
116
+ const jsonLd = route?.jsonLd;
105
117
 
106
118
  return (
107
119
  <Helmet htmlAttributes={{ lang: i18nInstance.language ?? fallbackLanguage }}>
@@ -132,9 +144,9 @@ export const UltramodernRouteHead = () => {
132
144
  <meta content="summary_large_image" name="twitter:card" />
133
145
  <meta content={title} name="twitter:title" />
134
146
  <meta content={description} name="twitter:description" />
135
- {jsonLd && (
147
+ {jsonLd !== undefined ? (
136
148
  <script type="application/ld+json">{sanitiseJsonLd(jsonLd)}</script>
137
- )}
149
+ ) : null}
138
150
  </>
139
151
  )}
140
152
  </Helmet>
@@ -16,7 +16,9 @@ export interface UltramodernPublicSitemapEntry {
16
16
  /**
17
17
  * Per-locale overrides when translated URLs use translated params.
18
18
  */
19
- localeParams?: Partial<Record<'en' | 'cs', Record<string, string | number | boolean>>>;
19
+ localeParams?: Partial<
20
+ Record<'en' | 'cs', Record<string, string | number | boolean>>
21
+ >;
20
22
  draft?: boolean;
21
23
  indexable?: boolean;
22
24
  lastModified?: string;
@@ -73,74 +75,64 @@ export const ultramodernWorkspaceContract = {
73
75
  topology: 'topology/reference-topology.json',
74
76
  } as const;
75
77
 
76
- export const tractorEventNames = {
77
- checkoutAddToCart: 'checkout:add-to-cart',
78
- checkoutCartUpdated: 'checkout:cart-updated',
79
- checkoutClearCart: 'checkout:clear-cart',
80
- checkoutRemoveFromCart: 'checkout:remove-from-cart',
81
- exploreSelectedShop: 'explore:selected-shop',
82
- mfNavigate: 'mf:navigate',
83
- } as const;
78
+ export type UltramodernWorkspaceLocale = 'en' | 'cs';
84
79
 
85
- export type TractorEventName =
86
- (typeof tractorEventNames)[keyof typeof tractorEventNames];
80
+ export type UltramodernPerformanceReadinessSignalStatus =
81
+ | 'pass'
82
+ | 'warn'
83
+ | 'fail';
87
84
 
88
- export interface CheckoutAddToCartPayload {
89
- sku: string;
90
- quantity: number;
91
- name?: string;
92
- shopId?: string;
93
- unitPriceCents?: number;
94
- }
95
-
96
- export interface CheckoutCartLinePayload {
97
- sku: string;
98
- quantity: number;
99
- name?: string;
100
- unitPriceCents?: number;
101
- }
85
+ export const ultramodernWorkspaceEventNames = {
86
+ navigate: 'ultramodern:navigate',
87
+ performanceSignal: 'ultramodern:performance-signal',
88
+ remoteReady: 'ultramodern:remote-ready',
89
+ routeSettled: 'ultramodern:route-settled',
90
+ } as const;
102
91
 
103
- export interface CheckoutCartUpdatedPayload {
104
- lines: readonly CheckoutCartLinePayload[];
105
- totalQuantity: number;
106
- subtotalCents?: number;
107
- }
92
+ export type UltramodernWorkspaceEventName =
93
+ (typeof ultramodernWorkspaceEventNames)[keyof typeof ultramodernWorkspaceEventNames];
108
94
 
109
- export interface CheckoutRemoveFromCartPayload {
110
- sku: string;
95
+ export interface UltramodernNavigatePayload {
96
+ to: string;
97
+ replace?: boolean;
98
+ state?: Record<string, unknown>;
111
99
  }
112
100
 
113
- export interface CheckoutClearCartPayload {
114
- reason?: string;
101
+ export interface UltramodernRouteSettledPayload {
102
+ pathname: string;
103
+ locale?: UltramodernWorkspaceLocale;
104
+ title?: string;
115
105
  }
116
106
 
117
- export interface ExploreSelectedShopPayload {
118
- shopId: string;
119
- name?: string;
107
+ export interface UltramodernRemoteReadyPayload {
108
+ appId: string;
109
+ build?: string;
110
+ surface?: string;
111
+ version?: string;
120
112
  }
121
113
 
122
- export interface MfNavigatePayload {
123
- to: string;
124
- replace?: boolean;
125
- state?: Record<string, unknown>;
114
+ export interface UltramodernPerformanceSignalPayload {
115
+ signalId: UltramodernPerformanceReadinessSignalId;
116
+ status: UltramodernPerformanceReadinessSignalStatus;
117
+ durationMs?: number;
118
+ detail?: Record<string, unknown>;
126
119
  }
127
120
 
128
- export interface TractorEventPayloadMap {
129
- 'checkout:add-to-cart': CheckoutAddToCartPayload;
130
- 'checkout:cart-updated': CheckoutCartUpdatedPayload;
131
- 'checkout:clear-cart': CheckoutClearCartPayload;
132
- 'checkout:remove-from-cart': CheckoutRemoveFromCartPayload;
133
- 'explore:selected-shop': ExploreSelectedShopPayload;
134
- 'mf:navigate': MfNavigatePayload;
121
+ export interface UltramodernWorkspaceEventPayloadMap {
122
+ 'ultramodern:navigate': UltramodernNavigatePayload;
123
+ 'ultramodern:performance-signal': UltramodernPerformanceSignalPayload;
124
+ 'ultramodern:remote-ready': UltramodernRemoteReadyPayload;
125
+ 'ultramodern:route-settled': UltramodernRouteSettledPayload;
135
126
  }
136
127
 
137
- export class TractorEventValidationError extends Error {
138
- readonly eventName: TractorEventName;
128
+ export class UltramodernWorkspaceEventValidationError {
129
+ readonly message: string;
130
+ readonly name = 'UltramodernWorkspaceEventValidationError';
131
+ readonly eventName: UltramodernWorkspaceEventName;
139
132
  readonly payload: unknown;
140
133
 
141
- constructor(eventName: TractorEventName, payload: unknown) {
142
- super(`Invalid payload for Tractor event "${eventName}"`);
143
- this.name = 'TractorEventValidationError';
134
+ constructor(eventName: UltramodernWorkspaceEventName, payload: unknown) {
135
+ this.message = `Invalid payload for UltraModern workspace event "${eventName}"`;
144
136
  this.eventName = eventName;
145
137
  this.payload = payload;
146
138
  }
@@ -152,11 +144,27 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
152
144
  const isNonEmptyString = (value: unknown): value is string =>
153
145
  typeof value === 'string' && value.trim().length > 0;
154
146
 
155
- const isPositiveInteger = (value: unknown): value is number =>
156
- Number.isInteger(value) && value > 0;
147
+ const isNonNegativeNumber = (value: unknown): value is number =>
148
+ typeof value === 'number' && Number.isFinite(value) && value >= 0;
149
+
150
+ const isUltramodernWorkspaceLocale = (
151
+ value: unknown,
152
+ ): value is UltramodernWorkspaceLocale => value === 'en' || value === 'cs';
157
153
 
158
- const isNonNegativeInteger = (value: unknown): value is number =>
159
- Number.isInteger(value) && value >= 0;
154
+ const isPerformanceReadinessSignalId = (
155
+ value: unknown,
156
+ ): value is UltramodernPerformanceReadinessSignalId =>
157
+ value === 'bfcache' ||
158
+ value === 'core-web-vitals-rum' ||
159
+ value === 'duplicate-prefetch-warmup' ||
160
+ value === 'cache-policy-sanity' ||
161
+ value === 'save-data-behavior' ||
162
+ value === 'cloudflare-ssr-cache-hints';
163
+
164
+ const isPerformanceReadinessSignalStatus = (
165
+ value: unknown,
166
+ ): value is UltramodernPerformanceReadinessSignalStatus =>
167
+ value === 'pass' || value === 'warn' || value === 'fail';
160
168
 
161
169
  const hasOptionalString = (value: Record<string, unknown>, key: string) =>
162
170
  value[key] === undefined || isNonEmptyString(value[key]);
@@ -167,127 +175,128 @@ const hasOptionalBoolean = (value: Record<string, unknown>, key: string) =>
167
175
  const hasOptionalRecord = (value: Record<string, unknown>, key: string) =>
168
176
  value[key] === undefined || isRecord(value[key]);
169
177
 
170
- const hasOptionalNonNegativeInteger = (
178
+ const hasOptionalNonNegativeNumber = (
171
179
  value: Record<string, unknown>,
172
180
  key: string,
173
- ) => value[key] === undefined || isNonNegativeInteger(value[key]);
181
+ ) => value[key] === undefined || isNonNegativeNumber(value[key]);
174
182
 
175
- export const isCheckoutAddToCartPayload = (
176
- payload: unknown,
177
- ): payload is CheckoutAddToCartPayload =>
178
- isRecord(payload) &&
179
- isNonEmptyString(payload['sku']) &&
180
- isPositiveInteger(payload['quantity']) &&
181
- hasOptionalString(payload, 'name') &&
182
- hasOptionalString(payload, 'shopId') &&
183
- hasOptionalNonNegativeInteger(payload, 'unitPriceCents');
183
+ const hasOptionalLocale = (value: Record<string, unknown>, key: string) =>
184
+ value[key] === undefined || isUltramodernWorkspaceLocale(value[key]);
184
185
 
185
- export const isCheckoutCartLinePayload = (
186
+ export const isUltramodernNavigatePayload = (
186
187
  payload: unknown,
187
- ): payload is CheckoutCartLinePayload =>
188
+ ): payload is UltramodernNavigatePayload =>
188
189
  isRecord(payload) &&
189
- isNonEmptyString(payload['sku']) &&
190
- isPositiveInteger(payload['quantity']) &&
191
- hasOptionalString(payload, 'name') &&
192
- hasOptionalNonNegativeInteger(payload, 'unitPriceCents');
190
+ isNonEmptyString(payload['to']) &&
191
+ hasOptionalBoolean(payload, 'replace') &&
192
+ hasOptionalRecord(payload, 'state');
193
193
 
194
- export const isCheckoutCartUpdatedPayload = (
194
+ export const isUltramodernRouteSettledPayload = (
195
195
  payload: unknown,
196
- ): payload is CheckoutCartUpdatedPayload =>
196
+ ): payload is UltramodernRouteSettledPayload =>
197
197
  isRecord(payload) &&
198
- Array.isArray(payload['lines']) &&
199
- payload['lines'].every(isCheckoutCartLinePayload) &&
200
- isNonNegativeInteger(payload['totalQuantity']) &&
201
- hasOptionalNonNegativeInteger(payload, 'subtotalCents');
198
+ isNonEmptyString(payload['pathname']) &&
199
+ hasOptionalLocale(payload, 'locale') &&
200
+ hasOptionalString(payload, 'title');
202
201
 
203
- export const isCheckoutRemoveFromCartPayload = (
202
+ export const isUltramodernRemoteReadyPayload = (
204
203
  payload: unknown,
205
- ): payload is CheckoutRemoveFromCartPayload =>
206
- isRecord(payload) && isNonEmptyString(payload['sku']);
207
-
208
- export const isCheckoutClearCartPayload = (
209
- payload: unknown,
210
- ): payload is CheckoutClearCartPayload =>
211
- isRecord(payload) && hasOptionalString(payload, 'reason');
212
-
213
- export const isExploreSelectedShopPayload = (
214
- payload: unknown,
215
- ): payload is ExploreSelectedShopPayload =>
204
+ ): payload is UltramodernRemoteReadyPayload =>
216
205
  isRecord(payload) &&
217
- isNonEmptyString(payload['shopId']) &&
218
- hasOptionalString(payload, 'name');
206
+ isNonEmptyString(payload['appId']) &&
207
+ hasOptionalString(payload, 'build') &&
208
+ hasOptionalString(payload, 'surface') &&
209
+ hasOptionalString(payload, 'version');
219
210
 
220
- export const isMfNavigatePayload = (
211
+ export const isUltramodernPerformanceSignalPayload = (
221
212
  payload: unknown,
222
- ): payload is MfNavigatePayload =>
213
+ ): payload is UltramodernPerformanceSignalPayload =>
223
214
  isRecord(payload) &&
224
- isNonEmptyString(payload['to']) &&
225
- hasOptionalBoolean(payload, 'replace') &&
226
- hasOptionalRecord(payload, 'state');
215
+ isPerformanceReadinessSignalId(payload['signalId']) &&
216
+ isPerformanceReadinessSignalStatus(payload['status']) &&
217
+ hasOptionalNonNegativeNumber(payload, 'durationMs') &&
218
+ hasOptionalRecord(payload, 'detail');
227
219
 
228
- const tractorEventValidators = {
229
- [tractorEventNames.checkoutAddToCart]: isCheckoutAddToCartPayload,
230
- [tractorEventNames.checkoutCartUpdated]: isCheckoutCartUpdatedPayload,
231
- [tractorEventNames.checkoutClearCart]: isCheckoutClearCartPayload,
232
- [tractorEventNames.checkoutRemoveFromCart]: isCheckoutRemoveFromCartPayload,
233
- [tractorEventNames.exploreSelectedShop]: isExploreSelectedShopPayload,
234
- [tractorEventNames.mfNavigate]: isMfNavigatePayload,
235
- } satisfies {
236
- [Name in TractorEventName]: (
220
+ const ultramodernWorkspaceEventValidators: {
221
+ [Name in UltramodernWorkspaceEventName]: (
237
222
  payload: unknown,
238
- ) => payload is TractorEventPayloadMap[Name];
223
+ ) => payload is UltramodernWorkspaceEventPayloadMap[Name];
224
+ } = {
225
+ [ultramodernWorkspaceEventNames.navigate]: isUltramodernNavigatePayload,
226
+ [ultramodernWorkspaceEventNames.performanceSignal]:
227
+ isUltramodernPerformanceSignalPayload,
228
+ [ultramodernWorkspaceEventNames.remoteReady]:
229
+ isUltramodernRemoteReadyPayload,
230
+ [ultramodernWorkspaceEventNames.routeSettled]:
231
+ isUltramodernRouteSettledPayload,
239
232
  };
240
233
 
241
- export const isTractorEventPayload = <Name extends TractorEventName>(
234
+ export const isUltramodernWorkspaceEventPayload = <
235
+ Name extends UltramodernWorkspaceEventName,
236
+ >(
242
237
  eventName: Name,
243
238
  payload: unknown,
244
- ): payload is TractorEventPayloadMap[Name] =>
245
- tractorEventValidators[eventName](payload);
239
+ ): payload is UltramodernWorkspaceEventPayloadMap[Name] =>
240
+ ultramodernWorkspaceEventValidators[eventName](payload);
246
241
 
247
- export const assertTractorEventPayload = <Name extends TractorEventName>(
242
+ export const assertUltramodernWorkspaceEventPayload = <
243
+ Name extends UltramodernWorkspaceEventName,
244
+ >(
248
245
  eventName: Name,
249
246
  payload: unknown,
250
- ): TractorEventPayloadMap[Name] => {
251
- if (!isTractorEventPayload(eventName, payload)) {
252
- throw new TractorEventValidationError(eventName, payload);
247
+ ): UltramodernWorkspaceEventPayloadMap[Name] => {
248
+ if (!isUltramodernWorkspaceEventPayload(eventName, payload)) {
249
+ throw new UltramodernWorkspaceEventValidationError(eventName, payload);
253
250
  }
254
251
 
255
252
  return payload;
256
253
  };
257
254
 
258
- export const createTractorEvent = <Name extends TractorEventName>(
255
+ export const createUltramodernWorkspaceEvent = <
256
+ Name extends UltramodernWorkspaceEventName,
257
+ >(
259
258
  eventName: Name,
260
- payload: TractorEventPayloadMap[Name],
261
- ): CustomEvent<TractorEventPayloadMap[Name]> =>
259
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
260
+ ): CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]> =>
262
261
  new CustomEvent(eventName, {
263
262
  bubbles: true,
264
263
  composed: true,
265
- detail: assertTractorEventPayload(eventName, payload),
264
+ detail: assertUltramodernWorkspaceEventPayload(eventName, payload),
266
265
  });
267
266
 
268
- export const dispatchTractorEvent = <Name extends TractorEventName>(
267
+ export const dispatchUltramodernWorkspaceEvent = <
268
+ Name extends UltramodernWorkspaceEventName,
269
+ >(
269
270
  target: EventTarget,
270
271
  eventName: Name,
271
- payload: TractorEventPayloadMap[Name],
272
- ) => target.dispatchEvent(createTractorEvent(eventName, payload));
272
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
273
+ ) =>
274
+ target.dispatchEvent(
275
+ createUltramodernWorkspaceEvent(eventName, payload),
276
+ );
273
277
 
274
- export const onTractorEvent = <Name extends TractorEventName>(
278
+ export const onUltramodernWorkspaceEvent = <
279
+ Name extends UltramodernWorkspaceEventName,
280
+ >(
275
281
  target: EventTarget,
276
282
  eventName: Name,
277
283
  handler: (
278
- payload: TractorEventPayloadMap[Name],
279
- event: CustomEvent<TractorEventPayloadMap[Name]>,
284
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
285
+ event: CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]>,
280
286
  ) => void,
281
287
  ) => {
282
288
  const listener = (event: Event) => {
283
289
  if (!('detail' in event)) {
284
- throw new TractorEventValidationError(eventName, undefined);
290
+ throw new UltramodernWorkspaceEventValidationError(
291
+ eventName,
292
+ undefined,
293
+ );
285
294
  }
286
295
 
287
296
  const customEvent = event as CustomEvent<unknown>;
288
297
  handler(
289
- assertTractorEventPayload(eventName, customEvent.detail),
290
- customEvent as CustomEvent<TractorEventPayloadMap[Name]>,
298
+ assertUltramodernWorkspaceEventPayload(eventName, customEvent.detail),
299
+ customEvent as CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]>,
291
300
  );
292
301
  };
293
302
 
@@ -298,169 +307,94 @@ export const onTractorEvent = <Name extends TractorEventName>(
298
307
  };
299
308
  };
300
309
 
301
- const normalizeCheckoutLine = (
302
- line: CheckoutCartLinePayload,
303
- ): CheckoutCartLinePayload => {
304
- if (!isCheckoutCartLinePayload(line)) {
305
- throw new TractorEventValidationError('checkout:cart-updated', line);
306
- }
307
-
308
- return {
309
- quantity: line.quantity,
310
- sku: line.sku,
311
- ...(line.name === undefined ? {} : { name: line.name }),
312
- ...(line.unitPriceCents === undefined
313
- ? {}
314
- : { unitPriceCents: line.unitPriceCents }),
315
- };
316
- };
317
-
318
- export const createCheckoutCartSnapshot = (
319
- lines: readonly CheckoutCartLinePayload[],
320
- ): CheckoutCartUpdatedPayload => {
321
- const normalizedLines = lines.map(line => normalizeCheckoutLine(line));
322
- const subtotalCents = normalizedLines.reduce(
323
- (total, line) => total + (line.unitPriceCents ?? 0) * line.quantity,
324
- 0,
325
- );
326
-
327
- return {
328
- lines: normalizedLines,
329
- totalQuantity: normalizedLines.reduce(
330
- (total, line) => total + line.quantity,
331
- 0,
332
- ),
333
- ...(subtotalCents === 0 ? {} : { subtotalCents }),
334
- };
335
- };
336
-
337
- export const applyCheckoutCartEvent = (
338
- cart: CheckoutCartUpdatedPayload,
339
- eventName:
340
- | 'checkout:add-to-cart'
341
- | 'checkout:remove-from-cart'
342
- | 'checkout:clear-cart',
343
- payload:
344
- | CheckoutAddToCartPayload
345
- | CheckoutRemoveFromCartPayload
346
- | CheckoutClearCartPayload,
347
- ): CheckoutCartUpdatedPayload => {
348
- if (eventName === 'checkout:clear-cart') {
349
- assertTractorEventPayload(eventName, payload);
350
- return createCheckoutCartSnapshot([]);
351
- }
352
-
353
- if (eventName === 'checkout:remove-from-cart') {
354
- const removePayload = assertTractorEventPayload(eventName, payload);
355
- return createCheckoutCartSnapshot(
356
- cart.lines.filter(line => line.sku !== removePayload.sku),
357
- );
358
- }
359
-
360
- const addPayload = assertTractorEventPayload(eventName, payload);
361
- const lines = cart.lines.map(normalizeCheckoutLine);
362
- const existingIndex = lines.findIndex(line => line.sku === addPayload.sku);
363
- const nextLine = normalizeCheckoutLine({
364
- quantity: addPayload.quantity,
365
- sku: addPayload.sku,
366
- ...(addPayload.name === undefined ? {} : { name: addPayload.name }),
367
- ...(addPayload.unitPriceCents === undefined
368
- ? {}
369
- : { unitPriceCents: addPayload.unitPriceCents }),
370
- });
371
-
372
- if (existingIndex === -1) {
373
- return createCheckoutCartSnapshot([...lines, nextLine]);
374
- }
375
-
376
- const existing = lines[existingIndex];
377
- return createCheckoutCartSnapshot(
378
- lines.map((line, index) =>
379
- index === existingIndex
380
- ? normalizeCheckoutLine({
381
- ...line,
382
- ...nextLine,
383
- quantity: existing.quantity + addPayload.quantity,
384
- })
385
- : line,
386
- ),
387
- );
388
- };
389
-
390
- export const dispatchCheckoutAddToCart = (
391
- target: EventTarget,
392
- payload: CheckoutAddToCartPayload,
393
- ) => dispatchTractorEvent(target, 'checkout:add-to-cart', payload);
394
-
395
- export const dispatchCheckoutCartUpdated = (
310
+ export const dispatchUltramodernNavigate = (
396
311
  target: EventTarget,
397
- payload: CheckoutCartUpdatedPayload,
398
- ) => dispatchTractorEvent(target, 'checkout:cart-updated', payload);
399
-
400
- export const dispatchCheckoutRemoveFromCart = (
401
- target: EventTarget,
402
- payload: CheckoutRemoveFromCartPayload,
403
- ) => dispatchTractorEvent(target, 'checkout:remove-from-cart', payload);
404
-
405
- export const dispatchCheckoutClearCart = (
406
- target: EventTarget,
407
- payload: CheckoutClearCartPayload = {},
408
- ) => dispatchTractorEvent(target, 'checkout:clear-cart', payload);
409
-
410
- export const dispatchExploreSelectedShop = (
411
- target: EventTarget,
412
- payload: ExploreSelectedShopPayload,
413
- ) => dispatchTractorEvent(target, 'explore:selected-shop', payload);
312
+ payload: UltramodernNavigatePayload,
313
+ ) =>
314
+ dispatchUltramodernWorkspaceEvent(
315
+ target,
316
+ ultramodernWorkspaceEventNames.navigate,
317
+ payload,
318
+ );
414
319
 
415
- export const dispatchMfNavigate = (
320
+ export const dispatchUltramodernRouteSettled = (
416
321
  target: EventTarget,
417
- payload: MfNavigatePayload,
418
- ) => dispatchTractorEvent(target, 'mf:navigate', payload);
322
+ payload: UltramodernRouteSettledPayload,
323
+ ) =>
324
+ dispatchUltramodernWorkspaceEvent(
325
+ target,
326
+ ultramodernWorkspaceEventNames.routeSettled,
327
+ payload,
328
+ );
419
329
 
420
- export const onCheckoutAddToCart = (
330
+ export const dispatchUltramodernRemoteReady = (
421
331
  target: EventTarget,
422
- handler: (
423
- payload: CheckoutAddToCartPayload,
424
- event: CustomEvent<CheckoutAddToCartPayload>,
425
- ) => void,
426
- ) => onTractorEvent(target, 'checkout:add-to-cart', handler);
332
+ payload: UltramodernRemoteReadyPayload,
333
+ ) =>
334
+ dispatchUltramodernWorkspaceEvent(
335
+ target,
336
+ ultramodernWorkspaceEventNames.remoteReady,
337
+ payload,
338
+ );
427
339
 
428
- export const onCheckoutCartUpdated = (
340
+ export const dispatchUltramodernPerformanceSignal = (
429
341
  target: EventTarget,
430
- handler: (
431
- payload: CheckoutCartUpdatedPayload,
432
- event: CustomEvent<CheckoutCartUpdatedPayload>,
433
- ) => void,
434
- ) => onTractorEvent(target, 'checkout:cart-updated', handler);
342
+ payload: UltramodernPerformanceSignalPayload,
343
+ ) =>
344
+ dispatchUltramodernWorkspaceEvent(
345
+ target,
346
+ ultramodernWorkspaceEventNames.performanceSignal,
347
+ payload,
348
+ );
435
349
 
436
- export const onCheckoutRemoveFromCart = (
350
+ export const onUltramodernNavigate = (
437
351
  target: EventTarget,
438
352
  handler: (
439
- payload: CheckoutRemoveFromCartPayload,
440
- event: CustomEvent<CheckoutRemoveFromCartPayload>,
353
+ payload: UltramodernNavigatePayload,
354
+ event: CustomEvent<UltramodernNavigatePayload>,
441
355
  ) => void,
442
- ) => onTractorEvent(target, 'checkout:remove-from-cart', handler);
356
+ ) =>
357
+ onUltramodernWorkspaceEvent(
358
+ target,
359
+ ultramodernWorkspaceEventNames.navigate,
360
+ handler,
361
+ );
443
362
 
444
- export const onCheckoutClearCart = (
363
+ export const onUltramodernRouteSettled = (
445
364
  target: EventTarget,
446
365
  handler: (
447
- payload: CheckoutClearCartPayload,
448
- event: CustomEvent<CheckoutClearCartPayload>,
366
+ payload: UltramodernRouteSettledPayload,
367
+ event: CustomEvent<UltramodernRouteSettledPayload>,
449
368
  ) => void,
450
- ) => onTractorEvent(target, 'checkout:clear-cart', handler);
369
+ ) =>
370
+ onUltramodernWorkspaceEvent(
371
+ target,
372
+ ultramodernWorkspaceEventNames.routeSettled,
373
+ handler,
374
+ );
451
375
 
452
- export const onExploreSelectedShop = (
376
+ export const onUltramodernRemoteReady = (
453
377
  target: EventTarget,
454
378
  handler: (
455
- payload: ExploreSelectedShopPayload,
456
- event: CustomEvent<ExploreSelectedShopPayload>,
379
+ payload: UltramodernRemoteReadyPayload,
380
+ event: CustomEvent<UltramodernRemoteReadyPayload>,
457
381
  ) => void,
458
- ) => onTractorEvent(target, 'explore:selected-shop', handler);
382
+ ) =>
383
+ onUltramodernWorkspaceEvent(
384
+ target,
385
+ ultramodernWorkspaceEventNames.remoteReady,
386
+ handler,
387
+ );
459
388
 
460
- export const onMfNavigate = (
389
+ export const onUltramodernPerformanceSignal = (
461
390
  target: EventTarget,
462
391
  handler: (
463
- payload: MfNavigatePayload,
464
- event: CustomEvent<MfNavigatePayload>,
392
+ payload: UltramodernPerformanceSignalPayload,
393
+ event: CustomEvent<UltramodernPerformanceSignalPayload>,
465
394
  ) => void,
466
- ) => onTractorEvent(target, 'mf:navigate', handler);
395
+ ) =>
396
+ onUltramodernWorkspaceEvent(
397
+ target,
398
+ ultramodernWorkspaceEventNames.performanceSignal,
399
+ handler,
400
+ );