taskchef 7.23.3 → 7.24.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 (33) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/package.json +24 -2
  3. package/src/dashboard/app.js +2 -82
  4. package/src/dashboard/dist/app.js +164 -0
  5. package/src/dashboard/dist/app.js.map +1 -0
  6. package/src/dashboard/dist/index.html +16 -0
  7. package/src/dashboard/dist/styles.css +1 -0
  8. package/src/dashboard/react/App.tsx +432 -0
  9. package/src/dashboard/react/api.test.ts +24 -0
  10. package/src/dashboard/react/api.ts +95 -0
  11. package/src/dashboard/react/components/ActivityTimeline.tsx +63 -0
  12. package/src/dashboard/react/components/GitHubLinks.tsx +25 -0
  13. package/src/dashboard/react/components/NotificationCenter.test.tsx +71 -0
  14. package/src/dashboard/react/components/NotificationCenter.tsx +111 -0
  15. package/src/dashboard/react/components/RelativeTime.tsx +28 -0
  16. package/src/dashboard/react/components/ShimmerText.tsx +9 -0
  17. package/src/dashboard/react/components/StatusBadge.tsx +7 -0
  18. package/src/dashboard/react/components/TaskCard.tsx +62 -0
  19. package/src/dashboard/react/components/TaskDetail.test.tsx +62 -0
  20. package/src/dashboard/react/components/TaskDetail.tsx +251 -0
  21. package/src/dashboard/react/components/UsagePanel.tsx +41 -0
  22. package/src/dashboard/react/fixtures.ts +52 -0
  23. package/src/dashboard/react/index.html +15 -0
  24. package/src/dashboard/react/main.tsx +12 -0
  25. package/src/dashboard/react/overlay-state.test.ts +79 -0
  26. package/src/dashboard/react/overlay-state.ts +38 -0
  27. package/src/dashboard/react/presentation.test.tsx +128 -0
  28. package/src/dashboard/react/presentation.ts +80 -0
  29. package/src/dashboard/react/styles.css +123 -0
  30. package/src/dashboard/react/test-setup.ts +36 -0
  31. package/src/dashboard/react/types.ts +106 -0
  32. package/src/dashboard/styles.css +16 -3
  33. package/src/dashboard.js +29 -18
@@ -185,12 +185,25 @@ dialog section + section { margin-top: 24px; }
185
185
  .usage-pending, .usage-calculating { display: flex; align-items: center; }
186
186
  .text-shimmer {
187
187
  color: var(--muted);
188
+ display: inline-block;
189
+ width: fit-content;
190
+ max-width: 100%;
191
+ justify-self: start;
192
+ vertical-align: top;
193
+ background: linear-gradient(100deg, var(--muted) 48%, var(--text) 50%, var(--muted) 52%);
194
+ background-size: 250% 100%;
195
+ background-position: 100% 0;
196
+ background-clip: text;
197
+ -webkit-background-clip: text;
198
+ -webkit-text-fill-color: transparent;
199
+ animation: text-shimmer-sweep 6.4s linear infinite;
188
200
  }
