@markbrutx/promptbook-viewer 0.1.0

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/server/annotations.d.ts +30 -0
  8. package/dist/server/annotations.d.ts.map +1 -0
  9. package/dist/server/annotations.js +62 -0
  10. package/dist/server/annotations.js.map +1 -0
  11. package/dist/server/api.d.ts +16 -0
  12. package/dist/server/api.d.ts.map +1 -0
  13. package/dist/server/api.js +134 -0
  14. package/dist/server/api.js.map +1 -0
  15. package/dist/server/book-source.d.ts +20 -0
  16. package/dist/server/book-source.d.ts.map +1 -0
  17. package/dist/server/book-source.js +32 -0
  18. package/dist/server/book-source.js.map +1 -0
  19. package/dist/server/responses.d.ts +12 -0
  20. package/dist/server/responses.d.ts.map +1 -0
  21. package/dist/server/responses.js +106 -0
  22. package/dist/server/responses.js.map +1 -0
  23. package/dist/server/segments.d.ts +12 -0
  24. package/dist/server/segments.d.ts.map +1 -0
  25. package/dist/server/segments.js +24 -0
  26. package/dist/server/segments.js.map +1 -0
  27. package/dist/server/server.d.ts +24 -0
  28. package/dist/server/server.d.ts.map +1 -0
  29. package/dist/server/server.js +71 -0
  30. package/dist/server/server.js.map +1 -0
  31. package/dist/server/static.d.ts +8 -0
  32. package/dist/server/static.d.ts.map +1 -0
  33. package/dist/server/static.js +55 -0
  34. package/dist/server/static.js.map +1 -0
  35. package/dist/server/used-in.d.ts +9 -0
  36. package/dist/server/used-in.d.ts.map +1 -0
  37. package/dist/server/used-in.js +19 -0
  38. package/dist/server/used-in.js.map +1 -0
  39. package/dist/shared/types.d.ts +113 -0
  40. package/dist/shared/types.d.ts.map +1 -0
  41. package/dist/shared/types.js +2 -0
  42. package/dist/shared/types.js.map +1 -0
  43. package/dist/web/assets/index-B2Wxtb-f.css +1 -0
  44. package/dist/web/assets/index-C8f_6lr_.js +51 -0
  45. package/dist/web/index.html +13 -0
  46. package/package.json +47 -0
  47. package/src/index.ts +19 -0
  48. package/src/server/annotations.ts +96 -0
  49. package/src/server/api.ts +164 -0
  50. package/src/server/book-source.ts +54 -0
  51. package/src/server/responses.ts +127 -0
  52. package/src/server/segments.ts +26 -0
  53. package/src/server/server.ts +96 -0
  54. package/src/server/static.ts +60 -0
  55. package/src/server/used-in.ts +23 -0
  56. package/src/shared/types.ts +137 -0
  57. package/src/web/App.tsx +307 -0
  58. package/src/web/annotations.ts +58 -0
  59. package/src/web/api.ts +44 -0
  60. package/src/web/colors.ts +21 -0
  61. package/src/web/components/Addons.tsx +109 -0
  62. package/src/web/components/Canvas.tsx +180 -0
  63. package/src/web/components/CodePromptView.tsx +87 -0
  64. package/src/web/components/Controls.tsx +71 -0
  65. package/src/web/components/Diff.tsx +30 -0
  66. package/src/web/components/FragmentView.tsx +54 -0
  67. package/src/web/components/Sidebar.tsx +178 -0
  68. package/src/web/diff.ts +51 -0
  69. package/src/web/env.d.ts +3 -0
  70. package/src/web/format.ts +8 -0
  71. package/src/web/index.html +12 -0
  72. package/src/web/main.tsx +15 -0
  73. package/src/web/selection.ts +5 -0
  74. package/src/web/styles.css +484 -0
  75. package/src/web/tree.ts +134 -0
  76. package/src/web/types.ts +17 -0
