@clef-sh/ui 0.1.20 → 0.1.21

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 (103) hide show
  1. package/dist/client/assets/index-DPWHjBbB.js +34 -0
  2. package/dist/client/assets/index-qsLTYpc9.css +2 -0
  3. package/dist/client/clef.svg +2 -0
  4. package/dist/client/index.html +3 -31
  5. package/dist/client-lib/components/Button.d.ts +1 -1
  6. package/dist/client-lib/components/Button.d.ts.map +1 -1
  7. package/dist/client-lib/components/CopyButton.d.ts.map +1 -1
  8. package/dist/client-lib/components/EnvBadge.d.ts.map +1 -1
  9. package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -1
  10. package/dist/client-lib/components/Sidebar.d.ts +1 -1
  11. package/dist/client-lib/components/Sidebar.d.ts.map +1 -1
  12. package/dist/client-lib/components/StatusDot.d.ts.map +1 -1
  13. package/dist/client-lib/components/SyncPanel.d.ts.map +1 -1
  14. package/dist/client-lib/components/TopBar.d.ts +6 -0
  15. package/dist/client-lib/components/TopBar.d.ts.map +1 -1
  16. package/dist/client-lib/primitives/Badge.d.ts +11 -0
  17. package/dist/client-lib/primitives/Badge.d.ts.map +1 -0
  18. package/dist/client-lib/primitives/Card.d.ts +28 -0
  19. package/dist/client-lib/primitives/Card.d.ts.map +1 -0
  20. package/dist/client-lib/primitives/Dialog.d.ts +30 -0
  21. package/dist/client-lib/primitives/Dialog.d.ts.map +1 -0
  22. package/dist/client-lib/primitives/EmptyState.d.ts +10 -0
  23. package/dist/client-lib/primitives/EmptyState.d.ts.map +1 -0
  24. package/dist/client-lib/primitives/Field.d.ts +36 -0
  25. package/dist/client-lib/primitives/Field.d.ts.map +1 -0
  26. package/dist/client-lib/primitives/Input.d.ts +6 -0
  27. package/dist/client-lib/primitives/Input.d.ts.map +1 -0
  28. package/dist/client-lib/primitives/Stat.d.ts +11 -0
  29. package/dist/client-lib/primitives/Stat.d.ts.map +1 -0
  30. package/dist/client-lib/primitives/Table.d.ts +37 -0
  31. package/dist/client-lib/primitives/Table.d.ts.map +1 -0
  32. package/dist/client-lib/primitives/Tabs.d.ts +29 -0
  33. package/dist/client-lib/primitives/Tabs.d.ts.map +1 -0
  34. package/dist/client-lib/primitives/Toast.d.ts +16 -0
  35. package/dist/client-lib/primitives/Toast.d.ts.map +1 -0
  36. package/dist/client-lib/primitives/Toolbar.d.ts +29 -0
  37. package/dist/client-lib/primitives/Toolbar.d.ts.map +1 -0
  38. package/dist/client-lib/primitives/index.d.ts +23 -0
  39. package/dist/client-lib/primitives/index.d.ts.map +1 -0
  40. package/dist/client-lib/theme.d.ts +18 -41
  41. package/dist/client-lib/theme.d.ts.map +1 -1
  42. package/dist/server/api.d.ts.map +1 -1
  43. package/dist/server/api.js +215 -0
  44. package/dist/server/api.js.map +1 -1
  45. package/dist/server/envelope.d.ts +15 -0
  46. package/dist/server/envelope.d.ts.map +1 -0
  47. package/dist/server/envelope.js +310 -0
  48. package/dist/server/envelope.js.map +1 -0
  49. package/package.json +7 -2
  50. package/src/client/App.tsx +16 -41
  51. package/src/client/components/Button.tsx +13 -22
  52. package/src/client/components/CopyButton.tsx +5 -12
  53. package/src/client/components/EnvBadge.tsx +30 -15
  54. package/src/client/components/MatrixGrid.tsx +108 -252
  55. package/src/client/components/Sidebar.tsx +123 -199
  56. package/src/client/components/StatusDot.tsx +10 -15
  57. package/src/client/components/SyncPanel.tsx +14 -62
  58. package/src/client/components/TopBar.tsx +11 -36
  59. package/src/client/index.html +1 -30
  60. package/src/client/main.tsx +1 -0
  61. package/src/client/primitives/Badge.test.tsx +47 -0
  62. package/src/client/primitives/Badge.tsx +64 -0
  63. package/src/client/primitives/Card.test.tsx +50 -0
  64. package/src/client/primitives/Card.tsx +85 -0
  65. package/src/client/primitives/Dialog.test.tsx +55 -0
  66. package/src/client/primitives/Dialog.tsx +96 -0
  67. package/src/client/primitives/EmptyState.test.tsx +25 -0
  68. package/src/client/primitives/EmptyState.tsx +38 -0
  69. package/src/client/primitives/Field.test.tsx +46 -0
  70. package/src/client/primitives/Field.tsx +95 -0
  71. package/src/client/primitives/Input.tsx +26 -0
  72. package/src/client/primitives/Stat.test.tsx +32 -0
  73. package/src/client/primitives/Stat.tsx +52 -0
  74. package/src/client/primitives/Table.test.tsx +58 -0
  75. package/src/client/primitives/Table.tsx +113 -0
  76. package/src/client/primitives/Tabs.test.tsx +44 -0
  77. package/src/client/primitives/Tabs.tsx +100 -0
  78. package/src/client/primitives/Toast.test.tsx +77 -0
  79. package/src/client/primitives/Toast.tsx +89 -0
  80. package/src/client/primitives/Toolbar.test.tsx +50 -0
  81. package/src/client/primitives/Toolbar.tsx +86 -0
  82. package/src/client/primitives/index.ts +43 -0
  83. package/src/client/public/clef.svg +2 -0
  84. package/src/client/screens/BackendScreen.tsx +104 -363
  85. package/src/client/screens/DiffView.tsx +187 -378
  86. package/src/client/screens/EnvelopeScreen.test.tsx +542 -0
  87. package/src/client/screens/EnvelopeScreen.tsx +948 -0
  88. package/src/client/screens/GitLogView.tsx +48 -106
  89. package/src/client/screens/ImportScreen.tsx +105 -308
  90. package/src/client/screens/LintView.tsx +184 -379
  91. package/src/client/screens/ManifestScreen.tsx +283 -445
  92. package/src/client/screens/MatrixView.tsx +75 -91
  93. package/src/client/screens/NamespaceEditor.tsx +234 -609
  94. package/src/client/screens/PolicyView.tsx +183 -453
  95. package/src/client/screens/RecipientsScreen.tsx +71 -350
  96. package/src/client/screens/ResetScreen.tsx +67 -237
  97. package/src/client/screens/ScanScreen.tsx +85 -249
  98. package/src/client/screens/SchemaEditor.test.tsx +237 -0
  99. package/src/client/screens/SchemaEditor.tsx +435 -0
  100. package/src/client/screens/ServiceIdentitiesScreen.tsx +251 -788
  101. package/src/client/styles.css +77 -0
  102. package/src/client/theme.ts +27 -48
  103. package/dist/client/assets/index-Db6WgHgY.js +0 -38
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
- import { theme } from "../theme";
2
+ import { Hash } from "lucide-react";
3
3
  import { EnvBadge } from "./EnvBadge";
