@happyvertical/smrt-workbench 0.40.66

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 (52) hide show
  1. package/AGENTS.md +28 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +31 -0
  5. package/dist/chunks/discovery-G9-foyW8.js +979 -0
  6. package/dist/chunks/discovery-G9-foyW8.js.map +1 -0
  7. package/dist/chunks/runtime-BTiu5fuP.js +93 -0
  8. package/dist/chunks/runtime-BTiu5fuP.js.map +1 -0
  9. package/dist/discovery.d.ts +14 -0
  10. package/dist/discovery.d.ts.map +1 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +3 -0
  14. package/dist/runtime.d.ts +10 -0
  15. package/dist/runtime.d.ts.map +1 -0
  16. package/dist/runtime.js +2 -0
  17. package/dist/svelte/MarkdownDocument.svelte +200 -0
  18. package/dist/svelte/MarkdownDocument.svelte.d.ts +7 -0
  19. package/dist/svelte/MarkdownDocument.svelte.d.ts.map +1 -0
  20. package/dist/svelte/WorkbenchHost.svelte +2058 -0
  21. package/dist/svelte/WorkbenchHost.svelte.d.ts +13 -0
  22. package/dist/svelte/WorkbenchHost.svelte.d.ts.map +1 -0
  23. package/dist/svelte/command.d.ts +6 -0
  24. package/dist/svelte/command.d.ts.map +1 -0
  25. package/dist/svelte/command.js +18 -0
  26. package/dist/svelte/index.d.ts +2 -0
  27. package/dist/svelte/index.d.ts.map +1 -0
  28. package/dist/svelte/index.js +1 -0
  29. package/dist/types.d.ts +202 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +0 -0
  32. package/dist/utils.d.ts +3 -0
  33. package/dist/utils.d.ts.map +1 -0
  34. package/dist/vite.d.ts +6 -0
  35. package/dist/vite.d.ts.map +1 -0
  36. package/dist/vite.js +124 -0
  37. package/dist/vite.js.map +1 -0
  38. package/host/README.md +12 -0
  39. package/host/package.json +21 -0
  40. package/host/src/app.html +11 -0
  41. package/host/src/hooks.client.ts +5 -0
  42. package/host/src/routes/+error.svelte +44 -0
  43. package/host/src/routes/+page.svelte +15 -0
  44. package/host/svelte.config.js +12 -0
  45. package/host/tsconfig.json +10 -0
  46. package/host/vite.config.ts +202 -0
  47. package/package.json +96 -0
  48. package/src/discovery.ts +1748 -0
  49. package/src/runtime.ts +165 -0
  50. package/src/types.ts +243 -0
  51. package/src/utils.ts +16 -0
  52. package/src/vite.ts +303 -0
