patchwarden 0.6.1 → 1.1.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 (261) hide show
  1. package/PatchWarden-Control-Tray.cmd +11 -0
  2. package/PatchWarden-Control.cmd +6 -0
  3. package/PatchWarden-Desktop.cmd +5 -0
  4. package/PatchWarden.cmd +1 -1
  5. package/README.en.md +112 -24
  6. package/README.md +36 -25
  7. package/Restart-PatchWarden-Control.cmd +6 -0
  8. package/Stop-PatchWarden.cmd +11 -0
  9. package/dist/agents/agentRouter.d.ts +40 -0
  10. package/dist/agents/agentRouter.js +95 -0
  11. package/dist/config.d.ts +1 -1
  12. package/dist/config.js +6 -2
  13. package/dist/controlCenter.d.ts +14 -0
  14. package/dist/controlCenter.js +2015 -0
  15. package/dist/doctor.js +35 -4
  16. package/dist/goal/acceptanceEngine.d.ts +62 -0
  17. package/dist/goal/acceptanceEngine.js +103 -0
  18. package/dist/goal/acceptanceTemplate.d.ts +10 -0
  19. package/dist/goal/acceptanceTemplate.js +104 -0
  20. package/dist/goal/goalGraph.d.ts +58 -0
  21. package/dist/goal/goalGraph.js +189 -0
  22. package/dist/goal/goalProgress.d.ts +81 -0
  23. package/dist/goal/goalProgress.js +216 -0
  24. package/dist/goal/goalStatus.d.ts +60 -0
  25. package/dist/goal/goalStatus.js +137 -0
  26. package/dist/goal/goalStore.d.ts +79 -0
  27. package/dist/goal/goalStore.js +211 -0
  28. package/dist/goal/handoffExport.d.ts +34 -0
  29. package/dist/goal/handoffExport.js +183 -0
  30. package/dist/goal/subgoalSync.d.ts +40 -0
  31. package/dist/goal/subgoalSync.js +72 -0
  32. package/dist/goal/worktreeManager.d.ts +96 -0
  33. package/dist/goal/worktreeManager.js +292 -0
  34. package/dist/logging.d.ts +44 -0
  35. package/dist/logging.js +68 -0
  36. package/dist/release/releaseGate.d.ts +99 -0
  37. package/dist/release/releaseGate.js +475 -0
  38. package/dist/runner/postTaskCleanup.d.ts +13 -0
  39. package/dist/runner/postTaskCleanup.js +147 -0
  40. package/dist/runner/runTask.js +50 -10
  41. package/dist/security/discoveryTokenStore.d.ts +62 -0
  42. package/dist/security/discoveryTokenStore.js +100 -0
  43. package/dist/security/toolInvocationGuard.d.ts +35 -0
  44. package/dist/security/toolInvocationGuard.js +153 -0
  45. package/dist/smoke-test.js +18 -10
  46. package/dist/taskRuntime.d.ts +17 -0
  47. package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
  48. package/dist/test/unit/acceptance-engine.test.js +228 -0
  49. package/dist/test/unit/agent-router.test.d.ts +1 -0
  50. package/dist/test/unit/agent-router.test.js +287 -0
  51. package/dist/test/unit/audit-checks.test.d.ts +1 -0
  52. package/dist/test/unit/audit-checks.test.js +350 -0
  53. package/dist/test/unit/diagnose-task.test.d.ts +1 -0
  54. package/dist/test/unit/diagnose-task.test.js +457 -0
  55. package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
  56. package/dist/test/unit/discovery-token-store.test.js +139 -0
  57. package/dist/test/unit/goal-graph.test.d.ts +1 -0
  58. package/dist/test/unit/goal-graph.test.js +298 -0
  59. package/dist/test/unit/goal-progress.test.d.ts +1 -0
  60. package/dist/test/unit/goal-progress.test.js +381 -0
  61. package/dist/test/unit/goal-status.test.d.ts +1 -0
  62. package/dist/test/unit/goal-status.test.js +215 -0
  63. package/dist/test/unit/goal-store.test.d.ts +1 -0
  64. package/dist/test/unit/goal-store.test.js +253 -0
  65. package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
  66. package/dist/test/unit/goal-subgoal-task.test.js +55 -0
  67. package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
  68. package/dist/test/unit/goal-tools-registry.test.js +190 -0
  69. package/dist/test/unit/handoff-export.test.d.ts +1 -0
  70. package/dist/test/unit/handoff-export.test.js +263 -0
  71. package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
  72. package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
  73. package/dist/test/unit/logging.test.js +127 -5
  74. package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
  75. package/dist/test/unit/post-task-cleanup.test.js +48 -0
  76. package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
  77. package/dist/test/unit/reconcile-tasks.test.js +456 -0
  78. package/dist/test/unit/release-gate.test.d.ts +1 -0
  79. package/dist/test/unit/release-gate.test.js +242 -0
  80. package/dist/test/unit/safe-views.test.d.ts +1 -0
  81. package/dist/test/unit/safe-views.test.js +171 -0
  82. package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
  83. package/dist/test/unit/schema-drift-check.test.js +175 -0
  84. package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
  85. package/dist/test/unit/subgoal-sync.test.js +183 -0
  86. package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
  87. package/dist/test/unit/tool-invocation-guard.test.js +432 -0
  88. package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
  89. package/dist/test/unit/tool-usage-stats.test.js +300 -0
  90. package/dist/test/unit/toolSearch.test.d.ts +1 -0
  91. package/dist/test/unit/toolSearch.test.js +571 -0
  92. package/dist/test/unit/worktree-manager.test.d.ts +1 -0
  93. package/dist/test/unit/worktree-manager.test.js +176 -0
  94. package/dist/tools/auditTask.d.ts +103 -1
  95. package/dist/tools/auditTask.js +461 -8
  96. package/dist/tools/cancelTask.js +1 -1
  97. package/dist/tools/checkReleaseGate.d.ts +21 -0
  98. package/dist/tools/checkReleaseGate.js +22 -0
  99. package/dist/tools/createTask.d.ts +18 -2
  100. package/dist/tools/createTask.js +29 -1
  101. package/dist/tools/diagnoseTask.d.ts +45 -0
  102. package/dist/tools/diagnoseTask.js +347 -0
  103. package/dist/tools/discardWorktree.d.ts +24 -0
  104. package/dist/tools/discardWorktree.js +27 -0
  105. package/dist/tools/discoverTools.d.ts +11 -0
  106. package/dist/tools/discoverTools.js +39 -0
  107. package/dist/tools/explainTool.d.ts +11 -0
  108. package/dist/tools/explainTool.js +21 -0
  109. package/dist/tools/getTaskSummary.js +2 -1
  110. package/dist/tools/goalSubgoalTask.d.ts +51 -0
  111. package/dist/tools/goalSubgoalTask.js +110 -0
  112. package/dist/tools/healthCheck.d.ts +1 -0
  113. package/dist/tools/healthCheck.js +5 -1
  114. package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
  115. package/dist/tools/invokeDiscoveredTool.js +112 -0
  116. package/dist/tools/listTasks.d.ts +3 -1
  117. package/dist/tools/listTasks.js +14 -1
  118. package/dist/tools/mergeWorktree.d.ts +24 -0
  119. package/dist/tools/mergeWorktree.js +27 -0
  120. package/dist/tools/reconcileTasks.d.ts +41 -0
  121. package/dist/tools/reconcileTasks.js +352 -0
  122. package/dist/tools/registry.js +550 -0
  123. package/dist/tools/safeStatus.d.ts +31 -1
  124. package/dist/tools/safeStatus.js +43 -1
  125. package/dist/tools/safeViews.d.ts +256 -0
  126. package/dist/tools/safeViews.js +250 -0
  127. package/dist/tools/schemaDriftCheck.d.ts +46 -0
  128. package/dist/tools/schemaDriftCheck.js +80 -0
  129. package/dist/tools/toolCatalog.d.ts +4 -3
  130. package/dist/tools/toolCatalog.js +33 -11
  131. package/dist/tools/toolRegistry.d.ts +61 -0
  132. package/dist/tools/toolRegistry.js +724 -0
  133. package/dist/tools/toolSearch.d.ts +110 -0
  134. package/dist/tools/toolSearch.js +331 -0
  135. package/dist/tools/toolUsageStats.d.ts +41 -0
  136. package/dist/tools/toolUsageStats.js +116 -0
  137. package/dist/tools/waitForTask.js +1 -0
  138. package/dist/version.d.ts +2 -2
  139. package/dist/version.js +2 -2
  140. package/dist/watcherStatus.d.ts +29 -0
  141. package/dist/watcherStatus.js +92 -1
  142. package/docs/control-center/README.md +33 -0
  143. package/docs/control-center/control-center-daily-driver.md +211 -0
  144. package/docs/control-center/control-center-mvp.md +205 -0
  145. package/docs/control-center/control-center-phase2.md +159 -0
  146. package/docs/demo.md +3 -0
  147. package/docs/release-v0.6.4.md +45 -0
  148. package/examples/openai-tunnel/README.md +5 -5
  149. package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
  150. package/package.json +25 -16
  151. package/scripts/README.md +47 -0
  152. package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
  153. package/scripts/checks/control-center-smoke.js +1098 -0
  154. package/scripts/{control-smoke.js → checks/control-smoke.js} +19 -5
  155. package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +2 -1
  156. package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +2 -2
  157. package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +25 -21
  158. package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +54 -7
  159. package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +40 -11
  160. package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
  161. package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
  162. package/scripts/{unit-tests.js → checks/unit-tests.js} +1 -1
  163. package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +13 -7
  164. package/scripts/control/control-center-tray.ps1 +281 -0
  165. package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
  166. package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
  167. package/scripts/control/restart-control-center.ps1 +173 -0
  168. package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
  169. package/scripts/control/start-control-center.ps1 +263 -0
  170. package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
  171. package/scripts/control/stop-patchwarden.ps1 +114 -0
  172. package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
  173. package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
  174. package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
  175. package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
  176. package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
  177. package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
  178. package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
  179. package/scripts/{pack-clean.js → release/pack-clean.js} +9 -1
  180. package/src/agents/agentRouter.ts +149 -0
  181. package/src/config.ts +9 -3
  182. package/src/controlCenter.ts +2166 -0
  183. package/src/doctor.ts +40 -5
  184. package/src/goal/acceptanceEngine.ts +160 -0
  185. package/src/goal/acceptanceTemplate.ts +121 -0
  186. package/src/goal/goalGraph.ts +225 -0
  187. package/src/goal/goalProgress.ts +301 -0
  188. package/src/goal/goalStatus.ts +234 -0
  189. package/src/goal/goalStore.ts +306 -0
  190. package/src/goal/handoffExport.ts +211 -0
  191. package/src/goal/subgoalSync.ts +82 -0
  192. package/src/goal/worktreeManager.ts +404 -0
  193. package/src/logging.ts +91 -0
  194. package/src/release/releaseGate.ts +567 -0
  195. package/src/runner/postTaskCleanup.ts +154 -0
  196. package/src/runner/runTask.ts +49 -10
  197. package/src/security/discoveryTokenStore.ts +157 -0
  198. package/src/security/toolInvocationGuard.ts +251 -0
  199. package/src/smoke-test.ts +15 -7
  200. package/src/taskRuntime.ts +17 -0
  201. package/src/test/unit/acceptance-engine.test.ts +261 -0
  202. package/src/test/unit/agent-router.test.ts +342 -0
  203. package/src/test/unit/audit-checks.test.ts +567 -0
  204. package/src/test/unit/diagnose-task.test.ts +544 -0
  205. package/src/test/unit/discovery-token-store.test.ts +181 -0
  206. package/src/test/unit/goal-graph.test.ts +347 -0
  207. package/src/test/unit/goal-progress.test.ts +538 -0
  208. package/src/test/unit/goal-status.test.ts +270 -0
  209. package/src/test/unit/goal-store.test.ts +318 -0
  210. package/src/test/unit/goal-subgoal-task.test.ts +72 -0
  211. package/src/test/unit/goal-tools-registry.test.ts +243 -0
  212. package/src/test/unit/handoff-export.test.ts +295 -0
  213. package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
  214. package/src/test/unit/logging.test.ts +177 -5
  215. package/src/test/unit/post-task-cleanup.test.ts +53 -0
  216. package/src/test/unit/reconcile-tasks.test.ts +551 -0
  217. package/src/test/unit/release-gate.test.ts +314 -0
  218. package/src/test/unit/safe-views.test.ts +184 -0
  219. package/src/test/unit/schema-drift-check.test.ts +258 -0
  220. package/src/test/unit/subgoal-sync.test.ts +236 -0
  221. package/src/test/unit/tool-invocation-guard.test.ts +542 -0
  222. package/src/test/unit/tool-usage-stats.test.ts +384 -0
  223. package/src/test/unit/toolSearch.test.ts +756 -0
  224. package/src/test/unit/worktree-manager.test.ts +247 -0
  225. package/src/tools/auditTask.ts +831 -402
  226. package/src/tools/cancelTask.ts +1 -1
  227. package/src/tools/checkReleaseGate.ts +45 -0
  228. package/src/tools/createTask.ts +64 -6
  229. package/src/tools/diagnoseTask.ts +460 -0
  230. package/src/tools/discardWorktree.ts +42 -0
  231. package/src/tools/discoverTools.ts +51 -0
  232. package/src/tools/explainTool.ts +26 -0
  233. package/src/tools/getTaskSummary.ts +2 -1
  234. package/src/tools/goalSubgoalTask.ts +170 -0
  235. package/src/tools/healthCheck.ts +4 -1
  236. package/src/tools/invokeDiscoveredTool.ts +163 -0
  237. package/src/tools/listTasks.ts +16 -2
  238. package/src/tools/mergeWorktree.ts +42 -0
  239. package/src/tools/reconcileTasks.ts +464 -0
  240. package/src/tools/registry.ts +618 -1
  241. package/src/tools/safeStatus.ts +73 -2
  242. package/src/tools/safeViews.ts +271 -0
  243. package/src/tools/schemaDriftCheck.ts +120 -0
  244. package/src/tools/toolCatalog.ts +34 -11
  245. package/src/tools/toolRegistry.ts +786 -0
  246. package/src/tools/toolSearch.ts +464 -0
  247. package/src/tools/toolUsageStats.ts +151 -0
  248. package/src/tools/waitForTask.ts +1 -0
  249. package/src/version.ts +2 -2
  250. package/src/watcherStatus.ts +97 -1
  251. package/ui/colors_and_type.css +141 -0
  252. package/ui/pages/audit.html +743 -0
  253. package/ui/pages/dashboard.html +1154 -0
  254. package/ui/pages/direct-sessions.html +652 -0
  255. package/ui/pages/logs.html +502 -0
  256. package/ui/pages/task-detail.html +1229 -0
  257. package/ui/pages/tasks.html +702 -0
  258. package/ui/pages/workspace.html +947 -0
  259. package/ui/partials/project-shell.html +362 -0
  260. package/ui/vendor/lucide.js +12 -0
  261. package/ui/vendor/tailwindcss-browser.js +947 -0
