@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
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerEnvelopeRoutes = registerEnvelopeRoutes;
37
+ const fs = __importStar(require("fs"));
38
+ const core_1 = require("@clef-sh/core");
39
+ const runtime_1 = require("@clef-sh/runtime");
40
+ // Source label for all UI-initiated artifact inspections — makes it obvious
41
+ // in logs that the artifact came from a pasted input, not a file or URL.
42
+ const UI_SOURCE = "paste";
43
+ function setNoCacheHeaders(res) {
44
+ res.set({
45
+ "Cache-Control": "no-store, no-cache, must-revalidate",
46
+ Pragma: "no-cache",
47
+ Expires: "0",
48
+ });
49
+ }
50
+ // Precedence for the envelope debugger:
51
+ // 1. $CLEF_AGE_KEY (explicit env override — inline)
52
+ // 2. $CLEF_AGE_KEY_FILE (explicit env override — file path)
53
+ // 3. deps.ageKey (from CLI's resolveAgeCredential — may be keychain)
54
+ // 4. deps.ageKeyFile (from CLI's resolveAgeCredential — may be .clef/config.yaml)
55
+ //
56
+ // Env vars deliberately win over deps. The CLI's credential resolver
57
+ // prefers the OS keychain when configured, so without this override an
58
+ // operator who sets `CLEF_AGE_KEY_FILE=svc.key clef ui` to debug a
59
+ // service-identity-packed envelope would see their keychain key used
60
+ // instead — silently. Making env vars authoritative for the debugger
61
+ // lets operators target a specific identity without affecting the rest
62
+ // of `clef ui`.
63
+ function resolveAgeIdentity(deps) {
64
+ const envInline = process.env.CLEF_AGE_KEY;
65
+ if (envInline) {
66
+ return { configured: true, source: "CLEF_AGE_KEY", path: null };
67
+ }
68
+ const envFile = process.env.CLEF_AGE_KEY_FILE;
69
+ if (envFile) {
70
+ return { configured: true, source: "CLEF_AGE_KEY_FILE", path: envFile };
71
+ }
72
+ if (deps.ageKey) {
73
+ return { configured: true, source: "CLEF_AGE_KEY", path: null };
74
+ }
75
+ if (deps.ageKeyFile) {
76
+ return { configured: true, source: "CLEF_AGE_KEY_FILE", path: deps.ageKeyFile };
77
+ }
78
+ return { configured: false, source: null, path: null };
79
+ }
80
+ function loadAgePrivateKey(deps) {
81
+ const ageDecryptor = new runtime_1.AgeDecryptor();
82
+ // Same precedence as resolveAgeIdentity — env vars win over deps.
83
+ if (process.env.CLEF_AGE_KEY) {
84
+ return ageDecryptor.resolveKey(process.env.CLEF_AGE_KEY);
85
+ }
86
+ if (process.env.CLEF_AGE_KEY_FILE) {
87
+ return ageDecryptor.resolveKey(undefined, process.env.CLEF_AGE_KEY_FILE);
88
+ }
89
+ if (deps.ageKey)
90
+ return ageDecryptor.resolveKey(deps.ageKey);
91
+ if (deps.ageKeyFile)
92
+ return ageDecryptor.resolveKey(undefined, deps.ageKeyFile);
93
+ throw new Error("No age identity configured on the server. " +
94
+ "Set CLEF_AGE_KEY_FILE or CLEF_AGE_KEY before launching `clef ui`.");
95
+ }
96
+ function registerEnvelopeRoutes(router, deps) {
97
+ // POST /api/envelope/inspect
98
+ router.post("/envelope/inspect", (req, res) => {
99
+ setNoCacheHeaders(res);
100
+ const raw = (req.body ?? {}).raw;
101
+ const verifyHash = (req.body ?? {}).verifyHash;
102
+ if (typeof raw !== "string") {
103
+ res.status(400).json({ error: "raw is required", code: "BAD_REQUEST" });
104
+ return;
105
+ }
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(raw);
109
+ }
110
+ catch (err) {
111
+ res.json((0, core_1.buildInspectError)(UI_SOURCE, "parse_failed", err.message));
112
+ return;
113
+ }
114
+ try {
115
+ (0, core_1.assertPackedArtifact)(parsed);
116
+ }
117
+ catch (err) {
118
+ const message = err instanceof core_1.InvalidArtifactError ? err.message : err.message;
119
+ res.json((0, core_1.buildInspectError)(UI_SOURCE, "invalid_artifact", message));
120
+ return;
121
+ }
122
+ const artifact = parsed;
123
+ const hashOk = verifyHash === false
124
+ ? true
125
+ : (0, core_1.computeCiphertextHash)(artifact.ciphertext) === artifact.ciphertextHash;
126
+ res.json((0, core_1.buildInspectResult)(UI_SOURCE, artifact, hashOk));
127
+ });
128
+ // POST /api/envelope/verify
129
+ router.post("/envelope/verify", (req, res) => {
130
+ setNoCacheHeaders(res);
131
+ const raw = (req.body ?? {}).raw;
132
+ const signerKey = (req.body ?? {}).signerKey;
133
+ if (typeof raw !== "string") {
134
+ res.status(400).json({ error: "raw is required", code: "BAD_REQUEST" });
135
+ return;
136
+ }
137
+ if (signerKey !== undefined && typeof signerKey !== "string") {
138
+ res.status(400).json({ error: "signerKey must be a string", code: "BAD_REQUEST" });
139
+ return;
140
+ }
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(raw);
144
+ }
145
+ catch (err) {
146
+ res.json((0, core_1.buildVerifyError)(UI_SOURCE, "parse_failed", err.message));
147
+ return;
148
+ }
149
+ try {
150
+ (0, core_1.assertPackedArtifact)(parsed);
151
+ }
152
+ catch (err) {
153
+ const message = err instanceof core_1.InvalidArtifactError ? err.message : err.message;
154
+ res.json((0, core_1.buildVerifyError)(UI_SOURCE, "invalid_artifact", message));
155
+ return;
156
+ }
157
+ const artifact = parsed;
158
+ const hashStatus = (0, core_1.computeCiphertextHash)(artifact.ciphertext) === artifact.ciphertextHash ? "ok" : "mismatch";
159
+ const signature = {
160
+ status: "absent",
161
+ algorithm: artifact.signatureAlgorithm ?? null,
162
+ };
163
+ if (typeof artifact.signature === "string") {
164
+ if (signerKey) {
165
+ let signerKeyBase64;
166
+ try {
167
+ // UI is paste-only: no file paths accepted (D4).
168
+ signerKeyBase64 = (0, core_1.parseSignerKey)(signerKey);
169
+ }
170
+ catch (err) {
171
+ res.json((0, core_1.buildVerifyError)(UI_SOURCE, "signer_key_invalid", err.message));
172
+ return;
173
+ }
174
+ const payload = (0, core_1.buildSigningPayload)(artifact);
175
+ try {
176
+ signature.status = (0, core_1.verifySignature)(payload, artifact.signature, signerKeyBase64)
177
+ ? "valid"
178
+ : "invalid";
179
+ }
180
+ catch (err) {
181
+ signature.status = "invalid";
182
+ signature.algorithm = `error: ${err.message}`;
183
+ }
184
+ }
185
+ else {
186
+ signature.status = "not_verified";
187
+ }
188
+ }
189
+ const now = Date.now();
190
+ let expiry;
191
+ if (artifact.expiresAt) {
192
+ const expired = new Date(artifact.expiresAt).getTime() < now;
193
+ expiry = { status: expired ? "expired" : "ok", expiresAt: artifact.expiresAt };
194
+ }
195
+ else {
196
+ expiry = { status: "absent", expiresAt: null };
197
+ }
198
+ const revocation = artifact.revokedAt
199
+ ? { status: "revoked", revokedAt: artifact.revokedAt }
200
+ : { status: "absent", revokedAt: null };
201
+ const inputs = { hash: hashStatus, signature, expiry, revocation };
202
+ res.json((0, core_1.buildVerifyResult)(UI_SOURCE, inputs));
203
+ });
204
+ // POST /api/envelope/decrypt
205
+ router.post("/envelope/decrypt", async (req, res) => {
206
+ setNoCacheHeaders(res);
207
+ const raw = (req.body ?? {}).raw;
208
+ const reveal = (req.body ?? {}).reveal === true;
209
+ const key = (req.body ?? {}).key;
210
+ if (typeof raw !== "string") {
211
+ res.status(400).json({ error: "raw is required", code: "BAD_REQUEST" });
212
+ return;
213
+ }
214
+ if (key !== undefined && typeof key !== "string") {
215
+ res.status(400).json({ error: "key must be a string", code: "BAD_REQUEST" });
216
+ return;
217
+ }
218
+ if (reveal && key) {
219
+ res.status(400).json({
220
+ error: "reveal and key are mutually exclusive; pick one",
221
+ code: "BAD_REQUEST",
222
+ });
223
+ return;
224
+ }
225
+ let parsed;
226
+ try {
227
+ parsed = JSON.parse(raw);
228
+ }
229
+ catch (err) {
230
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "parse_failed", err.message));
231
+ return;
232
+ }
233
+ try {
234
+ (0, core_1.assertPackedArtifact)(parsed);
235
+ }
236
+ catch (err) {
237
+ const message = err instanceof core_1.InvalidArtifactError ? err.message : err.message;
238
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "invalid_artifact", message));
239
+ return;
240
+ }
241
+ const artifact = parsed;
242
+ if ((0, core_1.computeCiphertextHash)(artifact.ciphertext) !== artifact.ciphertextHash) {
243
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "hash_mismatch", "ciphertext hash does not match declared value"));
244
+ return;
245
+ }
246
+ if (artifact.expiresAt && new Date(artifact.expiresAt).getTime() < Date.now()) {
247
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "expired", `artifact expired at ${artifact.expiresAt}`));
248
+ return;
249
+ }
250
+ if (artifact.revokedAt) {
251
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "revoked", `artifact was revoked at ${artifact.revokedAt}`));
252
+ return;
253
+ }
254
+ // Age identity is resolved from server environment only. Clients never
255
+ // send keys. For KMS-enveloped artifacts the private key is not used —
256
+ // ArtifactDecryptor reads ambient AWS credentials internally.
257
+ let privateKey;
258
+ if (!artifact.envelope) {
259
+ try {
260
+ privateKey = loadAgePrivateKey(deps);
261
+ }
262
+ catch (err) {
263
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "key_resolution_failed", err.message));
264
+ return;
265
+ }
266
+ }
267
+ let values;
268
+ try {
269
+ const decryptor = new runtime_1.ArtifactDecryptor({ privateKey });
270
+ ({ values } = await decryptor.decrypt(artifact));
271
+ }
272
+ catch (err) {
273
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "decrypt_failed", err.message));
274
+ return;
275
+ }
276
+ const keys = Object.keys(values);
277
+ if (key) {
278
+ if (!(key in values)) {
279
+ res.json((0, core_1.buildDecryptError)(UI_SOURCE, "unknown_key", `key "${key}" not present in decrypted payload`));
280
+ return;
281
+ }
282
+ res.json((0, core_1.buildDecryptResult)(UI_SOURCE, { keys, singleKey: { name: key, value: values[key] } }));
283
+ return;
284
+ }
285
+ if (reveal) {
286
+ res.json((0, core_1.buildDecryptResult)(UI_SOURCE, { keys, allValues: values }));
287
+ return;
288
+ }
289
+ res.json((0, core_1.buildDecryptResult)(UI_SOURCE, { keys }));
290
+ });
291
+ // GET /api/envelope/config — tells the client which server-side identity
292
+ // will be used for decrypt, so operators can diagnose key_resolution_failed.
293
+ // Does NOT return key material, only metadata (env var name + optional path).
294
+ router.get("/envelope/config", (_req, res) => {
295
+ setNoCacheHeaders(res);
296
+ const ageIdentity = resolveAgeIdentity(deps);
297
+ const awsProfile = process.env.AWS_PROFILE ?? null;
298
+ const hasCredentials = !!process.env.AWS_PROFILE ||
299
+ !!process.env.AWS_ACCESS_KEY_ID ||
300
+ !!process.env.AWS_ROLE_ARN ||
301
+ !!process.env.AWS_WEB_IDENTITY_TOKEN_FILE ||
302
+ // Well-known shared credentials file — if present assume AWS SDK will find creds.
303
+ (!!process.env.HOME && fs.existsSync(`${process.env.HOME}/.aws/credentials`));
304
+ res.json({
305
+ ageIdentity,
306
+ aws: { hasCredentials, profile: awsProfile },
307
+ });
308
+ });
309
+ }
310
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/server/envelope.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clef-sh/ui",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Local web UI for Clef — git-native secrets management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,12 +34,16 @@
34
34
  "test:coverage": "jest --coverage"
