static-fire 0.0.0-stage → 0.44.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -2
  3. package/bin/static-fire.js +46 -0
  4. package/package.json +29 -4
  5. package/pricing.yaml +20 -0
  6. package/src/adapters/index.ts +153 -0
  7. package/src/api.ts +218 -0
  8. package/src/cli.ts +297 -0
  9. package/src/core/apkit-install.ts +47 -0
  10. package/src/core/authored.ts +47 -0
  11. package/src/core/batch-state.ts +25 -0
  12. package/src/core/batch.ts +435 -0
  13. package/src/core/batches.ts +221 -0
  14. package/src/core/checks.ts +63 -0
  15. package/src/core/commands.ts +42 -0
  16. package/src/core/compare.ts +203 -0
  17. package/src/core/config.ts +25 -0
  18. package/src/core/copying.ts +18 -0
  19. package/src/core/db.ts +572 -0
  20. package/src/core/derived.ts +74 -0
  21. package/src/core/engine.ts +85 -0
  22. package/src/core/facets.ts +128 -0
  23. package/src/core/grade.ts +197 -0
  24. package/src/core/grading-versions.ts +281 -0
  25. package/src/core/identity.ts +263 -0
  26. package/src/core/init.ts +231 -0
  27. package/src/core/judge-schema.ts +37 -0
  28. package/src/core/judge.ts +166 -0
  29. package/src/core/label.ts +43 -0
  30. package/src/core/lock.ts +40 -0
  31. package/src/core/migrate.ts +288 -0
  32. package/src/core/new-task.ts +85 -0
  33. package/src/core/outside-tools.ts +129 -0
  34. package/src/core/profiles.ts +42 -0
  35. package/src/core/project.ts +54 -0
  36. package/src/core/regrade.ts +233 -0
  37. package/src/core/score.ts +148 -0
  38. package/src/core/settings.ts +44 -0
  39. package/src/core/task-file.ts +95 -0
  40. package/src/core/trial-detail.ts +73 -0
  41. package/src/core/types.ts +195 -0
  42. package/src/core/view-query.ts +85 -0
  43. package/src/core/view.ts +498 -0
  44. package/src/guide.md +547 -0
  45. package/src/server.ts +24 -0
  46. package/web/api.ts +6 -0
  47. package/web/app.tsx +1019 -0
  48. package/web/brand/offset-tokens.css +126 -0
  49. package/web/charts.tsx +190 -0
  50. package/web/compare.tsx +317 -0
  51. package/web/encoding.tsx +55 -0
  52. package/web/filter-menu.ts +164 -0
  53. package/web/index.html +17 -0
  54. package/web/pages.ts +16 -0
  55. package/web/run-status.ts +51 -0
  56. package/web/styles.css +333 -0
  57. package/web/suite-notes.ts +20 -0
  58. package/web/wording.ts +38 -0
