@treasuryspatial/surface-kit 0.1.9 → 0.1.16

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/dist/index.js CHANGED
@@ -1,4 +1,300 @@
1
- import { DEFAULT_COMPOSER_SURFACE_MODE_POLICY, mergeSurfaceModePolicy } from '@treasuryspatial/mode-policy';
1
+ import { DEFAULT_COMPOSER_SURFACE_MODE_POLICY, mergeSurfaceModePolicy, resolveModeLaunchState, } from '@treasuryspatial/mode-policy';
2
+ export const orderSurfaceModeTabs = (tabs) => {
3
+ const seen = new Set();
4
+ return tabs.filter((tab) => {
5
+ const key = `${tab.id}:${tabs.indexOf(tab)}`;
6
+ if (seen.has(key))
7
+ return false;
8
+ seen.add(key);
9
+ return true;
10
+ });
11
+ };
12
+ const hasDefinedKeys = (value) => Boolean(value && Object.values(value).some((entry) => entry !== undefined));
13
+ const compactRecord = (value) => Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
14
+ const mergeSurfaceComposerTopbar = (base, override) => {
15
+ const contexts = base?.contexts || override?.contexts
16
+ ? ['landing', 'login', 'composer', 'admin'].reduce((acc, context) => {
17
+ const mergedContext = {
18
+ ...(base?.contexts?.[context] ?? {}),
19
+ ...(override?.contexts?.[context] ?? {}),
20
+ };
21
+ if (hasDefinedKeys(mergedContext)) {
22
+ acc[context] = mergedContext;
23
+ }
24
+ return acc;
25
+ }, {})
26
+ : undefined;
27
+ const merged = {
28
+ ...(base ?? {}),
29
+ ...(override ?? {}),
30
+ ...(contexts && hasDefinedKeys(contexts) ? { contexts } : {}),
31
+ };
32
+ return hasDefinedKeys(merged) ? merged : undefined;
33
+ };
34
+ const hasEntries = (value) => Boolean(value && Object.keys(value).length > 0);
35
+ export const buildClientSkinControlPlaneWarnings = (surfaceLabel, runtimeSource, isClientSkinnedSurface) => {
36
+ if (!isClientSkinnedSurface)
37
+ return [];
38
+ const warnings = [];
39
+ if (runtimeSource?.modeTabs?.length) {
40
+ warnings.push(`${surfaceLabel}: modeTabs are control-plane policy and must live in composer_data.`);
41
+ }
42
+ if (runtimeSource?.routePolicy) {
43
+ warnings.push(`${surfaceLabel}: routePolicy is control-plane policy and must live in composer_data.`);
44
+ }
45
+ if (runtimeSource?.runtime) {
46
+ warnings.push(`${surfaceLabel}: runtime/toolset/prompt-pack policy is control-plane data and must live in composer_data.`);
47
+ }
48
+ if (runtimeSource?.featureFlags) {
49
+ warnings.push(`${surfaceLabel}: featureFlags are control-plane policy and must live in composer_data.`);
50
+ }
51
+ return warnings;
52
+ };
53
+ export const buildSurfaceTenantUiComposerSeed = (manifest) => {
54
+ if (!manifest)
55
+ return null;
56
+ const typographyCandidate = manifest.theme?.typography
57
+ ? {
58
+ fontFamily: manifest.theme.typography.fontFamily,
59
+ weights: manifest.theme.typography.weights,
60
+ }
61
+ : undefined;
62
+ const typography = typographyCandidate && hasDefinedKeys(typographyCandidate)
63
+ ? compactRecord(typographyCandidate)
64
+ : undefined;
65
+ const topbarCandidate = manifest.topbar
66
+ ? {
67
+ title: manifest.topbar.title,
68
+ subtitle: manifest.topbar.subtitle,
69
+ productLabel: manifest.topbar.productLabel,
70
+ productSuffixLabel: manifest.topbar.productSuffixLabel,
71
+ links: manifest.topbar.links,
72
+ contexts: manifest.topbar.contexts,
73
+ }
74
+ : undefined;
75
+ const topbar = topbarCandidate && hasDefinedKeys(topbarCandidate)
76
+ ? compactRecord(topbarCandidate)
77
+ : undefined;
78
+ const seed = {
79
+ ...(typography ? { theme: { typography } } : {}),
80
+ ...(topbar ? { topbar } : {}),
81
+ };
82
+ return hasDefinedKeys(seed) ? seed : null;
83
+ };
84
+ export const buildSurfaceShellNarrative = (spec, overrides = {}) => {
85
+ const surface = (spec.manifest.surface ?? {});
86
+ const topbar = (spec.manifest.topbar ?? {});
87
+ const productLabel = overrides.surfaceTitle ?? spec.navigation?.surfaceTitle ?? topbar.productLabel ?? surface.label ?? spec.id;
88
+ const summary = typeof surface.summary === 'string' ? surface.summary.trim() : '';
89
+ const description = typeof surface.description === 'string' ? surface.description.trim() : '';
90
+ const promptFamily = typeof surface.promptFamily === 'string' ? surface.promptFamily.trim() : '';
91
+ const scaffoldBadge = typeof surface.scaffoldShell?.badge === 'string' ? surface.scaffoldShell.badge.trim() : '';
92
+ const sharedSubtitle = overrides.subtitle ?? (summary || description || `Treasury ${productLabel}.`);
93
+ const loginHeading = overrides.loginHeading ?? topbar.productLabel ?? surface.label ?? productLabel;
94
+ const loginStrap = overrides.loginStrap ?? sharedSubtitle;
95
+ const loginBody = overrides.loginBody === null ? undefined : (overrides.loginBody ?? (description || undefined));
96
+ const loginDisclaimer = overrides.loginDisclaimer === null ? undefined : (overrides.loginDisclaimer ?? spec.content?.login?.disclaimer);
97
+ const landingHeader = overrides.landingHeader ?? topbar.productLabel ?? surface.label ?? productLabel;
98
+ const landingStrap = overrides.landingStrap ?? sharedSubtitle;
99
+ const landingBodyParagraphs = (overrides.landingBodyParagraphs ?? [
100
+ description || summary || `${productLabel} runs on the shared Treasury runtime.`,
101
+ promptFamily
102
+ ? `Prompt family: ${promptFamily}.`
103
+ : 'Modes, tools, and prompt packs are governed by shared surface policy.',
104
+ scaffoldBadge
105
+ ? `${scaffoldBadge} remains shell-level copy while the runtime stays shared.`
106
+ : 'Branding stays in the skin layer while the runtime remains shared.',
107
+ ]).filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
108
+ return {
109
+ navigation: {
110
+ surfaceTitle: productLabel,
111
+ introHref: overrides.introHref ?? spec.navigation?.introHref ?? '/',
112
+ composeHref: overrides.composeHref ?? spec.navigation?.composeHref ?? '/composer/compose',
113
+ introTitle: overrides.introTitle ?? spec.navigation?.introTitle,
114
+ composeTitle: overrides.composeTitle ?? spec.navigation?.composeTitle,
115
+ searchEnabled: overrides.searchEnabled ?? spec.navigation?.searchEnabled ?? true,
116
+ },
117
+ content: {
118
+ subtitle: sharedSubtitle,
119
+ login: {
120
+ heading: loginHeading,
121
+ strap: loginStrap,
122
+ body: loginBody,
123
+ disclaimer: loginDisclaimer,
124
+ },
125
+ landing: {
126
+ header: landingHeader,
127
+ strap: landingStrap,
128
+ bodyParagraphs: landingBodyParagraphs,
129
+ },
130
+ },
131
+ };
132
+ };
133
+ const resolveSurfaceModePolicy = (spec) => mergeSurfaceModePolicy(DEFAULT_COMPOSER_SURFACE_MODE_POLICY, (spec.manifest.modes ?? {}));
134
+ const resolveModeHref = (composeHref, modeId, defaultModeId) => {
135
+ if (modeId === 'images') {
136
+ if (composeHref.endsWith('/compose')) {
137
+ return `${composeHref.slice(0, -'/compose'.length)}/images`;
138
+ }
139
+ return '/composer/images';
140
+ }
141
+ if (!modeId || modeId === defaultModeId)
142
+ return composeHref;
143
+ const separator = composeHref.includes('?') ? '&' : '?';
144
+ return `${composeHref}${separator}mode=${encodeURIComponent(modeId)}`;
145
+ };
146
+ export const buildSurfaceModeTabs = (spec, options = {}) => {
147
+ const policy = resolveSurfaceModePolicy(spec);
148
+ if (Object.prototype.hasOwnProperty.call(policy.modes ?? {}, 'gallery')) {
149
+ throw new Error(`${spec.id}: gallery is a first-class route and must not be authored as a composer mode.`);
150
+ }
151
+ const composeHref = options.composeHref ?? spec.navigation?.composeHref ?? '/composer/compose';
152
+ const gatedModeIds = new Set([...(spec.routePolicy?.gatedModeIds ?? []), ...(options.gatedModeIds ?? [])]);
153
+ const visibleModeIds = Object.keys(policy.modes ?? {}).filter((modeId) => resolveModeLaunchState(modeId, policy) === 'live');
154
+ const defaultModeId = policy.defaultMode && visibleModeIds.includes(policy.defaultMode) ? policy.defaultMode : undefined;
155
+ const orderedModeIds = defaultModeId
156
+ ? [defaultModeId, ...visibleModeIds.filter((modeId) => modeId !== defaultModeId)]
157
+ : visibleModeIds;
158
+ const canonicalModeIds = (() => {
159
+ if (orderedModeIds.includes('images'))
160
+ return orderedModeIds;
161
+ const coreModeIds = new Set(['imagine', 'design', 'map']);
162
+ const lastCoreIndex = orderedModeIds.reduce((latestIndex, modeId, index) => (coreModeIds.has(modeId) ? index : latestIndex), -1);
163
+ if (lastCoreIndex === -1)
164
+ return [...orderedModeIds, 'images'];
165
+ return [
166
+ ...orderedModeIds.slice(0, lastCoreIndex + 1),
167
+ 'images',
168
+ ...orderedModeIds.slice(lastCoreIndex + 1),
169
+ ];
170
+ })();
171
+ return orderSurfaceModeTabs(canonicalModeIds.map((modeId) => ({
172
+ id: modeId,
173
+ label: modeId === 'images' ? 'image stream' : policy.modes?.[modeId]?.label ?? modeId,
174
+ href: resolveModeHref(composeHref, modeId, defaultModeId),
175
+ requiresAdmin: gatedModeIds.has(modeId) || undefined,
176
+ })));
177
+ };
178
+ export const buildSurfaceRuntimeAvailability = (spec) => {
179
+ if (!spec.runtime) {
180
+ return undefined;
181
+ }
182
+ const surfaceId = spec.id.trim();
183
+ const promptFamily = spec.manifest.surface?.promptFamily?.trim() || surfaceId;
184
+ const toolCatalogSource = spec.runtime.toolCatalogSource;
185
+ if (toolCatalogSource !== 'registry-live' && toolCatalogSource !== 'toolset') {
186
+ throw new Error(`${surfaceId}: runtime.toolCatalogSource must be explicitly set to "registry-live" or "toolset". Legacy shell defaults are removed.`);
187
+ }
188
+ const toolsetId = toolCatalogSource === 'toolset'
189
+ ? spec.runtime.toolsetId?.trim() || spec.runtime.toolset?.id?.trim()
190
+ : undefined;
191
+ if (toolCatalogSource === 'toolset' && !toolsetId) {
192
+ throw new Error(`${surfaceId}: runtime.toolsetId or runtime.toolset.id is required when runtime.toolCatalogSource is "toolset".`);
193
+ }
194
+ const promptPolicyId = spec.runtime.promptPolicyId?.trim() || spec.runtime.promptPolicy?.id?.trim() || promptFamily;
195
+ const visibleToolIds = spec.runtime.visibleToolIds?.map((toolId) => toolId.trim()).filter(Boolean);
196
+ const hiddenToolIds = spec.runtime.hiddenToolIds?.map((toolId) => toolId.trim()).filter(Boolean);
197
+ const promptPackCatalogMode = spec.runtime.promptPackCatalogMode ?? (surfaceId === 'composer' ? 'all' : 'surface');
198
+ const moduleCatalogMode = spec.runtime.moduleCatalogMode ?? (surfaceId === 'composer' ? 'all' : 'surface');
199
+ return {
200
+ toolCatalogSource,
201
+ toolset: toolsetId ? { id: toolsetId } : undefined,
202
+ toolsetId: toolsetId || undefined,
203
+ promptPolicy: { id: promptPolicyId },
204
+ promptPolicyId,
205
+ visibleToolIds: visibleToolIds && visibleToolIds.length > 0 ? visibleToolIds : undefined,
206
+ hiddenToolIds: hiddenToolIds && hiddenToolIds.length > 0 ? hiddenToolIds : undefined,
207
+ promptPackCatalogMode,
208
+ moduleCatalogMode,
209
+ };
210
+ };
211
+ export const buildSurfaceComposerDataDocument = (target, spec, overrides = {}) => {
212
+ const isClientSkinnedSurface = spec.manifest.surface?.brandProfile === 'client-skinned';
213
+ const controlPlaneWarnings = buildClientSkinControlPlaneWarnings(spec.id, spec, isClientSkinnedSurface).concat(buildClientSkinControlPlaneWarnings(spec.id, overrides, isClientSkinnedSurface));
214
+ const mergedContent = spec.content || overrides.content
215
+ ? {
216
+ subtitle: overrides.content?.subtitle ?? spec.content?.subtitle,
217
+ login: overrides.content?.login ?? spec.content?.login,
218
+ landing: overrides.content?.landing ?? spec.content?.landing,
219
+ }
220
+ : undefined;
221
+ const mergedTopbar = mergeSurfaceComposerTopbar(spec.manifest.topbar
222
+ ? {
223
+ secondaryLogo: spec.manifest.topbar.secondaryLogo,
224
+ title: spec.manifest.topbar.title,
225
+ subtitle: spec.manifest.topbar.subtitle,
226
+ productLabel: spec.manifest.topbar.productLabel,
227
+ productSuffixLabel: spec.manifest.topbar.productSuffixLabel,
228
+ links: spec.manifest.topbar.links,
229
+ contexts: spec.manifest.topbar.contexts,
230
+ }
231
+ : undefined, overrides.topbar);
232
+ const mergedRuntime = overrides.runtime || (!isClientSkinnedSurface && spec.runtime)
233
+ ? {
234
+ ...(!isClientSkinnedSurface ? (spec.runtime ?? {}) : {}),
235
+ ...(overrides.runtime ?? {}),
236
+ }
237
+ : undefined;
238
+ const runtimeAvailability = !isClientSkinnedSurface && spec.runtime ? buildSurfaceRuntimeAvailability(spec) : undefined;
239
+ const runtimeToolCatalogSource = mergedRuntime?.toolCatalogSource ?? runtimeAvailability?.toolCatalogSource;
240
+ const runtimeToolset = mergedRuntime?.toolset ?? runtimeAvailability?.toolset;
241
+ const runtimeToolsetId = mergedRuntime?.toolsetId ?? runtimeAvailability?.toolsetId;
242
+ const runtimePromptPolicy = mergedRuntime?.promptPolicy ?? runtimeAvailability?.promptPolicy;
243
+ const runtimePromptPolicyId = mergedRuntime?.promptPolicyId ?? runtimeAvailability?.promptPolicyId;
244
+ const runtimePromptPackCatalogMode = mergedRuntime?.promptPackCatalogMode ?? runtimeAvailability?.promptPackCatalogMode;
245
+ const runtimeModuleCatalogMode = mergedRuntime?.moduleCatalogMode ?? runtimeAvailability?.moduleCatalogMode;
246
+ const mergedFeatureFlags = overrides.featureFlags || (!isClientSkinnedSurface && spec.featureFlags)
247
+ ? {
248
+ ...(!isClientSkinnedSurface ? (spec.featureFlags ?? {}) : {}),
249
+ ...(overrides.featureFlags ?? {}),
250
+ }
251
+ : undefined;
252
+ return {
253
+ tenantSlug: target.tenantSlug,
254
+ subtenantSlug: target.subtenantSlug ?? null,
255
+ surfaceId: target.surfaceId,
256
+ title: overrides.title ??
257
+ spec.navigation?.surfaceTitle ??
258
+ spec.manifest.surface?.label ??
259
+ spec.manifest.topbar?.productLabel ??
260
+ spec.id,
261
+ lockedLabel: overrides.lockedLabel,
262
+ topbar: mergedTopbar ? compactRecord(mergedTopbar) : undefined,
263
+ navigation: overrides.navigation ?? spec.navigation,
264
+ content: mergedContent,
265
+ modeTabs: overrides.modeTabs ||
266
+ (!isClientSkinnedSurface && (spec.modeTabs || buildSurfaceModeTabs(spec)))
267
+ ? orderSurfaceModeTabs((overrides.modeTabs ??
268
+ (!isClientSkinnedSurface ? spec.modeTabs ?? buildSurfaceModeTabs(spec) : undefined)))
269
+ : undefined,
270
+ routePolicy: overrides.routePolicy || (!isClientSkinnedSurface && spec.routePolicy)
271
+ ? {
272
+ ...(!isClientSkinnedSurface ? (spec.routePolicy ?? {}) : {}),
273
+ ...(overrides.routePolicy ?? {}),
274
+ }
275
+ : undefined,
276
+ runtime: mergedRuntime || runtimeAvailability
277
+ ? {
278
+ ...(mergedRuntime ?? {}),
279
+ ...(runtimeToolCatalogSource ? { toolCatalogSource: runtimeToolCatalogSource } : {}),
280
+ ...(runtimeToolset ? { toolset: runtimeToolset } : {}),
281
+ ...(runtimeToolsetId ? { toolsetId: runtimeToolsetId } : {}),
282
+ ...(runtimePromptPolicy ? { promptPolicy: runtimePromptPolicy } : {}),
283
+ ...(runtimePromptPolicyId ? { promptPolicyId: runtimePromptPolicyId } : {}),
284
+ ...(runtimePromptPackCatalogMode ? { promptPackCatalogMode: runtimePromptPackCatalogMode } : {}),
285
+ ...(runtimeModuleCatalogMode ? { moduleCatalogMode: runtimeModuleCatalogMode } : {}),
286
+ }
287
+ : undefined,
288
+ featureFlags: mergedFeatureFlags,
289
+ controlPlaneWarnings: controlPlaneWarnings.length > 0 ? controlPlaneWarnings : undefined,
290
+ assetAccess: overrides.assetAccess,
291
+ };
292
+ };
293
+ export const buildSurfaceCanonicalProvisioning = (target, spec, options = {}) => ({
294
+ tenantBranding: options.tenantBranding,
295
+ tenantUiComposer: buildSurfaceTenantUiComposerSeed(spec.manifest),
296
+ composerData: buildSurfaceComposerDataDocument(target, spec, options.composerData),
297
+ });
2
298
  export const DEFAULT_MARKETING_SLUGS = [
3
299
  'localhost',
4
300
  'treasury',
@@ -19,7 +315,7 @@ export const TREASURY_BASE_UI_MANIFEST = {
19
315
  id: 'composer',
20
316
  family: 'composer',
21
317
  label: 'Composer',
22
- promptFamily: 'composer',
318
+ promptFamily: 'spatial',
23
319
  brandProfile: 'treasury-native',
24
320
  summary: 'Treasury design and render surface.',
25
321
  standaloneCapable: true,
@@ -32,8 +328,8 @@ export const TREASURY_BASE_UI_MANIFEST = {
32
328
  },
33
329
  theme: {
34
330
  typography: {
35
- fontFamily: "'Aeonik', system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
36
- weights: [300, 400, 500],
331
+ fontFamily: "'Aeonik', -apple-system, BlinkMacSystemFont, system-ui, sans-serif",
332
+ weights: [300, 500, 700],
37
333
  },
38
334
  },
39
335
  layout: {
@@ -47,7 +343,6 @@ export const TREASURY_BASE_UI_MANIFEST = {
47
343
  productLabel: 'Composer',
48
344
  links: [
49
345
  { label: 'Treasury', href: 'https://treasury.space', target: '_blank' },
50
- { label: 'Science', href: 'https://science.treasury.space', target: '_blank' },
51
346
  ],
52
347
  },
53
348
  modules: {
@@ -55,24 +350,25 @@ export const TREASURY_BASE_UI_MANIFEST = {
55
350
  panelTabs: [
56
351
  { id: 'scene', label: 'scene' },
57
352
  { id: 'controls', label: 'controls' },
353
+ { id: 'map-controls', label: 'map controls' },
58
354
  { id: 'lighting', label: 'lighting' },
59
355
  { id: 'materials', label: 'materials' },
60
356
  { id: 'render', label: 'render' },
61
- { id: 'presets', label: 'presets' },
357
+ { id: 'compositions', label: 'compositions' },
62
358
  { id: 'upload', label: 'upload' },
63
359
  ],
64
360
  rightRail: {
65
361
  enabled: true,
66
- defaultOpen: false,
67
- defaultTab: 'images',
362
+ defaultOpen: true,
363
+ defaultTab: 'feed',
68
364
  tabs: [
69
- { id: 'images', label: 'images' },
365
+ { id: 'feed', label: 'feed' },
70
366
  { id: 'metrics', label: 'metrics' },
71
367
  { id: 'feedback', label: 'feedback' },
72
368
  ],
73
369
  },
74
- metricsPanel: { enabled: true, defaultOpen: false },
75
- inspector: { enabled: true, defaultOpen: true },
370
+ metricsPanel: { enabled: true, defaultOpen: true },
371
+ inspector: { enabled: true, defaultOpen: false },
76
372
  hud: { enabled: true },
77
373
  viewControls: { enabled: true },
78
374
  veils: { enabled: true, defaultOn: false },
@@ -86,6 +382,10 @@ export const TRANSITGUY_LOGO = {
86
382
  src: 'https://images.squarespace-cdn.com/content/v1/60074cff46d4c56a53f5b0f8/c9051ab0-28f7-4840-ae97-4d70737abffe/the%2Btransit%2Bgay%2Blogo%2B%281%29.png',
87
383
  alt: 'The Transit Guy',
88
384
  };
385
+ export const COURTYARD_URBANIST_LOGO = {
386
+ src: '/postcard/courtyard-urbanist.png',
387
+ alt: 'Courtyard Urbanist',
388
+ };
89
389
  const buildManifestImage = (src, alt) => {
90
390
  if (!src)
91
391
  return null;
@@ -188,9 +488,7 @@ export const resolveSurfaceBrandingImages = (target, payload) => {
188
488
  target.surfaceLabel ||
189
489
  'Subtenant';
190
490
  const topbarLogo = buildManifestImage(primaryLogoSrc, resolvedPrimaryAlt);
191
- const subtenantLogo = subtenantLogoSrc && subtenantLogoSrc !== primaryLogoSrc
192
- ? buildManifestImage(subtenantLogoSrc, resolvedSubtenantAlt)
193
- : null;
491
+ const subtenantLogo = subtenantLogoSrc ? buildManifestImage(subtenantLogoSrc, resolvedSubtenantAlt) : null;
194
492
  return {
195
493
  topbarLogo,
196
494
  subtenantLogo,
@@ -275,32 +573,55 @@ export const mapSurfaceBrandingManifestImages = (manifest, mapSrc) => {
275
573
  : manifest.surface,
276
574
  };
277
575
  };
278
- const TRANSIT_MODE_POLICY = mergeSurfaceModePolicy(DEFAULT_COMPOSER_SURFACE_MODE_POLICY, {
279
- defaultMode: 'streetmix',
576
+ const COMPOSER_MODE_POLICY = mergeSurfaceModePolicy(DEFAULT_COMPOSER_SURFACE_MODE_POLICY, {
280
577
  modes: {
281
- design: {
282
- launchState: 'hidden',
283
- shell: { leftPanelOpen: false },
578
+ streetmix: {
579
+ enabled: true,
580
+ label: 'streetmix',
284
581
  },
285
- map: {
286
- launchState: 'coming-soon',
287
- panelTabs: ['render'],
288
- defaultPanelTab: 'render',
289
- kits: { upload: false, 'compute-target': false },
290
- modules: { hud: false, inspector: false, viewControls: false },
291
- actions: { 'generate-geometry': false, 'asset-download': false },
292
- shell: { leftPanelOpen: false },
582
+ streetview: {
583
+ enabled: true,
584
+ label: 'streetview',
293
585
  },
586
+ },
587
+ });
588
+ const TRANSIT_MODE_POLICY = mergeSurfaceModePolicy(DEFAULT_COMPOSER_SURFACE_MODE_POLICY, {
589
+ defaultMode: 'imagine',
590
+ modes: {
294
591
  streetmix: {
295
592
  enabled: true,
296
593
  label: 'streetmix',
297
- panelTabs: ['render'],
298
- defaultPanelTab: 'render',
299
- kits: { upload: false, 'compute-target': false },
300
- actions: { 'generate-geometry': false, 'asset-download': false },
594
+ },
595
+ streetview: {
596
+ enabled: true,
597
+ label: 'streetview',
301
598
  },
302
599
  },
303
600
  });
601
+ const SCIENCE_MODE_POLICY = {
602
+ defaultMode: 'design',
603
+ modes: {
604
+ imagine: DEFAULT_COMPOSER_SURFACE_MODE_POLICY.modes?.imagine ?? { label: 'imagine' },
605
+ design: {
606
+ ...(DEFAULT_COMPOSER_SURFACE_MODE_POLICY.modes?.design ?? { label: 'design' }),
607
+ shell: { leftPanelOpen: true, rightPanelOpen: true, metricsOpen: true, inspectorOpen: false },
608
+ },
609
+ map: {
610
+ ...(DEFAULT_COMPOSER_SURFACE_MODE_POLICY.modes?.map ?? { label: 'map' }),
611
+ shell: { leftPanelOpen: true, rightPanelOpen: true, metricsOpen: true, inspectorOpen: false },
612
+ },
613
+ },
614
+ };
615
+ const SCIENCE_BRAND_COLORS = {
616
+ primary: '#314390',
617
+ secondary: '#0b1320',
618
+ background: '#f9f9f9',
619
+ panel: '#ffffff',
620
+ panelBorder: '#e5e7eb',
621
+ textPrimary: '#0f172a',
622
+ textSecondary: '#64748b',
623
+ accent: '#263773',
624
+ };
304
625
  export const SURFACE_KITS = {
305
626
  composer: {
306
627
  id: 'composer',
@@ -308,8 +629,8 @@ export const SURFACE_KITS = {
308
629
  surface: {
309
630
  id: 'composer',
310
631
  family: 'composer',
311
- label: 'Composer',
312
- promptFamily: 'composer',
632
+ label: 'Spatial Composer',
633
+ promptFamily: 'spatial',
313
634
  brandProfile: 'treasury-native',
314
635
  summary: 'Treasury design and render surface.',
315
636
  standaloneCapable: true,
@@ -318,6 +639,10 @@ export const SURFACE_KITS = {
318
639
  profile: 'treasury-native',
319
640
  poweredBy: { enabled: false },
320
641
  },
642
+ modes: {
643
+ defaultMode: COMPOSER_MODE_POLICY.defaultMode,
644
+ modes: COMPOSER_MODE_POLICY.modes,
645
+ },
321
646
  },
322
647
  },
323
648
  transit: {
@@ -327,16 +652,12 @@ export const SURFACE_KITS = {
327
652
  surface: {
328
653
  id: 'transit',
329
654
  family: 'transit',
330
- label: 'Transit',
655
+ label: 'Transit Composer',
331
656
  promptFamily: 'transit',
332
657
  brandProfile: 'treasury-native',
333
- summary: 'Transit surface built on the Composer runtime.',
334
- description: 'Transit is the packaged surface for corridor visuals, stop environments, and street-section scaffold workflows.',
658
+ summary: 'Spatial Assets for Transit System Visualization and Development',
659
+ description: 'Treasury Transit Composer is an asset development system for public transit design, communication, and development. It enables calibrated, replicable visual renders at any location, using Streetmix, Google Street View, and custom 3D modelling tools.',
335
660
  standaloneCapable: true,
336
- scaffoldShell: {
337
- badge: 'Streetmix',
338
- footerText: 'Surface logos, copy, and support content live in the Treasury shell. Embedded Streetmix runtime customization remains a separate adapter concern.',
339
- },
340
661
  },
341
662
  branding: {
342
663
  profile: 'treasury-native',
@@ -346,7 +667,7 @@ export const SURFACE_KITS = {
346
667
  },
347
668
  topbar: {
348
669
  title: 'Treasury',
349
- productLabel: 'Composer',
670
+ productLabel: 'Transit Composer',
350
671
  links: [
351
672
  { label: 'Treasury', href: 'https://treasury.space/', target: '_blank' },
352
673
  { label: 'Composer', href: 'https://composer.treasury.space/', target: '_blank' },
@@ -365,7 +686,7 @@ export const SURFACE_KITS = {
365
686
  surface: {
366
687
  id: 'courtyard',
367
688
  family: 'courtyard',
368
- label: 'Courtyard',
689
+ label: 'Courtyard Composer',
369
690
  promptFamily: 'courtyard',
370
691
  brandProfile: 'treasury-native',
371
692
  summary: 'Treasury-native courtyard surface built on shared Composer kits.',
@@ -380,12 +701,83 @@ export const SURFACE_KITS = {
380
701
  },
381
702
  topbar: {
382
703
  title: 'Treasury',
383
- productLabel: 'Composer',
704
+ productLabel: 'Courtyard Composer',
384
705
  links: [
385
706
  { label: 'Treasury', href: 'https://treasury.space/', target: '_blank' },
386
707
  { label: 'Composer', href: 'https://composer.treasury.space/', target: '_blank' },
387
708
  ],
388
709
  },
710
+ modes: {
711
+ defaultMode: DEFAULT_COMPOSER_SURFACE_MODE_POLICY.defaultMode,
712
+ modes: DEFAULT_COMPOSER_SURFACE_MODE_POLICY.modes,
713
+ },
714
+ },
715
+ },
716
+ science: {
717
+ id: 'science',
718
+ manifest: {
719
+ tenantId: 'science',
720
+ surface: {
721
+ id: 'science',
722
+ family: 'science',
723
+ label: 'Science Composer',
724
+ promptFamily: 'spatial',
725
+ brandProfile: 'treasury-native',
726
+ summary: 'Calibrated Spatial Environments for Human Science',
727
+ description: 'Treasury Science Composer is a calibrated environment design and rendering surface for spatial cognition, perception, and research-facing scene development.',
728
+ standaloneCapable: true,
729
+ },
730
+ branding: {
731
+ profile: 'treasury-native',
732
+ poweredBy: {
733
+ enabled: false,
734
+ },
735
+ },
736
+ topbar: {
737
+ title: 'Treasury',
738
+ productLabel: 'Science',
739
+ contexts: {
740
+ composer: {
741
+ productSuffixLabel: 'Spatial Composer',
742
+ },
743
+ admin: {
744
+ productSuffixLabel: 'Spatial Composer',
745
+ },
746
+ },
747
+ links: [
748
+ { label: 'Treasury', href: 'https://treasury.space/', target: '_blank' },
749
+ { label: 'Spatial Composer', href: 'https://composer.treasury.space/', target: '_blank' },
750
+ ],
751
+ },
752
+ modules: {
753
+ rightRail: {
754
+ enabled: true,
755
+ defaultOpen: true,
756
+ defaultTab: 'data',
757
+ tabs: [
758
+ { id: 'metrics', label: 'metrics' },
759
+ { id: 'data', label: 'data' },
760
+ { id: 'feed', label: 'feed' },
761
+ { id: 'feedback', label: 'feedback' },
762
+ ],
763
+ },
764
+ metricsPanel: { enabled: true, defaultOpen: true },
765
+ inspector: { enabled: true, defaultOpen: false },
766
+ hud: { enabled: true },
767
+ viewControls: { enabled: true },
768
+ veils: { enabled: true, defaultOn: false },
769
+ },
770
+ modes: {
771
+ defaultMode: SCIENCE_MODE_POLICY.defaultMode,
772
+ modes: SCIENCE_MODE_POLICY.modes,
773
+ },
774
+ theme: {
775
+ typography: {
776
+ fontFamily: "'Aeonik', -apple-system, BlinkMacSystemFont, system-ui, sans-serif",
777
+ weights: [300, 500, 700],
778
+ },
779
+ colors: SCIENCE_BRAND_COLORS,
780
+ },
389
781
  },
390
782
  },
391
783
  };
@@ -397,11 +789,11 @@ export const SURFACE_SKINS = {
397
789
  surface: {
398
790
  id: 'transitguy',
399
791
  family: 'transit',
400
- label: 'Transit',
792
+ label: 'Composer',
401
793
  promptFamily: 'transit',
402
794
  brandProfile: 'client-skinned',
403
795
  summary: 'Client-skinned transit surface built on the Treasury Transit runtime.',
404
- description: 'Transitguy runs as a branded skin of the Treasury Transit surface, keeping scaffold-first transit modes and Treasury runtime behavior while swapping client identity and content.',
796
+ description: 'Transitguy runs as a branded skin of the Treasury Transit surface, keeping street-structure transit modes and Treasury runtime behavior while swapping client identity and content.',
405
797
  scaffoldShell: {
406
798
  badge: 'Streetmix',
407
799
  secondaryLogo: TRANSITGUY_LOGO,
@@ -438,6 +830,49 @@ export const SURFACE_SKINS = {
438
830
  },
439
831
  },
440
832
  },
833
+ courtyardurbanist: {
834
+ id: 'courtyardurbanist',
835
+ manifest: {
836
+ tenantId: 'courtyardurbanist',
837
+ surface: {
838
+ id: 'courtyardurbanist',
839
+ family: 'courtyard',
840
+ label: 'Composer',
841
+ promptFamily: 'courtyard',
842
+ brandProfile: 'client-skinned',
843
+ summary: 'Client-skinned courtyard surface built on the Treasury Courtyard runtime.',
844
+ description: 'Courtyard Urbanist runs as a branded skin of the Treasury Courtyard surface, keeping the courtyard prompt packs, gallery flows, and shared runtime behavior while swapping client identity and shell voice.',
845
+ },
846
+ branding: {
847
+ profile: 'client-skinned',
848
+ poweredBy: {
849
+ enabled: true,
850
+ href: 'https://treasury.space',
851
+ logo: TREASURY_LOGO,
852
+ kicker: 'Powered by',
853
+ labelPrefix: 'Treasury',
854
+ labelAccent: 'Courtyard Runtime',
855
+ },
856
+ },
857
+ theme: {
858
+ typography: {
859
+ fontFamily: "'Epilogue', system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
860
+ weights: [400, 700],
861
+ },
862
+ },
863
+ topbar: {
864
+ logo: COURTYARD_URBANIST_LOGO,
865
+ secondaryLogo: TREASURY_LOGO,
866
+ title: 'Urbanist',
867
+ subtitle: 'Courtyard skin on Treasury Composer',
868
+ productLabel: 'Composer',
869
+ links: [
870
+ { label: 'Treasury', href: 'https://treasury.space/', target: '_blank' },
871
+ { label: 'Composer', href: 'https://composer.treasury.space/', target: '_blank' },
872
+ ],
873
+ },
874
+ },
875
+ },
441
876
  };
442
877
  export const SURFACE_HOST_PRESETS = {
443
878
  composer: {
@@ -454,7 +889,7 @@ export const SURFACE_HOST_PRESETS = {
454
889
  subtenantSlug: 'transit',
455
890
  surfaceId: 'transit',
456
891
  brandProfile: 'treasury-native',
457
- compatibilityMode: 'tenant-subtenant-bridge',
892
+ compatibilityMode: 'direct-tenant',
458
893
  },
459
894
  transitguy: {
460
895
  tenantSlug: 'transitguy',
@@ -464,15 +899,80 @@ export const SURFACE_HOST_PRESETS = {
464
899
  brandProfile: 'client-skinned',
465
900
  compatibilityMode: 'direct-tenant',
466
901
  },
902
+ 'transit-test': {
903
+ tenantSlug: 'treasury',
904
+ effectiveTenantSlug: 'transit',
905
+ subtenantSlug: 'transit',
906
+ surfaceId: 'transit',
907
+ brandProfile: 'treasury-native',
908
+ compatibilityMode: 'direct-tenant',
909
+ },
910
+ 'neu-new': {
911
+ tenantSlug: 'neu',
912
+ effectiveTenantSlug: 'neu',
913
+ subtenantSlug: null,
914
+ surfaceId: 'composer',
915
+ brandProfile: 'client-skinned',
916
+ compatibilityMode: 'direct-tenant',
917
+ },
918
+ neu: {
919
+ tenantSlug: 'neu',
920
+ effectiveTenantSlug: 'neu',
921
+ subtenantSlug: null,
922
+ surfaceId: 'composer',
923
+ brandProfile: 'client-skinned',
924
+ compatibilityMode: 'direct-tenant',
925
+ },
926
+ 'neu-test': {
927
+ tenantSlug: 'neu',
928
+ effectiveTenantSlug: 'neu',
929
+ subtenantSlug: null,
930
+ surfaceId: 'composer',
931
+ brandProfile: 'client-skinned',
932
+ compatibilityMode: 'direct-tenant',
933
+ },
934
+ courtyardurbanist: {
935
+ tenantSlug: 'courtyardurbanist',
936
+ effectiveTenantSlug: 'courtyardurbanist',
937
+ subtenantSlug: null,
938
+ surfaceId: 'courtyard',
939
+ brandProfile: 'client-skinned',
940
+ compatibilityMode: 'direct-tenant',
941
+ },
942
+ 'courtyard-test': {
943
+ tenantSlug: 'treasury',
944
+ effectiveTenantSlug: 'courtyard',
945
+ subtenantSlug: 'courtyard',
946
+ surfaceId: 'courtyard',
947
+ brandProfile: 'treasury-native',
948
+ compatibilityMode: 'direct-tenant',
949
+ },
467
950
  courtyard: {
468
951
  tenantSlug: 'treasury',
469
952
  effectiveTenantSlug: 'courtyard',
470
953
  subtenantSlug: 'courtyard',
471
954
  surfaceId: 'courtyard',
472
955
  brandProfile: 'treasury-native',
473
- compatibilityMode: 'tenant-subtenant-bridge',
956
+ compatibilityMode: 'direct-tenant',
957
+ },
958
+ science: {
959
+ tenantSlug: 'treasury',
960
+ effectiveTenantSlug: 'science',
961
+ subtenantSlug: 'science',
962
+ surfaceId: 'science',
963
+ brandProfile: 'treasury-native',
964
+ compatibilityMode: 'direct-tenant',
965
+ },
966
+ scienv: {
967
+ tenantSlug: 'treasury',
968
+ effectiveTenantSlug: 'science',
969
+ subtenantSlug: 'science',
970
+ surfaceId: 'science',
971
+ brandProfile: 'treasury-native',
972
+ compatibilityMode: 'direct-tenant',
474
973
  },
475
974
  };
975
+ const EXPLICIT_HOST_PRESETS = new Set(Object.keys(SURFACE_HOST_PRESETS).filter((key) => key !== 'composer' && key !== 'transit'));
476
976
  export const resolveSurfaceKitProfile = (surfaceId) => {
477
977
  const normalized = String(surfaceId || '').trim().toLowerCase();
478
978
  return SURFACE_KITS[normalized] ?? null;
@@ -525,7 +1025,11 @@ export const createSurfaceHostResolver = (options) => {
525
1025
  const candidate = hostname.split('.')[0] ?? '';
526
1026
  const normalizedCandidate = candidate.toLowerCase();
527
1027
  const marketingHost = !normalizedCandidate || marketingSlugs.has(normalizedCandidate);
528
- const hostSlug = marketingHost ? fallbackHostSlug : normalizedCandidate;
1028
+ const hostSlug = (normalizedCandidate && EXPLICIT_HOST_PRESETS.has(normalizedCandidate)
1029
+ ? normalizedCandidate
1030
+ : marketingHost
1031
+ ? fallbackHostSlug
1032
+ : normalizedCandidate) || fallbackHostSlug;
529
1033
  const preset = hostPresets[hostSlug] ??
530
1034
  buildDirectTenantContext(hostSlug, {
531
1035
  defaultTenantSlug: options.defaultTenantSlug,