35
35
  },
36
36
  "dependencies": {
37
- "@clef-sh/core": "0.1.20",
37
+ "@clef-sh/core": "0.1.21",
38
+ "@clef-sh/design": "0.1.1",
39
+ "@clef-sh/runtime": "0.1.24",
38
40
  "express": "^5.1.0",
39
41
  "express-rate-limit": "^8.3.2",
42
+ "lucide-react": "^0.468.0",
40
43
  "yaml": "^2.8.3"
41
44
  },
42
45
  "devDependencies": {
46
+ "@tailwindcss/vite": "^4.1.0",
43
47
  "@testing-library/jest-dom": "^6.4.0",
44
48
  "@testing-library/react": "^14.2.0",
45
49
  "@testing-library/user-event": "^14.5.0",
@@ -55,6 +59,7 @@
55
59
  "react": "^18.2.0",
56
60
  "react-dom": "^18.2.0",
57
61
  "supertest": "^7.2.2",
62
+ "tailwindcss": "^4.1.0",
58
63
  "ts-jest": "^29.4.9",
59
64
  "typescript": "^5.9.3",
60
65
  "vite": "^8.0.9"
@@ -1,9 +1,9 @@
1
1
  import { useState, useEffect, useCallback } from "react";
2
- import { theme } from "./theme";
3
2
  import { apiFetch } from "./api";
4
3
  import { Sidebar, ViewName } from "./components/Sidebar";
5
4
  import { MatrixView } from "./screens/MatrixView";
6
5
  import { NamespaceEditor } from "./screens/NamespaceEditor";
6
+ import { SchemaEditor } from "./screens/SchemaEditor";
7
7
  import { DiffView } from "./screens/DiffView";
8
8
  import { LintView } from "./screens/LintView";
9
9
  import { ScanScreen } from "./screens/ScanScreen";
@@ -15,6 +15,7 @@ import { ServiceIdentitiesScreen } from "./screens/ServiceIdentitiesScreen";
15
15
  import { BackendScreen } from "./screens/BackendScreen";
16
16
  import { ResetScreen } from "./screens/ResetScreen";
17
17
  import { GitLogView } from "./screens/GitLogView";
18
+ import { EnvelopeScreen } from "./screens/EnvelopeScreen";
18
19
  import type { ClefManifest, MatrixStatus, GitStatus, LintResult } from "@clef-sh/core";
19
20
 
20
21
  export default function App() {
@@ -124,44 +125,23 @@ export default function App() {
124
125
 
125
126
  if (loading) {
126
127
  return (
127
- <div
128
- style={{
129
- display: "flex",
130
- alignItems: "center",
131
- justifyContent: "center",
132
- height: "100vh",
133
- background: theme.bg,
134
- color: theme.textMuted,
135
- fontFamily: theme.sans,
136
- }}
137
- >
138
- <div style={{ textAlign: "center" }}>
139
- <div
140
- style={{
141
- fontSize: 24,
142
- color: theme.accent,
143
- marginBottom: 12,
144
- }}
145
- >
146
- {"\u266A"}
147
- </div>
148
- <div style={{ fontSize: 13 }}>Loading...</div>
128
+ <div className="flex h-screen items-center justify-center bg-ink-950 font-sans text-ash">
129
+ <div className="text-center">
130
+ <img
131
+ src="/clef.svg"
132
+ alt=""
133
+ width="20"
134
+ height="44"
135
+ className="mx-auto mb-3 [filter:drop-shadow(0_0_10px_rgba(240,165,0,0.35))]"
136
+ />
137
+ <div className="text-[13px]">Loading...</div>
149
138
  </div>
150
139
  </div>
151
140
  );
152
141
  }
153
142
 
154
143
  return (
155
- <div
156
- style={{
157
- display: "flex",
158
- height: "100vh",
159
- background: theme.bg,
160
- color: theme.text,
161
- fontFamily: theme.sans,
162
- overflow: "hidden",
163
- }}
164
- >
144
+ <div className="flex h-screen overflow-hidden bg-ink-950 font-sans text-bone">
165
145
  <Sidebar
166
146
  activeView={view}
167
147
  setView={setView}
@@ -175,14 +155,7 @@ export default function App() {
175
155
  policyOverdueCount={policyOverdueCount}
176
156
  />
177
157
 
178
- <div
179
- style={{
180
- flex: 1,
181
- display: "flex",
182
- flexDirection: "column",
183
- overflow: "hidden",
184
- }}
185
- >
158
+ <div className="flex flex-1 flex-col overflow-hidden">
186
159
  {view === "matrix" && (
187
160
  <MatrixView
188
161
  setView={setView}
@@ -196,6 +169,7 @@ export default function App() {
196
169
  {view === "editor" && (
197
170
  <NamespaceEditor ns={activeNs} initialEnv={activeEnv} manifest={manifest} />
198
171
  )}
172
+ {view === "schema" && <SchemaEditor ns={activeNs} manifest={manifest} />}
199
173
  {view === "diff" && <DiffView manifest={manifest} />}
200
174
  {view === "lint" && <LintView setView={setView} setNs={setActiveNs} />}
201
175
  {view === "scan" && <ScanScreen />}
@@ -213,6 +187,7 @@ export default function App() {
213
187
  {view === "manifest" && (
214
188
  <ManifestScreen manifest={manifest} reloadManifest={loadManifest} />
215
189
  )}
190
+ {view === "envelope" && <EnvelopeScreen />}
216
191
  </div>
217
192
  </div>
218
193
  );
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import { theme } from "../theme";
3
2
 
4
3
  interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
5
4
  children: React.ReactNode;
@@ -8,10 +7,10 @@ interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>
8
7
  type?: "button" | "submit";
9
8
  }
10
9
 
11
- const VARIANT_STYLES = {
12
- primary: { bg: theme.accent, color: "#000", border: "none" },
13
- ghost: { bg: "transparent", color: theme.text, border: `1px solid ${theme.borderLight}` },
14
- danger: { bg: theme.redDim, color: theme.red, border: `1px solid ${theme.red}44` },
10
+ const VARIANT_CLASSES: Record<NonNullable<ButtonProps["variant"]>, string> = {
11
+ primary: "bg-gold-500 text-ink-950 border border-transparent hover:bg-gold-400",
12
+ ghost: "bg-transparent text-bone border border-edge-strong hover:bg-ink-800",
13
+ danger: "bg-stop-500/15 text-stop-500 border border-stop-500/40 hover:bg-stop-500/25",
15
14
  };
16
15
 
17
16
  export function Button({
@@ -20,32 +19,24 @@ export function Button({
20
19
  onClick,
21
20
  icon,
22
21
  type = "button",
22
+ className,
23
23
  style: _styleProp,
24
24
  ...rest
25
25
  }: ButtonProps) {
26
- const s = VARIANT_STYLES[variant];
27
26
  return (
28
27
  <button
29
28
  type={type}
30
29
  onClick={onClick}
31
30
  {...rest}
32
- style={{
33
- display: "flex",
34
- alignItems: "center",
35
- gap: 6,
36
- padding: "5px 12px",
37
- borderRadius: 6,
38
- cursor: "pointer",
39
- fontFamily: theme.sans,
40
- fontSize: 12,
41
- fontWeight: 600,
42
- background: s.bg,
43
- color: s.color,
44
- border: s.border,
45
- transition: "all 0.12s",
46
- }}
31
+ className={[
32
+ "inline-flex items-center gap-1.5 rounded-md px-3 py-1 font-sans text-[12px] font-semibold cursor-pointer transition-colors disabled:cursor-not-allowed disabled:opacity-60",
33
+ VARIANT_CLASSES[variant],
34
+ className ?? "",
35
+ ]
36
+ .join(" ")
37
+ .trim()}
47
38
  >
48
- {icon && <span style={{ display: "flex" }}>{icon}</span>}
39
+ {icon && <span className="flex">{icon}</span>}
49
40
  {children}
50
41
  </button>
51
42
  );
@@ -1,5 +1,4 @@
1
1
  import React, { useState, useCallback } from "react";
2
- import { theme } from "../theme";
3
2
 
4
3
  interface CopyButtonProps {
5
4
  text: string;
@@ -18,17 +17,11 @@ export function CopyButton({ text }: CopyButtonProps) {
18
17
  <button
19
18
  data-testid="copy-button"
20
19
  onClick={handleCopy}
21
- style={{
22
- background: copied ? theme.greenDim : "none",
23
- border: `1px solid ${copied ? theme.green + "55" : theme.borderLight}`,
24
- borderRadius: 4,
25
- cursor: "pointer",
26
- color: copied ? theme.green : theme.textDim,
27
- fontFamily: theme.mono,
28
- fontSize: 10,
29
- padding: "2px 8px",
30
- transition: "all 0.15s",
31
- }}
20
+ className={`cursor-pointer rounded-sm border px-2 py-0.5 font-mono text-[10px] transition-colors ${
21
+ copied
22
+ ? "border-go-500/40 bg-go-500/10 text-go-500"
23
+ : "border-edge-strong bg-transparent text-ash-dim hover:bg-ink-800"
24
+ }`}
32
25
  >
33
26
  {copied ? "copied!" : "copy"}
34
27
  </button>
@@ -1,30 +1,45 @@
1
1
  import React from "react";
2
- import { theme, ENV_COLORS } from "../theme";
3
2
 
4
3
  interface EnvBadgeProps {
5
4
  env: string;
6
5
  small?: boolean;
7
6
  }
8
7
 
8
+ // Per-env color set. Mirrors `ENV_COLORS` from the design tokens but expressed
9
+ // as Tailwind class strings so we don't pay the inline-style cost. The unknown
10
+ // fallback ("ash") is also handled here.
11
+ const ENV_CLASSES: Record<string, { text: string; bg: string; border: string; label: string }> = {
12
+ dev: {
13
+ text: "text-go-500",
14
+ bg: "bg-go-500/10",
15
+ border: "border-go-500/20",
16
+ label: "DEV",
17
+ },
18
+ staging: {
19
+ text: "text-warn-500",
20
+ bg: "bg-warn-500/10",
21
+ border: "border-warn-500/20",
22
+ label: "STG",
23
+ },
24
+ production: {
25
+ text: "text-stop-500",
26
+ bg: "bg-stop-500/10",
27
+ border: "border-stop-500/20",
28
+ label: "PRD",
29
+ },
30
+ };
31
+
9
32
  export function EnvBadge({ env, small }: EnvBadgeProps) {
10
- const c = ENV_COLORS[env] ?? {
11
- color: theme.textMuted,
12
- bg: "transparent",
33
+ const c = ENV_CLASSES[env] ?? {
34
+ text: "text-ash",
35
+ bg: "bg-transparent",
36
+ border: "border-ash/20",
13
37
  label: env.toUpperCase().slice(0, 3),
14
38
  };
39
+ const sizeClasses = small ? "text-[9px] px-1.5 py-px" : "text-[10px] px-2 py-0.5";
15
40
  return (
16
41
  <span
17
- style={{
18
- fontFamily: theme.mono,
19
- fontSize: small ? "9px" : "10px",
20
- fontWeight: 700,
21
- color: c.color,
22
- background: c.bg,
23
- border: `1px solid ${c.color}33`,
24
- borderRadius: "3px",
25
- padding: small ? "1px 5px" : "2px 7px",
26
- letterSpacing: "0.08em",
27
- }}
42
+ className={`inline-block rounded-sm border font-mono font-bold tracking-[0.08em] ${c.text} ${c.bg} ${c.border} ${sizeClasses}`}
28
43
  >
29
44
  {c.label}
30
45
  </span>