package/web/styles.css ADDED
@@ -0,0 +1,333 @@
1
+ /* Offset (The AI Launchpad visual system). Every colour, font, radius and shadow comes from
2
+ ./brand/offset-tokens.css; nothing is defined here that is not a token or a token role.
3
+ Light scheme only: Offset's dark scheme is for packaging. */
4
+ @import url("./brand/offset-tokens.css");
5
+
6
+ * { box-sizing: border-box; }
7
+ html, body { margin: 0; background: var(--ground); color: var(--fg); font-family: var(--font-text); font-size: 15px; line-height: var(--leading-text); -webkit-font-smoothing: antialiased; }
8
+ button { font: inherit; color: inherit; cursor: pointer; }
9
+ code { font-family: var(--font-mono); font-size: 0.9em; }
10
+ .app { max-width: 1240px; margin: 0 auto; padding: var(--spacing-24) var(--spacing-16) var(--spacing-64); }
11
+ .muted { color: var(--fg-3); }
12
+ .error { color: var(--fg); background: var(--color-blush); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-xs); padding: var(--spacing-8) var(--spacing-12); font-size: var(--text-sm); }
13
+ .kicker { font-family: var(--font-kicker); font-weight: var(--weight-kicker); font-size: var(--text-sm); letter-spacing: var(--tracking-kicker); text-transform: uppercase; color: var(--fg-3); display: flex; align-items: center; gap: var(--spacing-8); }
14
+
15
+ /* top bar */
16
+ .topbar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--spacing-12); margin-bottom: var(--spacing-32); }
17
+ .brand { font-family: var(--font-display); font-weight: var(--weight-display); letter-spacing: var(--tracking-display); font-size: var(--text-xl); display: flex; align-items: center; gap: var(--spacing-8); }
18
+ .brand::before { content: ""; width: 22px; height: 22px; border-radius: var(--radius-xs); background: var(--accent); border: var(--outline-width) solid var(--outline); }
19
+ .spacer { flex: 1; }
20
+
21
+ /* page navigation (#24): one pill per page, the current one filled */
22
+ .nav { display: flex; gap: var(--spacing-4, 4px); }
23
+ .nav a { font-weight: 800; color: var(--fg-3); text-decoration: none; border: var(--outline-width) solid transparent; border-radius: var(--radius-pill); padding: 4px var(--spacing-16); }
24
+ .nav a:hover { color: var(--fg); }
25
+ .nav a.on { color: var(--fg); background: var(--accent); border-color: var(--outline); }
26
+ .page-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--spacing-16); }
27
+ .page-head .sub { color: var(--fg-3); margin: 0; max-width: 72ch; }
28
+ select, .btn, input[type="number"] { border: var(--outline-width) solid var(--outline); background: var(--raised); border-radius: var(--radius-sm); padding: var(--spacing-8) var(--spacing-12); font: inherit; color: var(--fg); max-width: 100%; }
29
+ .btn { font-weight: 700; box-shadow: var(--shadow-sm); transition: transform .08s, box-shadow .08s; }
30
+ .btn:hover:not(:disabled) { transform: translateY(var(--press-y)); box-shadow: var(--shadow-sm-pressed); }
31
+ .btn-primary { background: var(--accent); }
32
+ .btn-quiet { box-shadow: none; background: var(--surface); margin-top: var(--spacing-16); font-size: var(--text-sm); }
33
+ .btn-quiet:hover:not(:disabled) { transform: none; box-shadow: none; background: var(--color-cream); }
34
+ .btn:disabled { opacity: 0.5; cursor: default; }
35
+
36
+ /* hero */
37
+ .hero { margin-bottom: var(--spacing-24); }
38
+ .hero h1 { font-family: var(--font-display); font-weight: var(--weight-display); letter-spacing: var(--tracking-display); line-height: var(--leading-display); font-size: clamp(2rem, 5vw, var(--text-5xl)); margin: var(--spacing-8) 0 var(--spacing-12); }
39
+ .meta { display: flex; flex-wrap: wrap; gap: var(--spacing-8) var(--spacing-24); color: var(--fg-3); font-size: var(--text-sm); margin-bottom: var(--spacing-16); }
40
+ .meta b { color: var(--fg); }
41
+ .progress { display: flex; align-items: center; gap: var(--spacing-12); }
42
+ .progress-track { flex: 1; height: 12px; border-radius: var(--radius-pill); background: var(--raised); border: var(--outline-width) solid var(--outline); overflow: hidden; }
43
+ .progress-fill { height: 100%; background: var(--accent); transition: width .4s ease; }
44
+ .pill { font-size: var(--text-sm); font-weight: 700; padding: 2px var(--spacing-12); border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); background: var(--raised); white-space: nowrap; }
45
+ .pill.live { background: var(--color-mint); }
46
+ .pill.live::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--outline); margin-right: 6px; animation: pulse 1.4s infinite; }
47
+ @keyframes pulse { 50% { opacity: 0.25; } }
48
+
49
+ /* view chips */
50
+ .chips { display: flex; flex-wrap: wrap; gap: var(--spacing-8); margin-bottom: var(--spacing-16); }
51
+ .chip { border: var(--outline-width) solid var(--outline); background: var(--raised); border-radius: var(--radius-pill); padding: 4px var(--spacing-16); font-weight: 700; font-size: var(--text-sm); }
52
+ .chip.on { background: var(--accent); box-shadow: var(--shadow-sm-pressed); }
53
+
54
+ /* filter bar */
55
+ .filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--spacing-8) var(--spacing-12); margin-bottom: var(--spacing-24); }
56
+ .filters label { display: flex; flex-direction: column; gap: 4px; font-size: var(--text-sm); font-weight: 700; color: var(--fg-3); min-width: 0; }
57
+ .filters select, .filters input[type="date"] { border: var(--outline-width) solid var(--outline); background: var(--raised); border-radius: var(--radius-sm); padding: 6px var(--spacing-8); font: inherit; font-weight: 500; color: var(--fg); max-width: 260px; }
58
+ .filters .btn-quiet { margin-top: 0; }
59
+
60
+ /* filter bar: one Filter button with a searchable menu, and a tag per active filter (#26) */
61
+ .filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-8); margin-bottom: var(--spacing-24); }
62
+ .filter-bar .pop { position: relative; }
63
+ .filter-btn { display: inline-flex; align-items: center; gap: 6px; padding: 4px var(--spacing-16); border-radius: var(--radius-pill); font-size: var(--text-sm); }
64
+ .filter-btn.on { background: var(--accent); }
65
+ .filter-bar .menu { position: absolute; z-index: 20; top: calc(100% + 8px); left: 0; width: min(340px, calc(100vw - 32px)); background: var(--raised); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); padding: var(--spacing-8); }
66
+ .filter-bar .menu input[type="search"] { width: 100%; box-sizing: border-box; font: inherit; font-size: var(--text-sm); border: 1.5px solid var(--outline); border-radius: var(--radius-xs); padding: 6px var(--spacing-8); margin-bottom: 6px; background: var(--ground); color: var(--fg); }
67
+ .filter-bar .menu-list { max-height: 320px; overflow: auto; }
68
+ .filter-bar .sec, .filter-bar .opt { display: flex; width: 100%; justify-content: space-between; align-items: center; gap: var(--spacing-8); border: 0; background: none; font: inherit; color: var(--fg); text-align: left; cursor: pointer; border-radius: 6px; }
69
+ .filter-bar .sec { padding: 6px; font-weight: 700; font-size: var(--text-sm); }
70
+ .filter-bar .sec i { display: inline-block; width: 14px; font-style: normal; }
71
+ .filter-bar .sec span:last-child { color: var(--fg-3); font-weight: 400; font-size: 12px; text-align: right; }
72
+ .filter-bar .sec span.sec-on { color: var(--fg); font-weight: 700; }
73
+ .filter-bar .sec:hover, .filter-bar .opt:hover, .filter-bar .sec:focus-visible, .filter-bar .opt:focus-visible { background: var(--surface); }
74
+ .filter-bar .opt { padding: 4px 6px; font-size: var(--text-sm); }
75
+ .filter-bar .opt.on { font-weight: 700; }
76
+ .filter-bar .opt em, .filter-bar .menu-flat-dates em { font-style: normal; color: var(--fg-3); }
77
+ .filter-bar .opt small { display: block; color: var(--fg-3); font-weight: 400; font-size: 12px; }
78
+ .filter-bar .opt-count { color: var(--fg-3); font-family: var(--font-mono); font-size: 12px; }
79
+ .filter-bar .in { padding-left: 14px; }
80
+ .filter-bar .grp { font-weight: 800; font-size: var(--text-xs); letter-spacing: var(--tracking-kicker); text-transform: uppercase; color: var(--fg-3); padding: 6px 6px 2px; }
81
+ .filter-bar .menu-dates { display: flex; flex-wrap: wrap; gap: var(--spacing-8); padding: 4px 6px; }
82
+ .filter-bar .menu-dates label { display: flex; flex-direction: column; gap: 2px; font-size: 12px; font-weight: 700; color: var(--fg-3); }
83
+ .filter-bar .menu-dates input { border: 1.5px solid var(--outline); background: var(--raised); border-radius: var(--radius-xs); padding: 4px 6px; font: inherit; font-size: var(--text-sm); color: var(--fg); }
84
+ .filter-bar .menu-flat-dates { padding: 4px 6px; font-size: var(--text-sm); }
85
+ .filter-bar .menu-none { margin: 0; padding: 6px; font-size: var(--text-sm); }
86
+ .tag { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); background: var(--surface); border: 1.5px solid var(--outline); border-radius: var(--radius-xs); padding: 2px 4px 2px 8px; }
87
+ .tag em { font-style: normal; color: var(--fg-3); }
88
+ .tag.grading { background: var(--color-lavender); }
89
+ .tag button { font: inherit; border: 0; background: none; cursor: pointer; padding: 0 4px; font-weight: 800; color: var(--fg); }
90
+ .clear-all { padding: 3px var(--spacing-12); border-radius: var(--radius-pill); border-style: dashed; box-shadow: none; font-size: var(--text-sm); }
91
+ .clear-all:hover:not(:disabled) { transform: none; box-shadow: none; background: var(--surface); }
92
+
93
+ /* cards */
94
+ .card { background: var(--raised); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-md); box-shadow: var(--shadow-md); padding: var(--spacing-24); margin-bottom: var(--spacing-32); min-width: 0; }
95
+ .card h2 { font-family: var(--font-heading); font-weight: var(--weight-heading); letter-spacing: var(--tracking-heading); line-height: var(--leading-heading); font-size: var(--text-2xl); margin: var(--spacing-4) 0 var(--spacing-8); }
96
+ .card h3 { font-family: var(--font-heading); font-weight: var(--weight-heading-card); letter-spacing: -0.02em; font-size: var(--text-lg); margin: 0 0 2px; }
97
+ .card .sub { color: var(--fg-3); font-size: var(--text-sm); margin: 0 0 var(--spacing-16); max-width: 72ch; }
98
+ .grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--spacing-24); margin-bottom: var(--spacing-32); }
99
+ .grid3 .card { margin-bottom: 0; }
100
+ @media (max-width: 900px) { .grid3 { grid-template-columns: 1fr; } }
101
+ .note { color: var(--fg-3); font-size: var(--text-sm); margin: var(--spacing-12) 0 0; }
102
+ .empty { text-align: center; padding: var(--spacing-64) var(--spacing-24); }
103
+ .empty .kicker { justify-content: center; }
104
+ .empty h1 { font-family: var(--font-display); font-weight: var(--weight-display); letter-spacing: var(--tracking-display); font-size: var(--text-4xl); margin: var(--spacing-8) 0; }
105
+
106
+ /* legend */
107
+ .legend { display: flex; flex-wrap: wrap; gap: var(--spacing-8) var(--spacing-16); font-size: var(--text-sm); color: var(--fg-2); margin-bottom: var(--spacing-8); }
108
+ .legend span { display: inline-flex; align-items: center; gap: 6px; }
109
+ .legend .sep { width: 2px; height: 16px; background: var(--line); }
110
+ .swatch { width: 14px; height: 14px; border-radius: 4px; border: var(--outline-width) solid var(--outline); display: inline-block; }
111
+ .marker { flex: none; overflow: visible; }
112
+ .quad-key { width: 18px; height: 12px; background: var(--color-mint); border: var(--outline-width) solid var(--color-mint-2); border-radius: 3px; }
113
+ .pareto-key { width: 20px; border-top: 2px dotted var(--fg); }
114
+ .human-key { width: 20px; border-top: 3px solid var(--color-ink); }
115
+ .chart .human-line { stroke: var(--color-ink); stroke-width: 3; stroke-linecap: round; }
116
+ .chart .human-label { fill: var(--fg); font-size: 12.5px; font-weight: 700; paint-order: stroke; stroke: var(--raised); stroke-width: 4px; stroke-linejoin: round; }
117
+
118
+ /* charts */
119
+ .chart-wrap { position: relative; }
120
+ .chart { width: 100%; display: block; overflow: visible; }
121
+ .chart text { font-family: var(--font-text); fill: var(--fg-3); font-size: 12px; }
122
+ .chart .axis-title { fill: var(--fg-2); font-size: 13px; font-weight: 700; }
123
+ .chart .gridline { stroke: var(--dataviz-grid); }
124
+ .chart .quadrant { fill: var(--color-mint); opacity: 0.55; }
125
+ .chart .pt-label { fill: var(--fg); font-size: 12.5px; font-weight: 500; paint-order: stroke; stroke: var(--raised); stroke-width: 4px; stroke-linejoin: round; }
126
+ .chart .muted { fill: var(--fg-3); }
127
+ .tooltip { position: absolute; pointer-events: none; background: var(--raised); color: var(--fg); border: var(--outline-width) solid var(--outline); box-shadow: var(--shadow-sm); padding: var(--spacing-12); border-radius: var(--radius-sm); font-size: var(--text-sm); line-height: 1.5; min-width: 220px; transform: translate(-50%, calc(-100% - 16px)); z-index: 5; }
128
+ .tooltip .tt-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
129
+ .tooltip .row { display: flex; justify-content: space-between; gap: var(--spacing-16); }
130
+ .tooltip .row span:first-child { color: var(--fg-3); }
131
+
132
+ /* bars */
133
+ .bars { display: flex; flex-direction: column; gap: 10px; }
134
+ .bar-row { display: grid; grid-template-columns: minmax(0, 170px) minmax(0, 1fr); gap: var(--spacing-8); align-items: center; font-size: var(--text-sm); }
135
+ .bar-label { display: flex; align-items: center; gap: 6px; line-height: 1.25; color: var(--fg-2); }
136
+ .bar-label .marker { flex: none; }
137
+ .bar-track { position: relative; height: 20px; }
138
+ .bar { position: absolute; left: 0; top: 0; bottom: 0; border: var(--outline-width) solid var(--outline); border-radius: 0 4px 4px 0; min-width: 4px; transition: width .5s ease; }
139
+ .bar-val { position: absolute; top: 50%; transform: translateY(-50%); font-variant-numeric: tabular-nums; font-weight: 700; padding-left: 6px; white-space: nowrap; }
140
+
141
+ /* tables */
142
+ .table-wrap { overflow-x: auto; }
143
+ table { width: 100%; border-collapse: separate; border-spacing: 3px; font-size: var(--text-sm); }
144
+ th { font-weight: 700; color: var(--fg-3); font-size: 12px; text-align: center; padding: 6px 4px; vertical-align: bottom; }
145
+ th:first-child, td:first-child { text-align: left; }
146
+ td { padding: var(--spacing-8) 6px; border-radius: var(--radius-xs); text-align: center; font-variant-numeric: tabular-nums; }
147
+ td.cfg, .cfg { white-space: nowrap; display: flex; align-items: center; gap: var(--spacing-8); }
148
+ td.strong, th.strong { font-weight: 800; }
149
+ td .ci { color: var(--fg-3); font-weight: 500; }
150
+ tr.group td { text-align: left; font-family: var(--font-kicker); font-weight: var(--weight-kicker); text-transform: uppercase; letter-spacing: var(--tracking-kicker); font-size: 12px; color: var(--fg-3); padding-top: var(--spacing-16); }
151
+ table.criteria td.q { font-family: var(--font-mono); font-size: 12px; white-space: nowrap; }
152
+ .tag { display: inline-block; font-size: 11px; font-weight: 700; padding: 0 6px; border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); margin-left: 6px; }
153
+ .tag.floor { background: var(--color-cream); }
154
+ .tag.strong { background: var(--color-sky); }
155
+ .tag.ceiling { background: var(--color-lavender); }
156
+ .tag.warn { background: var(--color-blush); }
157
+ td > .tag:first-child { margin-left: 0; }
158
+ td.q .tag { margin-left: var(--spacing-8); }
159
+
160
+ /* trials */
161
+ .trial-task + .trial-task { margin-top: var(--spacing-24); }
162
+ .trial-rows { display: flex; flex-direction: column; gap: var(--spacing-8); margin-top: var(--spacing-8); }
163
+ .trial-row { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: var(--spacing-12); align-items: center; font-size: var(--text-sm); }
164
+ @media (max-width: 700px) { .trial-row { grid-template-columns: 1fr; } }
165
+ .trial-cells { display: flex; gap: var(--spacing-8); flex-wrap: wrap; }
166
+ .cell { min-width: 96px; padding: 6px 10px; border-radius: var(--radius-xs); border: var(--outline-width) solid var(--outline); background: var(--raised); font-size: 12px; text-align: left; display: flex; flex-direction: column; }
167
+ .cell .k { color: var(--fg-3); font-size: 11px; }
168
+ .cell .v { font-weight: 800; font-variant-numeric: tabular-nums; }
169
+ .cell.done { box-shadow: var(--shadow-sm); }
170
+ .cell.done:hover { transform: translateY(var(--press-y)); box-shadow: var(--shadow-sm-pressed); }
171
+ .cell.running, .cell.grading { background: var(--color-butter); }
172
+ .cell.running .v, .cell.grading .v { animation: pulse 1.4s infinite; font-weight: 600; }
173
+ .cell.invalid { background: var(--color-sun-2); }
174
+ .cell.failed { background: var(--color-blush); }
175
+ .cell.queued { opacity: 0.5; }
176
+
177
+ /* drawer */
178
+ .scrim { position: fixed; inset: 0; background: color-mix(in srgb, var(--color-ink) 35%, transparent); z-index: 10; }
179
+ .drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(1120px, 100vw); background: var(--ground); border-left: var(--outline-width) solid var(--outline); z-index: 11; display: flex; flex-direction: column; }
180
+ .drawer-head { padding: var(--spacing-16) var(--spacing-24); border-bottom: var(--outline-width) solid var(--outline); display: flex; align-items: flex-start; gap: var(--spacing-12); background: var(--raised); }
181
+ .drawer-head h2 { font-family: var(--font-heading); font-weight: var(--weight-heading); letter-spacing: var(--tracking-heading); font-size: var(--text-2xl); margin: 4px 0 0; }
182
+ .drawer-body { flex: 1; overflow: hidden; display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
183
+ @media (max-width: 800px) { .drawer-body { grid-template-columns: 1fr; overflow-y: auto; } .drawer-body > div { overflow: visible !important; } }
184
+ .drawer-body > div { overflow-y: auto; padding: var(--spacing-24); }
185
+ .drawer-body > div + div { border-left: var(--outline-width) solid var(--line); background: var(--surface); }
186
+ .drawer-body h3 { font-family: var(--font-heading); font-weight: var(--weight-heading); letter-spacing: var(--tracking-heading); font-size: var(--text-xl); margin: 0 0 var(--spacing-12); }
187
+ .stats { display: flex; gap: var(--spacing-24); flex-wrap: wrap; margin-top: var(--spacing-8); }
188
+ .stat .k { color: var(--fg-3); font-size: 12px; }
189
+ .stat .v { font-size: var(--text-lg); font-weight: 800; font-variant-numeric: tabular-nums; }
190
+ .close { margin-left: auto; border: var(--outline-width) solid var(--outline); background: var(--raised); border-radius: var(--radius-pill); width: 36px; height: 36px; font-size: 20px; line-height: 1; }
191
+ .tabs { display: flex; gap: 6px; margin-bottom: var(--spacing-16); }
192
+ .tab { border: var(--outline-width) solid var(--outline); background: var(--raised); border-radius: var(--radius-pill); padding: 2px var(--spacing-12); font-size: var(--text-sm); font-weight: 700; }
193
+ .tab.on { background: var(--accent); }
194
+ .prose { line-height: 1.65; max-width: 680px; }
195
+ .prose h1, .prose h2, .prose h3 { font-family: var(--font-heading); font-weight: var(--weight-heading-card); letter-spacing: -0.02em; line-height: 1.15; }
196
+ .prose code { background: var(--surface); padding: 1px 5px; border-radius: 6px; }
197
+ .prose pre { background: var(--surface); border: var(--outline-width) solid var(--line); padding: var(--spacing-12); border-radius: var(--radius-xs); overflow-x: auto; }
198
+ .prose pre code { background: none; padding: 0; }
199
+ .prose blockquote { border-left: 4px solid var(--accent); margin: 0; padding-left: var(--spacing-16); color: var(--fg-2); }
200
+ .prose img { display: none; }
201
+ .prose table { font-size: var(--text-sm); }
202
+ .prose td, .prose th { border: 1px solid var(--line); }
203
+ /* An image output in the trial drawer: never wider than the drawer column. */
204
+ .output-image { display: block; max-width: 100%; height: auto; border: var(--outline-width) solid var(--outline); border-radius: var(--radius-xs); }
205
+ pre.raw { white-space: pre-wrap; font-family: var(--font-mono); font-size: 12.5px; }
206
+ .verdict-group { margin-bottom: var(--spacing-24); }
207
+ .verdict-group > .crit-head { display: flex; justify-content: space-between; font-size: var(--text-lg); margin-bottom: var(--spacing-8); }
208
+ .crit-score { font-weight: 800; font-variant-numeric: tabular-nums; }
209
+ .crit { background: var(--raised); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); padding: var(--spacing-12); margin-bottom: var(--spacing-8); }
210
+ .crit-top { display: flex; gap: var(--spacing-8); align-items: flex-start; }
211
+ .crit-top .q { font-size: var(--text-sm); font-weight: 600; }
212
+ .crit-meta { display: flex; gap: var(--spacing-8); align-items: center; margin: 6px 0 2px; font-size: 12px; }
213
+ .crit-meta .tag { margin-left: 0; }
214
+ .reason { margin: 4px 0 0; font-size: var(--text-sm); color: var(--fg-2); }
215
+ .badge { flex: none; font-size: 12px; font-weight: 800; padding: 0 8px; border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); }
216
+ .badge.pass { background: var(--color-mint); }
217
+ .badge.fail { background: var(--color-blush); }
218
+ .badge.split { background: var(--color-sun-2); }
219
+ .badge.err { background: var(--color-cream); }
220
+ .conditions { color: var(--fg-3); font-size: 12px; font-weight: 400; }
221
+ .conditions-list, .gradings { margin-bottom: var(--spacing-24); }
222
+ .conditions-list dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px var(--spacing-12); margin: 0; font-size: var(--text-sm); }
223
+ .conditions-list dt { color: var(--fg-3); }
224
+ .conditions-list dd { margin: 0; overflow-wrap: anywhere; }
225
+ .gradings ul { list-style: none; margin: 0; padding: 0; }
226
+ .gradings li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--spacing-12); padding: var(--spacing-8) var(--spacing-12); margin-bottom: 6px; background: var(--raised); border: var(--outline-width) solid var(--line); border-radius: var(--radius-sm); font-size: var(--text-sm); }
227
+ .gradings li.counted { border-color: var(--outline); box-shadow: inset 4px 0 0 var(--accent); }
228
+ .gradings .g-name { flex-basis: 100%; font-weight: 700; }
229
+ .gradings .g-score { font-weight: 800; font-variant-numeric: tabular-nums; }
230
+ .gradings .g-status.invalid { color: var(--fg-2); }
231
+ .gradings .g-counted { margin-left: auto; font-size: 12px; font-weight: 800; padding: 0 8px; border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); background: var(--accent); }
232
+
233
+ /* modal */
234
+ .modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(560px, calc(100vw - 32px)); max-height: calc(100vh - 32px); overflow-y: auto; background: var(--raised); border: var(--outline-width) solid var(--outline); box-shadow: var(--shadow-lg); border-radius: var(--radius-md); padding: var(--spacing-24); z-index: 11; }
235
+ .modal h2 { font-family: var(--font-heading); font-weight: var(--weight-heading); letter-spacing: var(--tracking-heading); font-size: var(--text-2xl); margin: 0 0 var(--spacing-16); }
236
+ .modal fieldset { border: none; padding: 0; margin: 0 0 var(--spacing-16); }
237
+ .modal legend { font-family: var(--font-kicker); font-weight: var(--weight-kicker); text-transform: uppercase; letter-spacing: var(--tracking-kicker); font-size: 12px; color: var(--fg-3); margin-bottom: 4px; }
238
+ .modal label.opt { display: flex; align-items: center; gap: var(--spacing-8); padding: 6px 2px; border-bottom: 1px solid var(--line-soft); font-size: var(--text-sm); }
239
+ .modal label.opt .muted { margin-left: auto; font-size: 12px; }
240
+ .modal select { width: 100%; }
241
+ .modal .row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-16); margin-top: var(--spacing-12); font-size: var(--text-sm); }
242
+ .modal .row label { display: flex; align-items: center; gap: var(--spacing-8); }
243
+ .modal .row.end { justify-content: flex-end; }
244
+ .modal input[type="number"] { width: 72px; padding: 4px var(--spacing-8); }
245
+ .envelope { background: var(--surface); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); padding: var(--spacing-12) var(--spacing-16); margin-bottom: var(--spacing-16); font-size: var(--text-sm); }
246
+ .envelope .k { font-family: var(--font-kicker); font-weight: var(--weight-kicker); text-transform: uppercase; letter-spacing: var(--tracking-kicker); font-size: 11px; color: var(--fg-3); margin-top: 4px; }
247
+ .envelope .subject { font-weight: 800; font-size: var(--text-lg); }
248
+ .img-ph { display: inline-block; background: var(--color-sky); border: var(--outline-width) dashed var(--outline); border-radius: var(--radius-xs); padding: 2px var(--spacing-8); font-size: var(--text-sm); }
249
+
250
+ /* batches panel (#13) */
251
+ .batches { margin-bottom: var(--spacing-24); }
252
+ .batch-list { list-style: none; margin: var(--spacing-12) 0 0; padding: 0; display: grid; gap: var(--spacing-12); }
253
+ .batch { border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); padding: var(--spacing-12); background: var(--raised); }
254
+ /* one row per batch, in the columns of .batch-cols; stacked on narrow screens */
255
+ .batch-cols, .batch-head { display: grid; grid-template-columns: minmax(10em, 1fr) minmax(0, 2fr) minmax(150px, 1.5fr) 7.5em; gap: var(--spacing-8) var(--spacing-16); align-items: center; }
256
+ .batch-cols { margin: var(--spacing-12) 0 0; padding: 0 calc(var(--spacing-12) + var(--outline-width)); font-family: var(--font-kicker); font-weight: var(--weight-kicker); font-size: 12px; letter-spacing: var(--tracking-kicker); text-transform: uppercase; color: var(--fg-3); }
257
+ .batch-head { width: 100%; text-align: left; background: none; border: 0; padding: 0; }
258
+ .batch-id { display: flex; flex-direction: column; min-width: 0; }
259
+ .batch-id code { color: var(--fg-3); font-size: 12px; }
260
+ .batch-asked { color: var(--fg-3); font-size: var(--text-sm); min-width: 0; }
261
+ .batch-progress { display: flex; flex-direction: column; min-width: 0; }
262
+ .batch-head .batch-state { justify-self: start; }
263
+ @media (max-width: 720px) { .batch-cols { display: none; } .batch-head { grid-template-columns: 1fr; } }
264
+ .batch-state { font-size: var(--text-sm); font-weight: 700; padding: 2px var(--spacing-12); border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); background: var(--surface); white-space: nowrap; }
265
+ .batch-state.running { background: var(--color-mint); }
266
+ .batch-state.stalled { background: var(--color-sun-2); }
267
+ .batch-bar { display: flex; height: 10px; margin: 0 0 var(--spacing-4, 4px); border-radius: var(--radius-pill); border: var(--outline-width) solid var(--outline); overflow: hidden; background: var(--surface); }
268
+ .batch-bar .seg { flex-basis: 0; transition: flex-grow .4s ease; }
269
+ .batch-bar .seg.done { background: var(--color-mint); }
270
+ .batch-bar .seg.failed { background: var(--color-blush); }
271
+ .batch-bar .seg.invalid { background: var(--color-sun-2); }
272
+ .batch-bar .seg.running { background: var(--color-butter); }
273
+ .batch-bar .seg.queued { background: var(--surface); }
274
+ .batch-line { font-size: var(--text-sm); }
275
+ .batch-trials { width: 100%; border-collapse: collapse; margin-top: var(--spacing-8); font-size: var(--text-sm); }
276
+ .batch-trials th, .batch-trials td { text-align: left; padding: 2px var(--spacing-8); border-top: 1px solid var(--dataviz-grid); }
277
+ .status.done { color: var(--fg); }
278
+ .status.failed, .status.invalid { font-weight: 700; }
279
+
280
+ /* left-out notice (under the chart) */
281
+ .left-out { margin-top: var(--spacing-12); }
282
+ .left-out .btn-quiet { margin-top: var(--spacing-8); }
283
+ .left-out .error { margin-top: var(--spacing-8); }
284
+ .empty .left-out { text-align: left; max-width: 72ch; margin: var(--spacing-24) auto 0; }
285
+ .linkish { background: none; border: 0; padding: 0; font: inherit; color: var(--fg); text-decoration: underline; cursor: pointer; }
286
+
287
+ /* chart card: Chart / Table toggle and the config table */
288
+ .card-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--spacing-8) var(--spacing-16); }
289
+ .view-toggle { display: inline-flex; border: var(--outline-width) solid var(--outline); border-radius: var(--radius-pill); overflow: hidden; background: var(--raised); }
290
+ .view-toggle button { font: inherit; font-weight: 700; font-size: var(--text-sm); color: var(--fg); border: 0; background: transparent; padding: 4px var(--spacing-16); cursor: pointer; }
291
+ .view-toggle button + button { border-left: var(--outline-width) solid var(--outline); }
292
+ .view-toggle button.on { background: var(--accent); }
293
+ table.configs th.num, table.configs td.num { text-align: right; white-space: nowrap; }
294
+ table.configs th:last-child, table.configs td:last-child { text-align: left; white-space: nowrap; }
295
+ table.configs tbody tr { cursor: pointer; }
296
+ table.configs tbody tr:hover td { background: var(--ground); }
297
+ table.configs .why { color: var(--fg-3); font-size: 12px; margin-top: 2px; }
298
+
299
+ /* Compare page (#27): the two picked versions, what changed between them, and the moved criteria */
300
+ .compare-picks { margin-bottom: var(--spacing-16); }
301
+ .compare-picks select { max-width: 420px; }
302
+ .two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-12); }
303
+ @media (max-width: 720px) { .two { grid-template-columns: 1fr; } }
304
+ .ver { border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); padding: var(--spacing-12); background: var(--color-lavender); min-width: 0; }
305
+ .ver .ver-name { display: block; font-size: var(--text-lg); margin: 2px 0; }
306
+ .ver .muted { font-size: var(--text-sm); }
307
+ .pill.current { background: var(--accent); font-size: 11px; padding: 0 var(--spacing-8); letter-spacing: 0; text-transform: none; }
308
+ .changes { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--spacing-12); }
309
+ .change { min-width: 0; }
310
+ .diff { font-family: var(--font-mono); font-size: var(--text-sm); background: var(--ground); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-xs); padding: var(--spacing-8) var(--spacing-12); margin-top: 6px; white-space: pre-wrap; overflow-wrap: anywhere; }
311
+ .diff del { background: var(--color-blush); text-decoration: line-through; }
312
+ .diff ins { background: var(--color-mint); text-decoration: none; }
313
+ .diff .field { color: var(--fg-3); font-weight: 700; }
314
+ .pill.mark { font-size: 11px; padding: 0 var(--spacing-8); }
315
+ .pill.mark.changed, .pill.mark.added, .pill.mark.removed { background: var(--color-lavender); }
316
+ .pill.mark.same { background: var(--color-cream); }
317
+ .delta { font-weight: 700; padding: 1px 6px; border-radius: var(--radius-xs); }
318
+ .delta.up { background: var(--color-mint); }
319
+ .delta.down { background: var(--color-blush); }
320
+ .moved-head { margin-top: var(--spacing-24) !important; }
321
+ .reading { background: var(--color-butter); border: var(--outline-width) solid var(--outline); border-radius: var(--radius-sm); padding: var(--spacing-8) var(--spacing-12); margin: var(--spacing-16) 0 0; font-weight: 600; }
322
+ /* run status indicator in the top bar (#25): a pill that opens the Runs page */
323
+ .run-status { display: flex; align-items: center; gap: var(--spacing-8); font-size: var(--text-sm); font-weight: 600; border: var(--outline-width) solid var(--outline); border-radius: var(--radius-pill); padding: 3px var(--spacing-12); background: var(--raised); max-width: 100%; text-align: left; }
324
+ .run-status:hover { background: var(--color-cream); }
325
+ .run-status .dot { flex: none; width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--outline); background: var(--color-mint-2); }
326
+ .run-status .dot.none { background: var(--surface); }
327
+ .run-status .dot.run { background: var(--color-sun); animation: run-pulse 1.2s infinite; }
328
+ .run-status .dot.stalled, .run-status .dot.unavailable { background: var(--color-blush-2); }
329
+ @keyframes run-pulse { 50% { opacity: .35; } }
330
+ @media (prefers-reduced-motion: reduce) { .run-status .dot.run { animation: none; } }
331
+ .run-status .mini { flex: none; width: 70px; height: 7px; border: 1.5px solid var(--outline); border-radius: 4px; overflow: hidden; background: var(--raised); }
332
+ .run-status .mini b { display: block; height: 100%; background: var(--color-sun); transition: width .4s ease; }
333
+ .run-status-count { white-space: nowrap; font-variant-numeric: tabular-nums; }
@@ -0,0 +1,20 @@
1
+ // The suite descriptions the Results page shows with a chart. A suite can state facts that must stand next to any
2
+ // chart of it (for example where its tasks and criteria come from), so every chart that draws a suite shows them.
3
+ import type { Suite } from "../src/core/types";
4
+ import type { Scope } from "../src/core/view-query";
5
+
6
+ export interface SuiteNote { title: string; description: string }
7
+
8
+ /**
9
+ * The described suites a chart draws: the chosen suite, every suite in the view for the Workflow Index, or each
10
+ * suite that holds the chosen task (a task's view has no suites of its own). Suites without a description are left out.
11
+ */
12
+ export function suiteNotes(sel: Scope, suiteIds: string[], suites: Map<string, Suite>): SuiteNote[] {
13
+ const ids = sel.kind === "suite" ? [sel.id]
14
+ : sel.kind === "index" ? suiteIds
15
+ : [...suites.keys()].filter((id) => suites.get(id)!.tasks.includes(sel.id));
16
+ return ids.flatMap((id) => {
17
+ const s = suites.get(id), description = s?.description?.trim();
18
+ return s && description ? [{ title: s.title, description }] : [];
19
+ });
20
+ }
package/web/wording.ts ADDED
@@ -0,0 +1,38 @@
1
+ // The changed words between two wordings of one text (a reworded question, a changed context), for the Compare
2
+ // page to mark. Display only: what changed between two grading versions comes from the server's difference.
3
+
4
+ export interface WordPart { kind: "same" | "added" | "removed"; text: string }
5
+
6
+ /** Words, runs of spaces and single punctuation marks, so "(the subject" and "(subject" share the "(". */
7
+ const tokens = (s: string) => s.match(/\w+|\s+|[^\w\s]/g) ?? [];
8
+
9
+ /** Above this many token pairs the texts are shown as one removed and one added part. */
10
+ const MAX_CELLS = 4_000_000;
11
+
12
+ /**
13
+ * The parts of `b` against `a`, in reading order: kept, removed (only in `a`) and added (only in `b`). In a run of
14
+ * changes the removed part comes first. Joined, the kept and removed parts give `a`; the kept and added parts give `b`.
15
+ */
16
+ export function wordChanges(a: string, b: string): WordPart[] {
17
+ const x = tokens(a), y = tokens(b);
18
+ const parts: WordPart[] = [];
19
+ const push = (kind: WordPart["kind"], text: string) => {
20
+ const last = parts.at(-1);
21
+ if (last?.kind === kind) last.text += text; else if (text) parts.push({ kind, text });
22
+ };
23
+ if (x.length * y.length > MAX_CELLS) { push("removed", a); push("added", b); return parts; }
24
+ // lcs[i][j]: the longest common run of tokens of x from i and y from j.
25
+ const w = y.length + 1, lcs = new Uint32Array((x.length + 1) * w);
26
+ for (let i = x.length - 1; i >= 0; i--) {
27
+ for (let j = y.length - 1; j >= 0; j--) {
28
+ lcs[i * w + j] = x[i] === y[j] ? lcs[(i + 1) * w + j + 1]! + 1 : Math.max(lcs[(i + 1) * w + j]!, lcs[i * w + j + 1]!);
29
+ }
30
+ }
31
+ let i = 0, j = 0;
32
+ while (i < x.length || j < y.length) {
33
+ if (i < x.length && j < y.length && x[i] === y[j]) { push("same", x[i]!); i++; j++; }
34
+ else if (j >= y.length || (i < x.length && lcs[(i + 1) * w + j]! >= lcs[i * w + j + 1]!)) { push("removed", x[i]!); i++; }
35
+ else { push("added", y[j]!); j++; }
36
+ }
37
+ return parts;
38
+ }