failproofai 0.0.11-beta.8 → 0.0.11
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/[externals]__1_g_b3t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
- 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 +3 -3
- 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]__1d4gx_t._.js → [root-of-the-server]__00uwqi6._.js} +2 -2
- 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]__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} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15i0juc._.js → [root-of-the-server]__0kjb_s4._.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]__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/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +49 -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_0ihmbv4.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/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/{0d49wc5zca0u1.js → 02fywjt0by40a.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/0xdx2ehtbdoeg.js +1 -0
- package/.next/standalone/.next/static/chunks/{1nd1e30h8s_mc.js → 1-a5rvq67k7ed.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1m2yj97j7f_km.js → 15csyj1_rf0-w.js} +1 -1
- package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +2 -0
- package/.next/standalone/.next/static/chunks/24cv31x607n7k.js +1 -0
- package/.next/standalone/.next/static/chunks/{3w8d8k_dca5rp.js → 2h0dkzyy0vocp.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n_s8v1ae38_a.js +69 -0
- package/.next/standalone/.next/static/chunks/2y-jmvrjxz60x.js +6 -0
- package/.next/standalone/.next/static/chunks/{24z-bgbisv379.js → 3eik_d9qrvoft.js} +1 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +1 -0
- package/.next/standalone/.next/static/chunks/{0j969hb6nujdf.js → 3v61675vr6jav.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/invite/route.ts +192 -0
- package/.next/standalone/app/api/audit/run/route.ts +35 -0
- package/.next/standalone/app/api/auth/login-request/route.ts +2 -2
- package/.next/standalone/app/api/auth/login-verify/route.ts +10 -2
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +39 -63
- package/.next/standalone/app/audit/_components/audit-poster.tsx +326 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +23 -49
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +336 -0
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +187 -0
- package/.next/standalone/app/audit/_components/invite-dialog.tsx +230 -0
- package/.next/standalone/app/audit/_components/quirks-section.tsx +75 -0
- package/.next/standalone/app/audit/_components/share-templates.ts +63 -32
- 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 +778 -1786
- package/.next/standalone/app/components/sessions-list.tsx +77 -80
- package/.next/standalone/app/globals.css +241 -34
- package/.next/standalone/app/layout.tsx +1 -10
- package/.next/standalone/app/policies/hooks-client.tsx +45 -28
- 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/components/reach-developers.tsx +10 -25
- package/.next/standalone/lib/auth/api-server-client.ts +28 -0
- package/.next/standalone/lib/client-telemetry.ts +4 -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 +19 -14
- package/.next/standalone/server.js +1 -1
- package/README.md +2 -2
- package/bin/failproofai.mjs +24 -5
- package/dist/cli.mjs +2328 -381
- package/lib/auth/api-server-client.ts +28 -0
- package/lib/client-telemetry.ts +4 -0
- package/package.json +19 -14
- package/scripts/launch.ts +30 -4
- package/scripts/postinstall.mjs +10 -1
- package/scripts/skew-log-filter.ts +46 -0
- package/scripts/validate-mdx.ts +139 -0
- package/src/audit/cli.ts +330 -0
- package/src/audit/open-browser.ts +69 -0
- package/src/audit/social-proof.ts +34 -0
- package/src/auth/cli.ts +16 -13
- 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_1k58rb_.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/0qxb5czqxe-vu.js +0 -1
- package/.next/standalone/.next/static/chunks/1dh06515j265n.js +0 -41
- package/.next/standalone/.next/static/chunks/29gs4efgi3hme.js +0 -6
- package/.next/standalone/.next/static/chunks/2mni177pnjx6u.js +0 -1
- 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/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/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{CVv2A0hMd24t0c0x3V-W_ → P_MIRSeoE296wkbE-Icin}/_ssgManifest.js +0 -0
|
@@ -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
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Section 03 — SCORE CARD.
|
|
5
|
-
*
|
|
6
|
-
* Left column only: YOUR AUDIT SCORE (big number, tier badge, progress
|
|
7
|
-
* bar, 3 stat boxes, prescribed-policies chip strip).
|
|
8
|
-
*
|
|
9
|
-
* Share actions have moved to IdentitySection below the archetype sigil.
|
|
10
|
-
*/
|
|
11
|
-
import React, { useMemo } from "react";
|
|
12
|
-
import type { AuditResult } from "@/src/audit/types";
|
|
13
|
-
import { ARCHETYPES, type ArchetypeKey } from "@/src/audit/archetypes";
|
|
14
|
-
import { type Grade } from "@/src/audit/scoring";
|
|
15
|
-
|
|
16
|
-
const GRADE_THRESHOLDS: { g: Grade; t: number }[] = [
|
|
17
|
-
{ g: "S", t: 90 }, { g: "A", t: 80 }, { g: "B", t: 71 },
|
|
18
|
-
{ g: "C", t: 55 }, { g: "D", t: 40 },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function pointsToNextFor(score: number): { next: Grade; delta: number } {
|
|
22
|
-
for (const { g, t } of GRADE_THRESHOLDS) {
|
|
23
|
-
if (score < t) return { next: g, delta: t - score };
|
|
24
|
-
}
|
|
25
|
-
return { next: "S", delta: 0 };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface Props {
|
|
29
|
-
result: AuditResult;
|
|
30
|
-
score: number;
|
|
31
|
-
grade: Grade;
|
|
32
|
-
archetypeKey: ArchetypeKey;
|
|
33
|
-
/** Display name shown in the card header. */
|
|
34
|
-
project: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function ScoreSection({ result, score, grade, archetypeKey, project }: Props) {
|
|
38
|
-
const archetype = ARCHETYPES[archetypeKey];
|
|
39
|
-
// Cheap scan of 5 thresholds — plain computation. React Compiler memoizes
|
|
40
|
-
// the surrounding render anyway, and `useMemo` here tripped the
|
|
41
|
-
// preserve-manual-memoization rule.
|
|
42
|
-
const pointsToNext = pointsToNextFor(score);
|
|
43
|
-
|
|
44
|
-
/** Slipping-through builtin policies (the same heuristic ReturnSection uses
|
|
45
|
-
* for its [install policies] CTA). Used as the "policies missing" stat. */
|
|
46
|
-
const missing = useMemo(
|
|
47
|
-
() => result.results.filter((r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0).length,
|
|
48
|
-
[result],
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
/** Rough "days to fix" — capped 1..14. One day per slipping policy, with a
|
|
52
|
-
* baseline of 3d on any non-S grade. */
|
|
53
|
-
const daysToFix = useMemo(() => {
|
|
54
|
-
if (grade === "S" || missing === 0) return 0;
|
|
55
|
-
return Math.max(1, Math.min(14, missing + 1));
|
|
56
|
-
}, [grade, missing]);
|
|
57
|
-
|
|
58
|
-
/** % of 0–100 bar to fill — simply score/100. */
|
|
59
|
-
const progressPct = score;
|
|
60
|
-
|
|
61
|
-
/** Top-N slipping policies → chip strip on the left card. Capped at 6. */
|
|
62
|
-
const policyChips = useMemo(() => {
|
|
63
|
-
const slipping = result.results
|
|
64
|
-
.filter((r) => r.source === "builtin" && !r.enabledInConfig && r.hits > 0)
|
|
65
|
-
.sort((a, b) => b.hits - a.hits)
|
|
66
|
-
.slice(0, 6)
|
|
67
|
-
.map((r) => ({ name: shortPolicyLabel(r.name), missing: true as const }));
|
|
68
|
-
const enabled = result.results
|
|
69
|
-
.filter((r) => r.source === "builtin" && r.enabledInConfig)
|
|
70
|
-
.slice(0, Math.max(0, 6 - slipping.length))
|
|
71
|
-
.map((r) => ({ name: shortPolicyLabel(r.name), missing: false as const }));
|
|
72
|
-
return [...slipping, ...enabled];
|
|
73
|
-
}, [result]);
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<section className="section" data-screen-label="03 Score">
|
|
77
|
-
<div className="section-mast">
|
|
78
|
-
<div className="section-label">
|
|
79
|
-
<span className="glyph">━━</span> score
|
|
80
|
-
<span style={{ color: "var(--accent-green)", marginLeft: 10, letterSpacing: "0.04em", textTransform: "none", fontSize: 11 }}>· SEE HOW YOUR AGENT IS PERFORMING</span>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
<h2 className="section-h">your audit score.</h2>
|
|
84
|
-
|
|
85
|
-
<div className="panel score-share-card">
|
|
86
|
-
<div className="score-card-header">
|
|
87
|
-
<span style={{ color: "var(--ink)" }}>{project}</span>
|
|
88
|
-
<span style={{ color: "var(--dim)" }}> · </span>
|
|
89
|
-
<span style={{ color: "var(--accent-pink)" }}>{archetype.name.toLowerCase()}</span>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div className="ss-score-row">
|
|
93
|
-
<span className={"ss-score g-" + grade}>{score}</span>
|
|
94
|
-
<span className="ss-score-of">/100</span>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<div className="ss-tier-row">
|
|
98
|
-
<span className={"ss-tier-badge g-" + grade}>{grade} tier</span>
|
|
99
|
-
<span className="ss-arch">{archetype.name.toLowerCase()}</span>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<div className="ss-progress-label">
|
|
103
|
-
<span style={{ color: "var(--dim)" }}>score</span>
|
|
104
|
-
{pointsToNext.delta > 0 ? (
|
|
105
|
-
<span style={{ color: "var(--accent-pink)" }}>
|
|
106
|
-
+{pointsToNext.delta} pts to {pointsToNext.next} tier
|
|
107
|
-
</span>
|
|
108
|
-
) : (
|
|
109
|
-
<span style={{ color: "var(--accent-green)" }}>top tier ✓</span>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
112
|
-
<div className="ss-progress-track">
|
|
113
|
-
{[40, 55, 71, 80, 90].map((t) => (
|
|
114
|
-
<div key={t} className="ss-progress-tick" style={{ left: `${t}%` }} />
|
|
115
|
-
))}
|
|
116
|
-
<div
|
|
117
|
-
className="ss-progress-fill audit-bar-fill"
|
|
118
|
-
style={{ ["--bar-width" as string]: `${progressPct}%` }}
|
|
119
|
-
/>
|
|
120
|
-
</div>
|
|
121
|
-
<div className="ss-grade-stops">
|
|
122
|
-
{(["D", "C", "B", "A", "S"] as Grade[]).map((g, i) => {
|
|
123
|
-
const pos = [40, 55, 71, 80, 90][i];
|
|
124
|
-
return (
|
|
125
|
-
<span
|
|
126
|
-
key={g}
|
|
127
|
-
className={"ss-grade-stop" + (grade === g ? " active" : "")}
|
|
128
|
-
style={{ left: `${pos}%` }}
|
|
129
|
-
>{g}</span>
|
|
130
|
-
);
|
|
131
|
-
})}
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<div className="ss-stats">
|
|
135
|
-
<div className="ss-stat">
|
|
136
|
-
<div className="ss-stat-n" style={{ color: "var(--amber)" }}>{missing}</div>
|
|
137
|
-
<div className="ss-stat-l">policies<br />missing</div>
|
|
138
|
-
</div>
|
|
139
|
-
<div className="ss-stat">
|
|
140
|
-
<div className="ss-stat-n" style={{ color: "var(--accent-pink)" }}>
|
|
141
|
-
+{pointsToNext.delta}
|
|
142
|
-
</div>
|
|
143
|
-
<div className="ss-stat-l">pts to<br />{pointsToNext.next} tier</div>
|
|
144
|
-
</div>
|
|
145
|
-
<div className="ss-stat">
|
|
146
|
-
<div className="ss-stat-n" style={{ color: "var(--accent-green)" }}>
|
|
147
|
-
{daysToFix === 0 ? "—" : `~${daysToFix}d`}
|
|
148
|
-
</div>
|
|
149
|
-
<div className="ss-stat-l">est.<br />to fix</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
{policyChips.length > 0 && (
|
|
154
|
-
<>
|
|
155
|
-
<div className="ss-policy-label">policy status</div>
|
|
156
|
-
<div className="ss-policy-chips">
|
|
157
|
-
{policyChips.map((p, i) => (
|
|
158
|
-
<span
|
|
159
|
-
key={i}
|
|
160
|
-
className={"ss-chip" + (p.missing ? " missing" : " enabled")}
|
|
161
|
-
>
|
|
162
|
-
<span className="dot" aria-hidden="true" />
|
|
163
|
-
{p.name}
|
|
164
|
-
</span>
|
|
165
|
-
))}
|
|
166
|
-
</div>
|
|
167
|
-
</>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
</section>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Drop the "failproofai/" namespace prefix builtin policies carry so chips
|
|
175
|
-
* stay compact (`block-sudo` reads better than `failproofai/block-sudo`). */
|
|
176
|
-
function shortPolicyLabel(name: string): string {
|
|
177
|
-
const slash = name.indexOf("/");
|
|
178
|
-
return slash >= 0 ? name.slice(slash + 1) : name;
|
|
179
|
-
}
|