@@ -0,0 +1,484 @@
1
+ :root {
2
+ --bg: #fff;
3
+ --panel: #f6f8fa;
4
+ --border: #e1e4e8;
5
+ --text: #1f2328;
6
+ --muted: #6e7781;
7
+ --accent: #4f46e5;
8
+ --warn: #b35900;
9
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
10
+ font-size: 14px;
11
+ color: var(--text);
12
+ }
13
+
14
+ * {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ background: var(--bg);
21
+ }
22
+
23
+ .layout {
24
+ display: grid;
25
+ grid-template-columns: 260px minmax(0, 1fr) 380px;
26
+ height: 100vh;
27
+ }
28
+
29
+ /* Sidebar */
30
+ .sidebar {
31
+ border-right: 1px solid var(--border);
32
+ overflow-y: auto;
33
+ padding: 12px;
34
+ background: var(--panel);
35
+ }
36
+
37
+ .sidebar-title {
38
+ font-size: 11px;
39
+ text-transform: uppercase;
40
+ letter-spacing: 0.08em;
41
+ color: var(--muted);
42
+ margin: 16px 0 6px;
43
+ }
44
+
45
+ .tree {
46
+ list-style: none;
47
+ margin: 0;
48
+ padding-left: 12px;
49
+ }
50
+
51
+ .tree-folder,
52
+ .tree-composition {
53
+ cursor: pointer;
54
+ font-weight: 600;
55
+ padding: 2px 0;
56
+ }
57
+
58
+ .tree-item,
59
+ .link {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ gap: 6px;
63
+ width: 100%;
64
+ text-align: left;
65
+ background: none;
66
+ border: none;
67
+ padding: 3px 6px;
68
+ border-radius: 5px;
69
+ color: var(--text);
70
+ cursor: pointer;
71
+ font: inherit;
72
+ }
73
+
74
+ .tree-item:hover {
75
+ background: #eaeef2;
76
+ }
77
+
78
+ .tree-item.active {
79
+ background: var(--accent);
80
+ color: #fff;
81
+ }
82
+
83
+ .link {
84
+ width: auto;
85
+ color: var(--accent);
86
+ padding: 0;
87
+ text-decoration: underline;
88
+ }
89
+
90
+ .swatch {
91
+ width: 10px;
92
+ height: 10px;
93
+ border-radius: 2px;
94
+ flex: none;
95
+ }
96
+
97
+ /* Canvas */
98
+ .canvas {
99
+ overflow-y: auto;
100
+ padding: 20px 24px;
101
+ }
102
+
103
+ .canvas-head {
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: flex-start;
107
+ gap: 12px;
108
+ margin-bottom: 12px;
109
+ }
110
+
111
+ .canvas-title {
112
+ font-size: 18px;
113
+ margin: 0;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 8px;
117
+ }
118
+
119
+ .legend {
120
+ list-style: none;
121
+ display: flex;
122
+ flex-wrap: wrap;
123
+ gap: 10px;
124
+ padding: 0;
125
+ margin: 0 0 14px;
126
+ font-size: 12px;
127
+ color: var(--muted);
128
+ }
129
+
130
+ .legend li {
131
+ display: inline-flex;
132
+ align-items: center;
133
+ gap: 5px;
134
+ }
135
+
136
+ .prompt {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 14px;
140
+ }
141
+
142
+ .segment {
143
+ position: relative;
144
+ margin: 0;
145
+ padding: 16px 12px 12px;
146
+ border-radius: 6px;
147
+ border: 1px solid var(--border);
148
+ white-space: pre-wrap;
149
+ word-break: break-word;
150
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
151
+ font-size: 13px;
152
+ line-height: 1.5;
153
+ }
154
+
155
+ .segment-tag {
156
+ position: absolute;
157
+ top: 0;
158
+ right: 0;
159
+ font-size: 10px;
160
+ padding: 1px 6px;
161
+ background: rgba(0, 0, 0, 0.06);
162
+ border-bottom-left-radius: 6px;
163
+ font-family: system-ui, sans-serif;
164
+ color: var(--muted);
165
+ }
166
+
167
+ /* Right rail */
168
+ .rail {
169
+ border-left: 1px solid var(--border);
170
+ overflow-y: auto;
171
+ padding: 16px;
172
+ background: var(--panel);
173
+ }
174
+
175
+ .rail section,
176
+ .addons section {
177
+ margin-bottom: 18px;
178
+ }
179
+
180
+ .rail h3,
181
+ .addons h3 {
182
+ font-size: 11px;
183
+ text-transform: uppercase;
184
+ letter-spacing: 0.08em;
185
+ color: var(--muted);
186
+ margin: 0 0 8px;
187
+ }
188
+
189
+ .rail h4 {
190
+ margin: 12px 0 4px;
191
+ font-size: 12px;
192
+ }
193
+
194
+ select,
195
+ input {
196
+ width: 100%;
197
+ padding: 4px 6px;
198
+ border: 1px solid var(--border);
199
+ border-radius: 5px;
200
+ font: inherit;
201
+ background: #fff;
202
+ }
203
+
204
+ /* Controls */
205
+ .control-row {
206
+ display: grid;
207
+ grid-template-columns: 90px 1fr;
208
+ align-items: center;
209
+ gap: 8px;
210
+ margin-bottom: 6px;
211
+ }
212
+
213
+ .control-key {
214
+ font-family: ui-monospace, monospace;
215
+ font-size: 12px;
216
+ color: var(--muted);
217
+ }
218
+
219
+ .control-add {
220
+ display: grid;
221
+ grid-template-columns: 1fr auto;
222
+ gap: 6px;
223
+ margin-top: 6px;
224
+ }
225
+
226
+ .control-add button {
227
+ border: 1px solid var(--border);
228
+ border-radius: 5px;
229
+ background: #fff;
230
+ cursor: pointer;
231
+ padding: 0 10px;
232
+ }
233
+
234
+ /* Explain + lint */
235
+ .rules,
236
+ .findings,
237
+ .warnings,
238
+ .used-list {
239
+ list-style: none;
240
+ margin: 0;
241
+ padding: 0;
242
+ }
243
+
244
+ .rules li {
245
+ padding: 4px 0;
246
+ border-bottom: 1px solid var(--border);
247
+ font-size: 12px;
248
+ }
249
+
250
+ .rules li.skipped {
251
+ color: var(--muted);
252
+ }
253
+
254
+ .rule-mark {
255
+ margin-right: 6px;
256
+ }
257
+
258
+ .rule-action {
259
+ font-weight: 600;
260
+ margin-right: 6px;
261
+ }
262
+
263
+ .rule-effect {
264
+ font-family: ui-monospace, monospace;
265
+ margin-top: 2px;
266
+ }
267
+
268
+ .rule-reason {
269
+ color: var(--muted);
270
+ margin-top: 2px;
271
+ }
272
+
273
+ .order {
274
+ font-family: ui-monospace, monospace;
275
+ font-size: 12px;
276
+ word-break: break-word;
277
+ }
278
+
279
+ .finding {
280
+ display: flex;
281
+ flex-wrap: wrap;
282
+ gap: 6px;
283
+ align-items: baseline;
284
+ padding: 5px 0;
285
+ border-bottom: 1px solid var(--border);
286
+ font-size: 12px;
287
+ }
288
+
289
+ .finding-sev {
290
+ text-transform: uppercase;
291
+ font-size: 10px;
292
+ font-weight: 700;
293
+ padding: 1px 5px;
294
+ border-radius: 4px;
295
+ background: #eaeef2;
296
+ }
297
+
298
+ .finding.error .finding-sev {
299
+ background: #ffd7d5;
300
+ color: #a40e26;
301
+ }
302
+
303
+ .finding.warning .finding-sev {
304
+ background: #fff1c2;
305
+ color: var(--warn);
306
+ }
307
+
308
+ .finding-rule {
309
+ font-family: ui-monospace, monospace;
310
+ color: var(--muted);
311
+ }
312
+
313
+ /* Diff */
314
+ .diff-body {
315
+ margin: 8px 0 0;
316
+ font-family: ui-monospace, monospace;
317
+ font-size: 12px;
318
+ border: 1px solid var(--border);
319
+ border-radius: 6px;
320
+ overflow-x: auto;
321
+ background: #fff;
322
+ }
323
+
324
+ .diff-row {
325
+ padding: 0 8px;
326
+ white-space: pre-wrap;
327
+ }
328
+
329
+ .diff-row.add {
330
+ background: #e6ffec;
331
+ }
332
+
333
+ .diff-row.remove {
334
+ background: #ffebe9;
335
+ }
336
+
337
+ .diff-mark {
338
+ display: inline-block;
339
+ width: 12px;
340
+ color: var(--muted);
341
+ }
342
+
343
+ /* Annotations */
344
+ .annot-mark {
345
+ background: #fff3b0;
346
+ border-bottom: 2px solid var(--warn);
347
+ border-radius: 2px;
348
+ padding: 0 1px;
349
+ }
350
+
351
+ .annot-popover {
352
+ position: fixed;
353
+ z-index: 20;
354
+ width: 280px;
355
+ padding: 10px;
356
+ background: #fff;
357
+ border: 1px solid var(--border);
358
+ border-radius: 8px;
359
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
360
+ }
361
+
362
+ .annot-popover textarea {
363
+ width: 100%;
364
+ min-height: 64px;
365
+ resize: vertical;
366
+ margin: 6px 0;
367
+ }
368
+
369
+ .annot-actions {
370
+ display: flex;
371
+ justify-content: flex-end;
372
+ gap: 10px;
373
+ align-items: center;
374
+ }
375
+
376
+ .annot-actions button[type="button"]:last-child {
377
+ border: 1px solid var(--border);
378
+ border-radius: 5px;
379
+ background: var(--accent);
380
+ color: #fff;
381
+ padding: 4px 12px;
382
+ cursor: pointer;
383
+ }
384
+
385
+ .annot-actions button[disabled] {
386
+ opacity: 0.5;
387
+ cursor: default;
388
+ }
389
+
390
+ .annot-quote {
391
+ margin: 0;
392
+ font-style: italic;
393
+ color: var(--muted);
394
+ font-size: 12px;
395
+ word-break: break-word;
396
+ }
397
+
398
+ .annot-list {
399
+ list-style: none;
400
+ margin: 0;
401
+ padding: 0;
402
+ }
403
+
404
+ .annot-list li {
405
+ padding: 8px 0;
406
+ border-bottom: 1px solid var(--border);
407
+ }
408
+
409
+ .annot-comment {
410
+ margin: 4px 0;
411
+ font-size: 13px;
412
+ word-break: break-word;
413
+ }
414
+
415
+ .annot-meta {
416
+ display: flex;
417
+ justify-content: space-between;
418
+ align-items: center;
419
+ gap: 8px;
420
+ font-size: 12px;
421
+ }
422
+
423
+ /* Shared */
424
+ .badge {
425
+ font-size: 12px;
426
+ padding: 2px 8px;
427
+ border-radius: 999px;
428
+ background: #eaeef2;
429
+ white-space: nowrap;
430
+ }
431
+
432
+ .badge-code {
433
+ font-size: 10px;
434
+ text-transform: uppercase;
435
+ letter-spacing: 0.06em;
436
+ background: #2b2b40;
437
+ color: #fff;
438
+ }
439
+
440
+ /* Code-prompt canvas */
441
+ .code-tabs {
442
+ display: flex;
443
+ flex-wrap: wrap;
444
+ gap: 6px;
445
+ margin-bottom: 12px;
446
+ }
447
+
448
+ .code-tab {
449
+ font: inherit;
450
+ font-size: 12px;
451
+ padding: 3px 10px;
452
+ border-radius: 999px;
453
+ border: 1px solid var(--border);
454
+ background: var(--panel);
455
+ color: var(--text);
456
+ cursor: pointer;
457
+ }
458
+
459
+ .code-tab.active {
460
+ background: var(--accent);
461
+ border-color: var(--accent);
462
+ }
463
+
464
+ .code-diff {
465
+ margin-top: 18px;
466
+ }
467
+
468
+ .tokens {
469
+ font-size: 20px;
470
+ font-weight: 600;
471
+ margin: 0;
472
+ }
473
+
474
+ .muted {
475
+ color: var(--muted);
476
+ }
477
+
478
+ .warn {
479
+ color: var(--warn);
480
+ }
481
+
482
+ .used-list li {
483
+ padding: 3px 0;
484
+ }
@@ -0,0 +1,134 @@
1
+ import type { CodePromptSummary, CompositionSummary, FragmentSummary, VariantSummary } from "./types.js";
2
+
3
+ /** A selectable variant (composition assembled under a named context). */
4
+ interface VariantNode {
5
+ type: "variant";
6
+ composition: string;
7
+ variant: VariantSummary;
8
+ }
9
+
10
+ /** A composition leaf in the sidebar; its variants render when expanded. */
11
+ interface CompositionNode {
12
+ type: "composition";
13
+ /** Full composition name (path-like, e.g. `assistant/terse`). */
14
+ name: string;
15
+ /** Last path segment, shown as the leaf label. */
16
+ label: string;
17
+ variants: VariantNode[];
18
+ }
19
+
20
+ /** A code-prompt leaf in the sidebar; its samples render when expanded. */
21
+ interface CodePromptNode {
22
+ type: "code";
23
+ /** Full code-prompt name (path-like). */
24
+ name: string;
25
+ /** Last path segment, shown as the leaf label. */
26
+ label: string;
27
+ /** Sample labels, in declaration order. */
28
+ samples: string[];
29
+ }
30
+
31
+ /** A folder grouping path-like prompt names (Storybook-style headings). */
32
+ export interface GroupNode {
33
+ type: "group";
34
+ label: string;
35
+ children: CompositionTreeNode[];
36
+ }
37
+
38
+ export type CompositionTreeNode = GroupNode | CompositionNode | CodePromptNode;
39
+
40
+ /** The implicit, context-free variant every composition always has. */
41
+ export const DEFAULT_VARIANT: VariantSummary = { name: "Default", context: {} };
42
+
43
+ function variantNodes(composition: CompositionSummary): VariantNode[] {
44
+ const variants = [DEFAULT_VARIANT, ...composition.variants];
45
+ return variants.map((variant) => ({ type: "variant", composition: composition.name, variant }));
46
+ }
47
+
48
+ /** Walk/create folder groups for a path-like name, returning the leaf's group + label. */
49
+ function leafSlot(root: GroupNode, name: string): { group: GroupNode; label: string } {
50
+ const segments = name.split("/").filter((s) => s !== "");
51
+ let group = root;
52
+ for (const segment of segments.slice(0, -1)) {
53
+ let next = group.children.find(
54
+ (child): child is GroupNode => child.type === "group" && child.label === segment,
55
+ );
56
+ if (next === undefined) {
57
+ next = { type: "group", label: segment, children: [] };
58
+ group.children.push(next);
59
+ }
60
+ group = next;
61
+ }
62
+ return { group, label: segments[segments.length - 1] ?? name };
63
+ }
64
+
65
+ /**
66
+ * Build the hierarchical prompt tree: compositions and code-prompts side by
67
+ * side in one menu. A `/` in a name nests it under folder groups (like
68
+ * Storybook titles); the final segment is the leaf. Groups sort before leaves;
69
+ * leaves (composition or code) sort alphabetically at each level.
70
+ */
71
+ export function buildCompositionTree(
72
+ compositions: CompositionSummary[],
73
+ codePrompts: CodePromptSummary[] = [],
74
+ ): CompositionTreeNode[] {
75
+ const root: GroupNode = { type: "group", label: "", children: [] };
76
+
77
+ for (const composition of compositions) {
78
+ const { group, label } = leafSlot(root, composition.name);
79
+ group.children.push({
80
+ type: "composition",
81
+ name: composition.name,
82
+ label,
83
+ variants: variantNodes(composition),
84
+ });
85
+ }
86
+ for (const codePrompt of codePrompts) {
87
+ const { group, label } = leafSlot(root, codePrompt.name);
88
+ group.children.push({
89
+ type: "code",
90
+ name: codePrompt.name,
91
+ label,
92
+ samples: codePrompt.samples.map((s) => s.label),
93
+ });
94
+ }
95
+
96
+ sortGroup(root);
97
+ return root.children;
98
+ }
99
+
100
+ function sortGroup(group: GroupNode): void {
101
+ group.children.sort((a, b) => {
102
+ const aGroup = a.type === "group";
103
+ const bGroup = b.type === "group";
104
+ if (aGroup !== bGroup) {
105
+ return aGroup ? -1 : 1;
106
+ }
107
+ return a.label.localeCompare(b.label);
108
+ });
109
+ for (const child of group.children) {
110
+ if (child.type === "group") {
111
+ sortGroup(child);
112
+ }
113
+ }
114
+ }
115
+
116
+ /** A group of fragments sharing a `kind` (or "other" when unset). */
117
+ export interface FragmentGroup {
118
+ kind: string;
119
+ fragments: FragmentSummary[];
120
+ }
121
+
122
+ /** Group fragments by `kind` for the sidebar's Fragments section. */
123
+ export function buildFragmentGroups(fragments: FragmentSummary[]): FragmentGroup[] {
124
+ const byKind = new Map<string, FragmentSummary[]>();
125
+ for (const fragment of fragments) {
126
+ const kind = fragment.kind ?? "other";
127
+ const list = byKind.get(kind) ?? [];
128
+ list.push(fragment);
129
+ byKind.set(kind, list);
130
+ }
131
+ return [...byKind.entries()]
132
+ .sort((a, b) => a[0].localeCompare(b[0]))
133
+ .map(([kind, list]) => ({ kind, fragments: list }));
134
+ }
@@ -0,0 +1,17 @@
1
+ // Single import surface for the web app: core scalar/trace types plus the
2
+ // wire DTOs. Everything here is type-only and erased from the bundle.
3
+ export type { Context, ContextValue, Trace, When } from "@markbrutx/promptbook-core";
4
+ export type {
5
+ AnnotateRequest,
6
+ Annotation,
7
+ AnnotationsResponse,
8
+ BookResponse,
9
+ CodePromptSummary,
10
+ CompositionSummary,
11
+ FragmentSummary,
12
+ LintResponse,
13
+ ResolveResponse,
14
+ Segment,
15
+ UsedInResponse,
16
+ VariantSummary,
17
+ } from "../shared/types.js";