@@ -0,0 +1,2058 @@
1
+ <script lang="ts">
2
+ import type {
3
+ SmrtPlaygroundEntry,
4
+ SmrtPlaygroundModule,
5
+ } from '@happyvertical/smrt-playground';
6
+ import { PlaygroundHost } from '@happyvertical/smrt-playground/svelte';
7
+ import {
8
+ AdminShell,
9
+ createShellState,
10
+ } from '@happyvertical/smrt-svelte/workspace';
11
+ import {
12
+ ColorSchemeToggle,
13
+ ThemeProvider,
14
+ ThemeSwitcher,
15
+ } from '@happyvertical/smrt-ui/themes';
16
+ import type { Component } from 'svelte';
17
+ import { onMount } from 'svelte';
18
+ import { findWorkbenchRouteByHash, mergeWorkbenchModules } from '../runtime.js';
19
+ import type {
20
+ ResolvedWorkbenchRouteEntry,
21
+ SmrtWorkbenchModule,
22
+ SmrtWorkbenchProject,
23
+ WorkbenchApiObjectSummary,
24
+ WorkbenchApiParameterSummary,
25
+ WorkbenchCliCommandSummary,
26
+ WorkbenchDocumentSummary,
27
+ WorkbenchMcpToolSummary,
28
+ WorkbenchPackageSummary,
29
+ WorkbenchRestEndpointSummary,
30
+ } from '../types.js';
31
+ import { workbenchScriptCommand } from './command.js';
32
+ import MarkdownDocument from './MarkdownDocument.svelte';
33
+
34
+ export interface Props {
35
+ project: SmrtWorkbenchProject;
36
+ modules?: SmrtWorkbenchModule[];
37
+ playgroundModules?: SmrtPlaygroundModule[];
38
+ title?: string;
39
+ }
40
+
41
+ type WorkbenchTab =
42
+ | 'packages'
43
+ | 'playground'
44
+ | 'routes'
45
+ | 'api'
46
+ | 'docs'
47
+ | 'knowledge'
48
+ | 'specialist'
49
+ | 'scripts'
50
+ | 'dependencies'
51
+ | 'migrations'
52
+ | 'examples';
53
+
54
+ type WorkbenchApiTab = 'objects' | 'rest' | 'mcp' | 'cli';
55
+
56
+ interface WorkbenchPlaygroundNavEntry extends SmrtPlaygroundEntry {
57
+ qualifiedId: string;
58
+ }
59
+
60
+ let {
61
+ project,
62
+ modules = [],
63
+ playgroundModules = [],
64
+ title = 'Workbench',
65
+ }: Props = $props();
66
+
67
+ const tabs: Array<{ id: WorkbenchTab; label: string }> = [
68
+ { id: 'packages', label: 'Packages' },
69
+ { id: 'playground', label: 'Playground' },
70
+ { id: 'routes', label: 'Routes' },
71
+ { id: 'api', label: 'API' },
72
+ { id: 'knowledge', label: 'Knowledge' },
73
+ { id: 'specialist', label: 'Specialist' },
74
+ { id: 'scripts', label: 'Tests/Scripts' },
75
+ { id: 'dependencies', label: 'Dependencies' },
76
+ { id: 'migrations', label: 'Migrations' },
77
+ { id: 'examples', label: 'Examples' },
78
+ ];
79
+
80
+ const apiTabs: Array<{ id: WorkbenchApiTab; label: string }> = [
81
+ { id: 'objects', label: 'Objects' },
82
+ { id: 'rest', label: 'REST' },
83
+ { id: 'mcp', label: 'MCP' },
84
+ { id: 'cli', label: 'CLI' },
85
+ ];
86
+
87
+ const normalizedModules = $derived(mergeWorkbenchModules(modules));
88
+ const scopedPackageName = $derived(project.scope.packageName || null);
89
+ const isPackageScoped = $derived(Boolean(scopedPackageName));
90
+ const sectionTabs = $derived(tabs.filter((tab) => tab.id !== 'packages'));
91
+ const rootNavLabel = $derived(
92
+ project.scope.mode === 'consumer' ? 'Project Packages' : 'Workspace Packages',
93
+ );
94
+ const visiblePackages = $derived(
95
+ project.packages.filter((packageSummary) => {
96
+ if (scopedPackageName && packageSummary.name !== scopedPackageName) {
97
+ return false;
98
+ }
99
+
100
+ if (!searchTerm.trim()) {
101
+ return true;
102
+ }
103
+
104
+ const query = searchTerm.toLowerCase();
105
+ return (
106
+ packageSummary.name.toLowerCase().includes(query) ||
107
+ (packageSummary.description || '').toLowerCase().includes(query)
108
+ );
109
+ }),
110
+ );
111
+
112
+ let activeTab = $state<WorkbenchTab>('packages');
113
+ let searchTerm = $state('');
114
+ let selectedPackageName = $state<string | null>(null);
115
+ let expandedPackageName = $state<string | null>(null);
116
+ let selectedRouteId = $state<string | null>(null);
117
+ let selectedPlaygroundEntryId = $state<string | null>(null);
118
+ let selectedDocumentPath = $state<string | null>(null);
119
+ let activeApiTab = $state<WorkbenchApiTab>('objects');
120
+ let copiedValue = $state<string | null>(null);
121
+ let isHydrated = $state(false);
122
+
123
+ const shellState = createShellState({
124
+ config: {
125
+ top: {
126
+ label: 'Workbench',
127
+ initial: 'collapsed',
128
+ collapsedSize: '3.25rem',
129
+ expandedSize: '13rem',
130
+ },
131
+ left: {
132
+ label: 'Packages',
133
+ initial: 'expanded',
134
+ collapsedSize: '4rem',
135
+ expandedSize: '22rem',
136
+ },
137
+ right: false,
138
+ bottom: {
139
+ label: 'System',
140
+ initial: 'collapsed',
141
+ collapsedSize: '3.25rem',
142
+ expandedSize: '14rem',
143
+ },
144
+ },
145
+ settings: {
146
+ panels: {
147
+ top: 'collapsed',
148
+ left: 'expanded',
149
+ bottom: 'collapsed',
150
+ },
151
+ },
152
+ storageKey: 'smrt-workbench-shell',
153
+ });
154
+
155
+ onMount(() => {
156
+ isHydrated = true;
157
+ const selectRouteFromHash = () => {
158
+ const route = findWorkbenchRouteByHash(
159
+ normalizedModules.flatMap((module) => module.routes),
160
+ window.location.hash,
161
+ );
162
+ if (route) {
163
+ selectRoute(route);
164
+ }
165
+ };
166
+
167
+ window.addEventListener('hashchange', selectRouteFromHash);
168
+ selectRouteFromHash();
169
+
170
+ return () => {
171
+ window.removeEventListener('hashchange', selectRouteFromHash);
172
+ };
173
+ });
174
+
175
+ const packageStats = $derived(
176
+ new Map(
177
+ project.packages.map((packageSummary) => {
178
+ const workbenchModule = normalizedModules.find(
179
+ (module) => module.packageName === packageSummary.name,
180
+ );
181
+ const playgroundModule = playgroundModules.find(
182
+ (module) => module.packageName === packageSummary.name,
183
+ );
184
+
185
+ return [
186
+ packageSummary.name,
187
+ {
188
+ routes: workbenchModule?.routes.length || packageSummary.routeCount,
189
+ routeModules:
190
+ workbenchModule?.routeModules.length ||
191
+ packageSummary.routeModuleCount,
192
+ playground:
193
+ playgroundModule?.entries.length ||
194
+ packageSummary.playgroundEntryCount,
195
+ },
196
+ ] as const;
197
+ }),
198
+ ),
199
+ );
200
+
201
+ $effect(() => {
202
+ const firstPackage = visiblePackages[0] || project.packages[0] || null;
203
+ const selectedStillVisible = visiblePackages.some(
204
+ (packageSummary) => packageSummary.name === selectedPackageName,
205
+ );
206
+
207
+ if (!selectedStillVisible) {
208
+ selectedPackageName = firstPackage?.name || null;
209
+ expandedPackageName = firstPackage?.name || null;
210
+ selectedRouteId = null;
211
+ selectedPlaygroundEntryId = null;
212
+ }
213
+ });
214
+
215
+ const selectedBasePackage = $derived(
216
+ project.packages.find(
217
+ (packageSummary) => packageSummary.name === selectedPackageName,
218
+ ) ||
219
+ visiblePackages[0] ||
220
+ project.packages[0] ||
221
+ null,
222
+ );
223
+
224
+ const selectedModule = $derived(
225
+ normalizedModules.find(
226
+ (module) => module.packageName === selectedBasePackage?.name,
227
+ ) || null,
228
+ );
229
+
230
+ const selectedPackage = $derived.by(() => {
231
+ if (!selectedBasePackage) return null;
232
+ if (!selectedModule) return selectedBasePackage;
233
+
234
+ return {
235
+ ...selectedBasePackage,
236
+ docs: [...selectedBasePackage.docs, ...(selectedModule.docs || [])],
237
+ examples: [
238
+ ...selectedBasePackage.examples,
239
+ ...(selectedModule.examples || []),
240
+ ],
241
+ recommendedCommands: [
242
+ ...selectedBasePackage.recommendedCommands,
243
+ ...(selectedModule.recommendedCommands || []),
244
+ ],
245
+ };
246
+ });
247
+
248
+ $effect(() => {
249
+ if (isPackageScoped && activeTab === 'packages') {
250
+ activeTab = defaultPackageTab(selectedPackage);
251
+ }
252
+ });
253
+
254
+ const selectedDocument = $derived(
255
+ selectedPackage?.docs.find((doc) => doc.path === selectedDocumentPath) ||
256
+ defaultDocumentFor(selectedPackage) ||
257
+ null,
258
+ );
259
+
260
+ $effect(() => {
261
+ if (!selectedPackage) {
262
+ selectedDocumentPath = null;
263
+ return;
264
+ }
265
+
266
+ const selectedStillAvailable = selectedPackage.docs.some(
267
+ (doc) => doc.path === selectedDocumentPath,
268
+ );
269
+ if (!selectedStillAvailable) {
270
+ selectedDocumentPath = defaultDocumentFor(selectedPackage)?.path || null;
271
+ }
272
+ });
273
+
274
+ const selectedRoutes = $derived(selectedModule?.routes || []);
275
+ const selectedRoute = $derived(
276
+ selectedRoutes.find((route) => route.qualifiedId === selectedRouteId) ||
277
+ selectedRoutes[0] ||
278
+ null,
279
+ );
280
+
281
+ $effect(() => {
282
+ if (!selectedRoute) {
283
+ selectedRouteId = null;
284
+ return;
285
+ }
286
+
287
+ if (selectedRoute.qualifiedId !== selectedRouteId) {
288
+ selectedRouteId = selectedRoute.qualifiedId;
289
+ }
290
+ });
291
+
292
+ const filteredPlaygroundModules = $derived(
293
+ selectedPackage
294
+ ? playgroundModules.filter(
295
+ (module) => module.packageName === selectedPackage.name,
296
+ )
297
+ : playgroundModules,
298
+ );
299
+
300
+ const selectedPlaygroundModule = $derived(
301
+ playgroundModules.find(
302
+ (module) => module.packageName === selectedPackage?.name,
303
+ ) || null,
304
+ );
305
+
306
+ const selectedPlaygroundEntries = $derived(
307
+ (selectedPlaygroundModule?.entries || []).map((entry) => ({
308
+ ...entry,
309
+ qualifiedId: `${selectedPlaygroundModule?.packageName}:${entry.id}`,
310
+ })),
311
+ );
312
+
313
+ const selectedPlaygroundEntry = $derived(
314
+ selectedPlaygroundEntries.find(
315
+ (entry) => entry.qualifiedId === selectedPlaygroundEntryId,
316
+ ) ||
317
+ selectedPlaygroundEntries[0] ||
318
+ null,
319
+ );
320
+
321
+ $effect(() => {
322
+ if (!selectedPlaygroundEntry) {
323
+ selectedPlaygroundEntryId = null;
324
+ return;
325
+ }
326
+
327
+ if (selectedPlaygroundEntry.qualifiedId !== selectedPlaygroundEntryId) {
328
+ selectedPlaygroundEntryId = selectedPlaygroundEntry.qualifiedId;
329
+ }
330
+ });
331
+
332
+ let RouteComponent = $state<Component<Record<string, unknown>> | null>(null);
333
+ let routeComponentId = $state<string | null>(null);
334
+ let routeLoadError = $state<string | null>(null);
335
+ let routeIsLoading = $state(false);
336
+
337
+ function resolveComponent(
338
+ loaded:
339
+ | Component<Record<string, unknown>>
340
+ | { default: Component<Record<string, unknown>> },
341
+ ): Component<Record<string, unknown>> {
342
+ if (
343
+ loaded &&
344
+ typeof loaded === 'object' &&
345
+ 'default' in loaded &&
346
+ loaded.default
347
+ ) {
348
+ return loaded.default;
349
+ }
350
+
351
+ return loaded as Component<Record<string, unknown>>;
352
+ }
353
+
354
+ $effect(() => {
355
+ const route = selectedRoute;
356
+ let cancelled = false;
357
+ routeLoadError = null;
358
+
359
+ if (!route) {
360
+ RouteComponent = null;
361
+ routeComponentId = null;
362
+ routeIsLoading = false;
363
+ return;
364
+ }
365
+
366
+ if (route.component) {
367
+ RouteComponent = route.component as Component<Record<string, unknown>>;
368
+ routeComponentId = route.qualifiedId;
369
+ routeIsLoading = false;
370
+ return;
371
+ }
372
+
373
+ if (!route.loadComponent) {
374
+ RouteComponent = null;
375
+ routeComponentId = null;
376
+ routeIsLoading = false;
377
+ routeLoadError = 'Route has no renderable component.';
378
+ return;
379
+ }
380
+
381
+ RouteComponent = null;
382
+ routeComponentId = null;
383
+ routeIsLoading = true;
384
+
385
+ void route
386
+ .loadComponent()
387
+ .then((loaded) => {
388
+ if (cancelled) {
389
+ return;
390
+ }
391
+ RouteComponent = resolveComponent(loaded);
392
+ routeComponentId = route.qualifiedId;
393
+ routeIsLoading = false;
394
+ })
395
+ .catch((error) => {
396
+ if (cancelled) {
397
+ return;
398
+ }
399
+ RouteComponent = null;
400
+ routeComponentId = null;
401
+ routeLoadError =
402
+ error instanceof Error ? error.message : 'Failed to load route.';
403
+ routeIsLoading = false;
404
+ });
405
+
406
+ return () => {
407
+ cancelled = true;
408
+ };
409
+ });
410
+
411
+ const totals = $derived({
412
+ packages: project.packages.length,
413
+ objects: project.packages.reduce(
414
+ (sum, packageSummary) => sum + packageSummary.knowledge.objectCount,
415
+ 0,
416
+ ),
417
+ routes: [...packageStats.values()].reduce(
418
+ (sum, stats) => sum + stats.routes,
419
+ 0,
420
+ ),
421
+ playground: [...packageStats.values()].reduce(
422
+ (sum, stats) => sum + stats.playground,
423
+ 0,
424
+ ),
425
+ });
426
+
427
+ function selectPackage(packageSummary: WorkbenchPackageSummary) {
428
+ const isSelected = selectedPackageName === packageSummary.name;
429
+
430
+ if (isSelected) {
431
+ expandedPackageName =
432
+ expandedPackageName === packageSummary.name ? null : packageSummary.name;
433
+ return;
434
+ }
435
+
436
+ selectedPackageName = packageSummary.name;
437
+ expandedPackageName = packageSummary.name;
438
+ selectedRouteId = null;
439
+ selectedPlaygroundEntryId = null;
440
+ selectedDocumentPath = null;
441
+ activeApiTab = 'objects';
442
+ }
443
+
444
+ function selectSection(tab: WorkbenchTab) {
445
+ activeTab = tab;
446
+ if (tab === 'api') {
447
+ activeApiTab = 'objects';
448
+ }
449
+ }
450
+
451
+ function selectPlaygroundEntry(entry: WorkbenchPlaygroundNavEntry) {
452
+ selectedPlaygroundEntryId = entry.qualifiedId;
453
+ activeTab = 'playground';
454
+ }
455
+
456
+ function selectRoute(route: ResolvedWorkbenchRouteEntry) {
457
+ selectedPackageName = route.packageName;
458
+ expandedPackageName = route.packageName;
459
+ selectedRouteId = route.qualifiedId;
460
+ activeTab = 'routes';
461
+ }
462
+
463
+ function selectDocument(doc: WorkbenchDocumentSummary) {
464
+ selectedDocumentPath = doc.path;
465
+ activeTab = 'docs';
466
+ }
467
+
468
+ function openPackage(
469
+ packageSummary: WorkbenchPackageSummary,
470
+ tab?: WorkbenchTab,
471
+ ) {
472
+ selectedPackageName = packageSummary.name;
473
+ expandedPackageName = packageSummary.name;
474
+ selectedRouteId = null;
475
+ selectedPlaygroundEntryId = null;
476
+ selectedDocumentPath = defaultDocumentFor(packageSummary)?.path || null;
477
+ activeApiTab = 'objects';
478
+ activeTab = tab || defaultPackageTab(packageSummary);
479
+ }
480
+
481
+ async function copyValue(value: string) {
482
+ if (!isHydrated || !navigator.clipboard) {
483
+ copiedValue = value;
484
+ return;
485
+ }
486
+
487
+ await navigator.clipboard.writeText(value);
488
+ copiedValue = value;
489
+ }
490
+
491
+ function relativePath(path: string | undefined) {
492
+ if (!path) {
493
+ return '';
494
+ }
495
+
496
+ const root = `${project.scope.projectRoot}/`;
497
+ return path.startsWith(root) ? path.slice(root.length) : path;
498
+ }
499
+
500
+ function commandFor(
501
+ packageSummary: WorkbenchPackageSummary,
502
+ scriptName: string,
503
+ ) {
504
+ return workbenchScriptCommand(
505
+ packageSummary,
506
+ scriptName,
507
+ project.scope.packageManager,
508
+ );
509
+ }
510
+
511
+ function recommendedCommandFor(
512
+ packageSummary: WorkbenchPackageSummary,
513
+ command: { id: string; command: string },
514
+ ) {
515
+ const pnpmFilterPrefix = `pnpm --filter ${packageSummary.name} `;
516
+ return command.command.startsWith(pnpmFilterPrefix)
517
+ ? commandFor(packageSummary, command.command.slice(pnpmFilterPrefix.length))
518
+ : command.command;
519
+ }
520
+
521
+ function dependencyEntries(packageSummary: WorkbenchPackageSummary) {
522
+ return Object.entries({
523
+ ...packageSummary.dependencies,
524
+ ...packageSummary.devDependencies,
525
+ ...packageSummary.peerDependencies,
526
+ }).sort(([left], [right]) => left.localeCompare(right));
527
+ }
528
+
529
+ function apiObjects(
530
+ packageSummary: WorkbenchPackageSummary,
531
+ ): WorkbenchApiObjectSummary[] {
532
+ if (packageSummary.api.objects?.length > 0) {
533
+ return packageSummary.api.objects;
534
+ }
535
+
536
+ return (packageSummary.api.objectNames || []).map((name) => ({
537
+ name,
538
+ qualifiedName: name,
539
+ fields: [],
540
+ }));
541
+ }
542
+
543
+ function apiRestEndpoints(
544
+ packageSummary: WorkbenchPackageSummary,
545
+ ): WorkbenchRestEndpointSummary[] {
546
+ return packageSummary.api.restEndpoints || [];
547
+ }
548
+
549
+ function apiCliCommands(
550
+ packageSummary: WorkbenchPackageSummary,
551
+ ): WorkbenchCliCommandSummary[] {
552
+ return packageSummary.api.cliCommands || [];
553
+ }
554
+
555
+ function apiMcpTools(
556
+ packageSummary: WorkbenchPackageSummary,
557
+ ): WorkbenchMcpToolSummary[] {
558
+ return packageSummary.api.mcpTools || [];
559
+ }
560
+
561
+ function defaultDocumentFor(
562
+ packageSummary: WorkbenchPackageSummary | null,
563
+ ): WorkbenchDocumentSummary | null {
564
+ return (
565
+ packageSummary?.docs.find((doc) => doc.kind === 'readme') ||
566
+ packageSummary?.docs[0] ||
567
+ null
568
+ );
569
+ }
570
+
571
+ function defaultPackageTab(
572
+ packageSummary: WorkbenchPackageSummary | null,
573
+ ): WorkbenchTab {
574
+ if (!packageSummary) {
575
+ return 'packages';
576
+ }
577
+
578
+ if (defaultDocumentFor(packageSummary)) {
579
+ return 'docs';
580
+ }
581
+
582
+ const stats = packageStats.get(packageSummary.name);
583
+ if ((stats?.playground || 0) > 0) {
584
+ return 'playground';
585
+ }
586
+ if ((stats?.routes || 0) > 0) {
587
+ return 'routes';
588
+ }
589
+
590
+ return 'api';
591
+ }
592
+
593
+ function specialistUri(packageName: string) {
594
+ return `smrt://workbench/package/${encodeURIComponent(packageName)}/specialist`;
595
+ }
596
+
597
+ function routeProps(route: ResolvedWorkbenchRouteEntry | null) {
598
+ return route?.props || {};
599
+ }
600
+
601
+ function parameterMeta(parameter: WorkbenchApiParameterSummary) {
602
+ const parts: string[] = [parameter.location];
603
+ if (parameter.type) {
604
+ parts.push(parameter.type);
605
+ }
606
+ parts.push(parameter.required ? 'required' : 'optional');
607
+ if (parameter.defaultValue !== undefined) {
608
+ parts.push(`default ${parameter.defaultValue}`);
609
+ }
610
+ return parts.join(' · ');
611
+ }
612
+
613
+ function apiTabCount(
614
+ tab: WorkbenchApiTab,
615
+ objects: WorkbenchApiObjectSummary[],
616
+ restEndpoints: WorkbenchRestEndpointSummary[],
617
+ mcpTools: WorkbenchMcpToolSummary[],
618
+ cliCommands: WorkbenchCliCommandSummary[],
619
+ ) {
620
+ switch (tab) {
621
+ case 'objects':
622
+ return objects.length;
623
+ case 'rest':
624
+ return restEndpoints.length;
625
+ case 'mcp':
626
+ return mcpTools.length;
627
+ case 'cli':
628
+ return cliCommands.length;
629
+ }
630
+ }
631
+ </script>
632
+
633
+ <ThemeProvider colorScheme="dark" persist={true} storageKey="smrt-workbench-theme">
634
+ <div
635
+ class="workbench-shell-root"
636
+ data-hydrated={isHydrated ? 'true' : 'false'}
637
+ >
638
+ <AdminShell
639
+ title={title}
640
+ subtitle={rootNavLabel}
641
+ state={shellState}
642
+ >
643
+ {#snippet topLeftCorner()}
644
+ <div class="workbench-shell-mark" aria-label="SMRT">
645
+ <svg viewBox="0 0 32 32" role="img" aria-label="SMRT">
646
+ <path
647
+ d="M19.7 3.4c1.2 4.1-.3 6.9-2.2 9 2.8-1.1 5-3 6.6-5.5 2.7 4.8 3.1 9.1 1.1 12.9-1.9 3.5-5.3 5.7-9.2 5.7-4 0-7.4-2.2-9.3-5.7-2.1-4-.9-8.5 3.7-13.5-.1 2.8.8 5.1 2.5 6.9 2.5-2.7 4.6-5.8 6.8-9.8Z"
648
+ fill="var(--smrt-color-primary)"
649
+ />
650
+ <path
651
+ d="M15.9 12.1c2.8 3 4.4 5.4 4.4 8.1 0 2.7-1.8 4.9-4.4 4.9s-4.4-2.2-4.4-4.9c0-2.5 1.5-5 4.4-8.1Z"
652
+ fill="var(--smrt-color-tertiary)"
653
+ />
654
+ <path
655
+ d="M8.1 15.7h7.2v4.5H8.1zM16.7 15.7h7.2v4.5h-7.2z"
656
+ fill="none"
657
+ stroke="var(--smrt-color-on-surface)"
658
+ stroke-linejoin="round"
659
+ stroke-width="2"
660
+ />
661
+ <path
662
+ d="M15.2 17.7h1.6M6.6 16.2l1.5.5M23.9 16.7l1.5-.5"
663
+ fill="none"
664
+ stroke="var(--smrt-color-on-surface)"
665
+ stroke-linecap="round"
666
+ stroke-width="2"
667
+ />
668
+ </svg>
669
+ </div>
670
+ {/snippet}
671
+
672
+ {#snippet appPanel()}
673
+ <section class="workbench-app-panel" aria-label="Workbench scope">
674
+ <div>
675
+ <p class="eyebrow">{project.scope.projectRoot}</p>
676
+ <h2>{rootNavLabel}</h2>
677
+ <p>
678
+ {project.scope.mode === 'consumer'
679
+ ? 'Installed SMRT packages and local project surfaces discovered for this project.'
680
+ : 'Workspace packages and package-owned surfaces discovered for this repository.'}
681
+ </p>
682
+ </div>
683
+ <dl class="workbench-scope-metrics">
684
+ <div><dt>Packages</dt><dd>{totals.packages}</dd></div>
685
+ <div><dt>Routes</dt><dd>{totals.routes}</dd></div>
686
+ <div><dt>Previews</dt><dd>{totals.playground}</dd></div>
687
+ <div><dt>Objects</dt><dd>{totals.objects}</dd></div>
688
+ </dl>
689
+ </section>
690
+ {/snippet}
691
+
692
+ {#snippet tenantPanel()}
693
+ <div class="workbench-nav-shell">
694
+ <header class="workbench-nav-header">
695
+ <div class="workbench-brand">
696
+ <p class="eyebrow brand-badge">
697
+ <svg
698
+ viewBox="0 0 32 32"
699
+ role="img"
700
+ aria-label="SMRT"
701
+ class="brand-mark"
702
+ >
703
+ <path
704
+ d="M19.7 3.4c1.2 4.1-.3 6.9-2.2 9 2.8-1.1 5-3 6.6-5.5 2.7 4.8 3.1 9.1 1.1 12.9-1.9 3.5-5.3 5.7-9.2 5.7-4 0-7.4-2.2-9.3-5.7-2.1-4-.9-8.5 3.7-13.5-.1 2.8.8 5.1 2.5 6.9 2.5-2.7 4.6-5.8 6.8-9.8Z"
705
+ fill="var(--smrt-color-primary)"
706
+ />
707
+ <path
708
+ d="M15.9 12.1c2.8 3 4.4 5.4 4.4 8.1 0 2.7-1.8 4.9-4.4 4.9s-4.4-2.2-4.4-4.9c0-2.5 1.5-5 4.4-8.1Z"
709
+ fill="var(--smrt-color-tertiary)"
710
+ />
711
+ <path
712
+ d="M8.1 15.7h7.2v4.5H8.1zM16.7 15.7h7.2v4.5h-7.2z"
713
+ fill="none"
714
+ stroke="var(--smrt-color-on-surface)"
715
+ stroke-linejoin="round"
716
+ stroke-width="2"
717
+ />
718
+ <path
719
+ d="M15.2 17.7h1.6M6.6 16.2l1.5.5M23.9 16.7l1.5-.5"
720
+ fill="none"
721
+ stroke="var(--smrt-color-on-surface)"
722
+ stroke-linecap="round"
723
+ stroke-width="2"
724
+ />
725
+ </svg>
726
+ <span>s-m-r-t</span>
727
+ </p>
728
+ <h1>{title}</h1>
729
+ <p>{rootNavLabel}</p>
730
+ </div>
731
+ <button
732
+ type="button"
733
+ class="workbench-nav-toggle"
734
+ aria-label="Close navigation"
735
+ aria-expanded={shellState.panels.left === 'expanded'}
736
+ title="Close navigation"
737
+ onclick={() => shellState.togglePanel('left')}
738
+ >
739
+ <svg viewBox="0 0 24 24" aria-hidden="true">
740
+ <rect
741
+ x="4"
742
+ y="4"
743
+ width="16"
744
+ height="16"
745
+ rx="2"
746
+ fill="none"
747
+ stroke="currentColor"
748
+ stroke-width="1.8"
749
+ />
750
+ <path
751
+ d="M9 4v16"
752
+ fill="none"
753
+ stroke="currentColor"
754
+ stroke-width="1.8"
755
+ />
756
+ <path
757
+ d="m16 9-3 3 3 3"
758
+ fill="none"
759
+ stroke="currentColor"
760
+ stroke-linecap="round"
761
+ stroke-linejoin="round"
762
+ stroke-width="1.8"
763
+ />
764
+ </svg>
765
+ </button>
766
+ </header>
767
+
768
+ <nav class="nav-tree" aria-label="Workbench navigation">
769
+ {#if !isPackageScoped}
770
+ <button
771
+ type="button"
772
+ class:active={activeTab === 'packages'}
773
+ class="root-nav"
774
+ onclick={() => selectSection('packages')}
775
+ >
776
+ <strong>{rootNavLabel}</strong>
777
+ <span>{totals.packages} cataloged · {totals.routes} routes · {totals.playground} previews</span>
778
+ </button>
779
+
780
+ <label class="search">
781
+ <span>Filter packages</span>
782
+ <input bind:value={searchTerm} placeholder="Package name or description" />
783
+ </label>
784
+ {/if}
785
+
786
+ <div class="package-list" data-testid="workbench-package-list">
787
+ {#if visiblePackages.length === 0}
788
+ <div class="empty-panel">
789
+ <strong>No packages</strong>
790
+ <p>No package metadata matched the current filter.</p>
791
+ </div>
792
+ {:else}
793
+ {#each visiblePackages as packageSummary (packageSummary.name)}
794
+ {@const stats = packageStats.get(packageSummary.name)}
795
+ {@const packageIsSelected = packageSummary.name === selectedPackage?.name}
796
+ {@const packageIsExpanded = packageSummary.name === expandedPackageName}
797
+ <div
798
+ class:package-node={true}
799
+ class:package-node--selected={packageIsSelected}
800
+ class:package-node--expanded={packageIsExpanded}
801
+ >
802
+ <button
803
+ type="button"
804
+ class:selected={packageIsSelected}
805
+ aria-expanded={packageIsExpanded}
806
+ data-workbench-package={packageSummary.name}
807
+ onclick={() => selectPackage(packageSummary)}
808
+ >
809
+ <strong>{packageSummary.name}</strong>
810
+ <span>{stats?.routes || 0} routes · {stats?.playground || 0} previews</span>
811
+ </button>
812
+
813
+ {#if packageIsSelected && packageIsExpanded}
814
+ <div class="section-list" aria-label={`${packageSummary.name} sections`}>
815
+ {#if packageSummary.docs.length > 0}
816
+ <div
817
+ class="document-entry-list"
818
+ data-testid="workbench-document-entry-list"
819
+ >
820
+ {#each packageSummary.docs as doc (doc.path)}
821
+ <button
822
+ type="button"
823
+ class:active={activeTab === 'docs' && doc.path === selectedDocument?.path}
824
+ aria-pressed={activeTab === 'docs' && doc.path === selectedDocument?.path}
825
+ data-workbench-document={doc.title}
826
+ onclick={() => selectDocument(doc)}
827
+ >
828
+ <span>{doc.title}</span>
829
+ </button>
830
+ {/each}
831
+ </div>
832
+ {/if}
833
+ {#each sectionTabs as tab}
834
+ <div class="section-node">
835
+ <button
836
+ type="button"
837
+ class:active={activeTab === tab.id}
838
+ aria-pressed={activeTab === tab.id}
839
+ onclick={() => selectSection(tab.id)}
840
+ >
841
+ {tab.label}
842
+ </button>
843
+ {#if tab.id === 'playground' && selectedPlaygroundEntries.length > 0}
844
+ <div
845
+ class="playground-entry-list"
846
+ data-testid="workbench-playground-entry-list"
847
+ >
848
+ {#each selectedPlaygroundEntries as entry (entry.qualifiedId)}
849
+ <button
850
+ type="button"
851
+ class:active={activeTab === 'playground' && entry.qualifiedId === selectedPlaygroundEntry?.qualifiedId}
852
+ aria-pressed={activeTab === 'playground' && entry.qualifiedId === selectedPlaygroundEntry?.qualifiedId}
853
+ data-workbench-playground-entry={entry.qualifiedId}
854
+ onclick={() => selectPlaygroundEntry(entry)}
855
+ >
856
+ <span>{entry.title}</span>
857
+ </button>
858
+ {/each}
859
+ </div>
860
+ {/if}
861
+ {#if tab.id === 'routes' && selectedRoutes.length > 0}
862
+ <div
863
+ class="route-entry-list"
864
+ data-testid="workbench-route-entry-list"
865
+ >
866
+ {#each selectedRoutes as route (route.qualifiedId)}
867
+ <button
868
+ type="button"
869
+ class:active={activeTab === 'routes' && route.qualifiedId === selectedRoute?.qualifiedId}
870
+ aria-pressed={activeTab === 'routes' && route.qualifiedId === selectedRoute?.qualifiedId}
871
+ data-workbench-route-entry={route.qualifiedId}
872
+ onclick={() => selectRoute(route)}
873
+ >
874
+ <span>{route.title}</span>
875
+ </button>
876
+ {/each}
877
+ </div>
878
+ {/if}
879
+ </div>
880
+ {/each}
881
+ </div>
882
+ {/if}
883
+ </div>
884
+ {/each}
885
+ {/if}
886
+ </div>
887
+ </nav>
888
+ </div>
889
+ {/snippet}
890
+
891
+ {#snippet systemPanel()}
892
+ <section class="workbench-system-panel" aria-label="Workbench system">
893
+ <div class="theme-controls">
894
+ <ThemeSwitcher variant="select" label="Theme" />
895
+ <ColorSchemeToggle />
896
+ </div>
897
+ <dl class="workbench-system-summary">
898
+ <div><dt>Scope</dt><dd>{project.scope.mode}</dd></div>
899
+ <div><dt>Packages</dt><dd>{totals.packages}</dd></div>
900
+ <div><dt>Routes</dt><dd>{totals.routes}</dd></div>
901
+ <div><dt>Previews</dt><dd>{totals.playground}</dd></div>
902
+ </dl>
903
+ </section>
904
+ {/snippet}
905
+
906
+ <div class="workbench-main">
907
+ <header class="main-header">
908
+ <div>
909
+ <p class="eyebrow" data-testid="workbench-scope">{project.scope.projectRoot}</p>
910
+ <h2>{activeTab === 'packages' && !isPackageScoped ? rootNavLabel : selectedPackage?.name || 'No package selected'}</h2>
911
+ {#if activeTab === 'packages' && !isPackageScoped}
912
+ <p>{project.scope.mode === 'consumer' ? 'Installed SMRT packages and local project surfaces discovered for this project.' : 'Workspace packages and package-owned surfaces discovered for this repository.'}</p>
913
+ {:else if selectedPackage?.description}
914
+ <p>{selectedPackage.description}</p>
915
+ {/if}
916
+ </div>
917
+ <div class="scope-pills">
918
+ <span>{project.scope.mode}</span>
919
+ {#if scopedPackageName}
920
+ <span>focused</span>
921
+ {/if}
922
+ </div>
923
+ </header>
924
+
925
+ <section class="tab-panel" data-testid={`workbench-tab-${activeTab}`}>
926
+ {#if activeTab === 'packages'}
927
+ <div class="package-index" data-testid="workbench-package-index">
928
+ {#each visiblePackages as packageSummary (packageSummary.name)}
929
+ {@const stats = packageStats.get(packageSummary.name)}
930
+ <button
931
+ type="button"
932
+ class="package-index-item"
933
+ class:selected={packageSummary.name === selectedPackage?.name}
934
+ onclick={() => openPackage(packageSummary)}
935
+ >
936
+ <span class="package-index-copy">
937
+ <strong>{packageSummary.name}</strong>
938
+ <span>{packageSummary.description || 'No package description published.'}</span>
939
+ </span>
940
+ <span class="package-index-meta">
941
+ <span>{stats?.routes || 0} routes</span>
942
+ <span>{stats?.playground || 0} previews</span>
943
+ <span>{packageSummary.version || 'unversioned'}</span>
944
+ </span>
945
+ </button>
946
+ {/each}
947
+ </div>
948
+ {:else if activeTab === 'playground'}
949
+ <div class="playground-embed">
950
+ <PlaygroundHost
951
+ embedded={true}
952
+ hideEntryList={true}
953
+ selectedEntryId={selectedPlaygroundEntryId}
954
+ title={selectedPackage?.name || 'SMRT Playground'}
955
+ subtitle="Package previews inside the workbench"
956
+ modules={filteredPlaygroundModules}
957
+ />
958
+ </div>
959
+ {:else if activeTab === 'routes'}
960
+ {#if selectedRoutes.length === 0}
961
+ <div class="empty-panel">
962
+ <strong>No inline routes</strong>
963
+ <p>This package has no workbench route module.</p>
964
+ </div>
965
+ {:else}
966
+ <div class="route-stage" data-testid="workbench-route-stage">
967
+ {#if selectedRoute && RouteComponent && routeComponentId === selectedRoute.qualifiedId}
968
+ <div class="route-meta">
969
+ <div>
970
+ <p class="eyebrow">{selectedRoute.packageName}</p>
971
+ <h3>{selectedRoute.title}</h3>
972
+ {#if selectedRoute.description}
973
+ <p>{selectedRoute.description}</p>
974
+ {/if}
975
+ </div>
976
+ <code>{selectedRoute.defaultPath}</code>
977
+ </div>
978
+ <div class="route-render">
979
+ <RouteComponent {...routeProps(selectedRoute)} />
980
+ </div>
981
+ {:else if routeIsLoading}
982
+ <div class="empty-panel"><strong>Loading route</strong></div>
983
+ {:else}
984
+ <div class="empty-panel">
985
+ <strong>Route unavailable</strong>
986
+ <p>{routeLoadError || 'The selected route could not be rendered.'}</p>
987
+ </div>
988
+ {/if}
989
+ </div>
990
+ {/if}
991
+ {:else if activeTab === 'api' && selectedPackage}
992
+ {@const objects = apiObjects(selectedPackage)}
993
+ {@const restEndpoints = apiRestEndpoints(selectedPackage)}
994
+ {@const cliCommands = apiCliCommands(selectedPackage)}
995
+ {@const mcpTools = apiMcpTools(selectedPackage)}
996
+ <div class="api-layout">
997
+ <div
998
+ class="api-tab-list"
999
+ role="tablist"
1000
+ aria-label={`${selectedPackage.name} API surfaces`}
1001
+ data-testid="workbench-api-tabs"
1002
+ >
1003
+ {#each apiTabs as apiTab (apiTab.id)}
1004
+ <button
1005
+ type="button"
1006
+ role="tab"
1007
+ aria-selected={activeApiTab === apiTab.id}
1008
+ class:active={activeApiTab === apiTab.id}
1009
+ data-testid={`workbench-api-tab-${apiTab.id}`}
1010
+ onclick={() => (activeApiTab = apiTab.id)}
1011
+ >
1012
+ <span>{apiTab.label}</span>
1013
+ <strong>{apiTabCount(apiTab.id, objects, restEndpoints, mcpTools, cliCommands)}</strong>
1014
+ </button>
1015
+ {/each}
1016
+ </div>
1017
+
1018
+ {#if activeApiTab === 'objects'}
1019
+ <section class="api-tab-panel" data-testid="workbench-api-objects">
1020
+ <h3>Objects <span>{objects.length}</span></h3>
1021
+ {#if objects.length === 0}
1022
+ <p class="muted">No object metadata found.</p>
1023
+ {:else}
1024
+ <div class="api-object-list">
1025
+ {#each objects as object (object.qualifiedName || object.name)}
1026
+ <article class="api-object-doc">
1027
+ <header>
1028
+ <div>
1029
+ <h4>{object.className || object.name}</h4>
1030
+ {#if object.qualifiedName}
1031
+ <code>{object.qualifiedName}</code>
1032
+ {/if}
1033
+ </div>
1034
+ {#if object.collection}
1035
+ <span>{object.collection}</span>
1036
+ {/if}
1037
+ </header>
1038
+ {#if object.description}
1039
+ <MarkdownDocument content={object.description} />
1040
+ {:else}
1041
+ <p class="muted">No TypeDoc summary found for this object.</p>
1042
+ {/if}
1043
+ {#if object.typedocPath}
1044
+ <p class="muted">TypeDoc: {relativePath(object.typedocPath)}</p>
1045
+ {/if}
1046
+ {#if object.sourcePath}
1047
+ <p class="muted">{relativePath(object.sourcePath)}</p>
1048
+ {/if}
1049
+ {#if object.fields.length > 0}
1050
+ <details>
1051
+ <summary>{object.fields.length} fields</summary>
1052
+ <div class="field-table">
1053
+ {#each object.fields as field (field.name)}
1054
+ <div>
1055
+ <strong>{field.name}</strong>
1056
+ <code>{field.type || 'unknown'}</code>
1057
+ <span>{field.required ? 'required' : 'optional'}</span>
1058
+ {#if field.related}
1059
+ <span>{field.related}</span>
1060
+ {/if}
1061
+ {#if field.description}
1062
+ <p>{field.description}</p>
1063
+ {/if}
1064
+ </div>
1065
+ {/each}
1066
+ </div>
1067
+ </details>
1068
+ {/if}
1069
+ </article>
1070
+ {/each}
1071
+ </div>
1072
+ {/if}
1073
+ </section>
1074
+ {:else if activeApiTab === 'rest'}
1075
+ <section class="api-tab-panel" data-testid="workbench-api-rest">
1076
+ <h3>REST <span>{restEndpoints.length}</span></h3>
1077
+ {#if restEndpoints.length === 0}
1078
+ <p class="muted">No REST endpoints exposed.</p>
1079
+ {:else}
1080
+ <div class="api-surface-list">
1081
+ {#each restEndpoints as endpoint, index (`rest:${index}:${endpoint.method}:${endpoint.path}:${endpoint.action}`)}
1082
+ {@const copyText = `${endpoint.method} ${endpoint.path}`}
1083
+ <div>
1084
+ <code>{endpoint.method}</code>
1085
+ <span>{endpoint.path}</span>
1086
+ <p>{endpoint.description}</p>
1087
+ {#if endpoint.parameters.length > 0}
1088
+ <div class="api-parameter-list" aria-label={`${endpoint.method} ${endpoint.path} parameters`}>
1089
+ {#each endpoint.parameters as parameter (`${endpoint.method}:${endpoint.path}:${parameter.location}:${parameter.name}`)}
1090
+ <div>
1091
+ <code>{parameter.name}</code>
1092
+ <span>{parameterMeta(parameter)}</span>
1093
+ {#if parameter.description}
1094
+ <p>{parameter.description}</p>
1095
+ {/if}
1096
+ </div>
1097
+ {/each}
1098
+ </div>
1099
+ {/if}
1100
+ <button type="button" onclick={() => copyValue(copyText)}>
1101
+ {copiedValue === copyText ? 'Copied' : 'Copy'}
1102
+ </button>
1103
+ </div>
1104
+ {/each}
1105
+ </div>
1106
+ {/if}
1107
+ </section>
1108
+ {:else if activeApiTab === 'mcp'}
1109
+ <section class="api-tab-panel" data-testid="workbench-api-mcp">
1110
+ <h3>MCP <span>{mcpTools.length}</span></h3>
1111
+ {#if mcpTools.length === 0}
1112
+ <p class="muted">No MCP tools exposed.</p>
1113
+ {:else}
1114
+ <div class="api-surface-list">
1115
+ {#each mcpTools as tool, index (`mcp:${index}:${tool.toolName}:${tool.action}`)}
1116
+ <div>
1117
+ <code>{tool.toolName}</code>
1118
+ <span>{tool.description}</span>
1119
+ {#if tool.parameters.length > 0}
1120
+ <div class="api-parameter-list" aria-label={`${tool.toolName} parameters`}>
1121
+ {#each tool.parameters as parameter (`${tool.toolName}:${parameter.location}:${parameter.name}`)}
1122
+ <div>
1123
+ <code>{parameter.name}</code>
1124
+ <span>{parameterMeta(parameter)}</span>
1125
+ {#if parameter.description}
1126
+ <p>{parameter.description}</p>
1127
+ {/if}
1128
+ </div>
1129
+ {/each}
1130
+ </div>
1131
+ {/if}
1132
+ <button type="button" onclick={() => copyValue(tool.toolName)}>
1133
+ {copiedValue === tool.toolName ? 'Copied' : 'Copy'}
1134
+ </button>
1135
+ </div>
1136
+ {/each}
1137
+ </div>
1138
+ {/if}
1139
+ </section>
1140
+ {:else}
1141
+ <section class="api-tab-panel" data-testid="workbench-api-cli">
1142
+ <h3>CLI <span>{cliCommands.length}</span></h3>
1143
+ {#if cliCommands.length === 0}
1144
+ <p class="muted">No CLI commands exposed.</p>
1145
+ {:else}
1146
+ <div class="api-surface-list">
1147
+ {#each cliCommands as command, index (`cli:${index}:${command.command}:${command.action}`)}
1148
+ <div>
1149
+ <code>{command.command}</code>
1150
+ <span>{command.description}</span>
1151
+ {#if command.parameters.length > 0}
1152
+ <div class="api-parameter-list" aria-label={`${command.command} parameters`}>
1153
+ {#each command.parameters as parameter (`${command.command}:${parameter.location}:${parameter.name}`)}
1154
+ <div>
1155
+ <code>{parameter.name}</code>
1156
+ <span>{parameterMeta(parameter)}</span>
1157
+ {#if parameter.description}
1158
+ <p>{parameter.description}</p>
1159
+ {/if}
1160
+ </div>
1161
+ {/each}
1162
+ </div>
1163
+ {/if}
1164
+ <button type="button" onclick={() => copyValue(command.command)}>
1165
+ {copiedValue === command.command ? 'Copied' : 'Copy'}
1166
+ </button>
1167
+ </div>
1168
+ {/each}
1169
+ </div>
1170
+ {/if}
1171
+ </section>
1172
+ {/if}
1173
+ </div>
1174
+ {:else if activeTab === 'docs' && selectedPackage}
1175
+ <div class="document-list">
1176
+ {#if selectedDocument}
1177
+ <article class="document-view" data-testid="workbench-document-view">
1178
+ <header>
1179
+ <h3>{selectedDocument.title}</h3>
1180
+ <button type="button" onclick={() => copyValue(selectedDocument.content || '')}>
1181
+ {copiedValue === (selectedDocument.content || '') ? 'Copied' : 'Copy'}
1182
+ </button>
1183
+ </header>
1184
+ <p class="muted">{relativePath(selectedDocument.path)}</p>
1185
+ <MarkdownDocument content={selectedDocument.content || ''} />
1186
+ {#if selectedDocument.truncated}
1187
+ <p class="muted">Document truncated for workbench display.</p>
1188
+ {/if}
1189
+ </article>
1190
+ {:else}
1191
+ <div class="empty-panel">
1192
+ <strong>No documents</strong>
1193
+ <p>This package has no README, AGENTS, CHANGELOG, or generated docs entry.</p>
1194
+ </div>
1195
+ {/if}
1196
+ </div>
1197
+ {:else if activeTab === 'knowledge' && selectedPackage}
1198
+ <div class="surface-grid">
1199
+ <section>
1200
+ <h3>Knowledge Summary</h3>
1201
+ <dl class="definition-list">
1202
+ <div><dt>Objects</dt><dd>{selectedPackage.knowledge.objectCount}</dd></div>
1203
+ <div><dt>Relationships</dt><dd>{selectedPackage.knowledge.relationshipCount}</dd></div>
1204
+ <div><dt>Prompts</dt><dd>{selectedPackage.knowledge.promptCount}</dd></div>
1205
+ <div><dt>MCP tools</dt><dd>{selectedPackage.knowledge.mcpToolCount}</dd></div>
1206
+ <div><dt>Surfaces</dt><dd>{selectedPackage.knowledge.surfaceCount}</dd></div>
1207
+ </dl>
1208
+ </section>
1209
+ <section>
1210
+ <h3>Sources</h3>
1211
+ <ul class="file-list">
1212
+ <li>{selectedPackage.knowledge.knowledgePath ? relativePath(selectedPackage.knowledge.knowledgePath) : 'smrt-knowledge.json missing'}</li>
1213
+ <li>{selectedPackage.knowledge.manifestPath ? relativePath(selectedPackage.knowledge.manifestPath) : 'manifest missing'}</li>
1214
+ </ul>
1215
+ </section>
1216
+ </div>
1217
+ {:else if activeTab === 'specialist' && selectedPackage}
1218
+ <div class="specialist-panel">
1219
+ <section>
1220
+ <h3>Package Specialist Context</h3>
1221
+ <p class="muted">{specialistUri(selectedPackage.name)}</p>
1222
+ <button type="button" onclick={() => copyValue(specialistUri(selectedPackage.name))}>
1223
+ {copiedValue === specialistUri(selectedPackage.name) ? 'Copied' : 'Copy resource URI'}
1224
+ </button>
1225
+ </section>
1226
+ <section>
1227
+ <h3>Source List</h3>
1228
+ <ul class="file-list">
1229
+ {#each selectedPackage.docs as doc}
1230
+ <li>{relativePath(doc.path)}</li>
1231
+ {/each}
1232
+ {#if selectedPackage.knowledge.knowledgePath}
1233
+ <li>{relativePath(selectedPackage.knowledge.knowledgePath)}</li>
1234
+ {/if}
1235
+ {#if selectedPackage.knowledge.manifestPath}
1236
+ <li>{relativePath(selectedPackage.knowledge.manifestPath)}</li>
1237
+ {/if}
1238
+ </ul>
1239
+ </section>
1240
+ </div>
1241
+ {:else if activeTab === 'scripts' && selectedPackage}
1242
+ <div class="script-list">
1243
+ {#each Object.entries(selectedPackage.scripts) as [scriptName, script]}
1244
+ {@const command = commandFor(selectedPackage, scriptName)}
1245
+ <div>
1246
+ <div>
1247
+ <strong>{scriptName}</strong>
1248
+ <code>{script}</code>
1249
+ </div>
1250
+ <button type="button" onclick={() => copyValue(command)}>
1251
+ {copiedValue === command ? 'Copied' : 'Copy'}
1252
+ </button>
1253
+ </div>
1254
+ {/each}
1255
+ {#each selectedPackage.recommendedCommands as recommendation (`${recommendation.id}:${recommendation.command}`)}
1256
+ {@const command = recommendedCommandFor(selectedPackage, recommendation)}
1257
+ <div>
1258
+ <div>
1259
+ <strong>{recommendation.label}</strong>
1260
+ {#if recommendation.description}<span>{recommendation.description}</span>{/if}
1261
+ <code>{command}</code>
1262
+ </div>
1263
+ <button type="button" onclick={() => copyValue(command)}>
1264
+ {copiedValue === command ? 'Copied' : 'Copy'}
1265
+ </button>
1266
+ </div>
1267
+ {/each}
1268
+ </div>
1269
+ {:else if activeTab === 'dependencies' && selectedPackage}
1270
+ <div class="surface-grid">
1271
+ <section>
1272
+ <h3>SMRT Dependencies</h3>
1273
+ <div class="chip-list">
1274
+ {#each selectedPackage.smrtDependencies as dependency}
1275
+ <code>{dependency}</code>
1276
+ {/each}
1277
+ </div>
1278
+ </section>
1279
+ <section>
1280
+ <h3>SDK Dependencies</h3>
1281
+ <div class="chip-list">
1282
+ {#each selectedPackage.sdkDependencies as dependency}
1283
+ <code>{dependency}</code>
1284
+ {/each}
1285
+ </div>
1286
+ </section>
1287
+ <section>
1288
+ <h3>All Dependencies</h3>
1289
+ <div class="dependency-list">
1290
+ {#each dependencyEntries(selectedPackage) as [dependency, version]}
1291
+ <div><span>{dependency}</span><code>{version}</code></div>
1292
+ {/each}
1293
+ </div>
1294
+ </section>
1295
+ </div>
1296
+ {:else if activeTab === 'migrations' && selectedPackage}
1297
+ <div class="document-list">
1298
+ <article>
1299
+ <h3>Migrations</h3>
1300
+ {#if selectedPackage.migrations.length === 0}
1301
+ <p class="muted">No migration files found.</p>
1302
+ {:else}
1303
+ <ul class="file-list">
1304
+ {#each selectedPackage.migrations as file}
1305
+ <li>{relativePath(file)}</li>
1306
+ {/each}
1307
+ </ul>
1308
+ {/if}
1309
+ </article>
1310
+ </div>
1311
+ {:else if activeTab === 'examples' && selectedPackage}
1312
+ <div class="document-list">
1313
+ {#if selectedPackage.examples.length === 0}
1314
+ <div class="empty-panel">
1315
+ <strong>No examples found</strong>
1316
+ <p>No examples or README code blocks were discovered.</p>
1317
+ </div>
1318
+ {:else}
1319
+ {#each selectedPackage.examples as example}
1320
+ <article>
1321
+ <header>
1322
+ <h3>{example.title}</h3>
1323
+ {#if example.code}
1324
+ <button type="button" onclick={() => copyValue(example.code || '')}>
1325
+ {copiedValue === (example.code || '') ? 'Copied' : 'Copy'}
1326
+ </button>
1327
+ {/if}
1328
+ </header>
1329
+ {#if example.path}
1330
+ <p class="muted">{relativePath(example.path)}</p>
1331
+ {/if}
1332
+ {#if example.code}
1333
+ <pre>{example.code}</pre>
1334
+ {/if}
1335
+ </article>
1336
+ {/each}
1337
+ {/if}
1338
+ </div>
1339
+ {/if}
1340
+ </section>
1341
+ </div>
1342
+ </AdminShell>
1343
+ </div>
1344
+ </ThemeProvider>
1345
+
1346
+ <style>
1347
+ :global(body) {
1348
+ margin: 0;
1349
+ min-height: 100svh;
1350
+ }
1351
+
1352
+ .workbench-shell-root {
1353
+ min-height: 100svh;
1354
+ }
1355
+
1356
+ .workbench-shell-mark {
1357
+ display: grid;
1358
+ place-items: center;
1359
+ min-inline-size: 0;
1360
+ }
1361
+
1362
+ .workbench-shell-mark svg {
1363
+ inline-size: 1.35rem;
1364
+ block-size: 1.35rem;
1365
+ }
1366
+
1367
+ .workbench-app-panel,
1368
+ .workbench-nav-shell,
1369
+ .workbench-system-panel,
1370
+ .search,
1371
+ .package-list,
1372
+ .section-list,
1373
+ .workbench-main,
1374
+ .surface-grid,
1375
+ .document-list,
1376
+ .specialist-panel {
1377
+ display: grid;
1378
+ gap: 0.75rem;
1379
+ }
1380
+
1381
+ .workbench-brand h1,
1382
+ .workbench-app-panel h2,
1383
+ .main-header h2,
1384
+ h3 {
1385
+ margin: 0;
1386
+ }
1387
+
1388
+ .workbench-brand p,
1389
+ .workbench-app-panel p,
1390
+ .main-header p,
1391
+ .empty-panel p,
1392
+ .muted {
1393
+ margin: 0;
1394
+ color: var(--smrt-color-on-surface-variant);
1395
+ line-height: 1.5;
1396
+ }
1397
+
1398
+ .workbench-nav-header {
1399
+ display: flex;
1400
+ gap: 0.75rem;
1401
+ align-items: start;
1402
+ justify-content: space-between;
1403
+ }
1404
+
1405
+ .workbench-brand {
1406
+ display: grid;
1407
+ gap: 0.15rem;
1408
+ min-width: 0;
1409
+ }
1410
+
1411
+ .brand-badge {
1412
+ display: inline-flex;
1413
+ gap: 0.45rem;
1414
+ align-items: center;
1415
+ }
1416
+
1417
+ .brand-mark {
1418
+ width: 1rem;
1419
+ height: 1rem;
1420
+ display: block;
1421
+ }
1422
+
1423
+ .workbench-nav-toggle {
1424
+ display: inline-grid;
1425
+ flex: 0 0 auto;
1426
+ width: 2rem;
1427
+ height: 2rem;
1428
+ place-items: center;
1429
+ cursor: pointer;
1430
+ border: 1px solid var(--smrt-color-outline-variant);
1431
+ border-radius: 6px;
1432
+ background: var(--smrt-color-surface-container-low);
1433
+ color: var(--smrt-color-on-surface);
1434
+ }
1435
+
1436
+ .workbench-nav-toggle:hover {
1437
+ border-color: var(--smrt-color-primary);
1438
+ background: color-mix(in srgb, var(--smrt-color-primary) 12%, transparent);
1439
+ }
1440
+
1441
+ .workbench-nav-toggle svg {
1442
+ width: 1rem;
1443
+ height: 1rem;
1444
+ }
1445
+
1446
+ .eyebrow {
1447
+ margin: 0 0 0.35rem;
1448
+ font-size: 0.72rem;
1449
+ letter-spacing: 0.08em;
1450
+ text-transform: uppercase;
1451
+ color: var(--smrt-color-primary);
1452
+ }
1453
+
1454
+ .theme-controls {
1455
+ display: flex;
1456
+ align-items: flex-end;
1457
+ gap: 0.55rem;
1458
+ }
1459
+
1460
+ .theme-controls :global(.smrt-theme-switcher) {
1461
+ flex: 1;
1462
+ }
1463
+
1464
+ .workbench-scope-metrics,
1465
+ .workbench-system-summary {
1466
+ display: grid;
1467
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1468
+ gap: 0.75rem;
1469
+ margin: 0;
1470
+ }
1471
+
1472
+ .workbench-system-summary {
1473
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1474
+ }
1475
+
1476
+ .workbench-scope-metrics div,
1477
+ .workbench-system-summary div {
1478
+ border: 1px solid var(--smrt-color-outline-variant);
1479
+ border-radius: 8px;
1480
+ background: var(--smrt-color-surface-container-low);
1481
+ padding: 0.75rem;
1482
+ }
1483
+
1484
+ .workbench-scope-metrics dt,
1485
+ .workbench-system-summary dt {
1486
+ color: var(--smrt-color-on-surface-variant);
1487
+ font-size: 0.78rem;
1488
+ }
1489
+
1490
+ .workbench-scope-metrics dd,
1491
+ .workbench-system-summary dd {
1492
+ margin: 0.2rem 0 0;
1493
+ font-weight: 700;
1494
+ }
1495
+
1496
+ .nav-tree {
1497
+ display: grid;
1498
+ align-content: start;
1499
+ gap: 0.75rem;
1500
+ min-height: 0;
1501
+ overflow: auto;
1502
+ padding-right: 0.15rem;
1503
+ }
1504
+
1505
+ .search span {
1506
+ color: var(--smrt-color-on-surface-variant);
1507
+ font-size: 0.82rem;
1508
+ }
1509
+
1510
+ .search input {
1511
+ width: 100%;
1512
+ box-sizing: border-box;
1513
+ border: 1px solid var(--smrt-color-outline-variant);
1514
+ border-radius: 6px;
1515
+ background: var(--smrt-color-surface-container-high);
1516
+ color: inherit;
1517
+ padding: 0.72rem 0.8rem;
1518
+ }
1519
+
1520
+ button {
1521
+ font: inherit;
1522
+ }
1523
+
1524
+ .root-nav,
1525
+ .package-node > button {
1526
+ display: grid;
1527
+ gap: 0.2rem;
1528
+ width: 100%;
1529
+ text-align: left;
1530
+ cursor: pointer;
1531
+ border-radius: 6px;
1532
+ border: 1px solid transparent;
1533
+ padding: 0.75rem 0.85rem;
1534
+ }
1535
+
1536
+ .root-nav,
1537
+ .package-node > button {
1538
+ background: var(--smrt-color-surface-container-low);
1539
+ color: inherit;
1540
+ }
1541
+
1542
+ .root-nav.active,
1543
+ .root-nav:hover,
1544
+ .package-node > button.selected,
1545
+ .package-node > button:hover {
1546
+ border-color: var(--smrt-color-primary);
1547
+ background: color-mix(in srgb, var(--smrt-color-primary) 16%, transparent);
1548
+ }
1549
+
1550
+ .root-nav span,
1551
+ .package-node > button span {
1552
+ color: var(--smrt-color-on-surface-variant);
1553
+ font-size: 0.82rem;
1554
+ }
1555
+
1556
+ .package-node {
1557
+ display: grid;
1558
+ gap: 0.45rem;
1559
+ }
1560
+
1561
+ .section-list {
1562
+ padding-left: 0.65rem;
1563
+ border-left: 1px solid var(--smrt-color-outline-variant);
1564
+ margin-left: 0.65rem;
1565
+ }
1566
+
1567
+ .section-node,
1568
+ .playground-entry-list,
1569
+ .route-entry-list,
1570
+ .document-entry-list,
1571
+ .api-object-list,
1572
+ .api-surface-list {
1573
+ display: grid;
1574
+ gap: 0.25rem;
1575
+ }
1576
+
1577
+ .section-list button {
1578
+ cursor: pointer;
1579
+ border: 0;
1580
+ border-radius: 6px;
1581
+ background: transparent;
1582
+ color: var(--smrt-color-on-surface-variant);
1583
+ padding: 0.42rem 0.55rem;
1584
+ text-align: left;
1585
+ }
1586
+
1587
+ .playground-entry-list,
1588
+ .route-entry-list {
1589
+ padding-left: 0.65rem;
1590
+ }
1591
+
1592
+ .playground-entry-list button,
1593
+ .route-entry-list button,
1594
+ .document-entry-list button {
1595
+ padding: 0.32rem 0.5rem;
1596
+ font-size: 0.82rem;
1597
+ }
1598
+
1599
+ .playground-entry-list button span,
1600
+ .route-entry-list button span,
1601
+ .document-entry-list button span {
1602
+ display: block;
1603
+ overflow: hidden;
1604
+ text-overflow: ellipsis;
1605
+ white-space: nowrap;
1606
+ }
1607
+
1608
+ .section-list button.active,
1609
+ .section-list button:hover {
1610
+ color: var(--smrt-color-on-surface);
1611
+ background: var(--smrt-color-surface-container-high);
1612
+ }
1613
+
1614
+ .workbench-main {
1615
+ align-content: start;
1616
+ padding: 1rem;
1617
+ gap: 1rem;
1618
+ }
1619
+
1620
+ .main-header {
1621
+ display: flex;
1622
+ justify-content: space-between;
1623
+ gap: 1rem;
1624
+ align-items: start;
1625
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
1626
+ padding-bottom: 1rem;
1627
+ }
1628
+
1629
+ .scope-pills,
1630
+ .chip-list {
1631
+ display: flex;
1632
+ gap: 0.5rem;
1633
+ flex-wrap: wrap;
1634
+ }
1635
+
1636
+ .scope-pills span,
1637
+ .chip-list code {
1638
+ border: 1px solid var(--smrt-color-outline-variant);
1639
+ border-radius: 999px;
1640
+ padding: 0.24rem 0.55rem;
1641
+ background: var(--smrt-color-surface-container-low);
1642
+ font-size: 0.78rem;
1643
+ }
1644
+
1645
+ .tab-panel {
1646
+ min-height: 38rem;
1647
+ }
1648
+
1649
+ .surface-grid section,
1650
+ .document-list article,
1651
+ .specialist-panel section,
1652
+ .empty-panel,
1653
+ .route-stage,
1654
+ .script-list > div {
1655
+ border: 1px solid var(--smrt-color-outline-variant);
1656
+ border-radius: 8px;
1657
+ background: var(--smrt-color-surface);
1658
+ padding: 1rem;
1659
+ }
1660
+
1661
+ .surface-grid {
1662
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1663
+ }
1664
+
1665
+ .surface-grid ul,
1666
+ .file-list {
1667
+ margin: 0.75rem 0 0;
1668
+ padding-left: 1.1rem;
1669
+ line-height: 1.65;
1670
+ }
1671
+
1672
+ .package-index,
1673
+ .script-list,
1674
+ .dependency-list {
1675
+ display: grid;
1676
+ gap: 0.55rem;
1677
+ }
1678
+
1679
+ .package-index {
1680
+ gap: 0.7rem;
1681
+ }
1682
+
1683
+ .package-index-item {
1684
+ display: grid;
1685
+ grid-template-columns: minmax(0, 1fr) auto;
1686
+ align-items: center;
1687
+ gap: 1rem;
1688
+ width: 100%;
1689
+ cursor: pointer;
1690
+ border: 1px solid var(--smrt-color-outline-variant);
1691
+ border-radius: 8px;
1692
+ background: var(--smrt-color-surface);
1693
+ color: inherit;
1694
+ padding: 0.9rem 1rem;
1695
+ text-align: left;
1696
+ }
1697
+
1698
+ .package-index-item.selected,
1699
+ .package-index-item:hover {
1700
+ border-color: var(--smrt-color-primary);
1701
+ background: color-mix(in srgb, var(--smrt-color-primary) 10%, var(--smrt-color-surface));
1702
+ }
1703
+
1704
+ .package-index-copy {
1705
+ display: grid;
1706
+ gap: 0.28rem;
1707
+ min-width: 0;
1708
+ }
1709
+
1710
+ .package-index-copy > span {
1711
+ color: var(--smrt-color-on-surface-variant);
1712
+ line-height: 1.45;
1713
+ }
1714
+
1715
+ .package-index-meta {
1716
+ display: flex;
1717
+ gap: 0.45rem;
1718
+ flex-wrap: wrap;
1719
+ justify-content: flex-end;
1720
+ }
1721
+
1722
+ .package-index-meta span {
1723
+ border: 1px solid var(--smrt-color-outline-variant);
1724
+ border-radius: 999px;
1725
+ padding: 0.22rem 0.5rem;
1726
+ background: var(--smrt-color-surface-container-low);
1727
+ color: var(--smrt-color-on-surface-variant);
1728
+ font-size: 0.76rem;
1729
+ }
1730
+
1731
+ .playground-embed {
1732
+ min-width: 0;
1733
+ }
1734
+
1735
+ .route-stage {
1736
+ min-width: 0;
1737
+ overflow: hidden;
1738
+ }
1739
+
1740
+ .route-meta {
1741
+ display: flex;
1742
+ justify-content: space-between;
1743
+ gap: 1rem;
1744
+ align-items: start;
1745
+ margin-bottom: 1rem;
1746
+ }
1747
+
1748
+ .route-render {
1749
+ overflow: auto;
1750
+ max-height: 72vh;
1751
+ }
1752
+
1753
+ .api-layout {
1754
+ display: grid;
1755
+ gap: 1rem;
1756
+ align-items: start;
1757
+ }
1758
+
1759
+ .api-tab-list {
1760
+ display: flex;
1761
+ flex-wrap: wrap;
1762
+ gap: 0.5rem;
1763
+ align-items: center;
1764
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
1765
+ padding-bottom: 0.75rem;
1766
+ }
1767
+
1768
+ .api-tab-list button {
1769
+ display: inline-flex;
1770
+ cursor: pointer;
1771
+ align-items: center;
1772
+ gap: 0.45rem;
1773
+ border: 1px solid var(--smrt-color-outline-variant);
1774
+ border-radius: 999px;
1775
+ background: var(--smrt-color-surface-container-low);
1776
+ color: var(--smrt-color-on-surface-variant);
1777
+ padding: 0.42rem 0.75rem;
1778
+ }
1779
+
1780
+ .api-tab-list button.active,
1781
+ .api-tab-list button:hover {
1782
+ border-color: var(--smrt-color-primary);
1783
+ background: color-mix(in srgb, var(--smrt-color-primary) 12%, var(--smrt-color-surface));
1784
+ color: var(--smrt-color-on-surface);
1785
+ }
1786
+
1787
+ .api-tab-list strong {
1788
+ color: inherit;
1789
+ font-size: 0.78rem;
1790
+ font-weight: 700;
1791
+ }
1792
+
1793
+ .api-tab-panel {
1794
+ display: grid;
1795
+ gap: 1rem;
1796
+ }
1797
+
1798
+ .api-layout section {
1799
+ border: 1px solid var(--smrt-color-outline-variant);
1800
+ border-radius: 8px;
1801
+ background: var(--smrt-color-surface);
1802
+ padding: 1rem;
1803
+ scroll-margin-top: 1rem;
1804
+ }
1805
+
1806
+ .api-layout h3 span {
1807
+ margin-left: 0.35rem;
1808
+ color: var(--smrt-color-on-surface-variant);
1809
+ font-size: 0.78rem;
1810
+ font-weight: 500;
1811
+ }
1812
+
1813
+ .api-object-list {
1814
+ gap: 0.75rem;
1815
+ margin-top: 0.75rem;
1816
+ }
1817
+
1818
+ .api-surface-list {
1819
+ gap: 0.55rem;
1820
+ margin-top: 0.75rem;
1821
+ }
1822
+
1823
+ .api-object-doc {
1824
+ display: grid;
1825
+ gap: 0.75rem;
1826
+ border: 1px solid var(--smrt-color-outline-variant);
1827
+ border-radius: 8px;
1828
+ background: var(--smrt-color-surface-container-low);
1829
+ padding: 0.85rem;
1830
+ }
1831
+
1832
+ .api-object-doc header {
1833
+ display: flex;
1834
+ justify-content: space-between;
1835
+ gap: 1rem;
1836
+ align-items: start;
1837
+ }
1838
+
1839
+ .api-object-doc h4 {
1840
+ margin: 0 0 0.35rem;
1841
+ }
1842
+
1843
+ .api-object-doc code,
1844
+ .field-table code {
1845
+ word-break: break-word;
1846
+ }
1847
+
1848
+ .api-object-doc header > span,
1849
+ .field-table span {
1850
+ color: var(--smrt-color-on-surface-variant);
1851
+ font-size: 0.78rem;
1852
+ }
1853
+
1854
+ .api-object-doc details {
1855
+ border-top: 1px solid var(--smrt-color-outline-variant);
1856
+ padding-top: 0.65rem;
1857
+ }
1858
+
1859
+ .api-object-doc summary {
1860
+ cursor: pointer;
1861
+ color: var(--smrt-color-on-surface);
1862
+ }
1863
+
1864
+ .api-surface-list > div {
1865
+ display: grid;
1866
+ grid-template-columns: auto minmax(0, 1fr) auto;
1867
+ gap: 0.45rem 0.75rem;
1868
+ align-items: start;
1869
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
1870
+ padding: 0.55rem 0;
1871
+ }
1872
+
1873
+ .api-surface-list code {
1874
+ word-break: break-word;
1875
+ }
1876
+
1877
+ .api-surface-list span {
1878
+ min-width: 0;
1879
+ overflow-wrap: anywhere;
1880
+ }
1881
+
1882
+ .api-surface-list > div > p {
1883
+ grid-column: 2 / 3;
1884
+ margin: 0;
1885
+ color: var(--smrt-color-on-surface-variant);
1886
+ line-height: 1.4;
1887
+ }
1888
+
1889
+ .api-surface-list button {
1890
+ grid-column: 3 / 4;
1891
+ grid-row: 1 / 2;
1892
+ justify-self: end;
1893
+ cursor: pointer;
1894
+ border: 1px solid var(--smrt-color-outline-variant);
1895
+ border-radius: 6px;
1896
+ background: var(--smrt-color-surface-container-low);
1897
+ color: var(--smrt-color-on-surface);
1898
+ padding: 0.32rem 0.55rem;
1899
+ }
1900
+
1901
+ .api-parameter-list {
1902
+ grid-column: 1 / -1;
1903
+ display: grid;
1904
+ gap: 0.35rem;
1905
+ border-left: 2px solid var(--smrt-color-outline-variant);
1906
+ margin-top: 0.15rem;
1907
+ padding: 0.45rem 0 0.2rem 0.65rem;
1908
+ }
1909
+
1910
+ .api-parameter-list > div {
1911
+ display: grid;
1912
+ grid-template-columns: minmax(7rem, auto) minmax(0, 1fr);
1913
+ gap: 0.2rem 0.65rem;
1914
+ align-items: baseline;
1915
+ }
1916
+
1917
+ .api-parameter-list span {
1918
+ color: var(--smrt-color-on-surface-variant);
1919
+ font-size: 0.78rem;
1920
+ }
1921
+
1922
+ .api-parameter-list p {
1923
+ grid-column: 1 / -1;
1924
+ margin: 0;
1925
+ color: var(--smrt-color-on-surface-variant);
1926
+ font-size: 0.78rem;
1927
+ line-height: 1.35;
1928
+ }
1929
+
1930
+ .field-table {
1931
+ display: grid;
1932
+ gap: 0.5rem;
1933
+ margin-top: 0.65rem;
1934
+ }
1935
+
1936
+ .field-table > div {
1937
+ display: grid;
1938
+ grid-template-columns: minmax(8rem, 1fr) minmax(6rem, auto) auto auto;
1939
+ gap: 0.45rem 0.7rem;
1940
+ align-items: start;
1941
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
1942
+ padding-bottom: 0.5rem;
1943
+ }
1944
+
1945
+ .field-table p {
1946
+ grid-column: 1 / -1;
1947
+ margin: 0;
1948
+ color: var(--smrt-color-on-surface-variant);
1949
+ line-height: 1.45;
1950
+ }
1951
+
1952
+ .document-list pre {
1953
+ max-height: 30rem;
1954
+ overflow: auto;
1955
+ border: 1px solid var(--smrt-color-outline-variant);
1956
+ border-radius: 6px;
1957
+ padding: 0.75rem;
1958
+ background: var(--smrt-color-surface-container-lowest);
1959
+ color: var(--smrt-color-on-surface);
1960
+ white-space: pre-wrap;
1961
+ }
1962
+
1963
+ .document-list header,
1964
+ .script-list > div {
1965
+ display: flex;
1966
+ justify-content: space-between;
1967
+ gap: 1rem;
1968
+ align-items: start;
1969
+ }
1970
+
1971
+ .document-list button,
1972
+ .script-list button,
1973
+ .specialist-panel button {
1974
+ cursor: pointer;
1975
+ border: 1px solid var(--smrt-color-primary);
1976
+ border-radius: 6px;
1977
+ background: var(--smrt-color-primary-container);
1978
+ color: var(--smrt-color-on-primary-container);
1979
+ padding: 0.45rem 0.7rem;
1980
+ }
1981
+
1982
+ .script-list code,
1983
+ .dependency-list code,
1984
+ .route-meta code {
1985
+ word-break: break-word;
1986
+ }
1987
+
1988
+ .dependency-list > div {
1989
+ display: grid;
1990
+ grid-template-columns: minmax(0, 1fr) auto;
1991
+ gap: 1rem;
1992
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
1993
+ padding: 0.45rem 0;
1994
+ }
1995
+
1996
+ .definition-list {
1997
+ display: grid;
1998
+ gap: 0.45rem;
1999
+ margin: 0.75rem 0 0;
2000
+ }
2001
+
2002
+ .definition-list div {
2003
+ display: flex;
2004
+ justify-content: space-between;
2005
+ gap: 1rem;
2006
+ border-bottom: 1px solid var(--smrt-color-outline-variant);
2007
+ padding-bottom: 0.35rem;
2008
+ }
2009
+
2010
+ .definition-list dt {
2011
+ color: var(--smrt-color-on-surface-variant);
2012
+ }
2013
+
2014
+ .definition-list dd {
2015
+ margin: 0;
2016
+ font-weight: 700;
2017
+ }
2018
+
2019
+ @media (max-width: 960px) {
2020
+ .workbench-scope-metrics,
2021
+ .workbench-system-summary,
2022
+ .surface-grid,
2023
+ .api-layout {
2024
+ grid-template-columns: 1fr;
2025
+ }
2026
+
2027
+ .package-index-item {
2028
+ grid-template-columns: 1fr;
2029
+ }
2030
+
2031
+ .package-index-meta {
2032
+ justify-content: flex-start;
2033
+ }
2034
+
2035
+ .field-table > div {
2036
+ grid-template-columns: 1fr;
2037
+ }
2038
+
2039
+ .api-surface-list > div {
2040
+ grid-template-columns: 1fr;
2041
+ }
2042
+
2043
+ .api-surface-list > div > p {
2044
+ grid-column: auto;
2045
+ }
2046
+
2047
+ .api-surface-list button {
2048
+ grid-column: auto;
2049
+ grid-row: auto;
2050
+ justify-self: start;
2051
+ }
2052
+
2053
+ .api-parameter-list,
2054
+ .api-parameter-list > div {
2055
+ grid-template-columns: 1fr;
2056
+ }
2057
+ }
2058
+ </style>