@@ -191,9 +191,105 @@ export function derivePendingReason(
191
191
  if (watcher.status === "unreadable") return "queued_but_watcher_unreadable";
192
192
  return "queued_waiting_for_watcher";
193
193
  }
194
- if (task.status !== "running") return null;
194
+ if (task.status !== "running" && task.status !== "collecting_artifacts") return null;
195
195
  if (task.phase === "executing_agent") return "agent_running";
196
196
  if (task.phase === "running_tests") return "verification_running";
197
197
  if (task.phase === "collecting_artifacts") return "collecting_artifacts";
198
198
  return "preparing";
199
199
  }
200
+
201
+ // ── v0.7.0: Watcher ownership helpers ─────────────────────────────
202
+
203
+ /**
204
+ * Read the current watcher's instance_id from the heartbeat file.
205
+ * Returns null when the heartbeat is missing or unreadable.
206
+ *
207
+ * This is the source of truth for "which watcher is currently alive".
208
+ * Tasks whose runtime.watcher_instance_id differs from this value are
209
+ * considered orphaned by the diagnosis layer.
210
+ */
211
+ export function readWatcherInstanceId(
212
+ config: PatchWardenConfig = getConfig()
213
+ ): string | null {
214
+ const heartbeatPath = getWatcherHeartbeatPath(config);
215
+ if (!existsSync(heartbeatPath)) return null;
216
+ try {
217
+ const raw = readFileSync(heartbeatPath, "utf-8");
218
+ const data = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
219
+ return typeof data.instance_id === "string" ? data.instance_id : null;
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * v0.7.0: Determine whether the current watcher still owns a task.
227
+ *
228
+ * Ownership is established by comparing the watcher_instance_id recorded in
229
+ * the task's runtime.json with the instance_id of the currently alive
230
+ * watcher. A task is considered "owned" only when:
231
+ * - the current watcher heartbeat is healthy, AND
232
+ * - the task runtime recorded a watcher_instance_id, AND
233
+ * - both instance IDs match.
234
+ *
235
+ * If the task has no recorded watcher_instance_id (legacy tasks created
236
+ * before v0.7.0), ownership cannot be confirmed and this returns false.
237
+ * The diagnosis layer treats "no owner" as a stale signal, not as active.
238
+ */
239
+ export function isWatcherOwningTask(
240
+ taskDir: string,
241
+ config: PatchWardenConfig = getConfig()
242
+ ): { owned: boolean; reason: "owned" | "no_runtime_record" | "watcher_missing" | "instance_mismatch" | "watcher_unhealthy"; task_watcher_instance_id: string | null; current_watcher_instance_id: string | null } {
243
+ const runtimePath = join(taskDir, "runtime.json");
244
+ let taskWatcherInstanceId: string | null = null;
245
+ if (existsSync(runtimePath)) {
246
+ try {
247
+ const runtime = JSON.parse(readFileSync(runtimePath, "utf-8"));
248
+ if (typeof runtime.watcher_instance_id === "string") {
249
+ taskWatcherInstanceId = runtime.watcher_instance_id;
250
+ }
251
+ } catch {
252
+ // corrupted runtime — treat as no record
253
+ }
254
+ }
255
+ if (!taskWatcherInstanceId) {
256
+ return {
257
+ owned: false,
258
+ reason: "no_runtime_record",
259
+ task_watcher_instance_id: null,
260
+ current_watcher_instance_id: null,
261
+ };
262
+ }
263
+ const watcher = readWatcherStatus(config);
264
+ if (watcher.status !== "healthy") {
265
+ return {
266
+ owned: false,
267
+ reason: watcher.status === "missing" ? "watcher_missing" : "watcher_unhealthy",
268
+ task_watcher_instance_id: taskWatcherInstanceId,
269
+ current_watcher_instance_id: watcher.instance_id,
270
+ };
271
+ }
272
+ const currentInstanceId = watcher.instance_id;
273
+ if (!currentInstanceId) {
274
+ return {
275
+ owned: false,
276
+ reason: "watcher_missing",
277
+ task_watcher_instance_id: taskWatcherInstanceId,
278
+ current_watcher_instance_id: null,
279
+ };
280
+ }
281
+ if (currentInstanceId !== taskWatcherInstanceId) {
282
+ return {
283
+ owned: false,
284
+ reason: "instance_mismatch",
285
+ task_watcher_instance_id: taskWatcherInstanceId,
286
+ current_watcher_instance_id: currentInstanceId,
287
+ };
288
+ }
289
+ return {
290
+ owned: true,
291
+ reason: "owned",
292
+ task_watcher_instance_id: taskWatcherInstanceId,
293
+ current_watcher_instance_id: currentInstanceId,
294
+ };
295
+ }
@@ -0,0 +1,141 @@
1
+ /* PatchWarden Brand CSS — Dark Security Theme (default) + Light Mode */
2
+ /* Prefix: pw */
3
+
4
+ :root {
5
+ /* ── Primary Palette (Dark — default) ── */
6
+ --pw-bg: #0a0e14;
7
+ --pw-bg-elevated: #111820;
8
+ --pw-bg-surface: #161d27;
9
+ --pw-bg-hover: #1c2533;
10
+ --pw-bg-active: #222d3d;
11
+ --pw-border: #1e2a3a;
12
+ --pw-border-subtle: #172030;
13
+ --pw-border-focus: #2dd4a8;
14
+
15
+ /* ── Text ── */
16
+ --pw-text-primary: #e8edf4;
17
+ --pw-text-secondary: #8b99ad;
18
+ --pw-text-tertiary: #5a6a80;
19
+ --pw-text-inverse: #0a0e14;
20
+ --pw-text-accent: #2dd4a8;
21
+
22
+ /* ── Brand Accent — Security Green ── */
23
+ --pw-accent: #2dd4a8;
24
+ --pw-accent-hover: #34eabc;
25
+ --pw-accent-muted: rgba(45, 212, 168, 0.12);
26
+ --pw-accent-subtle: rgba(45, 212, 168, 0.06);
27
+
28
+ /* ── State Colors ── */
29
+ --pw-state-success: #22c55e;
30
+ --pw-state-success-bg: rgba(34, 197, 94, 0.1);
31
+ --pw-state-warning: #f59e0b;
32
+ --pw-state-warning-bg: rgba(245, 158, 11, 0.1);
33
+ --pw-state-error: #ef4444;
34
+ --pw-state-error-bg: rgba(239, 68, 68, 0.1);
35
+ --pw-state-info: #3b82f6;
36
+ --pw-state-info-bg: rgba(59, 130, 246, 0.1);
37
+
38
+ /* ── Radius ── */
39
+ --pw-radius-sm: 4px;
40
+ --pw-radius-md: 8px;
41
+ --pw-radius-lg: 12px;
42
+ --pw-radius-full: 9999px;
43
+
44
+ /* ── Shadows — static surfaces use border only; floating layers use subtle shadow ── */
45
+ --pw-shadow-float: 0 8px 32px rgba(0, 0, 0, 0.4);
46
+ --pw-shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.5);
47
+
48
+ /* ── Typography ── */
49
+ --pw-font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
50
+ --pw-font-mono: 'JetBrains Mono', 'Cascadia Code', 'Consolas', monospace;
51
+
52
+ --pw-text-xs: 0.75rem;
53
+ --pw-text-sm: 0.8125rem;
54
+ --pw-text-base: 0.875rem;
55
+ --pw-text-lg: 1rem;
56
+ --pw-text-xl: 1.25rem;
57
+ --pw-text-2xl: 1.5rem;
58
+ --pw-text-3xl: 2rem;
59
+
60
+ --pw-leading-tight: 1.25;
61
+ --pw-leading-normal: 1.5;
62
+ --pw-leading-relaxed: 1.625;
63
+
64
+ --pw-weight-normal: 400;
65
+ --pw-weight-medium: 500;
66
+ --pw-weight-semibold: 600;
67
+ --pw-weight-bold: 700;
68
+
69
+ /* ── Spacing ── */
70
+ --pw-space-1: 4px;
71
+ --pw-space-2: 8px;
72
+ --pw-space-3: 12px;
73
+ --pw-space-4: 16px;
74
+ --pw-space-5: 20px;
75
+ --pw-space-6: 24px;
76
+ --pw-space-8: 32px;
77
+ --pw-space-10: 40px;
78
+ --pw-space-12: 48px;
79
+
80
+ /* ── Transitions ── */
81
+ --pw-transition-fast: 150ms ease;
82
+ --pw-transition-base: 200ms ease;
83
+ --pw-transition-slow: 300ms ease;
84
+
85
+ /* ── Code block backgrounds ── */
86
+ --pw-code-bg: #0d1117;
87
+ --pw-code-add-bg: rgba(34, 197, 94, 0.1);
88
+ --pw-code-add-text: #3fb950;
89
+ --pw-code-remove-bg: rgba(239, 68, 68, 0.1);
90
+ --pw-code-remove-text: #f85149;
91
+ --pw-code-line-num: #5a6a80;
92
+ }
93
+
94
+ /* ── Dark mode (explicit, same as :root defaults) ── */
95
+ html.dark {
96
+ --pw-bg: #0a0e14;
97
+ --pw-bg-elevated: #111820;
98
+ --pw-bg-surface: #161d27;
99
+ --pw-bg-hover: #1c2533;
100
+ --pw-bg-active: #222d3d;
101
+ --pw-border: #1e2a3a;
102
+ --pw-border-subtle: #172030;
103
+ --pw-text-primary: #e8edf4;
104
+ --pw-text-secondary: #8b99ad;
105
+ --pw-text-tertiary: #5a6a80;
106
+ --pw-code-bg: #0d1117;
107
+ --pw-code-add-bg: rgba(34, 197, 94, 0.1);
108
+ --pw-code-add-text: #3fb950;
109
+ --pw-code-remove-bg: rgba(239, 68, 68, 0.1);
110
+ --pw-code-remove-text: #f85149;
111
+ --pw-code-line-num: #5a6a80;
112
+ }
113
+
114
+ /* ── Light mode ── */
115
+ html.light {
116
+ --pw-bg: #f8fafb;
117
+ --pw-bg-elevated: #ffffff;
118
+ --pw-bg-surface: #f1f5f7;
119
+ --pw-bg-hover: #e8eef2;
120
+ --pw-bg-active: #dde5eb;
121
+ --pw-border: #d1d9e0;
122
+ --pw-border-subtle: #e2e8ee;
123
+ --pw-border-focus: #1a9e7a;
124
+ --pw-text-primary: #1a2332;
125
+ --pw-text-secondary: #5a6a80;
126
+ --pw-text-tertiary: #8b99ad;
127
+ --pw-text-inverse: #ffffff;
128
+ --pw-text-accent: #1a9e7a;
129
+ --pw-accent: #1a9e7a;
130
+ --pw-accent-hover: #158c6c;
131
+ --pw-accent-muted: rgba(26, 158, 122, 0.1);
132
+ --pw-accent-subtle: rgba(26, 158, 122, 0.05);
133
+ --pw-shadow-float: 0 8px 32px rgba(0, 0, 0, 0.08);
134
+ --pw-shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.12);
135
+ --pw-code-bg: #f6f8fa;
136
+ --pw-code-add-bg: rgba(34, 197, 94, 0.08);
137
+ --pw-code-add-text: #1a7f37;
138
+ --pw-code-remove-bg: rgba(239, 68, 68, 0.08);
139
+ --pw-code-remove-text: #cf222e;
140
+ --pw-code-line-num: #8b99ad;
141
+ }