189
- .text-shimmer-glyph {
190
- color: var(--muted);
201
+ @keyframes text-shimmer-sweep {
202
+ 0% { background-position: 100% 0; }
203
+ 72%, 100% { background-position: -100% 0; }
191
204
  }
192
205
  @media (prefers-reduced-motion: reduce) {
193
- .text-shimmer-glyph { color: currentcolor; text-shadow: none; }
206
+ .text-shimmer { background: none; animation: none; -webkit-text-fill-color: currentcolor; }
194
207
  }
195
208
  pre { max-height: 280px; margin: 0; padding: 14px; overflow: auto; border-radius: 7px; background: var(--surface-muted); white-space: pre-wrap; overflow-wrap: anywhere; font: 0.86rem/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
196
209
  .metadata { display: grid; grid-template-columns: minmax(100px, 150px) 1fr; margin: 0; font-size: 0.88rem; }
package/src/dashboard.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { randomBytes } from "node:crypto";
2
3
  import { constants, watch } from "node:fs";
3
4
  import { open, readFile, realpath, stat } from "node:fs/promises";
4
5
  import http from "node:http";
@@ -45,6 +46,7 @@ import {
45
46
 
46
47
  const TASKS_FILE_NAME = "tasks.jsonl";
47
48
  const STATIC_ROOT = fileURLToPath(new URL("./dashboard/", import.meta.url));
49
+ const DASHBOARD_BUNDLE_ROOT = path.join(STATIC_ROOT, "dist");
48
50
  const ASSET_ROOT = fileURLToPath(new URL("../assets/", import.meta.url));
49
51
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1"]);
50
52
  const DEFAULT_MAX_FILE_BYTES = 16 * 1024 * 1024;
@@ -58,26 +60,30 @@ const DASHBOARD_CONTROL_NONCE_TTL_MS = 5 * 60_000;
58
60
  const MAX_MANUAL_TRANSITION_BODY_BYTES = 4 * 1024;
59
61
  export const DASHBOARD_HEALTH_PATH = "/api/health";
60
62
  export const DASHBOARD_HEALTH_MAX_BYTES = 8 * 1024;
61
- const CONTENT_SECURITY_POLICY = [
62
- "default-src 'self'",
63
- "base-uri 'none'",
64
- "connect-src 'self'",
65
- "form-action 'none'",
66
- "frame-ancestors 'none'",
67
- "img-src 'self' data:",
68
- "object-src 'none'",
69
- "script-src 'self'",
70
- "style-src 'self'",
71
- ].join("; ");
63
+ function contentSecurityPolicy(styleNonce = null) {
64
+ return [
65
+ "default-src 'self'",
66
+ "base-uri 'none'",
67
+ "connect-src 'self'",
68
+ "form-action 'none'",
69
+ "frame-ancestors 'none'",
70
+ "img-src 'self' data:",
71
+ "object-src 'none'",
72
+ "script-src 'self'",
73
+ `style-src 'self'${styleNonce ? ` 'nonce-${styleNonce}'` : ""}`,
74
+ // Mantine uses style attributes for component variables and dynamic positioning.
75
+ "style-src-attr 'unsafe-inline'",
76
+ ].join("; ");
77
+ }
72
78
 
73
79
  const STATIC_FILES = new Map([
74
- ["/", [path.join(STATIC_ROOT, "index.html"), "text/html; charset=utf-8"]],
80
+ ["/", [path.join(DASHBOARD_BUNDLE_ROOT, "index.html"), "text/html; charset=utf-8"]],
75
81
  ["/actions.js", [path.join(STATIC_ROOT, "actions.js"), "text/javascript; charset=utf-8"]],
76
- ["/app.js", [path.join(STATIC_ROOT, "app.js"), "text/javascript; charset=utf-8"]],
82
+ ["/app.js", [path.join(DASHBOARD_BUNDLE_ROOT, "app.js"), "text/javascript; charset=utf-8"]],
77
83
  ["/github-links.js", [path.join(STATIC_ROOT, "github-links.js"), "text/javascript; charset=utf-8"]],
78
84
  ["/time.js", [path.join(STATIC_ROOT, "time.js"), "text/javascript; charset=utf-8"]],
79
85
  ["/state.js", [path.join(STATIC_ROOT, "state.js"), "text/javascript; charset=utf-8"]],
80
- ["/styles.css", [path.join(STATIC_ROOT, "styles.css"), "text/css; charset=utf-8"]],
86
+ ["/styles.css", [path.join(DASHBOARD_BUNDLE_ROOT, "styles.css"), "text/css; charset=utf-8"]],
81
87
  ["/assets/taskchef-dark.svg", [path.join(ASSET_ROOT, "taskchef-dark.svg"), "image/svg+xml"]],
82
88
  ["/assets/taskchef.svg", [path.join(ASSET_ROOT, "taskchef.svg"), "image/svg+xml"]],
83
89
  ["/assets/codex-app-dark.png", [path.join(ASSET_ROOT, "codex-app-dark.png"), "image/png"]],
@@ -401,10 +407,10 @@ export class DashboardMonitor extends EventEmitter {
401
407
  }
402
408
  }
403
409
 
404
- function securityHeaders(contentType) {
410
+ function securityHeaders(contentType, { styleNonce = null } = {}) {
405
411
  return {
406
412
  "Cache-Control": "no-store",
407
- "Content-Security-Policy": CONTENT_SECURITY_POLICY,
413
+ "Content-Security-Policy": contentSecurityPolicy(styleNonce),
408
414
  "Content-Type": contentType,
409
415
  "Cross-Origin-Opener-Policy": "same-origin",
410
416
  "Referrer-Policy": "no-referrer",
@@ -1175,8 +1181,13 @@ export async function createDashboardServer({
1175
1181
  return;
1176
1182
  }
1177
1183
  const [filePath, contentType] = staticFile;
1178
- const body = await readFile(filePath);
1179
- response.writeHead(200, securityHeaders(contentType));
1184
+ let body = await readFile(filePath);
1185
+ let styleNonce = null;
1186
+ if (url.pathname === "/") {
1187
+ styleNonce = randomBytes(18).toString("base64");
1188
+ body = Buffer.from(body.toString("utf8").replaceAll("__TASKCHEF_STYLE_NONCE__", styleNonce));
1189
+ }
1190
+ response.writeHead(200, securityHeaders(contentType, { styleNonce }));
1180
1191
  response.end(method === "HEAD" ? undefined : body);
1181
1192
  };
1182
1193