4
4
  import { StatusDot } from "./StatusDot";
5
+ import { Table } from "../primitives";
5
6
  import type { MatrixStatus } from "@clef-sh/core";
6
7
 
7
8
  export interface MatrixGridProps {
@@ -44,261 +45,116 @@ export function MatrixGrid({
44
45
  syncingNs,
45
46
  }: MatrixGridProps) {
46
47
  return (
47
- <div
48
- data-testid="matrix-table"
49
- style={{
50
- background: theme.surface,
51
- border: `1px solid ${theme.border}`,
52
- borderRadius: 12,
53
- overflow: "hidden",
54
- }}
55
- >
56
- {/* Header row */}
57
- <div
58
- style={{
59
- display: "grid",
60
- gridTemplateColumns: `180px ${environments.map(() => "1fr").join(" ")}`,
61
- borderBottom: `1px solid ${theme.border}`,
62
- background: "#0D0F14",
63
- }}
64
- >
65
- <div
66
- style={{
67
- padding: "12px 20px",
68
- fontFamily: theme.sans,
69
- fontSize: 11,
70
- fontWeight: 600,
71
- color: theme.textMuted,
72
- textTransform: "uppercase",
73
- letterSpacing: "0.08em",
74
- }}
75
- >
76
- Namespace
77
- </div>
78
- {environments.map((env) => (
79
- <div
80
- key={env.name}
81
- style={{
82
- padding: "12px 20px",
83
- display: "flex",
84
- alignItems: "center",
85
- gap: 8,
86
- borderLeft: `1px solid ${theme.border}`,
87
- }}
88
- >
89
- <EnvBadge env={env.name} />
90
- <span
91
- style={{
92
- fontFamily: theme.sans,
93
- fontSize: 12,
94
- fontWeight: 500,
95
- color: theme.text,
96
- }}
97
- >
98
- {env.name}
99
- </span>
100
- </div>
101
- ))}
102
- </div>
103
-
104
- {/* Namespace rows */}
105
- {namespaces.map((ns, i) => {
106
- const nsCells = matrixStatuses.filter((s) => s.cell.namespace === ns.name);
107
- const hasDrift = nsCells.some((s) =>
108
- s.issues.some((issue) => issue.type === "missing_keys"),
109
- );
110
-
111
- return (
112
- <div
113
- key={ns.name}
114
- data-testid={`matrix-row-${ns.name}`}
115
- role="button"
116
- tabIndex={0}
117
- onClick={() => onNamespaceClick?.(ns.name)}
118
- onKeyDown={(e) => {
119
- if (e.key === "Enter") onNamespaceClick?.(ns.name);
120
- }}
121
- // Cell-level clicks pass the environment; row-level is fallback
122
- style={{
123
- display: "grid",
124
- gridTemplateColumns: `180px ${environments.map(() => "1fr").join(" ")}`,
125
- borderBottom: i < namespaces.length - 1 ? `1px solid ${theme.border}` : "none",
126
- cursor: onNamespaceClick ? "pointer" : "default",
127
- transition: "background 0.1s",
128
- }}
129
- onMouseEnter={(e) => {
130
- (e.currentTarget as HTMLElement).style.background = theme.surfaceHover;
131
- }}
132
- onMouseLeave={(e) => {
133
- (e.currentTarget as HTMLElement).style.background = "transparent";
134
- }}
135
- >
136
- {/* Namespace label */}
137
- <div
138
- style={{
139
- padding: "16px 20px",
140
- display: "flex",
141
- alignItems: "center",
142
- gap: 10,
48
+ <Table data-testid="matrix-table">
49
+ <Table.Header>
50
+ <tr>
51
+ <Table.HeaderCell className="w-[180px]">Namespace</Table.HeaderCell>
52
+ {environments.map((env) => (
53
+ <Table.HeaderCell key={env.name} className="border-l border-edge">
54
+ <span className="inline-flex items-center gap-2">
55
+ <EnvBadge env={env.name} />
56
+ <span className="font-sans text-[12px] font-medium text-bone normal-case tracking-normal">
57
+ {env.name}
58
+ </span>
59
+ </span>
60
+ </Table.HeaderCell>
61
+ ))}
62
+ </tr>
63
+ </Table.Header>
64
+ <tbody>
65
+ {namespaces.map((ns) => {
66
+ const nsCells = matrixStatuses.filter((s) => s.cell.namespace === ns.name);
67
+ const hasDrift = nsCells.some((s) =>
68
+ s.issues.some((issue) => issue.type === "missing_keys"),
69
+ );
70
+ return (
71
+ <Table.Row
72
+ key={ns.name}
73
+ data-testid={`matrix-row-${ns.name}`}
74
+ role="button"
75
+ tabIndex={0}
76
+ interactive={Boolean(onNamespaceClick)}
77
+ tone={hasDrift ? "drift" : undefined}
78
+ onClick={() => onNamespaceClick?.(ns.name)}
79
+ onKeyDown={(e) => {
80
+ if (e.key === "Enter") onNamespaceClick?.(ns.name);
143
81
  }}
144
82
  >
145
- <span
146
- style={{
147
- fontFamily: theme.mono,
148
- fontSize: 11,
149
- color: theme.textDim,
150
- }}
151
- >
152
- //
153
- </span>
154
- <span
155
- style={{
156
- fontFamily: theme.mono,
157
- fontSize: 13,
158
- fontWeight: 600,
159
- color: theme.text,
160
- flex: 1,
161
- }}
162
- >
163
- {ns.name}
164
- </span>
165
- {hasDrift && syncingNs !== ns.name && onSyncClick && (
166
- <button
167
- data-testid={`sync-btn-${ns.name}`}
168
- onClick={(e) => {
169
- e.stopPropagation();
170
- onSyncClick(ns.name);
171
- }}
172
- style={{
173
- fontFamily: theme.sans,
174
- fontSize: 10,
175
- fontWeight: 600,
176
- color: theme.accent,
177
- background: `${theme.accent}18`,
178
- border: `1px solid ${theme.accent}33`,
179
- borderRadius: 4,
180
- padding: "2px 8px",
181
- cursor: "pointer",
182
- }}
183
- >
184
- Sync
185
- </button>
186
- )}
187
- </div>
188
-
189
- {/* Environment cells */}
190
- {environments.map((env) => {
191
- const cellStatus = matrixStatuses.find(
192
- (s) => s.cell.namespace === ns.name && s.cell.environment === env.name,
193
- );
194
- const statusType = cellStatus ? getStatusType(cellStatus) : "ok";
195
- const keyCount = cellStatus?.keyCount ?? 0;
196
- const lastMod = cellStatus?.lastModified
197
- ? formatDate(
198
- cellStatus.lastModified instanceof Date
199
- ? cellStatus.lastModified
200
- : new Date(cellStatus.lastModified as unknown as string),
201
- )
202
- : "never";
203
- const missingKeyCount = cellStatus
204
- ? new Set(
205
- cellStatus.issues
206
- .filter((i) => i.type === "missing_keys" && i.key)
207
- .map((i) => i.key),
208
- ).size
209
- : 0;
210
- const warnKeyCount = cellStatus
211
- ? cellStatus.issues.filter((i) => i.type === "schema_warning").length
212
- : 0;
213
- const cellPending = cellStatus?.pendingCount ?? 0;
214
-
215
- return (
216
- <div
217
- key={env.name}
218
- onClick={(e) => {
219
- e.stopPropagation();
220
- onNamespaceClick?.(ns.name, env.name);
221
- }}
222
- style={{
223
- padding: "14px 20px",
224
- borderLeft: `1px solid ${theme.border}`,
225
- display: "flex",
226
- flexDirection: "column",
227
- gap: 5,
228
- }}
229
- >
230
- <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
231
- <StatusDot status={statusType} />
232
- <span
233
- style={{
234
- fontFamily: theme.mono,
235
- fontSize: 11,
236
- color: theme.textMuted,
83
+ <Table.Cell className="px-5 py-4">
84
+ <div className="flex items-center gap-2.5">
85
+ <Hash size={11} strokeWidth={1.75} className="text-ash-deep" aria-hidden="true" />
86
+ <span className="flex-1 font-mono text-[13px] font-semibold text-bone">
87
+ {ns.name}
88
+ </span>
89
+ {hasDrift && syncingNs !== ns.name && onSyncClick && (
90
+ <button
91
+ data-testid={`sync-btn-${ns.name}`}
92
+ onClick={(e) => {
93
+ e.stopPropagation();
94
+ onSyncClick(ns.name);
237
95
  }}
96
+ className="rounded border border-gold-500/30 bg-gold-500/10 px-2 py-0.5 font-sans text-[10px] font-semibold text-gold-500 cursor-pointer hover:bg-gold-500/20"
238
97
  >
239
- {keyCount} keys
240
- </span>
241
- {missingKeyCount > 0 && (
242
- <span
243
- style={{
244
- fontFamily: theme.mono,
245
- fontSize: 10,
246
- color: theme.red,
247
- background: theme.redDim,
248
- border: `1px solid ${theme.red}33`,
249
- borderRadius: 3,
250
- padding: "1px 5px",
251
- }}
252
- >
253
- -{missingKeyCount} missing
254
- </span>
255
- )}
256
- {warnKeyCount > 0 && (
257
- <span
258
- style={{
259
- fontFamily: theme.mono,
260
- fontSize: 10,
261
- color: theme.yellow,
262
- background: theme.yellowDim,
263
- border: `1px solid ${theme.yellow}33`,
264
- borderRadius: 3,
265
- padding: "1px 5px",
266
- }}
267
- >
268
- {warnKeyCount} warn
269
- </span>
270
- )}
271
- {cellPending > 0 && (
272
- <span
273
- style={{
274
- fontFamily: theme.mono,
275
- fontSize: 10,
276
- color: theme.accent,
277
- background: `${theme.accent}18`,
278
- border: `1px solid ${theme.accent}33`,
279
- borderRadius: 3,
280
- padding: "1px 5px",
281
- }}
282
- >
283
- {cellPending} pending
284
- </span>
285
- )}
286
- </div>
287
- <div
288
- style={{
289
- fontFamily: theme.mono,
290
- fontSize: 10,
291
- color: theme.textDim,
292
- }}
293
- >
294
- {lastMod}
295
- </div>
98
+ Sync
99
+ </button>
100
+ )}
296
101
  </div>
297
- );
298
- })}
299
- </div>
300
- );
301
- })}
302
- </div>
102
+ </Table.Cell>
103
+ {environments.map((env) => {
104
+ const cellStatus = matrixStatuses.find(
105
+ (s) => s.cell.namespace === ns.name && s.cell.environment === env.name,
106
+ );
107
+ const statusType = cellStatus ? getStatusType(cellStatus) : "ok";
108
+ const keyCount = cellStatus?.keyCount ?? 0;
109
+ const lastMod = cellStatus?.lastModified
110
+ ? formatDate(
111
+ cellStatus.lastModified instanceof Date
112
+ ? cellStatus.lastModified
113
+ : new Date(cellStatus.lastModified as unknown as string),
114
+ )
115
+ : "never";
116
+ const missingKeyCount = cellStatus
117
+ ? new Set(
118
+ cellStatus.issues
119
+ .filter((i) => i.type === "missing_keys" && i.key)
120
+ .map((i) => i.key),
121
+ ).size
122
+ : 0;
123
+ const warnKeyCount = cellStatus
124
+ ? cellStatus.issues.filter((i) => i.type === "schema_warning").length
125
+ : 0;
126
+ const cellPending = cellStatus?.pendingCount ?? 0;
127
+ return (
128
+ <Table.Cell key={env.name} className="border-l border-edge px-5 py-3.5">
129
+ <div className="flex flex-col gap-1.5">
130
+ <div className="flex items-center gap-1.5">
131
+ <StatusDot status={statusType} />
132
+ <span className="font-mono text-[11px] text-ash">{keyCount} keys</span>
133
+ {missingKeyCount > 0 && (
134
+ <span className="rounded-sm border border-stop-500/20 bg-stop-500/10 px-1.5 py-px font-mono text-[10px] text-stop-500">
135
+ -{missingKeyCount} missing
136
+ </span>
137
+ )}
138
+ {warnKeyCount > 0 && (
139
+ <span className="rounded-sm border border-warn-500/20 bg-warn-500/10 px-1.5 py-px font-mono text-[10px] text-warn-500">
140
+ {warnKeyCount} warn
141
+ </span>
142
+ )}
143
+ {cellPending > 0 && (
144
+ <span className="rounded-sm border border-gold-500/20 bg-gold-500/10 px-1.5 py-px font-mono text-[10px] text-gold-500">
145
+ {cellPending} pending
146
+ </span>
147
+ )}
148
+ </div>
149
+ <div className="font-mono text-[10px] text-ash-deep">{lastMod}</div>
150
+ </div>
151
+ </Table.Cell>
152
+ );
153
+ })}
154
+ </Table.Row>
155
+ );
156
+ })}
157
+ </tbody>
158
+ </Table>
303
159
  );
304
160
  }