@screenbook/ui 0.0.1

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 (101) hide show
  1. package/.astro/content-assets.mjs +1 -0
  2. package/.astro/content-modules.mjs +1 -0
  3. package/.astro/content.d.ts +199 -0
  4. package/.astro/data-store.json +1 -0
  5. package/.astro/settings.json +5 -0
  6. package/.astro/types.d.ts +2 -0
  7. package/.prettierrc +15 -0
  8. package/.screenbook/coverage.json +37 -0
  9. package/.screenbook/screens.json +90 -0
  10. package/LICENSE +21 -0
  11. package/astro.config.mjs +18 -0
  12. package/dist/client/_astro/_baseUniq.BGai4mcc.js +1 -0
  13. package/dist/client/_astro/arc.DUp0dfXj.js +1 -0
  14. package/dist/client/_astro/architectureDiagram-VXUJARFQ.De_Gt-YC.js +36 -0
  15. package/dist/client/_astro/blockDiagram-VD42YOAC.BBt_VNhR.js +122 -0
  16. package/dist/client/_astro/c4Diagram-YG6GDRKO.DfgUlHvt.js +10 -0
  17. package/dist/client/_astro/channel.CNyr52v1.js +1 -0
  18. package/dist/client/_astro/chunk-4BX2VUAB.BL0ar6du.js +1 -0
  19. package/dist/client/_astro/chunk-55IACEB6.CI6SkZkY.js +1 -0
  20. package/dist/client/_astro/chunk-B4BG7PRW.Z25N80K6.js +165 -0
  21. package/dist/client/_astro/chunk-DI55MBZ5.BqjPVmi1.js +220 -0
  22. package/dist/client/_astro/chunk-FMBD7UC4.bZ9DWnFm.js +15 -0
  23. package/dist/client/_astro/chunk-QN33PNHL.BkzuUgWB.js +1 -0
  24. package/dist/client/_astro/chunk-QZHKN3VN.C__d68N_.js +1 -0
  25. package/dist/client/_astro/chunk-TZMSLE5B.BIpu8bMn.js +1 -0
  26. package/dist/client/_astro/classDiagram-2ON5EDUG.CxT3aW-h.js +1 -0
  27. package/dist/client/_astro/classDiagram-v2-WZHVMYZB.CxT3aW-h.js +1 -0
  28. package/dist/client/_astro/clone.U_lSZ6fe.js +1 -0
  29. package/dist/client/_astro/cose-bilkent-S5V4N54A.D48yfMll.js +1 -0
  30. package/dist/client/_astro/coverage.CKIVg4LY.css +1 -0
  31. package/dist/client/_astro/coverage.DDJMzKCq.css +1 -0
  32. package/dist/client/_astro/cytoscape.esm.DtBltrT8.js +331 -0
  33. package/dist/client/_astro/dagre-6UL2VRFP.LKVH7b30.js +4 -0
  34. package/dist/client/_astro/defaultLocale.C4B-KCzX.js +1 -0
  35. package/dist/client/_astro/diagram-PSM6KHXK.AHgUjH56.js +24 -0
  36. package/dist/client/_astro/diagram-QEK2KX5R.DvS33xWZ.js +43 -0
  37. package/dist/client/_astro/diagram-S2PKOQOG.BWisaYrQ.js +24 -0
  38. package/dist/client/_astro/erDiagram-Q2GNP2WA.B7oID6oT.js +60 -0
  39. package/dist/client/_astro/flowDiagram-NV44I4VS.Bb1qJLxr.js +162 -0
  40. package/dist/client/_astro/ganttDiagram-JELNMOA3.3vGHETyo.js +267 -0
  41. package/dist/client/_astro/gitGraphDiagram-NY62KEGX.Co2SKcif.js +65 -0
  42. package/dist/client/_astro/graph.B5fevUwB.js +1 -0
  43. package/dist/client/_astro/graph.astro_astro_type_script_index_0_lang.1HlATQ1g.js +1 -0
  44. package/dist/client/_astro/impact.Cvhl64u1.css +1 -0
  45. package/dist/client/_astro/impact.astro_astro_type_script_index_0_lang.D4cAR9AL.js +6 -0
  46. package/dist/client/_astro/infoDiagram-WHAUD3N6.B6ULtps1.js +2 -0
  47. package/dist/client/_astro/init.Gi6I4Gst.js +1 -0
  48. package/dist/client/_astro/journeyDiagram-XKPGCS4Q.BSOCzWmw.js +139 -0
  49. package/dist/client/_astro/kanban-definition-3W4ZIXB7.D8KKGc1o.js +89 -0
  50. package/dist/client/_astro/katex.XbL3y5x-.js +261 -0
  51. package/dist/client/_astro/layout.8vv24qEg.js +1 -0
  52. package/dist/client/_astro/linear.B6O9ymuK.js +1 -0
  53. package/dist/client/_astro/mermaid.core.CReXU7YN.js +256 -0
  54. package/dist/client/_astro/min.CdGMGVU0.js +1 -0
  55. package/dist/client/_astro/mindmap-definition-VGOIOE7T.G14HgtDw.js +68 -0
  56. package/dist/client/_astro/ordinal.BYWQX77i.js +1 -0
  57. package/dist/client/_astro/pieDiagram-ADFJNKIX.bC2VkyoW.js +30 -0
  58. package/dist/client/_astro/quadrantDiagram-AYHSOK5B.BlLaQQxO.js +7 -0
  59. package/dist/client/_astro/requirementDiagram-UZGBJVZJ.DHRnMofO.js +64 -0
  60. package/dist/client/_astro/sankeyDiagram-TZEHDZUN.BMuaJBmt.js +10 -0
  61. package/dist/client/_astro/sequenceDiagram-WL72ISMW.CnU62wqy.js +145 -0
  62. package/dist/client/_astro/stateDiagram-FKZM4ZOC.CewI55YO.js +1 -0
  63. package/dist/client/_astro/stateDiagram-v2-4FDKWEC3.7xUQqoNr.js +1 -0
  64. package/dist/client/_astro/timeline-definition-IT6M3QCI.D1PLRwss.js +61 -0
  65. package/dist/client/_astro/treemap-KMMF4GRG.D3VNVvXF.js +128 -0
  66. package/dist/client/_astro/xychartDiagram-PRI3JC2R.CQex0-ul.js +7 -0
  67. package/dist/client/logo.svg +5 -0
  68. package/dist/server/_@astrojs-ssr-adapter.mjs +1 -0
  69. package/dist/server/_noop-middleware.mjs +3 -0
  70. package/dist/server/chunks/_@astrojs-ssr-adapter_DgsYudHz.mjs +4385 -0
  71. package/dist/server/chunks/astro/server_m7yT4wCr.mjs +2787 -0
  72. package/dist/server/chunks/astro-designed-error-pages_BvPhMmw0.mjs +364 -0
  73. package/dist/server/chunks/fs-lite_COtHaKzy.mjs +157 -0
  74. package/dist/server/chunks/impactAnalysis_Bz5lMdmy.mjs +188 -0
  75. package/dist/server/chunks/loadScreens_DJf-tycc.mjs +39 -0
  76. package/dist/server/chunks/node_DoTkMCOi.mjs +1673 -0
  77. package/dist/server/chunks/remote_B3W5fv4r.mjs +188 -0
  78. package/dist/server/chunks/sharp_DHNfMLYY.mjs +99 -0
  79. package/dist/server/entry.mjs +47 -0
  80. package/dist/server/manifest_-V1HEnR8.mjs +101 -0
  81. package/dist/server/noop-entrypoint.mjs +3 -0
  82. package/dist/server/pages/_image.astro.mjs +2 -0
  83. package/dist/server/pages/coverage.astro.mjs +65 -0
  84. package/dist/server/pages/graph.astro.mjs +107 -0
  85. package/dist/server/pages/impact.astro.mjs +52 -0
  86. package/dist/server/pages/index.astro.mjs +28 -0
  87. package/dist/server/pages/screen/_id_.astro.mjs +52 -0
  88. package/dist/server/renderers.mjs +3 -0
  89. package/package.json +42 -0
  90. package/public/logo.svg +5 -0
  91. package/src/layouts/Layout.astro +60 -0
  92. package/src/pages/coverage.astro +399 -0
  93. package/src/pages/graph.astro +330 -0
  94. package/src/pages/impact.astro +459 -0
  95. package/src/pages/index.astro +167 -0
  96. package/src/pages/screen/[id].astro +186 -0
  97. package/src/styles/global.css +862 -0
  98. package/src/utils/impactAnalysis.ts +297 -0
  99. package/src/utils/loadCoverage.ts +30 -0
  100. package/src/utils/loadScreens.ts +18 -0
  101. package/tsconfig.json +9 -0
