@oh-my-pi/omp-stats 18.3.5 → 18.4.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 (192) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +13 -11
  3. package/build.ts +5 -49
  4. package/dist/client/index.css +1 -1
  5. package/dist/client/index.html +2 -2
  6. package/dist/client/index.js +105 -126
  7. package/dist/types/aggregator.d.ts +25 -22
  8. package/dist/types/client/api.d.ts +11 -7
  9. package/dist/types/client/app/LiveChip.d.ts +2 -0
  10. package/dist/types/client/app/Shell.d.ts +16 -0
  11. package/dist/types/client/app/ThemeToggle.d.ts +1 -0
  12. package/dist/types/client/app/nav.d.ts +18 -0
  13. package/dist/types/client/charts/BarList.d.ts +17 -0
  14. package/dist/types/client/charts/Chart.d.ts +49 -0
  15. package/dist/types/client/charts/Legend.d.ts +16 -0
  16. package/dist/types/client/charts/ShareBar.d.ts +11 -0
  17. package/dist/types/client/charts/Sparkline.d.ts +11 -0
  18. package/dist/types/client/charts/TimeChart.d.ts +9 -0
  19. package/dist/types/client/charts/index.d.ts +7 -0
  20. package/dist/types/client/charts/types.d.ts +25 -0
  21. package/dist/types/client/charts/useWidth.d.ts +3 -0
  22. package/dist/types/client/data/colors.d.ts +25 -0
  23. package/dist/types/client/data/formatters.d.ts +9 -1
  24. package/dist/types/client/data/live.d.ts +29 -0
  25. package/dist/types/client/data/query.d.ts +35 -0
  26. package/dist/types/client/data/range.d.ts +31 -0
  27. package/dist/types/client/data/series.d.ts +28 -0
  28. package/dist/types/client/data/useHashRoute.d.ts +18 -4
  29. package/dist/types/client/data/view-models.d.ts +111 -28
  30. package/dist/types/client/index.d.ts +0 -1
  31. package/dist/types/client/routes/CostsRoute.d.ts +2 -2
  32. package/dist/types/client/routes/ErrorsRoute.d.ts +2 -2
  33. package/dist/types/client/routes/FrustrationRoute.d.ts +7 -0
  34. package/dist/types/client/routes/GainRoute.d.ts +1 -2
  35. package/dist/types/client/routes/ModelsRoute.d.ts +2 -2
  36. package/dist/types/client/routes/OverviewRoute.d.ts +1 -2
  37. package/dist/types/client/routes/ProjectsRoute.d.ts +2 -2
  38. package/dist/types/client/routes/ProvidersRoute.d.ts +2 -2
  39. package/dist/types/client/routes/RequestsRoute.d.ts +1 -2
  40. package/dist/types/client/routes/ToolsRoute.d.ts +2 -2
  41. package/dist/types/client/routes/TracesRoute.d.ts +2 -2
  42. package/dist/types/client/routes/index.d.ts +1 -1
  43. package/dist/types/client/traces/AggregatesPanel.d.ts +4 -3
  44. package/dist/types/client/traces/SummaryStrip.d.ts +2 -2
  45. package/dist/types/client/traces/trace-colors.d.ts +18 -12
  46. package/dist/types/client/ui/Badge.d.ts +19 -0
  47. package/dist/types/client/ui/Card.d.ts +25 -0
  48. package/dist/types/client/ui/Drawer.d.ts +25 -0
  49. package/dist/types/client/ui/JsonBlock.d.ts +2 -2
  50. package/dist/types/client/ui/Modal.d.ts +23 -0
  51. package/dist/types/client/ui/RequestDrawer.d.ts +10 -1
  52. package/dist/types/client/ui/SearchInput.d.ts +8 -0
  53. package/dist/types/client/ui/Segmented.d.ts +15 -0
  54. package/dist/types/client/ui/Stat.d.ts +36 -0
  55. package/dist/types/client/ui/States.d.ts +41 -0
  56. package/dist/types/client/ui/Table.d.ts +48 -0
  57. package/dist/types/client/ui/index.d.ts +9 -10
  58. package/dist/types/db.d.ts +90 -106
  59. package/dist/types/frustration.d.ts +70 -0
  60. package/dist/types/index.d.ts +3 -1
  61. package/dist/types/live.d.ts +30 -0
  62. package/dist/types/rollup.d.ts +115 -0
  63. package/dist/types/server.d.ts +6 -1
  64. package/dist/types/shared-types.d.ts +116 -56
  65. package/dist/types/types.d.ts +4 -0
  66. package/dist/types/user-metrics.d.ts +8 -0
  67. package/package.json +7 -13
  68. package/src/aggregator.ts +276 -211
  69. package/src/client/App.tsx +38 -80
  70. package/src/client/api.ts +65 -7
  71. package/src/client/app/LiveChip.tsx +97 -0
  72. package/src/client/app/Shell.tsx +181 -0
  73. package/src/client/app/ThemeToggle.tsx +5 -2
  74. package/src/client/app/nav.ts +78 -0
  75. package/src/client/charts/BarList.tsx +48 -0
  76. package/src/client/charts/Chart.tsx +388 -0
  77. package/src/client/charts/Legend.tsx +61 -0
  78. package/src/client/charts/ShareBar.tsx +26 -0
  79. package/src/client/charts/Sparkline.tsx +38 -0
  80. package/src/client/charts/TimeChart.tsx +22 -0
  81. package/src/client/charts/index.ts +7 -0
  82. package/src/client/charts/types.ts +27 -0
  83. package/src/client/charts/useWidth.ts +19 -0
  84. package/src/client/data/colors.ts +45 -0
  85. package/src/client/data/formatters.ts +30 -9
  86. package/src/client/data/live.tsx +85 -0
  87. package/src/client/data/query.ts +166 -0
  88. package/src/client/data/range.ts +82 -0
  89. package/src/client/data/series.ts +85 -0
  90. package/src/client/data/useHashRoute.ts +41 -83
  91. package/src/client/data/view-models.ts +347 -140
  92. package/src/client/index.tsx +3 -6
  93. package/src/client/routes/CostsRoute.tsx +394 -222
  94. package/src/client/routes/ErrorsRoute.tsx +401 -98
  95. package/src/client/routes/FrustrationRoute.tsx +924 -0
  96. package/src/client/routes/GainRoute.tsx +209 -206
  97. package/src/client/routes/ModelsRoute.tsx +540 -431
  98. package/src/client/routes/OverviewRoute.tsx +283 -311
  99. package/src/client/routes/ProjectsRoute.tsx +298 -156
  100. package/src/client/routes/ProvidersRoute.tsx +948 -416
  101. package/src/client/routes/RequestsRoute.tsx +295 -107
  102. package/src/client/routes/ToolsRoute.tsx +495 -398
  103. package/src/client/routes/TracesRoute.tsx +136 -117
  104. package/src/client/routes/costs.css +24 -0
  105. package/src/client/routes/errors.css +60 -0
  106. package/src/client/routes/frustration.css +123 -0
  107. package/src/client/routes/index.ts +1 -1
  108. package/src/client/routes/models.css +43 -0
  109. package/src/client/routes/projects.css +6 -0
  110. package/src/client/routes/providers.css +28 -0
  111. package/src/client/routes/tools.css +14 -0
  112. package/src/client/styles.css +1396 -1299
  113. package/src/client/traces/AggregatesPanel.tsx +55 -74
  114. package/src/client/traces/Minimap.tsx +6 -15
  115. package/src/client/traces/SpanDrawer.tsx +128 -194
  116. package/src/client/traces/SummaryStrip.tsx +21 -24
  117. package/src/client/traces/TimelineCanvas.tsx +27 -44
  118. package/src/client/traces/TraceView.tsx +175 -130
  119. package/src/client/traces/TranscriptList.tsx +13 -41
  120. package/src/client/traces/trace-colors.ts +60 -53
  121. package/src/client/traces/traces.css +279 -0
  122. package/src/client/ui/Badge.tsx +29 -0
  123. package/src/client/ui/Card.tsx +61 -0
  124. package/src/client/ui/Drawer.tsx +93 -0
  125. package/src/client/ui/JsonBlock.tsx +26 -44
  126. package/src/client/ui/Modal.tsx +106 -0
  127. package/src/client/ui/RequestDrawer.tsx +163 -188
  128. package/src/client/ui/SearchInput.tsx +25 -0
  129. package/src/client/ui/Segmented.tsx +61 -0
  130. package/src/client/ui/Stat.tsx +104 -0
  131. package/src/client/ui/States.tsx +81 -0
  132. package/src/client/ui/Table.tsx +229 -0
  133. package/src/client/ui/index.ts +9 -10
  134. package/src/client/ui/modal.css +54 -0
  135. package/src/client/ui/request-drawer.css +40 -0
  136. package/src/db.ts +379 -983
  137. package/src/frustration.ts +473 -0
  138. package/src/index.ts +22 -22
  139. package/src/live.ts +259 -0
  140. package/src/parser.ts +55 -66
  141. package/src/rollup.ts +951 -0
  142. package/src/server.ts +123 -40
  143. package/src/shared-types.ts +120 -56
  144. package/src/trace.ts +14 -7
  145. package/src/types.ts +4 -0
  146. package/src/user-metrics.ts +13 -0
  147. package/dist/client/styles.css +0 -1976
  148. package/dist/types/client/app/AppLayout.d.ts +0 -16
  149. package/dist/types/client/app/NavRail.d.ts +0 -7
  150. package/dist/types/client/app/RangeControl.d.ts +0 -7
  151. package/dist/types/client/app/SyncButton.d.ts +0 -14
  152. package/dist/types/client/app/TopBar.d.ts +0 -15
  153. package/dist/types/client/app/routes.d.ts +0 -12
  154. package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
  155. package/dist/types/client/components/chart-shared.d.ts +0 -178
  156. package/dist/types/client/components/models-table-shared.d.ts +0 -175
  157. package/dist/types/client/components/range-meta.d.ts +0 -21
  158. package/dist/types/client/data/charts.d.ts +0 -1
  159. package/dist/types/client/data/useResource.d.ts +0 -13
  160. package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
  161. package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
  162. package/dist/types/client/ui/DataTable.d.ts +0 -17
  163. package/dist/types/client/ui/EmptyState.d.ts +0 -7
  164. package/dist/types/client/ui/ErrorState.d.ts +0 -6
  165. package/dist/types/client/ui/MetricCluster.d.ts +0 -5
  166. package/dist/types/client/ui/Panel.d.ts +0 -7
  167. package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
  168. package/dist/types/client/ui/Skeleton.d.ts +0 -8
  169. package/dist/types/client/ui/StatusPill.d.ts +0 -7
  170. package/src/client/app/AppLayout.tsx +0 -93
  171. package/src/client/app/NavRail.tsx +0 -44
  172. package/src/client/app/RangeControl.tsx +0 -39
  173. package/src/client/app/SyncButton.tsx +0 -75
  174. package/src/client/app/TopBar.tsx +0 -73
  175. package/src/client/app/routes.ts +0 -93
  176. package/src/client/components/AgentTokenShare.tsx +0 -68
  177. package/src/client/components/chart-shared.tsx +0 -273
  178. package/src/client/components/models-table-shared.tsx +0 -255
  179. package/src/client/components/range-meta.ts +0 -73
  180. package/src/client/data/charts.ts +0 -14
  181. package/src/client/data/useResource.ts +0 -154
  182. package/src/client/routes/BehaviorRoute.tsx +0 -623
  183. package/src/client/ui/AsyncBoundary.tsx +0 -54
  184. package/src/client/ui/DataTable.tsx +0 -122
  185. package/src/client/ui/EmptyState.tsx +0 -16
  186. package/src/client/ui/ErrorState.tsx +0 -25
  187. package/src/client/ui/MetricCluster.tsx +0 -92
  188. package/src/client/ui/Panel.tsx +0 -24
  189. package/src/client/ui/SegmentedControl.tsx +0 -36
  190. package/src/client/ui/Skeleton.tsx +0 -17
  191. package/src/client/ui/StatusPill.tsx +0 -15
  192. package/tailwind.config.js +0 -40
