failproofai 1.0.6-beta.1 → 1.0.7-beta.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 (258) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +7 -5
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +13 -11
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +7 -7
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +41 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -9
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__1j-zsg5._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +5 -0
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  98. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1x3j9mj._.js → [root-of-the-server]__1p28m0u._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{_0qlygw6._.js → _0cf1olg._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1_ddpm3._.js → _135-c3y._.js} +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
  110. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  111. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +4 -4
  115. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_0az0sfq._.js +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  122. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  123. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  132. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  133. package/.next/standalone/.next/server/pages/404.html +1 -1
  134. package/.next/standalone/.next/server/pages/500.html +1 -1
  135. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/server-reference-manifest.json +56 -23
  137. package/.next/standalone/.next/static/chunks/{06fjx8-0_qufs.js → 00i9thnpht4l1.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/{3ht0xsco46pul.js → 0289oapk24zql.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/{1qf2uiipatr80.js → 0pyy9m5cm8zku.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/15heikmy3yjzc.js +1 -0
  141. package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
  142. package/.next/standalone/.next/static/chunks/{02vwr5js1x28j.js → 2plg9qae1yejv.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  144. package/.next/standalone/.next/static/chunks/{2x776s0x3n_tu.js → 2tqszh84mp_2l.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
  146. package/.next/standalone/.next/static/chunks/3cdkxrukvlvul.js +1 -0
  147. package/.next/standalone/.next/static/chunks/3dnd-_29uiaj3.js +6 -0
  148. package/.next/standalone/.next/static/chunks/{0y227_ohdqwvn.js → 3vp7c3knkh8mr.js} +1 -1
  149. package/.next/standalone/.next/static/chunks/{27_bwbg8sv6vj.js → 3vwnzfx9q80yl.js} +1 -1
  150. package/.next/standalone/app/actions/get-jev-config.ts +409 -0
  151. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  152. package/.next/standalone/app/actions/update-jev-config.ts +420 -0
  153. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  154. package/.next/standalone/app/policies/hooks-client.tsx +9 -0
  155. package/.next/standalone/app/settings/jev-panel.tsx +469 -0
  156. package/.next/standalone/app/settings/page.tsx +20 -1
  157. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  158. package/.next/standalone/app/settings/settings.css +79 -0
  159. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +51 -3
  160. package/.next/standalone/fp-cloud-cli/README.md +1 -1
  161. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
  162. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
  164. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
  165. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
  166. package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
  167. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
  168. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
  169. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
  170. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  171. package/.next/standalone/package.json +11 -11
  172. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  173. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  174. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
  175. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
  176. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
  177. package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
  178. package/.next/standalone/sdk/python/uv.lock +15 -15
  179. package/.next/standalone/server.js +1 -1
  180. package/bin/failproofai.mjs +115 -2
  181. package/dist/cli.mjs +10982 -3787
  182. package/dist/index.js +19 -1
  183. package/dist/worker.mjs +13761 -7834
  184. package/package.json +11 -11
  185. package/pi-extension/index.ts +11 -0
  186. package/scripts/build-policy-pack.mjs +39 -2
  187. package/src/hooks/builtin-policies.ts +21 -3
  188. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  189. package/src/hooks/cloud-managed-policies.ts +22 -0
  190. package/src/hooks/custom-hooks-loader.ts +45 -7
  191. package/src/hooks/custom-hooks-registry.ts +45 -1
  192. package/src/hooks/daemon-client.ts +128 -50
  193. package/src/hooks/daemon-service.ts +30 -0
  194. package/src/hooks/effective-reviewers.ts +79 -0
  195. package/src/hooks/first-run-gate.ts +5 -0
  196. package/src/hooks/fp-home.ts +23 -0
  197. package/src/hooks/handler.ts +265 -6
  198. package/src/hooks/hook-activity-store.ts +105 -1
  199. package/src/hooks/hook-telemetry.ts +41 -0
  200. package/src/hooks/integrations.ts +3 -2
  201. package/src/hooks/jev-activity.ts +385 -0
  202. package/src/hooks/jev-cli.ts +1193 -0
  203. package/src/hooks/loader-utils.ts +6 -0
  204. package/src/hooks/manager.ts +4 -4
  205. package/src/hooks/pack-cli.ts +237 -11
  206. package/src/hooks/pack-manifest.ts +479 -7
  207. package/src/hooks/pack-store.ts +156 -10
  208. package/src/hooks/policy-authority.ts +333 -0
  209. package/src/hooks/policy-catalog.ts +65 -0
  210. package/src/hooks/policy-evaluator.ts +940 -796
  211. package/src/hooks/policy-registry.ts +25 -0
  212. package/src/hooks/policy-reviewability.ts +229 -0
  213. package/src/hooks/policy-types.ts +126 -0
  214. package/src/hooks/semantic/combine.ts +541 -0
  215. package/src/hooks/semantic/compile.ts +176 -0
  216. package/src/hooks/semantic/decide.ts +392 -0
  217. package/src/hooks/semantic/envelope.ts +1296 -0
  218. package/src/hooks/semantic/evaluator.ts +547 -0
  219. package/src/hooks/semantic/facts.ts +292 -0
  220. package/src/hooks/semantic/intent.ts +1190 -0
  221. package/src/hooks/semantic/jev-client.ts +643 -0
  222. package/src/hooks/semantic/jev-config.ts +594 -0
  223. package/src/hooks/semantic/jev-review.ts +374 -0
  224. package/src/hooks/semantic/jev-stats.ts +289 -0
  225. package/src/hooks/semantic/jev-throttle.ts +421 -0
  226. package/src/hooks/semantic/pack-policies.ts +251 -0
  227. package/src/hooks/semantic/policies.ts +596 -0
  228. package/src/hooks/semantic/precondition-names.ts +60 -0
  229. package/src/hooks/semantic/preconditions.ts +58 -0
  230. package/src/hooks/semantic/redact.ts +2910 -0
  231. package/src/hooks/semantic/types.ts +145 -0
  232. package/src/hooks/semver-precedence.ts +128 -0
  233. package/src/hooks/worker-server.ts +119 -26
  234. package/src/index.ts +6 -0
  235. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  236. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  237. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  238. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  239. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  247. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +0 -10
  250. package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +0 -1
  251. package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
  252. package/.next/standalone/.next/static/chunks/1p4xauf-zu6zk.js +0 -1
  253. package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +0 -6
  254. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  255. package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
  256. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_buildManifest.js +0 -0
  257. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_clientMiddlewareManifest.js +0 -0
  258. /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_ssgManifest.js +0 -0
@@ -0,0 +1,469 @@
1
+ "use client";
2
+
3
+ /**
4
+ * The Jev panel on /settings — the dashboard's half of `failproofai jev setup`.
5
+ *
6
+ * ## What it is for
7
+ *
8
+ * Jev is the optional second evaluation tier. It is on when, and only when, a
9
+ * valid `~/.failproofai/jev.json` exists; without it the hook path is byte for
10
+ * byte what it was before. So this panel has two jobs, in this order:
11
+ *
12
+ * 1. say whether it is on, where it sends requests, in which mode, how much
13
+ * of the enabled policy set it is allowed to clear, and — once it has run
14
+ * — how often it fell back to the regex engine. A panel that only took
15
+ * input would leave "is this thing working" unanswerable from the
16
+ * dashboard.
17
+ * 2. take the endpoint and the token.
18
+ *
19
+ * It is a full-width cell in the same hairline console as the scheduled-audit
20
+ * panel, using the same tokens, the same `.btn-press` action and the same
21
+ * `toast()` on success. No new colour and no new control that did not already
22
+ * exist on this page, except a `<select>` — five providers do not fit a switch.
23
+ *
24
+ * ## The token is write-only
25
+ *
26
+ * The field is always blank on load, whatever is stored. `JevSettingsView`
27
+ * carries a presence flag and at most the last four characters, so the panel
28
+ * can say "configured, ending 3f2a" and nothing more; the value itself never
29
+ * leaves the machine's filesystem. Leaving the field empty on save KEEPS the
30
+ * key where it is — the stored one, or the environment's for a config that takes
31
+ * it from there — which the server decides, not this component.
32
+ *
33
+ * The stored MODEL is shown the same way when it does not look like a model id,
34
+ * for the same reason: it is the one routing field someone can paste a key into.
35
+ * The panel gets a `JevModelView`, never the string, so there is nothing here to
36
+ * leak even by accident. It is display-only — the form does not offer the field,
37
+ * and a save leaves whatever is stored alone.
38
+ *
39
+ * Client-side validation here is a convenience only. Every rule — the URL
40
+ * scheme, plain http being refused outside shadow mode, cloudflare's account id
41
+ * — is enforced server-side by the same `validateJevConfig` the loader runs.
42
+ */
43
+
44
+ import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
45
+ import {
46
+ getJevSettingsAction,
47
+ type JevModelView,
48
+ type JevSettingsView,
49
+ } from "@/app/actions/get-jev-config";
50
+ import {
51
+ removeJevConfigAction,
52
+ saveJevConfigAction,
53
+ } from "@/app/actions/update-jev-config";
54
+ import { toast } from "@/app/components/toast";
55
+
56
+ const PROVIDERS = [
57
+ { value: "typesafe", label: "typesafe" },
58
+ { value: "openrouter", label: "openrouter" },
59
+ { value: "vercel", label: "vercel ai gateway" },
60
+ { value: "cloudflare", label: "cloudflare workers ai" },
61
+ { value: "custom", label: "custom endpoint" },
62
+ ] as const;
63
+
64
+ const MODES = [
65
+ { value: "enforce", label: "enforce — jev's verdict counts" },
66
+ { value: "shadow", label: "shadow — log only, regex decides" },
67
+ ] as const;
68
+
69
+ /** "24h" / "7d" / "30m", for the stats window. */
70
+ function fmtWindow(ms: number): string {
71
+ const h = ms / 3_600_000;
72
+ if (Number.isInteger(h) && h >= 1) return h % 24 === 0 ? `${h / 24}d` : `${h}h`;
73
+ return `${Math.max(1, Math.round(ms / 60_000))}m`;
74
+ }
75
+
76
+ /** A rate as a whole percent. 0.0417 → "4%". */
77
+ function fmtRate(rate: number): string {
78
+ return `${Math.round(rate * 100)}%`;
79
+ }
80
+
81
+ /**
82
+ * The model row. `withheld` is the case worth having a row for at all: the file
83
+ * holds something in that slot that does not look like a model id, which is
84
+ * what a key pasted one field off looks like, so it is described and not
85
+ * printed — and the server never sent it here to print. The loader's own reason
86
+ * and its fix are already on the page above, in `view.problem`.
87
+ */
88
+ function fmtModel(model: JevModelView): string {
89
+ switch (model.kind) {
90
+ case "id":
91
+ return model.id;
92
+ case "withheld":
93
+ return "set to something that is not a model id — not shown here, in case it is a key";
94
+ default:
95
+ return "the provider's default";
96
+ }
97
+ }
98
+
99
+ interface FormState {
100
+ provider: string;
101
+ baseUrl: string;
102
+ accountId: string;
103
+ mode: string;
104
+ }
105
+
106
+ function formFrom(view: JevSettingsView): FormState {
107
+ return {
108
+ provider: view.provider ?? "typesafe",
109
+ baseUrl: view.baseUrl,
110
+ accountId: view.accountId,
111
+ mode: view.mode,
112
+ };
113
+ }
114
+
115
+ /** One labelled control, laid out like the interval row above it. */
116
+ function Field({
117
+ id,
118
+ label,
119
+ hint,
120
+ children,
121
+ }: {
122
+ id: string;
123
+ label: string;
124
+ hint?: string;
125
+ children: ReactNode;
126
+ }) {
127
+ return (
128
+ <div className="set-field">
129
+ <label className="set-field-label" htmlFor={id}>
130
+ {label}
131
+ </label>
132
+ {children}
133
+ {hint ? <span className="set-hint">{hint}</span> : null}
134
+ </div>
135
+ );
136
+ }
137
+
138
+ export default function JevPanel({ initial }: { initial: JevSettingsView | null }) {
139
+ const [view, setView] = useState<JevSettingsView | null>(initial);
140
+ const [form, setForm] = useState<FormState>(
141
+ initial ? formFrom(initial) : { provider: "typesafe", baseUrl: "", accountId: "", mode: "enforce" },
142
+ );
143
+ const [token, setToken] = useState("");
144
+ const [busy, setBusy] = useState(false);
145
+ const [problem, setProblem] = useState<string | null>(null);
146
+ /**
147
+ * The server's `needsToken`: the one refusal whose remedy is this field, so it
148
+ * is marked rather than leaving the person to match the message to a control.
149
+ * The refusals that no token can settle deliberately do not set it.
150
+ */
151
+ const [needsToken, setNeedsToken] = useState(false);
152
+ const mounted = useRef(true);
153
+ /**
154
+ * Whether the user has touched the form since the last read. A focus refresh
155
+ * must not overwrite a half-typed endpoint with what is still on disk — the
156
+ * page re-reads on every `visibilitychange`, which includes the tab hide that
157
+ * happens when somebody alt-tabs to their password manager mid-edit.
158
+ */
159
+ const dirty = useRef(false);
160
+
161
+ const reload = useCallback(async () => {
162
+ try {
163
+ const next = await getJevSettingsAction();
164
+ if (!mounted.current) return;
165
+ setView(next);
166
+ if (!dirty.current) setForm(formFrom(next));
167
+ } catch {
168
+ // Leave what is on screen: it describes real machine state, and blanking
169
+ // it would report something less true than what is already there.
170
+ }
171
+ }, []);
172
+
173
+ useEffect(() => {
174
+ mounted.current = true;
175
+ void reload();
176
+ return () => {
177
+ mounted.current = false;
178
+ };
179
+ }, [reload]);
180
+
181
+ useEffect(() => {
182
+ // Same reason as the scheduled-audit panel: `failproofai jev setup` in a
183
+ // terminal changes the same file, and a page left open would otherwise keep
184
+ // showing the old state until it was reloaded by hand.
185
+ const onFocus = () => void reload();
186
+ window.addEventListener("focus", onFocus);
187
+ document.addEventListener("visibilitychange", onFocus);
188
+ return () => {
189
+ window.removeEventListener("focus", onFocus);
190
+ document.removeEventListener("visibilitychange", onFocus);
191
+ };
192
+ }, [reload]);
193
+
194
+ const set = useCallback(<K extends keyof FormState>(key: K, value: FormState[K]) => {
195
+ dirty.current = true;
196
+ setForm((f) => ({ ...f, [key]: value }));
197
+ }, []);
198
+
199
+ const onSave = useCallback(async () => {
200
+ setBusy(true);
201
+ setProblem(null);
202
+ try {
203
+ // The form does not offer a model, so it says nothing about one: the
204
+ // server keeps a stored model when the provider is unchanged, and drops it
205
+ // for the new provider's default when it is not — the same rule `jev setup`
206
+ // applies. `JevConfigInput` has no model field for this to get wrong.
207
+ const res = await saveJevConfigAction({
208
+ provider: form.provider,
209
+ baseUrl: form.baseUrl,
210
+ accountId: form.accountId,
211
+ mode: form.mode,
212
+ token,
213
+ });
214
+ if (!res.ok) {
215
+ setProblem(res.problem);
216
+ setNeedsToken(res.needsToken === true);
217
+ return;
218
+ }
219
+ dirty.current = false;
220
+ setNeedsToken(false);
221
+ setToken("");
222
+ setView(res.view);
223
+ setForm(formFrom(res.view));
224
+ toast(res.view.on ? "jev is on." : "saved.");
225
+ } catch {
226
+ setProblem("could not save that.");
227
+ } finally {
228
+ setBusy(false);
229
+ }
230
+ }, [form, token]);
231
+
232
+ const onRemove = useCallback(async () => {
233
+ setBusy(true);
234
+ setProblem(null);
235
+ setNeedsToken(false);
236
+ try {
237
+ const res = await removeJevConfigAction();
238
+ if (!res.ok) {
239
+ setProblem(res.problem);
240
+ return;
241
+ }
242
+ dirty.current = false;
243
+ setToken("");
244
+ setView(res.view);
245
+ setForm(formFrom(res.view));
246
+ toast("jev is off. hooks run the regex policies.");
247
+ } catch {
248
+ setProblem("could not turn that off.");
249
+ } finally {
250
+ setBusy(false);
251
+ }
252
+ }, []);
253
+
254
+ const configured = view !== null && view.status !== "absent";
255
+ const stored = view?.token;
256
+ const tokenHint = needsToken
257
+ ? "needed for this save — type the token for this endpoint"
258
+ : stored
259
+ ? stored.source === "env"
260
+ ? "configured, read from the environment — leave blank to keep it"
261
+ : stored.hint
262
+ ? `configured, ending ${stored.hint} — leave blank to keep it`
263
+ : "configured — leave blank to keep it"
264
+ : // The file names the environment as the key's source and the variable is
265
+ // unset in this process. Leaving the field blank keeps that arrangement,
266
+ // which is not the same thing as "there is no key" — the endpoint and the
267
+ // mode are editable from here without one.
268
+ view?.status === "key-missing"
269
+ ? "read from the environment, which is not set in this shell — leave blank to keep it that way"
270
+ : "sent as a bearer token; stored owner-only at 0600";
271
+
272
+ return (
273
+ <div className="set-cell set-jev">
274
+ <h2 className="set-cell-title">jev · semantic evaluation</h2>
275
+
276
+ <div className="set-row set-row-main">
277
+ <div className="set-row-copy">
278
+ <span className={view?.on ? "set-strong" : "set-dim"}>
279
+ {view === null
280
+ ? "reading…"
281
+ : view.on
282
+ ? `on. every judged command is also asked of ${view.provider}.`
283
+ : "off. hooks run the regex policies exactly as before."}
284
+ </span>
285
+ <span className="set-dim">
286
+ {view?.on
287
+ ? view.mode === "shadow"
288
+ ? "shadow: jev's answers are logged, the regex result is what gets enforced."
289
+ : "enforce: jev's answers can clear a reviewable deny."
290
+ : "a second opinion on the commands the regex policies flag. needs your own endpoint and token."}
291
+ </span>
292
+ </div>
293
+ </div>
294
+
295
+ {view && configured && (
296
+ <dl className="set-how-list">
297
+ {view.endpoint && (
298
+ <div className="set-how-row">
299
+ <dt className="set-how-label">endpoint</dt>
300
+ <dd className="set-how-body">{view.endpoint}</dd>
301
+ </div>
302
+ )}
303
+ <div className="set-how-row">
304
+ <dt className="set-how-label">model</dt>
305
+ <dd className="set-how-body">{fmtModel(view.model)}</dd>
306
+ </div>
307
+ <div className="set-how-row">
308
+ <dt className="set-how-label">token</dt>
309
+ <dd className="set-how-body">
310
+ {stored
311
+ ? stored.source === "env"
312
+ ? "from FAILPROOFAI_JEV_API_KEY"
313
+ : stored.hint
314
+ ? `configured, ending ${stored.hint}`
315
+ : "configured"
316
+ : "none stored"}
317
+ </dd>
318
+ </div>
319
+ {view.permissions && (
320
+ <div className="set-how-row">
321
+ <dt className="set-how-label">file</dt>
322
+ <dd className="set-how-body">
323
+ {`${view.path} · ${view.permissions}`}
324
+ </dd>
325
+ </div>
326
+ )}
327
+ {view.reviewable && (
328
+ <div className="set-how-row">
329
+ <dt className="set-how-label">reviewable</dt>
330
+ {/* The count, in the server's words — the same sentence
331
+ `failproofai jev status` prints, because the two surfaces must
332
+ not disagree about what Jev is allowed to clear. */}
333
+ <dd className="set-how-body">{view.reviewable.summary}</dd>
334
+ </div>
335
+ )}
336
+ {view.stats && view.stats.total > 0 && (
337
+ <div className="set-how-row">
338
+ <dt className="set-how-label">fallbacks</dt>
339
+ {/* One string, not an interpolated fragment: a sentence split
340
+ across text nodes is a sentence a screen reader and a test both
341
+ have to reassemble. */}
342
+ <dd className="set-how-body">
343
+ {`${fmtRate(view.stats.fallbackRate)} of ${view.stats.total} calls in the last ${fmtWindow(view.stats.windowMs)} fell back to the regex engine.`}
344
+ </dd>
345
+ </div>
346
+ )}
347
+ </dl>
348
+ )}
349
+
350
+ {view?.problem && (
351
+ <p className="set-warn">
352
+ {view.problem}
353
+ {view.fix ? ` — ${view.fix}` : ""}
354
+ </p>
355
+ )}
356
+
357
+ {/* Jev is on, answering, and cannot clear a single verdict. Nothing else
358
+ on this page shows it: a policy set with no authority marks behaves
359
+ exactly like a healthy one until you notice that nothing is ever
360
+ cleared. The server sends the cause and the remedy together. */}
361
+ {view?.reviewable?.problem && <p className="set-warn">{view.reviewable.problem}</p>}
362
+
363
+ <div className="set-rule" />
364
+
365
+ <div className="set-jev-fields">
366
+ <Field id="jev-provider" label="provider">
367
+ <select
368
+ id="jev-provider"
369
+ className="set-select"
370
+ value={form.provider}
371
+ disabled={busy}
372
+ onChange={(e) => set("provider", e.target.value)}
373
+ >
374
+ {PROVIDERS.map((p) => (
375
+ <option key={p.value} value={p.value}>
376
+ {p.label}
377
+ </option>
378
+ ))}
379
+ </select>
380
+ </Field>
381
+
382
+ <Field
383
+ id="jev-url"
384
+ label="endpoint url"
385
+ hint={
386
+ form.provider === "custom"
387
+ ? "required — https, or http to localhost in shadow mode"
388
+ : "leave blank for the provider's own api"
389
+ }
390
+ >
391
+ <input
392
+ id="jev-url"
393
+ type="text"
394
+ className="set-text"
395
+ inputMode="url"
396
+ autoComplete="off"
397
+ spellCheck={false}
398
+ placeholder={form.provider === "custom" ? "https://…" : "the provider's own api"}
399
+ value={form.baseUrl}
400
+ disabled={busy}
401
+ onChange={(e) => set("baseUrl", e.target.value)}
402
+ />
403
+ </Field>
404
+
405
+ {form.provider === "cloudflare" && (
406
+ <Field id="jev-account" label="account id" hint="32 lowercase hex characters">
407
+ <input
408
+ id="jev-account"
409
+ type="text"
410
+ className="set-text"
411
+ autoComplete="off"
412
+ spellCheck={false}
413
+ value={form.accountId}
414
+ disabled={busy}
415
+ onChange={(e) => set("accountId", e.target.value)}
416
+ />
417
+ </Field>
418
+ )}
419
+
420
+ <Field id="jev-token" label="token" hint={tokenHint}>
421
+ <input
422
+ id="jev-token"
423
+ type="password"
424
+ className="set-text"
425
+ autoComplete="off"
426
+ spellCheck={false}
427
+ placeholder={stored ? "•••• kept" : ""}
428
+ value={token}
429
+ disabled={busy}
430
+ aria-invalid={needsToken || undefined}
431
+ onChange={(e) => {
432
+ dirty.current = true;
433
+ setToken(e.target.value);
434
+ }}
435
+ />
436
+ </Field>
437
+
438
+ <Field id="jev-mode" label="mode">
439
+ <select
440
+ id="jev-mode"
441
+ className="set-select"
442
+ value={form.mode}
443
+ disabled={busy}
444
+ onChange={(e) => set("mode", e.target.value)}
445
+ >
446
+ {MODES.map((m) => (
447
+ <option key={m.value} value={m.value}>
448
+ {m.label}
449
+ </option>
450
+ ))}
451
+ </select>
452
+ </Field>
453
+ </div>
454
+
455
+ {problem && <p className="set-warn">{problem}</p>}
456
+
457
+ <div className="set-actions set-jev-actions">
458
+ <button type="button" className="btn btn-press" disabled={busy} onClick={() => void onSave()}>
459
+ {busy ? "[ saving… ]" : configured ? "[ save changes ]" : "[ turn jev on ]"}
460
+ </button>
461
+ {configured && (
462
+ <button type="button" className="set-link" disabled={busy} onClick={() => void onRemove()}>
463
+ turn jev off
464
+ </button>
465
+ )}
466
+ </div>
467
+ </div>
468
+ );
469
+ }
@@ -4,6 +4,11 @@ import {
4
4
  getScheduledAuditAction,
5
5
  type ScheduledAuditView,
6
6
  } from "@/app/actions/get-scheduled-audit";
7
+ import {
8
+ getJevSettingsAction,
9
+ type JevSettingsView,
10
+ } from "@/app/actions/get-jev-config";
11
+ import JevPanel from "./jev-panel";
7
12
  import SettingsClient from "./settings-client";
8
13
 
9
14
  export const metadata: Metadata = {
@@ -44,5 +49,19 @@ export default async function SettingsPage() {
44
49
  // here would replace a page that can explain itself with an error boundary
45
50
  // that cannot.
46
51
  }
47
- return <SettingsClient initial={initial} />;
52
+ // Same reasoning as above, for the same reason it matters more here: this
53
+ // panel's headline fact is whether a second evaluator is switched on, and a
54
+ // client-side load would paint "off" first and correct itself after.
55
+ // `getJevSettingsAction` does not throw, but a failed read must not take the
56
+ // scheduled-audit panel down with it either.
57
+ let jev: JevSettingsView | null = null;
58
+ try {
59
+ jev = await getJevSettingsAction();
60
+ } catch {
61
+ // Left null; the panel reads as "reading…" until the client load lands.
62
+ }
63
+ // The panel is composed in rather than imported by `SettingsClient`, so the
64
+ // scheduled-audit console and the Jev console share a layout without sharing
65
+ // a dependency — see the `jevPanel` prop.
66
+ return <SettingsClient initial={initial} jevPanel={<JevPanel initial={jev} />} />;
48
67
  }
@@ -206,7 +206,24 @@ const HOW_IT_WORKS: ReadonlyArray<{ label: string; body: string }> = [
206
206
  },
207
207
  ];
208
208
 
209
- export default function SettingsClient({ initial }: { initial: ScheduledAuditView | null }) {
209
+ export default function SettingsClient({
210
+ initial,
211
+ jevPanel,
212
+ }: {
213
+ initial: ScheduledAuditView | null;
214
+ /**
215
+ * The Jev panel, rendered by the server page and handed in as an element
216
+ * rather than imported here.
217
+ *
218
+ * It is its own feature with its own file, its own actions and its own
219
+ * refresh — nothing on this page reads or writes what it does. Composing it
220
+ * in keeps that true in the code as well as in the design: this component
221
+ * does not import the Jev actions, so a test of the scheduled-audit panel
222
+ * neither renders nor touches Jev, and vice versa. Optional so the existing
223
+ * callers that only care about the audit panel keep working unchanged.
224
+ */
225
+ jevPanel?: ReactNode;
226
+ }) {
210
227
  // Seeded from the server render, so the first paint already tells the truth
211
228
  // about whether scheduled audits are on. See the note in `page.tsx`.
212
229
  const [view, setView] = useState<ScheduledAuditView | null>(initial);
@@ -507,6 +524,10 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
507
524
  dashboard can reach <code>~/.failproofai/config.json</code>.
508
525
  </p>
509
526
  </div>
527
+ {/* A different file. An unreadable `config.json` says nothing about
528
+ `jev.json`, and hiding the panel that can turn a second
529
+ evaluator off would be the worst moment to hide it. */}
530
+ {jevPanel}
510
531
  </div>
511
532
  ) : (
512
533
  <div className="set-console">
@@ -653,6 +674,11 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
653
674
  </dl>
654
675
  </div>
655
676
  </div>
677
+
678
+ {/* Full width, below the two panels: Jev is a second evaluator for
679
+ the whole machine, not a property of the scheduled scan, and its
680
+ endpoint would not fit a half-width column anyway. */}
681
+ {jevPanel}
656
682
  </div>
657
683
  )}
658
684
 
@@ -151,6 +151,51 @@
151
151
  margin: 0;
152
152
  }
153
153
 
154
+ /* ── jev ──────────────────────────────────────────────────────────────────── */
155
+
156
+ /* A full-width cell in the same console, below the two panels. The gap above it
157
+ * is the console's own 1px grid — this rule adds no border of its own. */
158
+ .set-jev {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: 16px;
162
+ }
163
+
164
+ /* Two columns at desktop: a provider or a mode needs about half the width an
165
+ * endpoint does, and stacking five controls in one column turns a short form
166
+ * into a scroll. */
167
+ .set-jev-fields {
168
+ display: grid;
169
+ grid-template-columns: repeat(2, minmax(0, 1fr));
170
+ gap: 16px 20px;
171
+ }
172
+
173
+ .set-field {
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 6px;
177
+ min-width: 0;
178
+ }
179
+ .set-field-label {
180
+ font-family: var(--font-mono);
181
+ font-size: 9.5px;
182
+ font-weight: 500;
183
+ letter-spacing: 0.18em;
184
+ text-transform: uppercase;
185
+ color: var(--dim);
186
+ }
187
+
188
+ .set-jev-actions {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 16px;
192
+ flex-wrap: wrap;
193
+ }
194
+
195
+ @media (max-width: 640px) {
196
+ .set-jev-fields { grid-template-columns: 1fr; }
197
+ }
198
+
154
199
  /* ── masthead ─────────────────────────────────────────────────────────────── */
155
200
 
156
201
  .set-mast {
@@ -228,6 +273,40 @@
228
273
  outline-offset: 1px;
229
274
  }
230
275
 
276
+ /* `.set-num`'s box, stretched. Same background, same hairline, same square
277
+ * corners and the same pink focus ring — a second field style would read as a
278
+ * second form on one page. */
279
+ .set-text,
280
+ .set-select {
281
+ font-family: var(--font-mono);
282
+ font-size: 13px;
283
+ width: 100%;
284
+ padding: 6px 10px;
285
+ background: var(--bg);
286
+ border: 1px solid var(--line-2);
287
+ color: var(--ink);
288
+ border-radius: 0;
289
+ }
290
+ .set-text:focus-visible,
291
+ .set-select:focus-visible {
292
+ outline: 2px solid var(--accent-pink);
293
+ outline-offset: 1px;
294
+ }
295
+ .set-text:disabled,
296
+ .set-select:disabled {
297
+ opacity: 0.5;
298
+ cursor: not-allowed;
299
+ }
300
+ .set-text::placeholder {
301
+ color: var(--dim);
302
+ }
303
+ /* The native arrow is drawn by the platform and cannot be tokenised; leaving
304
+ * the control's own appearance in place is the one honest option, so the box
305
+ * around it is what carries the page's style. */
306
+ .set-select {
307
+ cursor: pointer;
308
+ }
309
+
231
310
  .set-link {
232
311
  font-family: var(--font-mono);
233
312
  font-size: 12px;