@@ -0,0 +1,459 @@
1
+ ---
2
+ import Layout from "@/layouts/Layout.astro"
3
+ import { loadScreens } from "@/utils/loadScreens"
4
+ import {
5
+ analyzeImpact,
6
+ getAllApis,
7
+ generateImpactMermaid,
8
+ formatImpactMarkdown,
9
+ } from "@/utils/impactAnalysis"
10
+
11
+ const screens = loadScreens()
12
+ const allApis = getAllApis(screens)
13
+
14
+ const url = Astro.url
15
+ const apiQuery = url.searchParams.get("api") || ""
16
+
17
+ let result = null
18
+ let mermaidGraph = ""
19
+ let markdown = ""
20
+
21
+ if (apiQuery && screens.length > 0) {
22
+ result = analyzeImpact(screens, apiQuery, 3)
23
+ mermaidGraph = generateImpactMermaid(screens, result)
24
+ markdown = formatImpactMarkdown(result)
25
+ }
26
+ ---
27
+
28
+ <Layout title="Impact Analysis" currentPage="impact">
29
+ <div class="container">
30
+ <div class="page-header">
31
+ <h1 class="page-title">Impact Analysis</h1>
32
+ <p class="page-description">
33
+ Analyze which screens are affected when an API changes.
34
+ </p>
35
+ </div>
36
+
37
+ {screens.length === 0 ? (
38
+ <div class="empty-state">
39
+ <svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
40
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
41
+ </svg>
42
+ <h2 class="empty-state-title">No screen data</h2>
43
+ <p class="empty-state-description">
44
+ Run the build command to generate screen metadata.
45
+ </p>
46
+ <code class="empty-state-code">
47
+ <span class="prompt">$</span> screenbook build
48
+ </code>
49
+ </div>
50
+ ) : (
51
+ <>
52
+ <div class="impact-search">
53
+ <form method="get" class="search-form">
54
+ <div class="search-wrapper impact-search-wrapper">
55
+ <svg class="search-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
56
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
57
+ </svg>
58
+ <input
59
+ type="text"
60
+ name="api"
61
+ class="search-input"
62
+ placeholder="Enter API name (e.g., InvoiceAPI.getDetail)"
63
+ value={apiQuery}
64
+ list="api-suggestions"
65
+ />
66
+ <datalist id="api-suggestions">
67
+ {allApis.map((api) => (
68
+ <option value={api} />
69
+ ))}
70
+ </datalist>
71
+ <button type="submit" class="search-button">
72
+ Analyze
73
+ </button>
74
+ </div>
75
+ </form>
76
+
77
+ {allApis.length > 0 && !apiQuery && (
78
+ <div class="api-suggestions">
79
+ <p class="suggestions-label">Available APIs:</p>
80
+ <div class="tags">
81
+ {allApis.slice(0, 10).map((api) => (
82
+ <a href={`/impact?api=${encodeURIComponent(api)}`} class="tag">
83
+ {api}
84
+ </a>
85
+ ))}
86
+ {allApis.length > 10 && (
87
+ <span class="tag tag-more">+{allApis.length - 10} more</span>
88
+ )}
89
+ </div>
90
+ </div>
91
+ )}
92
+ </div>
93
+
94
+ {result && (
95
+ <div class="impact-results">
96
+ <div class="impact-summary">
97
+ <div class="stats">
98
+ <div class="stat">
99
+ <div class="stat-value stat-total">{result.totalCount}</div>
100
+ <div class="stat-label">Total Affected</div>
101
+ </div>
102
+ <div class="stat">
103
+ <div class="stat-value stat-direct">{result.direct.length}</div>
104
+ <div class="stat-label">Direct</div>
105
+ </div>
106
+ <div class="stat">
107
+ <div class="stat-value stat-transitive">{result.transitive.length}</div>
108
+ <div class="stat-label">Transitive</div>
109
+ </div>
110
+ </div>
111
+
112
+ <button
113
+ id="copy-markdown"
114
+ class="copy-button"
115
+ data-markdown={markdown}
116
+ >
117
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
118
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
119
+ </svg>
120
+ Copy as Markdown
121
+ </button>
122
+ </div>
123
+
124
+ {result.totalCount === 0 ? (
125
+ <div class="no-impact">
126
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
127
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
128
+ </svg>
129
+ <p>No screens depend on <code>{apiQuery}</code></p>
130
+ </div>
131
+ ) : (
132
+ <>
133
+ <div class="impact-graph">
134
+ <div class="graph-container">
135
+ <pre class="mermaid">{mermaidGraph}</pre>
136
+ </div>
137
+ <div class="graph-legend">
138
+ <div class="graph-legend-item">
139
+ <div class="legend-node legend-direct"></div>
140
+ <span>Direct dependency</span>
141
+ </div>
142
+ <div class="graph-legend-item">
143
+ <div class="legend-node legend-transitive"></div>
144
+ <span>Transitive</span>
145
+ </div>
146
+ <div class="graph-legend-item">
147
+ <div class="legend-node"></div>
148
+ <span>Not affected</span>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div class="impact-details">
154
+ {result.direct.length > 0 && (
155
+ <div class="section">
156
+ <h3 class="section-title">
157
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
158
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
159
+ </svg>
160
+ Direct Dependencies ({result.direct.length})
161
+ </h3>
162
+ <div class="screen-link-list">
163
+ {result.direct.map((screen) => (
164
+ <a href={`/screen/${screen.id}`} class="screen-link impact-screen direct">
165
+ <div class="screen-link-info">
166
+ <div class="screen-link-title">{screen.title}</div>
167
+ <div class="screen-link-id">{screen.route}</div>
168
+ </div>
169
+ {screen.owner && screen.owner.length > 0 && (
170
+ <div class="screen-link-owner">
171
+ {screen.owner.join(", ")}
172
+ </div>
173
+ )}
174
+ </a>
175
+ ))}
176
+ </div>
177
+ </div>
178
+ )}
179
+
180
+ {result.transitive.length > 0 && (
181
+ <div class="section">
182
+ <h3 class="section-title">
183
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
184
+ <path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
185
+ </svg>
186
+ Transitive Dependencies ({result.transitive.length})
187
+ </h3>
188
+ <div class="screen-link-list">
189
+ {result.transitive.map(({ screen, path }) => (
190
+ <a href={`/screen/${screen.id}`} class="screen-link impact-screen transitive">
191
+ <div class="screen-link-info">
192
+ <div class="screen-link-title">{screen.title}</div>
193
+ <div class="screen-link-path">
194
+ {path.join(" → ")}
195
+ </div>
196
+ </div>
197
+ </a>
198
+ ))}
199
+ </div>
200
+ </div>
201
+ )}
202
+ </div>
203
+ </>
204
+ )}
205
+ </div>
206
+ )}
207
+ </>
208
+ )}
209
+ </div>
210
+
211
+ <script>
212
+ import mermaid from "mermaid"
213
+
214
+ mermaid.initialize({
215
+ startOnLoad: true,
216
+ theme: "base",
217
+ themeVariables: {
218
+ darkMode: true,
219
+ background: "transparent",
220
+ fontFamily: "system-ui, sans-serif",
221
+ primaryColor: "#1e293b",
222
+ primaryTextColor: "#e2e8f0",
223
+ primaryBorderColor: "#64748b",
224
+ lineColor: "#64748b",
225
+ secondaryColor: "#1e293b",
226
+ tertiaryColor: "#1e293b",
227
+ nodeTextColor: "#ffffff",
228
+ },
229
+ flowchart: {
230
+ useMaxWidth: true,
231
+ htmlLabels: true,
232
+ curve: "basis",
233
+ padding: 20,
234
+ nodeSpacing: 50,
235
+ rankSpacing: 60,
236
+ },
237
+ })
238
+
239
+ // Copy markdown functionality
240
+ const copyButton = document.getElementById("copy-markdown")
241
+ if (copyButton) {
242
+ copyButton.addEventListener("click", async () => {
243
+ const markdown = copyButton.dataset.markdown
244
+ if (markdown) {
245
+ await navigator.clipboard.writeText(markdown)
246
+ const originalText = copyButton.innerHTML
247
+ copyButton.innerHTML = `
248
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
249
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
250
+ </svg>
251
+ Copied!
252
+ `
253
+ setTimeout(() => {
254
+ copyButton.innerHTML = originalText
255
+ }, 2000)
256
+ }
257
+ })
258
+ }
259
+ </script>
260
+ </Layout>
261
+
262
+ <style>
263
+ .impact-search {
264
+ margin-bottom: 32px;
265
+ }
266
+
267
+ .search-form {
268
+ margin-bottom: 16px;
269
+ }
270
+
271
+ .impact-search-wrapper {
272
+ display: flex;
273
+ gap: 12px;
274
+ max-width: 600px;
275
+ }
276
+
277
+ .impact-search-wrapper .search-input {
278
+ flex: 1;
279
+ }
280
+
281
+ .search-button {
282
+ padding: 10px 20px;
283
+ font-size: var(--text-sm);
284
+ font-weight: 500;
285
+ color: var(--color-bg);
286
+ background: var(--color-accent);
287
+ border: none;
288
+ border-radius: var(--radius-md);
289
+ cursor: pointer;
290
+ transition: background 0.15s ease;
291
+ }
292
+
293
+ .search-button:hover {
294
+ background: var(--color-accent-hover);
295
+ }
296
+
297
+ .api-suggestions {
298
+ margin-top: 16px;
299
+ }
300
+
301
+ .suggestions-label {
302
+ font-size: var(--text-sm);
303
+ color: var(--color-text-muted);
304
+ margin-bottom: 8px;
305
+ }
306
+
307
+ .tag-more {
308
+ background: transparent;
309
+ border-style: dashed;
310
+ cursor: default;
311
+ }
312
+
313
+ .impact-summary {
314
+ display: flex;
315
+ justify-content: space-between;
316
+ align-items: center;
317
+ padding: 24px;
318
+ background: var(--color-surface);
319
+ border: 1px solid var(--color-border);
320
+ border-radius: var(--radius-lg);
321
+ margin-bottom: 24px;
322
+ }
323
+
324
+ .stat-direct {
325
+ color: #f87171;
326
+ }
327
+
328
+ .stat-transitive {
329
+ color: #fb923c;
330
+ }
331
+
332
+ .copy-button {
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 8px;
336
+ padding: 10px 16px;
337
+ font-size: var(--text-sm);
338
+ font-weight: 500;
339
+ color: var(--color-text-secondary);
340
+ background: var(--color-bg-muted);
341
+ border: 1px solid var(--color-border);
342
+ border-radius: var(--radius-md);
343
+ cursor: pointer;
344
+ transition: all 0.15s ease;
345
+ }
346
+
347
+ .copy-button:hover {
348
+ border-color: var(--color-border-hover);
349
+ color: var(--color-text);
350
+ }
351
+
352
+ .copy-button svg {
353
+ width: 16px;
354
+ height: 16px;
355
+ }
356
+
357
+ .no-impact {
358
+ display: flex;
359
+ flex-direction: column;
360
+ align-items: center;
361
+ gap: 12px;
362
+ padding: 48px 24px;
363
+ background: var(--color-surface);
364
+ border: 1px solid var(--color-border);
365
+ border-radius: var(--radius-lg);
366
+ text-align: center;
367
+ }
368
+
369
+ .no-impact svg {
370
+ width: 48px;
371
+ height: 48px;
372
+ color: var(--color-success);
373
+ }
374
+
375
+ .no-impact p {
376
+ font-size: var(--text-lg);
377
+ color: var(--color-text-secondary);
378
+ }
379
+
380
+ .impact-graph {
381
+ margin-bottom: 32px;
382
+ }
383
+
384
+ /* Override Mermaid text colors for better contrast */
385
+ .impact-graph :global(.mermaid .node.direct .nodeLabel),
386
+ .impact-graph :global(.mermaid .node.direct .nodeLabel p),
387
+ .impact-graph :global(.mermaid .node.direct .nodeLabel span),
388
+ .impact-graph :global(.mermaid .node.transitive .nodeLabel),
389
+ .impact-graph :global(.mermaid .node.transitive .nodeLabel p),
390
+ .impact-graph :global(.mermaid .node.transitive .nodeLabel span) {
391
+ fill: #ffffff !important;
392
+ color: #ffffff !important;
393
+ font-weight: 600 !important;
394
+ }
395
+
396
+ .impact-graph :global(.mermaid .node.normal .nodeLabel),
397
+ .impact-graph :global(.mermaid .node.normal .nodeLabel p),
398
+ .impact-graph :global(.mermaid .node.normal .nodeLabel span) {
399
+ fill: #e2e8f0 !important;
400
+ color: #e2e8f0 !important;
401
+ }
402
+
403
+ .impact-graph :global(.mermaid .nodeLabel) {
404
+ font-weight: 500;
405
+ }
406
+
407
+ .impact-graph :global(.mermaid .nodeLabel p) {
408
+ margin: 0;
409
+ }
410
+
411
+ .legend-direct {
412
+ background: #dc2626 !important;
413
+ border-color: #fef2f2 !important;
414
+ }
415
+
416
+ .legend-transitive {
417
+ background: #ea580c !important;
418
+ border-color: #fff7ed !important;
419
+ }
420
+
421
+ .impact-details {
422
+ display: grid;
423
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
424
+ gap: 24px;
425
+ }
426
+
427
+ .section-title {
428
+ display: flex;
429
+ align-items: center;
430
+ gap: 8px;
431
+ }
432
+
433
+ .section-title svg {
434
+ width: 18px;
435
+ height: 18px;
436
+ }
437
+
438
+ .impact-screen.direct {
439
+ border-left: 3px solid #dc2626;
440
+ }
441
+
442
+ .impact-screen.transitive {
443
+ border-left: 3px solid #ea580c;
444
+ }
445
+
446
+ .screen-link-path {
447
+ font-size: var(--text-xs);
448
+ font-family: ui-monospace, monospace;
449
+ color: var(--color-text-muted);
450
+ }
451
+
452
+ .screen-link-owner {
453
+ font-size: var(--text-xs);
454
+ color: var(--color-text-muted);
455
+ padding: 4px 8px;
456
+ background: var(--color-bg);
457
+ border-radius: var(--radius-sm);
458
+ }
459
+ </style>
@@ -0,0 +1,167 @@
1
+ ---
2
+ import Layout from "@/layouts/Layout.astro"
3
+ import { loadScreens } from "@/utils/loadScreens"
4
+
5
+ const screens = loadScreens()
6
+ const allTags = [...new Set(screens.flatMap((s) => s.tags ?? []))]
7
+ ---
8
+
9
+ <Layout title="Screens" currentPage="screens">
10
+ <div class="container">
11
+ <div class="page-header">
12
+ <h1 class="page-title">Screens</h1>
13
+ <p class="page-description">
14
+ Browse all screens in your application. Click on a screen to see details and navigation flows.
15
+ </p>
16
+ </div>
17
+
18
+ {screens.length === 0 ? (
19
+ <div class="empty-state">
20
+ <svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
21
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
22
+ </svg>
23
+ <h2 class="empty-state-title">No screens found</h2>
24
+ <p class="empty-state-description">
25
+ Get started by generating screen metadata from your routes.
26
+ </p>
27
+
28
+ <div class="empty-state-steps">
29
+ <div class="empty-state-step">
30
+ <span class="step-number">1</span>
31
+ <div class="step-content">
32
+ <div class="step-title">Generate screen.meta.ts files</div>
33
+ <code class="step-code">screenbook generate</code>
34
+ </div>
35
+ </div>
36
+ <div class="empty-state-step">
37
+ <span class="step-number">2</span>
38
+ <div class="step-content">
39
+ <div class="step-title">Build the screen catalog</div>
40
+ <code class="step-code">screenbook build</code>
41
+ </div>
42
+ </div>
43
+ <div class="empty-state-step">
44
+ <span class="step-number">3</span>
45
+ <div class="step-content">
46
+ <div class="step-title">Restart the dev server</div>
47
+ <code class="step-code">screenbook dev</code>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="empty-state-features">
53
+ <p class="features-title">What you'll get:</p>
54
+ <ul class="features-list">
55
+ <li>Screen catalog with search & filter</li>
56
+ <li>Navigation graph visualization</li>
57
+ <li>Impact analysis for API changes</li>
58
+ <li>CI lint for documentation coverage</li>
59
+ </ul>
60
+ </div>
61
+ </div>
62
+ ) : (
63
+ <>
64
+ <div class="search-wrapper">
65
+ <svg class="search-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
66
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
67
+ </svg>
68
+ <input
69
+ type="text"
70
+ id="search"
71
+ placeholder="Search screens..."
72
+ class="search-input"
73
+ />
74
+ </div>
75
+
76
+ {allTags.length > 0 && (
77
+ <div class="tags">
78
+ {allTags.map((tag) => (
79
+ <button class="tag" data-tag={tag}>{tag}</button>
80
+ ))}
81
+ </div>
82
+ )}
83
+
84
+ <div class="screen-grid" id="screen-list">
85
+ {screens.map((screen) => (
86
+ <a
87
+ href={`/screen/${screen.id}`}
88
+ class="card-link"
89
+ data-tags={screen.tags?.join(",") ?? ""}
90
+ data-title={screen.title.toLowerCase()}
91
+ data-id={screen.id.toLowerCase()}
92
+ >
93
+ <div class="card">
94
+ <h3 class="card-title">{screen.title}</h3>
95
+ <div class="card-route">{screen.route}</div>
96
+ <div class="card-meta">
97
+ <span class="card-meta-item">
98
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
99
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5l-3.9 19.5m-2.1-19.5l-3.9 19.5" />
100
+ </svg>
101
+ {screen.id}
102
+ </span>
103
+ {screen.owner && screen.owner.length > 0 && (
104
+ <span class="card-meta-item">
105
+ <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
106
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
107
+ </svg>
108
+ {screen.owner.join(", ")}
109
+ </span>
110
+ )}
111
+ </div>
112
+ {screen.tags && screen.tags.length > 0 && (
113
+ <div class="card-tags">
114
+ {screen.tags.map((tag) => (
115
+ <span class="card-tag">{tag}</span>
116
+ ))}
117
+ </div>
118
+ )}
119
+ </div>
120
+ </a>
121
+ ))}
122
+ </div>
123
+ </>
124
+ )}
125
+ </div>
126
+
127
+ <script>
128
+ const searchInput = document.getElementById("search") as HTMLInputElement
129
+ const screenList = document.getElementById("screen-list")
130
+ const tagButtons = document.querySelectorAll(".tag")
131
+
132
+ let activeTag: string | null = null
133
+
134
+ function filterScreens() {
135
+ const query = searchInput?.value.toLowerCase() ?? ""
136
+ const cards = screenList?.querySelectorAll(".card-link") as NodeListOf<HTMLElement>
137
+
138
+ cards?.forEach((card) => {
139
+ const title = card.dataset.title ?? ""
140
+ const id = card.dataset.id ?? ""
141
+ const tags = card.dataset.tags ?? ""
142
+
143
+ const matchesSearch = title.includes(query) || id.includes(query)
144
+ const matchesTag = !activeTag || tags.includes(activeTag)
145
+
146
+ card.style.display = matchesSearch && matchesTag ? "block" : "none"
147
+ })
148
+ }
149
+
150
+ searchInput?.addEventListener("input", filterScreens)
151
+
152
+ tagButtons.forEach((btn) => {
153
+ btn.addEventListener("click", () => {
154
+ const tag = (btn as HTMLElement).dataset.tag
155
+ if (activeTag === tag) {
156
+ activeTag = null
157
+ tagButtons.forEach((b) => b.classList.remove("active"))
158
+ } else {
159
+ tagButtons.forEach((b) => b.classList.remove("active"))
160
+ activeTag = tag ?? null
161
+ btn.classList.add("active")
162
+ }
163
+ filterScreens()
164
+ })
165
+ })
166
+ </script>
167
+ </Layout>