@@ -0,0 +1,81 @@
1
+ import { Inbox, RotateCw, TriangleAlert } from "lucide-react";
2
+ import type { CSSProperties, ReactNode } from "react";
3
+
4
+ export function EmptyState({ title = "Nothing here yet", hint }: { title?: ReactNode; hint?: ReactNode }) {
5
+ return (
6
+ <div className="empty">
7
+ <Inbox size={20} />
8
+ <div className="empty-title">{title}</div>
9
+ {hint !== undefined && <div className="empty-hint">{hint}</div>}
10
+ </div>
11
+ );
12
+ }
13
+
14
+ export function ErrorState({ error, onRetry }: { error: Error | string; onRetry?: () => void }) {
15
+ return (
16
+ <div className="error-state" role="alert">
17
+ <TriangleAlert size={15} />
18
+ <span className="error-state-message">{typeof error === "string" ? error : error.message}</span>
19
+ {onRetry && (
20
+ <button type="button" className="btn" data-size="sm" data-variant="ghost" onClick={onRetry}>
21
+ <RotateCw size={13} /> Retry
22
+ </button>
23
+ )}
24
+ </div>
25
+ );
26
+ }
27
+
28
+ export function Skeleton({
29
+ width = "100%",
30
+ height = 14,
31
+ style,
32
+ }: {
33
+ width?: number | string;
34
+ height?: number | string;
35
+ style?: CSSProperties;
36
+ }) {
37
+ return <div className="skeleton" style={{ width, height, ...style }} />;
38
+ }
39
+
40
+ /** Placeholder block matching a chart's footprint. */
41
+ export function ChartSkeleton({ height = 220 }: { height?: number }) {
42
+ return <Skeleton height={height} style={{ borderRadius: 8 }} />;
43
+ }
44
+
45
+ /** Placeholder rows matching a table's footprint. */
46
+ export function TableSkeleton({ rows = 6 }: { rows?: number }) {
47
+ return (
48
+ <div style={{ display: "flex", flexDirection: "column", gap: 10, padding: "8px 16px 16px" }}>
49
+ {Array.from({ length: rows }, (_, i) => (
50
+ <Skeleton key={i} height={16} width={`${92 - ((i * 13) % 30)}%`} />
51
+ ))}
52
+ </div>
53
+ );
54
+ }
55
+
56
+ export interface QueryViewProps<T> {
57
+ query: { data: T | null; error: Error | null; loading: boolean; refetch: () => void };
58
+ /** Rendered on first load. */
59
+ skeleton: ReactNode;
60
+ /** Treat loaded data as empty (renders `empty`). */
61
+ isEmpty?: (data: T) => boolean;
62
+ empty?: ReactNode;
63
+ children: (data: T) => ReactNode;
64
+ }
65
+
66
+ /**
67
+ * Standard load/error/empty switch for one `useQuery` result. Errors with
68
+ * data already on screen keep the data and show the error above it.
69
+ */
70
+ export function QueryView<T>({ query, skeleton, isEmpty, empty, children }: QueryViewProps<T>) {
71
+ if (query.data === null) {
72
+ if (query.error) return <ErrorState error={query.error} onRetry={query.refetch} />;
73
+ return <>{skeleton}</>;
74
+ }
75
+ return (
76
+ <>
77
+ {query.error && <ErrorState error={query.error} onRetry={query.refetch} />}
78
+ {isEmpty?.(query.data) ? (empty ?? <EmptyState />) : children(query.data)}
79
+ </>
80
+ );
81
+ }
@@ -0,0 +1,229 @@
1
+ import { ArrowDown, ArrowUp } from "lucide-react";
2
+ import { Fragment, type ReactNode, useMemo, useState } from "react";
3
+ import { EmptyState } from "./States";
4
+
5
+ export interface Column<T> {
6
+ key: string;
7
+ header: ReactNode;
8
+ render: (row: T) => ReactNode;
9
+ /** Sort key; the column header becomes clickable when present. */
10
+ sort?: (row: T) => number | string;
11
+ align?: "left" | "right" | "center";
12
+ width?: number | string;
13
+ /** Allow the cell to wrap (cells are single-line by default). */
14
+ wrap?: boolean;
15
+ /** Header tooltip. */
16
+ title?: string;
17
+ }
18
+
19
+ export type SortDir = "asc" | "desc";
20
+
21
+ export interface TableProps<T> {
22
+ columns: readonly Column<T>[];
23
+ rows: readonly T[];
24
+ rowKey: (row: T) => string | number;
25
+ onRowClick?: (row: T) => void;
26
+ selectedKey?: string | number | null;
27
+ /** Initial sort; numeric columns usually start `desc`. */
28
+ initialSort?: { key: string; dir: SortDir };
29
+ /** Show the first `limit` rows; "Show more" reveals `limit` × 4 more at a time (rendering stays cheap for huge tables). */
30
+ limit?: number;
31
+ dense?: boolean;
32
+ empty?: ReactNode;
33
+ /** Detail row rendered below a row (return `null` for collapsed rows). */
34
+ expanded?: (row: T) => ReactNode | null;
35
+ }
36
+
37
+ /** Sortable data table. Clicking a sorted header flips its direction. */
38
+ export function Table<T>({
39
+ columns,
40
+ rows,
41
+ rowKey,
42
+ onRowClick,
43
+ selectedKey,
44
+ initialSort,
45
+ limit,
46
+ dense,
47
+ empty,
48
+ expanded,
49
+ }: TableProps<T>) {
50
+ const [sort, setSort] = useState(initialSort ?? null);
51
+ const [shown, setShown] = useState(limit ?? Number.POSITIVE_INFINITY);
52
+
53
+ const sorted = useMemo(() => {
54
+ const column = sort ? columns.find(c => c.key === sort.key) : undefined;
55
+ if (!sort || !column?.sort) return rows;
56
+ const valueOf = column.sort;
57
+ const dir = sort.dir === "asc" ? 1 : -1;
58
+ return [...rows].sort((a, b) => {
59
+ const va = valueOf(a);
60
+ const vb = valueOf(b);
61
+ if (typeof va === "number" && typeof vb === "number") return (va - vb) * dir;
62
+ return String(va).localeCompare(String(vb)) * dir;
63
+ });
64
+ }, [rows, columns, sort]);
65
+
66
+ if (rows.length === 0) return <>{empty ?? <EmptyState />}</>;
67
+
68
+ const visible = sorted.length > shown ? sorted.slice(0, shown) : sorted;
69
+
70
+ const toggleSort = (column: Column<T>) => {
71
+ if (!column.sort) return;
72
+ setSort(prev =>
73
+ prev?.key === column.key
74
+ ? { key: column.key, dir: prev.dir === "asc" ? "desc" : "asc" }
75
+ : { key: column.key, dir: column.align === "right" ? "desc" : "asc" },
76
+ );
77
+ };
78
+
79
+ return (
80
+ <>
81
+ <div className="table-wrap">
82
+ <table className="table" data-dense={dense ?? false}>
83
+ <thead>
84
+ <tr>
85
+ {columns.map(column => {
86
+ const sorted = sort?.key === column.key ? sort.dir : undefined;
87
+ return (
88
+ <th
89
+ key={column.key}
90
+ data-align={column.align ?? "left"}
91
+ data-sortable={column.sort !== undefined}
92
+ data-sorted={sorted}
93
+ style={column.width !== undefined ? { width: column.width } : undefined}
94
+ title={column.title}
95
+ onClick={() => toggleSort(column)}
96
+ aria-sort={sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : undefined}
97
+ >
98
+ {column.header}
99
+ {sorted && (
100
+ <span className="sort-arrow">
101
+ {sorted === "asc" ? <ArrowUp size={11} /> : <ArrowDown size={11} />}
102
+ </span>
103
+ )}
104
+ </th>
105
+ );
106
+ })}
107
+ </tr>
108
+ </thead>
109
+ <tbody>
110
+ {visible.map(row => {
111
+ const key = rowKey(row);
112
+ const detail = expanded?.(row) ?? null;
113
+ return (
114
+ <Fragment key={key}>
115
+ <tr
116
+ data-clickable={onRowClick !== undefined}
117
+ data-selected={selectedKey === key}
118
+ onClick={onRowClick ? () => onRowClick(row) : undefined}
119
+ onKeyDown={
120
+ onRowClick
121
+ ? e => {
122
+ if (e.key === "Enter" || e.key === " ") {
123
+ e.preventDefault();
124
+ onRowClick(row);
125
+ }
126
+ }
127
+ : undefined
128
+ }
129
+ tabIndex={onRowClick ? 0 : undefined}
130
+ >
131
+ {columns.map(column => (
132
+ <td
133
+ key={column.key}
134
+ data-align={column.align ?? "left"}
135
+ data-wrap={column.wrap ?? false}
136
+ >
137
+ {column.render(row)}
138
+ </td>
139
+ ))}
140
+ </tr>
141
+ {detail !== null && (
142
+ <tr>
143
+ <td colSpan={columns.length} data-wrap="true" style={{ padding: 0 }}>
144
+ {detail}
145
+ </td>
146
+ </tr>
147
+ )}
148
+ </Fragment>
149
+ );
150
+ })}
151
+ </tbody>
152
+ </table>
153
+ </div>
154
+ {limit !== undefined && sorted.length > limit && (
155
+ <div className="table-more">
156
+ <span className="micro">
157
+ {visible.length.toLocaleString()} of {sorted.length.toLocaleString()}
158
+ </span>
159
+ {sorted.length > shown && (
160
+ <button
161
+ type="button"
162
+ className="btn"
163
+ data-size="sm"
164
+ data-variant="ghost"
165
+ onClick={() => setShown(n => n + limit * 4)}
166
+ >
167
+ Show more
168
+ </button>
169
+ )}
170
+ {shown > limit && (
171
+ <button
172
+ type="button"
173
+ className="btn"
174
+ data-size="sm"
175
+ data-variant="ghost"
176
+ onClick={() => setShown(limit)}
177
+ >
178
+ Show fewer
179
+ </button>
180
+ )}
181
+ </div>
182
+ )}
183
+ </>
184
+ );
185
+ }
186
+
187
+ /** Right-aligned value with a proportional bar, for ranked numeric columns. */
188
+ export function MeterCell({
189
+ value,
190
+ max,
191
+ display,
192
+ color,
193
+ }: {
194
+ value: number;
195
+ max: number;
196
+ display: ReactNode;
197
+ color?: string;
198
+ }) {
199
+ const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0;
200
+ return (
201
+ <div className="meter-cell">
202
+ <span className="num">{display}</span>
203
+ <div className="meter">
204
+ <div className="meter-fill" style={{ width: `${pct}%`, background: color }} />
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+
210
+ /** Two-line cell: primary label and a dim secondary line. */
211
+ export function LabelCell({
212
+ primary,
213
+ secondary,
214
+ lead,
215
+ }: {
216
+ primary: ReactNode;
217
+ secondary?: ReactNode;
218
+ lead?: ReactNode;
219
+ }) {
220
+ return (
221
+ <div className="row" style={{ gap: 10 }}>
222
+ {lead}
223
+ <div className="stack" style={{ gap: 0 }}>
224
+ <span className="cell-primary truncate">{primary}</span>
225
+ {secondary !== undefined && <span className="cell-secondary truncate">{secondary}</span>}
226
+ </div>
227
+ </div>
228
+ );
229
+ }
@@ -1,11 +1,10 @@
1
- export * from "./AsyncBoundary";
2
- export * from "./DataTable";
3
- export * from "./EmptyState";
4
- export * from "./ErrorState";
1
+ export * from "./Badge";
2
+ export * from "./Card";
3
+ export * from "./Drawer";
5
4
  export * from "./JsonBlock";
6
- export * from "./MetricCluster";
7
- export * from "./Panel";
8
- export * from "./RequestDrawer";
9
- export * from "./SegmentedControl";
10
- export * from "./Skeleton";
11
- export * from "./StatusPill";
5
+ export * from "./Modal";
6
+ export * from "./SearchInput";
7
+ export * from "./Segmented";
8
+ export * from "./Stat";
9
+ export * from "./States";
10
+ export * from "./Table";
@@ -0,0 +1,54 @@
1
+ .modal-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 80;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ padding: 16px;
9
+ background: rgba(0, 0, 0, 0.55);
10
+ animation: fade var(--dur-2) var(--ease) both;
11
+ }
12
+
13
+ .modal {
14
+ width: min(480px, 100%);
15
+ border-radius: var(--r-panel);
16
+ border: 1px solid var(--line-2);
17
+ background: var(--overlay);
18
+ backdrop-filter: blur(20px) saturate(170%);
19
+ -webkit-backdrop-filter: blur(20px) saturate(170%);
20
+ box-shadow: var(--shadow-3);
21
+ animation: rise var(--dur-3) var(--ease-out) both;
22
+ overflow: hidden;
23
+ }
24
+
25
+ .modal-header {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ gap: 12px;
30
+ padding: 14px 14px 10px 18px;
31
+ }
32
+
33
+ .modal-title {
34
+ font-size: 15px;
35
+ font-weight: 600;
36
+ letter-spacing: -0.01em;
37
+ }
38
+
39
+ .modal-body {
40
+ padding: 4px 18px 16px;
41
+ color: var(--ink-2);
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 10px;
45
+ }
46
+
47
+ .modal-footer {
48
+ display: flex;
49
+ justify-content: flex-end;
50
+ gap: 8px;
51
+ padding: 12px 18px;
52
+ border-top: 1px solid var(--line-1);
53
+ background: color-mix(in srgb, var(--card) 60%, transparent);
54
+ }
@@ -0,0 +1,40 @@
1
+ /* Request detail drawer. */
2
+ .request-drawer-error {
3
+ --rd-tone: var(--bad);
4
+ --rd-tone-soft: var(--bad-soft);
5
+ padding: 10px 12px 12px;
6
+ border-radius: var(--r-control);
7
+ border: 1px solid color-mix(in srgb, var(--rd-tone) 35%, transparent);
8
+ background: var(--rd-tone-soft);
9
+ }
10
+
11
+ .request-drawer-error[data-tone="warn"] {
12
+ --rd-tone: var(--warn);
13
+ --rd-tone-soft: var(--warn-soft);
14
+ }
15
+
16
+ .request-drawer-error-label {
17
+ font-size: 12px;
18
+ font-weight: 550;
19
+ color: var(--rd-tone);
20
+ margin-bottom: 6px;
21
+ }
22
+
23
+ .request-drawer-error-text {
24
+ margin: 0;
25
+ font-family: var(--font-mono);
26
+ font-size: 12px;
27
+ line-height: 1.55;
28
+ color: var(--ink-1);
29
+ white-space: pre-wrap;
30
+ overflow-wrap: anywhere;
31
+ max-height: 240px;
32
+ overflow: auto;
33
+ }
34
+
35
+ .request-drawer-file {
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: 2px;
39
+ margin-top: 12px;
40
+ }