failproofai 0.0.11-beta.7 → 0.0.11-beta.9
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route/build-manifest.json +9 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +7 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_audit_invite_route_actions_0-2n5sy.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_0ttxbz7._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1epycqa._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_1wpdcgo._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +8 -8
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1cd25c7._.js → [root-of-the-server]__0e4-6d8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08gyb62._.js → [root-of-the-server]__0e446gb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-scthx._.js → [root-of-the-server]__0f62vu9._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l13qf2._.js → [root-of-the-server]__0k65l27._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__08v7sn2._.js → [root-of-the-server]__0wprfyc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0989_dx._.js → [root-of-the-server]__1e-x7j4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0il3fl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +21 -21
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0y4a-0q.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/functions-config-manifest.json +0 -3
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/{0hmxjseydp6as.js → 07_d165p5h5ys.js} +1 -1
- package/.next/standalone/.next/static/chunks/0f7d7hnbh4djs.js +1 -0
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +1 -0
- package/.next/standalone/.next/static/chunks/168k-8z6k7e8z.css +1 -0
- package/.next/standalone/.next/static/chunks/{3n28elq6kwpy3.js → 1kvadxkgnapyj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fpw1jzeg66_x.js → 277oc363p56n6.js} +2 -2
- package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +1 -0
- package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +41 -0
- package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +2 -0
- package/.next/standalone/.next/static/chunks/{41vwoxpkauj1w.js → 29tg7deqmq32l.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1eq7f419t6ln3.js → 2z42u62k-8-_q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0avgb78edu0i-.js → 3nj6g3xu9uy78.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0xkxbaa-oo180.js → 3ty6dhcuogout.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-00qy7zfa7m--m.js → turbopack-3lrm4f20fz89b.js} +1 -1
- package/.next/standalone/app/api/audit/_state.ts +40 -33
- package/.next/standalone/app/api/audit/invite/route.ts +183 -0
- package/.next/standalone/app/api/audit/run/route.ts +30 -16
- package/.next/standalone/app/api/audit/status/route.ts +1 -0
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +33 -65
- package/.next/standalone/app/audit/_components/audit-poster.tsx +322 -0
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +21 -49
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +316 -0
- package/.next/standalone/app/audit/_components/empty-state.tsx +8 -2
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
- package/.next/standalone/app/audit/_components/invite-dialog.tsx +227 -0
- package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
- package/.next/standalone/app/audit/_components/rerun-button.tsx +43 -14
- package/.next/standalone/app/audit/_components/run-progress.tsx +2 -2
- package/.next/standalone/app/audit/_components/share-templates.ts +23 -22
- package/.next/standalone/app/audit/_components/sigil.tsx +9 -66
- package/.next/standalone/app/audit/_components/strengths-section.tsx +20 -32
- package/.next/standalone/app/audit/audit-styles.css +781 -1784
- package/.next/standalone/app/components/sessions-list.tsx +77 -80
- package/.next/standalone/app/globals.css +214 -32
- package/.next/standalone/app/layout.tsx +1 -10
- package/.next/standalone/app/policies/hooks-client.tsx +12 -4
- package/.next/standalone/app/project/[name]/page.tsx +23 -79
- package/.next/standalone/app/projects/page.tsx +14 -23
- package/.next/standalone/assets/audit/poster-styles.css +1 -1
- package/.next/standalone/assets/audit/styles.css +11 -11
- package/.next/standalone/components/navbar.tsx +2 -37
- package/.next/standalone/lib/auth/api-server-client.ts +25 -0
- package/.next/standalone/node_modules/@next/env/package.json +2 -2
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +15 -15
- package/.next/standalone/package.json +18 -15
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +2 -2
- package/lib/auth/api-server-client.ts +25 -0
- package/package.json +18 -15
- package/src/audit/dashboard-cache.ts +13 -6
- package/src/audit/social-proof.ts +34 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07tgnzi._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oeun7z._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12pit4m._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13ra2jq._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1b9z5-i._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ixjiy8._.js +0 -3
- package/.next/standalone/.next/server/chunks/_1-1804f._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00jkjmt._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013du6r._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e85wxv._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gfxvb1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hlrq6y._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ihxdo5._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1vvfde2._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_0q4m0_h.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html2canvas_dist_html2canvas_esm_1n-0xws.js +0 -3
- package/.next/standalone/.next/static/chunks/09ueq8s1as8xs.css +0 -2
- package/.next/standalone/.next/static/chunks/0qassxjx1ef04.js +0 -1
- package/.next/standalone/.next/static/chunks/2452blhu4bcdr.js +0 -41
- package/.next/standalone/.next/static/chunks/2so39wg7mjbi7.js +0 -1
- package/.next/standalone/.next/static/chunks/3gti1qdk5epqn.js +0 -1
- package/.next/standalone/.next/static/chunks/3osmt-t2hsrng.js +0 -1
- package/.next/standalone/.next/static/chunks/3wycox197ouus.css +0 -1
- package/.next/standalone/app/audit/_components/findings-section.tsx +0 -135
- package/.next/standalone/app/audit/_components/identity-section.tsx +0 -126
- package/.next/standalone/app/audit/_components/policies-section.tsx +0 -194
- package/.next/standalone/app/audit/_components/return-section.tsx +0 -416
- package/.next/standalone/app/audit/_components/score-section.tsx +0 -179
- package/.next/standalone/app/audit/_components/share-dock.tsx +0 -265
- package/.next/standalone/app/audit/_components/show-off-cta.tsx +0 -135
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{90NFQus9VOnEzD6sP0EyP → NYPiJP6Rv_exQdSFVS8HP}/_ssgManifest.js +0 -0
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Section 05 — PRESCRIBED POLICIES. "enable these. close the gap."
|
|
5
|
-
*
|
|
6
|
-
* Grid of unenabled-builtin cards with install commands + projected
|
|
7
|
-
* score uplift callout.
|
|
8
|
-
*
|
|
9
|
-
* Sources two layers of "hits":
|
|
10
|
-
* 1. Unenabled builtin policies that fired on their own
|
|
11
|
-
* 2. Audit detectors → mapped via DETECTOR_TO_POLICY in findings.ts.
|
|
12
|
-
* The detector's hits get attributed to its primary policy so the
|
|
13
|
-
* report frames everything as failproofai-coverable.
|
|
14
|
-
*
|
|
15
|
-
* Same policy can collect hits from multiple sources; we sum them and
|
|
16
|
-
* render one card per policy.
|
|
17
|
-
*/
|
|
18
|
-
import React, { useMemo, useState } from "react";
|
|
19
|
-
import type { AuditResult } from "@/src/audit/types";
|
|
20
|
-
import { type Grade, tierName } from "@/src/audit/scoring";
|
|
21
|
-
import { usePostHog } from "@/contexts/PostHogContext";
|
|
22
|
-
|
|
23
|
-
interface Props {
|
|
24
|
-
result: AuditResult;
|
|
25
|
-
projected: number;
|
|
26
|
-
projectedGrade: Grade;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Mirror of DETECTOR_TO_POLICY in findings.ts. Could re-export but keep
|
|
30
|
-
// the dependency tree shallow — both modules are stable.
|
|
31
|
-
const DETECTOR_TO_PRIMARY_POLICY: Record<string, string> = {
|
|
32
|
-
"redundant-cd-cwd": "warn-repeated-tool-calls",
|
|
33
|
-
"prefer-edit-over-read-cat": "block-read-outside-cwd",
|
|
34
|
-
"prefer-edit-over-sed-awk": "warn-repeated-tool-calls",
|
|
35
|
-
"prefer-write-over-heredoc": "block-env-files",
|
|
36
|
-
"sleep-polling-loop": "warn-background-process",
|
|
37
|
-
"find-from-root": "block-read-outside-cwd",
|
|
38
|
-
"git-commit-no-verify": "warn-git-amend",
|
|
39
|
-
"reread-after-edit": "warn-repeated-tool-calls",
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const POLICY_DESC: Record<string, string> = {
|
|
43
|
-
"warn-repeated-tool-calls": "warns when the same tool is called 3+ times with identical parameters — catches the loops before they spiral.",
|
|
44
|
-
"block-read-outside-cwd": "denies any file read whose absolute path falls outside the project root, including symlinks.",
|
|
45
|
-
"block-env-files": "blocks reads and writes of `.env` files at the tool layer.",
|
|
46
|
-
"block-secrets-write": "blocks writes to .pem, id_rsa, credentials.json, and other secret-key files.",
|
|
47
|
-
"warn-background-process": "warns before starting nohup / & / screen / tmux / disown processes that get forgotten about.",
|
|
48
|
-
"warn-git-amend": "warns before amending git commits — dangerous-commit-flag class.",
|
|
49
|
-
"require-ci-green-before-stop": "requires CI checks to pass on HEAD before the agent declares the task done.",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function shortName(name: string): string {
|
|
53
|
-
const slash = name.indexOf("/");
|
|
54
|
-
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface PolicyCard {
|
|
58
|
-
name: string; // short slug
|
|
59
|
-
desc: string; // displayTitle (low-res) or impact
|
|
60
|
-
catches: string; // "would have caught X occurrences..." copy
|
|
61
|
-
hits: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function buildPolicyCards(result: AuditResult): PolicyCard[] {
|
|
65
|
-
const enabledSet = new Set(result.enabledBuiltinNames ?? []);
|
|
66
|
-
// policyName → aggregated counts
|
|
67
|
-
const buckets = new Map<string, { hits: number; projects: number; sources: Set<string> }>();
|
|
68
|
-
|
|
69
|
-
for (const row of result.results) {
|
|
70
|
-
if (row.hits === 0) continue;
|
|
71
|
-
|
|
72
|
-
let target: string;
|
|
73
|
-
let isFromDetector = false;
|
|
74
|
-
if (row.source === "audit-detector") {
|
|
75
|
-
const mapped = DETECTOR_TO_PRIMARY_POLICY[shortName(row.name)];
|
|
76
|
-
if (!mapped) continue;
|
|
77
|
-
target = mapped;
|
|
78
|
-
isFromDetector = true;
|
|
79
|
-
} else if (row.source === "builtin" && !row.enabledInConfig) {
|
|
80
|
-
target = shortName(row.name);
|
|
81
|
-
} else {
|
|
82
|
-
continue; // already-enabled builtins don't need to be prescribed
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Skip if the target policy is already in the user's enabled set
|
|
86
|
-
// (detector hits would land there in production already).
|
|
87
|
-
if (enabledSet.has(target)) continue;
|
|
88
|
-
|
|
89
|
-
const bucket = buckets.get(target) ?? { hits: 0, projects: 0, sources: new Set() };
|
|
90
|
-
bucket.hits += row.hits;
|
|
91
|
-
bucket.projects = Math.max(bucket.projects, row.projects);
|
|
92
|
-
bucket.sources.add(isFromDetector ? shortName(row.name) : "self");
|
|
93
|
-
buckets.set(target, bucket);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return [...buckets.entries()]
|
|
97
|
-
.sort((a, b) => b[1].hits - a[1].hits)
|
|
98
|
-
.map(([name, b]) => {
|
|
99
|
-
const viaList = [...b.sources].filter((s) => s !== "self");
|
|
100
|
-
const viaCopy = viaList.length > 0
|
|
101
|
-
? ` (via ${viaList.join(", ")})`
|
|
102
|
-
: "";
|
|
103
|
-
const catches = `would have caught ${b.hits} occurrence${b.hits === 1 ? "" : "s"} across ${b.projects} project${b.projects === 1 ? "" : "s"}${viaCopy}.`;
|
|
104
|
-
return {
|
|
105
|
-
name,
|
|
106
|
-
desc: POLICY_DESC[name] ?? "enable this builtin policy to close the gap.",
|
|
107
|
-
catches,
|
|
108
|
-
hits: b.hits,
|
|
109
|
-
};
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function PoliciesSection({ result, projected, projectedGrade }: Props) {
|
|
114
|
-
// Builds a Map+Set aggregation + sort over result.results — non-trivial.
|
|
115
|
-
// Memoize so unrelated parent re-renders don't recompute every frame.
|
|
116
|
-
const policies = useMemo(() => buildPolicyCards(result), [result]);
|
|
117
|
-
|
|
118
|
-
if (policies.length === 0) return null;
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<section className="section" data-screen-label="05 Policies">
|
|
122
|
-
<div className="section-mast">
|
|
123
|
-
<div className="section-label">
|
|
124
|
-
<span className="glyph">━━</span> policies{" "}
|
|
125
|
-
<span style={{ color: "var(--dim)" }}>·</span> prescribed
|
|
126
|
-
</div>
|
|
127
|
-
<div className="section-meta">
|
|
128
|
-
{policies.length} polic{policies.length === 1 ? "y" : "ies"}{" "}
|
|
129
|
-
<span style={{ color: "var(--dim)" }}>·</span>{" "}
|
|
130
|
-
<span className="g">covers your slipping-through hits</span>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
<h2 className="section-h">enable these. close the gap.</h2>
|
|
134
|
-
|
|
135
|
-
<div className="policy-callout">
|
|
136
|
-
<span>
|
|
137
|
-
enable all {policies.length === 1 ? "one" : policies.length}
|
|
138
|
-
</span>
|
|
139
|
-
<span className="arrow">→</span>
|
|
140
|
-
<span>projected score</span>
|
|
141
|
-
<span className="new-score">{projected}</span>
|
|
142
|
-
<span style={{ color: "var(--dim)" }}>·</span>
|
|
143
|
-
<span className="new-tier">{tierName(projectedGrade)}</span>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<div className="policies-grid">
|
|
147
|
-
{policies.map((p, i) => (
|
|
148
|
-
<PolicyTile key={p.name} policy={p} idx={i} />
|
|
149
|
-
))}
|
|
150
|
-
</div>
|
|
151
|
-
</section>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function PolicyTile({ policy, idx }: { policy: PolicyCard; idx: number }) {
|
|
156
|
-
const { capture } = usePostHog();
|
|
157
|
-
const [copied, setCopied] = useState(false);
|
|
158
|
-
const install = `failproof policy add ${policy.name}`;
|
|
159
|
-
|
|
160
|
-
const handleCopy = async (e: React.MouseEvent) => {
|
|
161
|
-
e.stopPropagation();
|
|
162
|
-
try {
|
|
163
|
-
await navigator.clipboard.writeText(install);
|
|
164
|
-
setCopied(true);
|
|
165
|
-
capture("audit_copy_clicked", {
|
|
166
|
-
source: "policies_section",
|
|
167
|
-
item_type: "single_policy_install_command",
|
|
168
|
-
policy_name: policy.name,
|
|
169
|
-
policy_rank: idx + 1,
|
|
170
|
-
});
|
|
171
|
-
setTimeout(() => setCopied(false), 1500);
|
|
172
|
-
} catch { /* ignore */ }
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<article className="policy-card">
|
|
177
|
-
<div className="head">
|
|
178
|
-
<div className="policy-name">{policy.name}</div>
|
|
179
|
-
<div className="policy-slug">№{String(idx + 1).padStart(2, "0")}</div>
|
|
180
|
-
</div>
|
|
181
|
-
<div className="policy-desc">{policy.desc}</div>
|
|
182
|
-
<div className="policy-impact">
|
|
183
|
-
<span className="check">✓</span>{policy.catches}
|
|
184
|
-
</div>
|
|
185
|
-
<div className="policy-install">
|
|
186
|
-
<span className="prompt">$</span>
|
|
187
|
-
<span>{install}</span>
|
|
188
|
-
<span className="copy" onClick={handleCopy}>
|
|
189
|
-
{copied ? "copied" : "copy"}
|
|
190
|
-
</span>
|
|
191
|
-
</div>
|
|
192
|
-
</article>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Section 06 — NEXT AUDIT / "come back better." Re-audit loop CTA.
|
|
5
|
-
*
|
|
6
|
-
* Behavior matrix:
|
|
7
|
-
* - unknown (probe in flight) → buttons disabled
|
|
8
|
-
* - anon (no session) → [ set a reminder ] opens AuthDialog,
|
|
9
|
-
* on success we flip to the authed panel
|
|
10
|
-
* below and persist the 7-day reminder.
|
|
11
|
-
* - authed (any) → consolidated status panel: "signed in as
|
|
12
|
-
* …" + either the persisted "next audit in
|
|
13
|
-
* X days" line OR a "no reminder set yet"
|
|
14
|
-
* line with an inline [ set a reminder ]
|
|
15
|
-
* button. The reminder persists across
|
|
16
|
-
* reloads via ~/.failproofai/next-audit.json
|
|
17
|
-
* — same as the CLI's auth.json.
|
|
18
|
-
*
|
|
19
|
-
* Also exposes [ re-audit now ] next to [ install policies ] so the user
|
|
20
|
-
* can trigger a fresh scan inline without leaving the page. The button
|
|
21
|
-
* fires POST /api/audit/run (same backend the empty-state CTA uses).
|
|
22
|
-
*/
|
|
23
|
-
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
24
|
-
import type { AuditResult } from "@/src/audit/types";
|
|
25
|
-
import { usePostHog } from "@/contexts/PostHogContext";
|
|
26
|
-
import { isAbortError } from "@/lib/fetch-with-timeout";
|
|
27
|
-
import { AuthDialog, type AuthedUser } from "./auth-dialog";
|
|
28
|
-
|
|
29
|
-
interface Props {
|
|
30
|
-
result: AuditResult;
|
|
31
|
-
/** Lifted by AuditDashboard so the bottom button and the top-of-page
|
|
32
|
-
* re-audit bar share state — only one rerun at a time. */
|
|
33
|
-
isRunning: boolean;
|
|
34
|
-
/** Lifted handler. Drives the sticky progress strip + soft refresh. */
|
|
35
|
-
onRerun: () => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const BULK_INSTALL_CMD = "failproofai policies --install";
|
|
39
|
-
const DEFAULT_REMINDER_DAYS = 7;
|
|
40
|
-
|
|
41
|
-
type AuthStatus =
|
|
42
|
-
| { kind: "unknown" }
|
|
43
|
-
| { kind: "anon" }
|
|
44
|
-
| { kind: "authed"; user: { id: string; email: string } };
|
|
45
|
-
|
|
46
|
-
interface Reminder {
|
|
47
|
-
next_audit_at: number; // unix seconds
|
|
48
|
-
user_email: string;
|
|
49
|
-
set_at: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function daysUntil(unixSecs: number): number {
|
|
53
|
-
const nowSecs = Math.floor(Date.now() / 1000);
|
|
54
|
-
return Math.max(0, Math.ceil((unixSecs - nowSecs) / 86400));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function formatNextAudit(unixSecs: number): string {
|
|
58
|
-
const d = new Date(unixSecs * 1000);
|
|
59
|
-
return d.toLocaleDateString(undefined, {
|
|
60
|
-
weekday: "short",
|
|
61
|
-
month: "short",
|
|
62
|
-
day: "numeric",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function ReturnSection({ result, isRunning, onRerun }: Props) {
|
|
67
|
-
const { capture } = usePostHog();
|
|
68
|
-
const hasUnenabled = result.results.some(
|
|
69
|
-
(r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0,
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const [copied, setCopied] = useState(false);
|
|
73
|
-
const [authStatus, setAuthStatus] = useState<AuthStatus>({ kind: "unknown" });
|
|
74
|
-
const [reminder, setReminder] = useState<Reminder | null>(null);
|
|
75
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
76
|
-
const [reminderBusy, setReminderBusy] = useState(false);
|
|
77
|
-
const ctaShownRef = useRef(false);
|
|
78
|
-
/** Throttle gate for focus/visibility-triggered refreshStatus calls —
|
|
79
|
-
* rapid alt-tabbing would otherwise hit /api/auth/status (two disk
|
|
80
|
-
* reads each) once per event. */
|
|
81
|
-
const lastRefreshAtRef = useRef(0);
|
|
82
|
-
|
|
83
|
-
// Probe /api/auth/status on mount — also returns the persisted reminder
|
|
84
|
-
// when one exists and belongs to the active session.
|
|
85
|
-
const refreshStatus = useCallback(async () => {
|
|
86
|
-
lastRefreshAtRef.current = Date.now();
|
|
87
|
-
try {
|
|
88
|
-
const res = await fetch("/api/auth/status", { cache: "no-store" });
|
|
89
|
-
const body = (await res.json()) as {
|
|
90
|
-
authenticated?: boolean;
|
|
91
|
-
user?: { id: string; email: string };
|
|
92
|
-
reminder?: Reminder | null;
|
|
93
|
-
};
|
|
94
|
-
if (body.authenticated && body.user) {
|
|
95
|
-
setAuthStatus({ kind: "authed", user: body.user });
|
|
96
|
-
setReminder(body.reminder ?? null);
|
|
97
|
-
} else {
|
|
98
|
-
setAuthStatus({ kind: "anon" });
|
|
99
|
-
setReminder(null);
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
setAuthStatus({ kind: "anon" });
|
|
103
|
-
setReminder(null);
|
|
104
|
-
}
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
void refreshStatus();
|
|
109
|
-
// Re-probe whenever the tab regains focus or visibility — picks up
|
|
110
|
-
// CLI `failproofai auth login` / `logout` and api-server restarts
|
|
111
|
-
// without the user having to hit reload manually. Throttled to 5s so
|
|
112
|
-
// rapid alt-tabbing (a focus + a visibility event for one switch)
|
|
113
|
-
// doesn't double-fire.
|
|
114
|
-
const REFRESH_MIN_INTERVAL_MS = 5_000;
|
|
115
|
-
const maybeRefresh = () => {
|
|
116
|
-
if (Date.now() - lastRefreshAtRef.current < REFRESH_MIN_INTERVAL_MS) return;
|
|
117
|
-
void refreshStatus();
|
|
118
|
-
};
|
|
119
|
-
const onFocus = () => maybeRefresh();
|
|
120
|
-
const onVisibility = () => {
|
|
121
|
-
if (document.visibilityState === "visible") maybeRefresh();
|
|
122
|
-
};
|
|
123
|
-
window.addEventListener("focus", onFocus);
|
|
124
|
-
document.addEventListener("visibilitychange", onVisibility);
|
|
125
|
-
return () => {
|
|
126
|
-
window.removeEventListener("focus", onFocus);
|
|
127
|
-
document.removeEventListener("visibilitychange", onVisibility);
|
|
128
|
-
};
|
|
129
|
-
}, [refreshStatus]);
|
|
130
|
-
|
|
131
|
-
// Fire-once "user saw the reminder CTA" event so we can compute the
|
|
132
|
-
// funnel shown → clicked → auth → saved. We wait until the auth probe
|
|
133
|
-
// finishes — before that the buttons are disabled and the CTA isn't
|
|
134
|
-
// really "shown".
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
if (ctaShownRef.current) return;
|
|
137
|
-
if (authStatus.kind === "unknown") return;
|
|
138
|
-
ctaShownRef.current = true;
|
|
139
|
-
capture("audit_reminder_cta_shown", {
|
|
140
|
-
auth_state: authStatus.kind,
|
|
141
|
-
has_existing_reminder: reminder !== null,
|
|
142
|
-
source: "return_section",
|
|
143
|
-
});
|
|
144
|
-
}, [authStatus, capture, reminder]);
|
|
145
|
-
|
|
146
|
-
const persistReminder = useCallback(async (): Promise<Reminder | null> => {
|
|
147
|
-
// 10s ceiling so a hung route can't permanently disable the CTA.
|
|
148
|
-
const controller = new AbortController();
|
|
149
|
-
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
150
|
-
try {
|
|
151
|
-
setReminderBusy(true);
|
|
152
|
-
const res = await fetch("/api/auth/reminder", {
|
|
153
|
-
method: "POST",
|
|
154
|
-
headers: { "content-type": "application/json" },
|
|
155
|
-
body: JSON.stringify({ in_days: DEFAULT_REMINDER_DAYS }),
|
|
156
|
-
signal: controller.signal,
|
|
157
|
-
});
|
|
158
|
-
if (!res.ok) {
|
|
159
|
-
// A 401 here means our local "authed" view of the world is out of
|
|
160
|
-
// date — the server-side session was revoked or expired between the
|
|
161
|
-
// status probe and this write. Flip back to anon so the user can
|
|
162
|
-
// re-authenticate instead of leaving them on a panel whose actions
|
|
163
|
-
// silently no-op.
|
|
164
|
-
if (res.status === 401) {
|
|
165
|
-
setAuthStatus({ kind: "anon" });
|
|
166
|
-
setReminder(null);
|
|
167
|
-
}
|
|
168
|
-
capture("audit_reminder_saved", {
|
|
169
|
-
status: `http_${res.status}`,
|
|
170
|
-
source: "return_section",
|
|
171
|
-
});
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const body = (await res.json()) as { reminder?: Reminder };
|
|
175
|
-
capture("audit_reminder_saved", {
|
|
176
|
-
status: body.reminder ? "success" : "empty",
|
|
177
|
-
source: "return_section",
|
|
178
|
-
});
|
|
179
|
-
return body.reminder ?? null;
|
|
180
|
-
} catch (err) {
|
|
181
|
-
const kind = isAbortError(err) ? "timeout" : "error";
|
|
182
|
-
capture("audit_reminder_saved", {
|
|
183
|
-
status: kind,
|
|
184
|
-
source: "return_section",
|
|
185
|
-
});
|
|
186
|
-
return null;
|
|
187
|
-
} finally {
|
|
188
|
-
clearTimeout(timer);
|
|
189
|
-
setReminderBusy(false);
|
|
190
|
-
}
|
|
191
|
-
}, [capture]);
|
|
192
|
-
|
|
193
|
-
const handleInstall = async () => {
|
|
194
|
-
capture("audit_install_policies_clicked", {
|
|
195
|
-
source: "return_section",
|
|
196
|
-
});
|
|
197
|
-
try {
|
|
198
|
-
await navigator.clipboard.writeText(BULK_INSTALL_CMD);
|
|
199
|
-
setCopied(true);
|
|
200
|
-
capture("audit_copy_clicked", {
|
|
201
|
-
source: "return_section_install_policies",
|
|
202
|
-
item_type: "bulk_install_command",
|
|
203
|
-
});
|
|
204
|
-
setTimeout(() => setCopied(false), 1500);
|
|
205
|
-
} catch {
|
|
206
|
-
/* ignore */
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const handleSetReminder = useCallback(async () => {
|
|
211
|
-
if (authStatus.kind === "unknown") return;
|
|
212
|
-
capture("audit_reminder_cta_clicked", {
|
|
213
|
-
auth_state: authStatus.kind,
|
|
214
|
-
has_existing_reminder: reminder !== null,
|
|
215
|
-
source: "return_section",
|
|
216
|
-
});
|
|
217
|
-
if (authStatus.kind === "authed") {
|
|
218
|
-
const next = await persistReminder();
|
|
219
|
-
if (next) setReminder(next);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
setDialogOpen(true);
|
|
223
|
-
}, [authStatus, capture, persistReminder, reminder]);
|
|
224
|
-
|
|
225
|
-
const handleAuthed = useCallback(
|
|
226
|
-
async (user: AuthedUser) => {
|
|
227
|
-
setAuthStatus({ kind: "authed", user });
|
|
228
|
-
capture("audit_auth_completed", {
|
|
229
|
-
source: "return_section",
|
|
230
|
-
});
|
|
231
|
-
// The dialog opened because the user wanted a reminder → persist
|
|
232
|
-
// immediately, no second click required.
|
|
233
|
-
const next = await persistReminder();
|
|
234
|
-
if (next) setReminder(next);
|
|
235
|
-
},
|
|
236
|
-
[capture, persistReminder],
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// Re-audit is now lifted into AuditDashboard so the top-of-page bar and
|
|
240
|
-
// this bottom button share state — concurrent clicks are impossible and
|
|
241
|
-
// success path soft-refreshes the report rather than reloading the page.
|
|
242
|
-
// PostHog `source: "return_section"` capture happens inside the lifted
|
|
243
|
-
// handler via the `source` argument it receives.
|
|
244
|
-
const handleRerun = useCallback(() => {
|
|
245
|
-
if (isRunning) return;
|
|
246
|
-
onRerun();
|
|
247
|
-
}, [isRunning, onRerun]);
|
|
248
|
-
|
|
249
|
-
const authed = authStatus.kind === "authed";
|
|
250
|
-
const hasReminder = authed && reminder !== null;
|
|
251
|
-
const days = reminder ? daysUntil(reminder.next_audit_at) : 0;
|
|
252
|
-
const authedEmail =
|
|
253
|
-
authStatus.kind === "authed" ? authStatus.user.email : "";
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<section className="section" data-screen-label="06 Next audit">
|
|
257
|
-
<div className="section-mast">
|
|
258
|
-
<div className="section-label">
|
|
259
|
-
<span className="glyph">━━</span> next audit{" "}
|
|
260
|
-
<span style={{ color: "var(--dim)" }}>·</span> improvement
|
|
261
|
-
</div>
|
|
262
|
-
<div className="section-meta">
|
|
263
|
-
<span className="g">●</span> recommended in 7d
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
<h2 className="section-h">come back better.</h2>
|
|
267
|
-
|
|
268
|
-
<div className="return-hook">
|
|
269
|
-
<div className="label">━━ the loop</div>
|
|
270
|
-
<h3>re-audit in 7 days.</h3>
|
|
271
|
-
<p>
|
|
272
|
-
after the prescribed policies have been live for a week, we'll show
|
|
273
|
-
your before/after score and which detectors went quiet.
|
|
274
|
-
</p>
|
|
275
|
-
<p style={{ marginTop: 16, color: "var(--dim)" }}>
|
|
276
|
-
most agents move from C to B in one session. some make it in a day.
|
|
277
|
-
</p>
|
|
278
|
-
|
|
279
|
-
{/* Once authed, the section stays in the consolidated status panel —
|
|
280
|
-
with the reminder line if one is set, or a "no reminder yet" line
|
|
281
|
-
+ inline [ set a reminder ] button otherwise. The anonymous CTA
|
|
282
|
-
layout only shows for genuinely-unauthed sessions. The action
|
|
283
|
-
buttons ([ set a reminder ] / [ re-audit now ] / [ install
|
|
284
|
-
policies ]) are identical in both branches — extracted into
|
|
285
|
-
<ReturnActions> below to keep them in sync. */}
|
|
286
|
-
{authed ? (
|
|
287
|
-
<div className="return-status">
|
|
288
|
-
{hasReminder && reminder ? (
|
|
289
|
-
<div className="rs-row rs-row-primary">
|
|
290
|
-
<span className="rs-dot rs-dot-pink" aria-hidden="true" />
|
|
291
|
-
<span>
|
|
292
|
-
next audit set for{" "}
|
|
293
|
-
<span className="rs-strong">{formatNextAudit(reminder.next_audit_at)}</span>
|
|
294
|
-
{" "}<span style={{ color: "var(--dim)" }}>·</span>{" "}
|
|
295
|
-
<span className="rs-strong">in {days} day{days === 1 ? "" : "s"}</span>
|
|
296
|
-
</span>
|
|
297
|
-
</div>
|
|
298
|
-
) : (
|
|
299
|
-
<div className="rs-row rs-row-primary">
|
|
300
|
-
<span className="rs-dot rs-dot-pink" aria-hidden="true" />
|
|
301
|
-
<span>
|
|
302
|
-
<span className="rs-strong">no reminder set yet</span>
|
|
303
|
-
{" "}<span style={{ color: "var(--dim)" }}>·</span>{" "}
|
|
304
|
-
recommended in {DEFAULT_REMINDER_DAYS} days
|
|
305
|
-
</span>
|
|
306
|
-
</div>
|
|
307
|
-
)}
|
|
308
|
-
<div className="rs-row">
|
|
309
|
-
<span className="rs-dot rs-dot-green" aria-hidden="true" />
|
|
310
|
-
<span>
|
|
311
|
-
signed in as <span className="rs-email">{authedEmail}</span>
|
|
312
|
-
</span>
|
|
313
|
-
</div>
|
|
314
|
-
<ReturnActions
|
|
315
|
-
style={{ marginTop: 18 }}
|
|
316
|
-
showSetReminder={!hasReminder}
|
|
317
|
-
setReminderDisabled={reminderBusy}
|
|
318
|
-
reminderBusy={reminderBusy}
|
|
319
|
-
rerunBusy={isRunning}
|
|
320
|
-
hasUnenabled={hasUnenabled}
|
|
321
|
-
copied={copied}
|
|
322
|
-
onSetReminder={handleSetReminder}
|
|
323
|
-
onRerun={handleRerun}
|
|
324
|
-
onInstall={handleInstall}
|
|
325
|
-
/>
|
|
326
|
-
</div>
|
|
327
|
-
) : (
|
|
328
|
-
<ReturnActions
|
|
329
|
-
showSetReminder
|
|
330
|
-
setReminderDisabled={authStatus.kind === "unknown" || reminderBusy}
|
|
331
|
-
reminderBusy={reminderBusy}
|
|
332
|
-
rerunBusy={isRunning}
|
|
333
|
-
hasUnenabled={hasUnenabled}
|
|
334
|
-
copied={copied}
|
|
335
|
-
onSetReminder={handleSetReminder}
|
|
336
|
-
onRerun={handleRerun}
|
|
337
|
-
onInstall={handleInstall}
|
|
338
|
-
/>
|
|
339
|
-
)}
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<AuthDialog
|
|
343
|
-
open={dialogOpen}
|
|
344
|
-
source="return_section"
|
|
345
|
-
headline="oops — you are unknown."
|
|
346
|
-
reason="verify yourself to get the re-audit reminder."
|
|
347
|
-
onClose={() => setDialogOpen(false)}
|
|
348
|
-
onAuthed={(u) => {
|
|
349
|
-
setDialogOpen(false);
|
|
350
|
-
void handleAuthed(u);
|
|
351
|
-
}}
|
|
352
|
-
/>
|
|
353
|
-
</section>
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
interface ReturnActionsProps {
|
|
358
|
-
/** Whether to render the `[ set a reminder ]` button. False when the
|
|
359
|
-
* user already has a reminder set (the authed-with-reminder case
|
|
360
|
-
* shows the panel meta instead). */
|
|
361
|
-
showSetReminder: boolean;
|
|
362
|
-
setReminderDisabled: boolean;
|
|
363
|
-
reminderBusy: boolean;
|
|
364
|
-
rerunBusy: boolean;
|
|
365
|
-
hasUnenabled: boolean;
|
|
366
|
-
copied: boolean;
|
|
367
|
-
onSetReminder: () => void;
|
|
368
|
-
onRerun: () => void;
|
|
369
|
-
onInstall: () => void;
|
|
370
|
-
style?: React.CSSProperties;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/** Action button strip shared by the authed and anon branches. Extracted
|
|
374
|
-
* to keep the three buttons in sync — the prior inline copies had
|
|
375
|
-
* already drifted on margin-top styling. */
|
|
376
|
-
function ReturnActions(props: ReturnActionsProps): React.ReactElement {
|
|
377
|
-
const {
|
|
378
|
-
showSetReminder,
|
|
379
|
-
setReminderDisabled,
|
|
380
|
-
reminderBusy,
|
|
381
|
-
rerunBusy,
|
|
382
|
-
hasUnenabled,
|
|
383
|
-
copied,
|
|
384
|
-
onSetReminder,
|
|
385
|
-
onRerun,
|
|
386
|
-
onInstall,
|
|
387
|
-
style,
|
|
388
|
-
} = props;
|
|
389
|
-
return (
|
|
390
|
-
<div className="return-actions" style={style}>
|
|
391
|
-
{showSetReminder && (
|
|
392
|
-
<button
|
|
393
|
-
type="button"
|
|
394
|
-
className="share-btn"
|
|
395
|
-
onClick={onSetReminder}
|
|
396
|
-
disabled={setReminderDisabled}
|
|
397
|
-
>
|
|
398
|
-
{reminderBusy ? "[ saving… ]" : "[ set a reminder ]"}
|
|
399
|
-
</button>
|
|
400
|
-
)}
|
|
401
|
-
<button
|
|
402
|
-
type="button"
|
|
403
|
-
className="share-btn alt"
|
|
404
|
-
onClick={onRerun}
|
|
405
|
-
disabled={rerunBusy}
|
|
406
|
-
>
|
|
407
|
-
{rerunBusy ? "[ scanning… ]" : "[ re-audit now ]"}
|
|
408
|
-
</button>
|
|
409
|
-
{hasUnenabled && (
|
|
410
|
-
<button type="button" className="share-btn alt" onClick={onInstall}>
|
|
411
|
-
{copied ? "[ ✓ copied — paste in your shell ]" : "[ install policies ]"}
|
|
412
|
-
</button>
|
|
413
|
-
)}
|
|
414
|
-
</div>
|
|
415
|
-
);
|
|
416
|
-
}
|