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
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Invite dialog — modal that takes a comma/newline-separated list of friend
|
|
5
|
+
* emails and POSTs them to /api/audit/invite. The api-server composes the
|
|
6
|
+
* actual invite email (Cc'ing the sender) using the same email infrastructure
|
|
7
|
+
* that backs the OTP flow.
|
|
8
|
+
*
|
|
9
|
+
* Anonymous users get bounced through the AuthDialog by the caller; this
|
|
10
|
+
* component assumes the session is already established.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { usePostHog } from "@/contexts/PostHogContext";
|
|
15
|
+
import { toast } from "@/app/components/toast";
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
/** Source tag for PostHog so we can split "invited from come-back-better"
|
|
21
|
+
* from any future entry points. */
|
|
22
|
+
source: string;
|
|
23
|
+
/** Called when the proxy returns 401 (session expired between probe and
|
|
24
|
+
* submit). The parent should re-open the AuthDialog so the user can
|
|
25
|
+
* re-authenticate; without this, a 401 dead-ends with an inline error. */
|
|
26
|
+
onUnauthorized?: () => void;
|
|
27
|
+
/** Sender's audit score (0–100), forwarded to the api-server so the invite
|
|
28
|
+
* body can show "mine came out at N/100". Omitted → score-free copy. */
|
|
29
|
+
score?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
33
|
+
const MAX_RECIPIENTS = 10;
|
|
34
|
+
|
|
35
|
+
function parseEmails(input: string): { valid: string[]; invalid: string[] } {
|
|
36
|
+
const tokens = input
|
|
37
|
+
.split(/[\s,;]+/)
|
|
38
|
+
.map((t) => t.trim().toLowerCase())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
const valid: string[] = [];
|
|
41
|
+
const invalid: string[] = [];
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
for (const t of tokens) {
|
|
44
|
+
if (seen.has(t)) continue;
|
|
45
|
+
seen.add(t);
|
|
46
|
+
if (EMAIL_RE.test(t)) valid.push(t);
|
|
47
|
+
else invalid.push(t);
|
|
48
|
+
}
|
|
49
|
+
return { valid, invalid };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function InviteDialog({ open, onClose, source, onUnauthorized, score }: Props): React.ReactElement | null {
|
|
53
|
+
const { capture } = usePostHog();
|
|
54
|
+
const [value, setValue] = useState("");
|
|
55
|
+
const [busy, setBusy] = useState(false);
|
|
56
|
+
const [error, setError] = useState<string | null>(null);
|
|
57
|
+
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (open) {
|
|
61
|
+
setValue("");
|
|
62
|
+
setBusy(false);
|
|
63
|
+
setError(null);
|
|
64
|
+
capture("audit_invite_dialog_opened", { source });
|
|
65
|
+
}
|
|
66
|
+
}, [capture, open, source]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!open) return;
|
|
70
|
+
const t = setTimeout(() => inputRef.current?.focus(), 50);
|
|
71
|
+
return () => clearTimeout(t);
|
|
72
|
+
}, [open]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!open) return;
|
|
76
|
+
const onKey = (e: KeyboardEvent): void => {
|
|
77
|
+
if (e.key === "Escape" && !busy) onClose();
|
|
78
|
+
};
|
|
79
|
+
window.addEventListener("keydown", onKey);
|
|
80
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
81
|
+
}, [open, busy, onClose]);
|
|
82
|
+
|
|
83
|
+
const { valid, invalid } = useMemo(() => parseEmails(value), [value]);
|
|
84
|
+
|
|
85
|
+
const submit = useCallback(
|
|
86
|
+
async (e: React.FormEvent<HTMLFormElement>) => {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
if (busy) return;
|
|
89
|
+
if (valid.length === 0) {
|
|
90
|
+
setError("add at least one valid email address.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (valid.length > MAX_RECIPIENTS) {
|
|
94
|
+
setError(`up to ${MAX_RECIPIENTS} emails at a time. send the rest in a follow-up.`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
setBusy(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
capture("audit_invite_submitted", {
|
|
100
|
+
source,
|
|
101
|
+
recipient_count: valid.length,
|
|
102
|
+
had_invalid: invalid.length > 0,
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch("/api/audit/invite", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "content-type": "application/json" },
|
|
108
|
+
body: JSON.stringify({ to: valid, score }),
|
|
109
|
+
});
|
|
110
|
+
const body = (await res.json().catch(() => ({}))) as {
|
|
111
|
+
sent?: string[];
|
|
112
|
+
failed?: string[];
|
|
113
|
+
code?: string;
|
|
114
|
+
message?: string;
|
|
115
|
+
};
|
|
116
|
+
if (res.status === 401) {
|
|
117
|
+
// Session expired between probe and submit — route back through auth.
|
|
118
|
+
// Without this, repeated submits would dead-end with the same 401.
|
|
119
|
+
if (onUnauthorized) {
|
|
120
|
+
onClose();
|
|
121
|
+
onUnauthorized();
|
|
122
|
+
} else {
|
|
123
|
+
setError("session expired. please sign in again.");
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!res.ok) {
|
|
128
|
+
const msg = body.message ?? "couldn't send invites.";
|
|
129
|
+
setError(msg);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const sent = body.sent?.length ?? 0;
|
|
133
|
+
const failed = body.failed?.length ?? 0;
|
|
134
|
+
toast(
|
|
135
|
+
failed > 0
|
|
136
|
+
? `📨 sent ${sent}, ${failed} bounced — copy the bounce and try again.`
|
|
137
|
+
: `📨 sent ${sent} ${sent === 1 ? "invite" : "invites"}. thanks for spreading the word.`,
|
|
138
|
+
);
|
|
139
|
+
onClose();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
+
setError(`network error: ${message}`);
|
|
143
|
+
} finally {
|
|
144
|
+
setBusy(false);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[busy, valid, invalid, capture, source, onClose, onUnauthorized, score],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (!open) return null;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div
|
|
154
|
+
className="auth-dialog-backdrop"
|
|
155
|
+
role="dialog"
|
|
156
|
+
aria-modal="true"
|
|
157
|
+
aria-labelledby="invite-dialog-title"
|
|
158
|
+
onClick={(e) => {
|
|
159
|
+
if (!busy && e.target === e.currentTarget) onClose();
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<div className="auth-dialog">
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
className="auth-close"
|
|
166
|
+
onClick={onClose}
|
|
167
|
+
disabled={busy}
|
|
168
|
+
aria-label="close"
|
|
169
|
+
>
|
|
170
|
+
×
|
|
171
|
+
</button>
|
|
172
|
+
|
|
173
|
+
<h2 id="invite-dialog-title" className="auth-headline">
|
|
174
|
+
invite friends to audit theirs
|
|
175
|
+
</h2>
|
|
176
|
+
<p className="auth-sub">
|
|
177
|
+
paste emails separated by commas, spaces, or newlines. you'll be Cc'd on every invite so they know it's from you.
|
|
178
|
+
</p>
|
|
179
|
+
|
|
180
|
+
<form onSubmit={submit} className="auth-form">
|
|
181
|
+
<textarea
|
|
182
|
+
ref={inputRef}
|
|
183
|
+
name="emails"
|
|
184
|
+
placeholder="alice@x.com, bob@y.com carol@z.com"
|
|
185
|
+
disabled={busy}
|
|
186
|
+
className="auth-input invite-textarea"
|
|
187
|
+
value={value}
|
|
188
|
+
onChange={(e) => setValue(e.target.value)}
|
|
189
|
+
rows={4}
|
|
190
|
+
required
|
|
191
|
+
/>
|
|
192
|
+
<div className="invite-summary">
|
|
193
|
+
{valid.length > 0 && (
|
|
194
|
+
<span className="invite-valid">{valid.length} valid</span>
|
|
195
|
+
)}
|
|
196
|
+
{invalid.length > 0 && (
|
|
197
|
+
<span className="invite-invalid">
|
|
198
|
+
{invalid.length} invalid: {invalid.slice(0, 3).join(", ")}
|
|
199
|
+
{invalid.length > 3 ? `, +${invalid.length - 3} more` : ""}
|
|
200
|
+
</span>
|
|
201
|
+
)}
|
|
202
|
+
{valid.length > MAX_RECIPIENTS && (
|
|
203
|
+
<span className="invite-invalid">
|
|
204
|
+
only {MAX_RECIPIENTS} per send — first {MAX_RECIPIENTS} will go.
|
|
205
|
+
</span>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
{error && <div className="auth-error">{error}</div>}
|
|
209
|
+
<div className="auth-actions">
|
|
210
|
+
<button
|
|
211
|
+
type="submit"
|
|
212
|
+
className="auth-btn primary"
|
|
213
|
+
disabled={busy || valid.length === 0}
|
|
214
|
+
>
|
|
215
|
+
{busy ? "sending…" : `send ${valid.length || ""} invite${valid.length === 1 ? "" : "s"}`.trim()}
|
|
216
|
+
</button>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
className="auth-btn"
|
|
220
|
+
onClick={onClose}
|
|
221
|
+
disabled={busy}
|
|
222
|
+
>
|
|
223
|
+
cancel
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
</form>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section 03 — QUIRKS. "what to improve."
|
|
5
|
+
*
|
|
6
|
+
* 4-column table: when · what slipped (+ policy that would've caught it) ·
|
|
7
|
+
* severity pill · recurrence. Each row corresponds to a triggered
|
|
8
|
+
* detector. No per-finding card chrome, no 4-quad body, no corner
|
|
9
|
+
* crosshairs — evidence and fix live in section 04 (How to improve).
|
|
10
|
+
*/
|
|
11
|
+
import React from "react";
|
|
12
|
+
import type { FindingCard } from "@/src/audit/findings";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
findings: FindingCard[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Severity = "low" | "medium" | "high";
|
|
19
|
+
|
|
20
|
+
/** Heuristic severity from occurrence count — until the audit pipeline
|
|
21
|
+
* carries a real severity field. Tuned so a single occurrence reads as
|
|
22
|
+
* low and >5 reads as high. */
|
|
23
|
+
function severityFromCount(count: number): Severity {
|
|
24
|
+
if (count >= 6) return "high";
|
|
25
|
+
if (count >= 2) return "medium";
|
|
26
|
+
return "low";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function recurrenceLabel(count: number): string {
|
|
30
|
+
if (count <= 1) return "new";
|
|
31
|
+
if (count < 10) return `${count}× seen`;
|
|
32
|
+
return "recurring";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function QuirksSection({ findings }: Props) {
|
|
36
|
+
if (findings.length === 0) return null;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<section className="audit-sec" data-screen-label="03 Quirks">
|
|
40
|
+
<div className="audit-sec-head">
|
|
41
|
+
<span className="audit-sec-eyebrow">
|
|
42
|
+
<span className="ix">03</span>{"// quirks"}
|
|
43
|
+
</span>
|
|
44
|
+
<span className="audit-sec-meta">{findings.length} slipped through</span>
|
|
45
|
+
</div>
|
|
46
|
+
<h2 className="audit-sec-title">what to improve</h2>
|
|
47
|
+
|
|
48
|
+
<div className="quirks-table">
|
|
49
|
+
<div className="quirks-thead">
|
|
50
|
+
<span>when</span>
|
|
51
|
+
<span>what slipped</span>
|
|
52
|
+
<span>severity</span>
|
|
53
|
+
<span>seen</span>
|
|
54
|
+
</div>
|
|
55
|
+
{findings.map((f) => {
|
|
56
|
+
const sev = severityFromCount(f.count);
|
|
57
|
+
return (
|
|
58
|
+
<div key={f.sourceSlug} className="quirks-row">
|
|
59
|
+
<span className="q-when">{f.lastSeen}</span>
|
|
60
|
+
<span className="q-what">
|
|
61
|
+
<span className="q-title">{f.title}</span>
|
|
62
|
+
<span className="q-policy">
|
|
63
|
+
would've been caught by:{" "}
|
|
64
|
+
<code>{f.policy}</code>
|
|
65
|
+
</span>
|
|
66
|
+
</span>
|
|
67
|
+
<span className={`q-pill q-pill-${sev}`}>{sev}</span>
|
|
68
|
+
<span className="q-recur">{recurrenceLabel(f.count)}</span>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Social-share copy for the audit identity card.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Ten templates for X and ten for LinkedIn. `pickTemplate` selects one
|
|
5
|
+
* deterministically from a seed (the behaviour fingerprint), so a given audit
|
|
6
|
+
* run always renders the same post while different runs / personas vary — then
|
|
7
|
+
* appends a clipboard paste hint. Pure — no React, no DOM — so it's
|
|
8
|
+
* unit-testable and shared by the client poster component.
|
|
9
|
+
*
|
|
10
|
+
* Each template references the score and/or archetype and ends on the
|
|
11
|
+
* install-free CTA + handle. We intentionally never include a URL: a bare link
|
|
12
|
+
* would make X/LinkedIn render a link-preview card and swallow the copied
|
|
13
|
+
* image. The npx command is the call to action instead.
|
|
9
14
|
*/
|
|
10
15
|
import type { Grade } from "@/src/audit/scoring";
|
|
11
16
|
|
|
12
|
-
const SITE_URL = "https://befailproof.ai";
|
|
13
|
-
|
|
14
17
|
export interface ShareCtx {
|
|
15
18
|
score: number;
|
|
16
19
|
/** Lowercased archetype name, e.g. "the cowboy". */
|
|
@@ -20,34 +23,60 @@ export interface ShareCtx {
|
|
|
20
23
|
missing: number;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
/** Call-to-action + handle per channel. No URL by design (see file header). */
|
|
27
|
+
const X_CTA = "npx -y failproofai audit · @failproofai";
|
|
28
|
+
const LI_CTA = "npx -y failproofai audit · @Failproof AI";
|
|
29
|
+
|
|
30
|
+
/** Appended to every picked share so the user knows the audit-card PNG is on
|
|
31
|
+
* their clipboard and just needs pasting into the post. */
|
|
32
|
+
const PASTE_LINE = "[ audit card copied to your clipboard — paste it into the post ]";
|
|
24
33
|
|
|
25
|
-
/**
|
|
34
|
+
/** Short, curiosity-forward — for X / Twitter. Ends on the npx CTA + @failproofai. */
|
|
26
35
|
export const X_TEMPLATES: ((c: ShareCtx) => string)[] = [
|
|
27
|
-
({ score, arch
|
|
28
|
-
`my
|
|
29
|
-
({ score, arch
|
|
30
|
-
`
|
|
31
|
-
({ score
|
|
32
|
-
|
|
33
|
-
({
|
|
34
|
-
`
|
|
35
|
-
({ score, arch
|
|
36
|
-
`
|
|
36
|
+
({ score, arch }) =>
|
|
37
|
+
`my coding agent has a personality and it's ${arch}. did not see that coming.\n\nran a failproof audit, scored ${score}/100, all local. find out what yours is → ${X_CTA}`,
|
|
38
|
+
({ score, arch }) =>
|
|
39
|
+
`spent 30 seconds auditing my coding agent. learned more about it than i did all month.\n\nit's ${arch}. scored ${score}/100. → ${X_CTA}`,
|
|
40
|
+
({ score }) =>
|
|
41
|
+
`my agent scored ${score}/100. think yours can beat it?\n\n30 sec audit, runs locally, gives you a personality too → ${X_CTA}`,
|
|
42
|
+
({ arch }) =>
|
|
43
|
+
`turns out my coding agent is ${arch} and honestly it explains everything.\n\n→ ${X_CTA}`,
|
|
44
|
+
({ score, arch }) =>
|
|
45
|
+
`ran a failproof audit on my agent. it reads how it actually behaves when things break, not just the happy path.\n\npersonality: ${arch}. score: ${score}/100. all local → ${X_CTA}`,
|
|
46
|
+
({ score, arch }) =>
|
|
47
|
+
`got to know my coding agent today. it's ${arch} and scored ${score}/100. wow.\n\naudit yours in 30s, nothing leaves your machine → ${X_CTA}`,
|
|
48
|
+
({ score, arch }) =>
|
|
49
|
+
`your coding agent has a personality. you just haven't met it.\n\ni met mine today: ${arch}, ${score}/100. → ${X_CTA}`,
|
|
50
|
+
({ score, arch }) =>
|
|
51
|
+
`everyone talks about what their agent can build. nobody talks about how it acts when it breaks.\n\nmine's ${arch}, scored ${score}/100, all local → ${X_CTA}`,
|
|
52
|
+
({ score, arch }) =>
|
|
53
|
+
`every agent builder should run this once.\n\n30 sec, local, gives your agent a personality and a quality score. mine: ${arch}, ${score}/100.\n\n${X_CTA}`,
|
|
54
|
+
({ score, arch }) =>
|
|
55
|
+
`i'll go first: ${arch}, ${score}/100.\n\nwhat's your coding agent? 30 sec audit, no signup → ${X_CTA}`,
|
|
37
56
|
];
|
|
38
57
|
|
|
39
|
-
/**
|
|
58
|
+
/** Longer, reflective — for LinkedIn. Ends on the npx CTA + @Failproof AI. */
|
|
40
59
|
export const LI_TEMPLATES: ((c: ShareCtx) => string)[] = [
|
|
41
|
-
({ score, arch
|
|
42
|
-
`I
|
|
43
|
-
({ score, arch
|
|
44
|
-
`
|
|
45
|
-
({ score, arch
|
|
46
|
-
`
|
|
47
|
-
({ score, arch
|
|
48
|
-
`
|
|
49
|
-
({ score, arch
|
|
50
|
-
`
|
|
60
|
+
({ score, arch }) =>
|
|
61
|
+
`I've spent more hours with my coding agent this month than with most people I know. Today I realized I couldn't tell you a single thing about how it actually behaves.\n\nSo I audited it. Turns out it's ${arch}, and it scored ${score}/100.\n\nThe audit reads its real history, including how it handles failures, not just the clean runs. 30 seconds, runs entirely locally.\n\nMeet yours: ${LI_CTA}`,
|
|
62
|
+
({ score, arch }) =>
|
|
63
|
+
`Took a personality test today. It wasn't for me — it was for my AI coding agent.\n\nThe result: ${arch}, with a quality score of ${score}/100.\n\nIt works by reading the agent's actual run history, so the personality comes from how it really behaves, not a vibe. Took 30 seconds and stayed local.\n\nTry it on yours: ${LI_CTA}`,
|
|
64
|
+
({ score, arch }) =>
|
|
65
|
+
`Everyone asks what their AI agent can build. I just found out mine has a temper.\n\nAfter watching how it reacts to a failed command, the audit called it ${arch}. It also scored its quality at ${score}/100.\n\nOddly useful to see it written down. 30 seconds, all local: ${LI_CTA}`,
|
|
66
|
+
({ score, arch }) =>
|
|
67
|
+
`My agent does this very specific thing every time a command fails. Today I learned there's basically a name for it.\n\nRan a quick audit and it came back ${arch}, scored ${score}/100. The whole read is based on how the agent handles things going wrong, which is where its real character shows.\n\n30 seconds, nothing leaves your machine: ${LI_CTA}`,
|
|
68
|
+
({ score, arch }) =>
|
|
69
|
+
`My AI coding agent scored ${score}/100 today. I didn't know you could even measure that.\n\nThe same audit also handed it a personality: ${arch}.\n\nIt reads the agent's real history and tells you where it's solid and where it slips. Took 30 seconds and ran fully locally: ${LI_CTA}`,
|
|
70
|
+
({ score, arch }) =>
|
|
71
|
+
`I'm usually the first to scroll past "audit your X" tools. This one took 30 seconds and actually told me something.\n\nMy coding agent is ${arch}, and it scored ${score}/100.\n\nIt reads the agent's real run history rather than asking me anything, and none of it leaves the machine: ${LI_CTA}`,
|
|
72
|
+
({ score, arch }) =>
|
|
73
|
+
`What is your coding agent's personality? I realized today that I had no answer.\n\nSo I checked. Mine is ${arch}, scored ${score}/100, with a clear read on where it shines and where it slips.\n\nIf you work with agents, run it and drop yours in the comments. 30 seconds, fully local: ${LI_CTA}`,
|
|
74
|
+
({ score, arch }) =>
|
|
75
|
+
`I trusted my coding agent a little less after this morning, and quite a bit more by the afternoon.\n\nI ran an audit on it. It showed me exactly where it's reliable and where it isn't, gave it a personality (${arch}), and scored it ${score}/100.\n\nKnowing the gaps is what made me trust it more. 30 seconds, all local: ${LI_CTA}`,
|
|
76
|
+
({ score, arch }) =>
|
|
77
|
+
`I almost skipped this because I assumed it would quietly ship my code off somewhere. It doesn't. Everything runs locally.\n\n30 seconds later I had my agent's personality (${arch}) and a quality score (${score}/100).\n\nIf privacy is usually what stops you from trying these, this one is different: ${LI_CTA}`,
|
|
78
|
+
({ score, arch }) =>
|
|
79
|
+
`Found out my AI coding agent has a personality type. Found out it's ${arch}. Found out that explains a lot.\n\nIt scored ${score}/100 too. The audit reads how the agent handles failures, not just the runs where everything goes fine.\n\n30 seconds, runs locally: ${LI_CTA}`,
|
|
51
80
|
];
|
|
52
81
|
|
|
53
82
|
/** djb2 hash — stable per seed so the template choice is deterministic. */
|
|
@@ -57,11 +86,13 @@ function hashStr(s: string): number {
|
|
|
57
86
|
return h >>> 0;
|
|
58
87
|
}
|
|
59
88
|
|
|
60
|
-
/** Deterministically pick + render one template for the given seed
|
|
89
|
+
/** Deterministically pick + render one template for the given seed, then append
|
|
90
|
+
* the clipboard paste hint so the user knows to paste the copied audit card. */
|
|
61
91
|
export function pickTemplate(
|
|
62
92
|
templates: ((c: ShareCtx) => string)[],
|
|
63
93
|
seed: string,
|
|
64
94
|
ctx: ShareCtx,
|
|
65
95
|
): string {
|
|
66
|
-
|
|
96
|
+
const body = templates[hashStr(seed) % templates.length](ctx);
|
|
97
|
+
return `${body}\n\n${PASTE_LINE}`;
|
|
67
98
|
}
|
|
@@ -1,39 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pixel sigil —
|
|
4
|
+
* Pixel sigil — 8×8 grid for the archetype. Bare implementation: no plate,
|
|
5
|
+
* no crosshairs, no reveal animation. The poster owns whatever framing it
|
|
6
|
+
* wants around the sigil.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* p = pink accent g = green accent d = dim
|
|
9
|
-
*
|
|
10
|
-
* Two rendering modes:
|
|
11
|
-
*
|
|
12
|
-
* • Default (used on the audit hero, identity-section): wraps the grid in
|
|
13
|
-
* a brutalist "instrument plate" — register crosshairs at the four
|
|
14
|
-
* corners, a header strip with the archetype index + an 8×8 coordinate
|
|
15
|
-
* label, the pixel grid mounted on a dashed inner frame with 20px cells,
|
|
16
|
-
* a footer strip naming the archetype, and a stacked pink + dim hard-
|
|
17
|
-
* offset shadow for depth. Cells fade in along a diagonal wave on
|
|
18
|
-
* mount (`--cx` / `--cy` custom properties), guarded by
|
|
19
|
-
* `prefers-reduced-motion`.
|
|
20
|
-
*
|
|
21
|
-
* • hideLabel (used in the ShowOff CTA + html2canvas capture): a bare
|
|
22
|
-
* `.sigil` grid with no plate or labels, so the showoff card can scale
|
|
23
|
-
* the sigil down independently and html2canvas doesn't have to capture
|
|
24
|
-
* the new plate chrome.
|
|
8
|
+
* Cell letters: `.` empty · `o` ink · `p` pink accent · `g` green accent ·
|
|
9
|
+
* `d` dim.
|
|
25
10
|
*/
|
|
26
11
|
import React from "react";
|
|
27
|
-
import {
|
|
12
|
+
import { SIGILS, type ArchetypeKey } from "@/src/audit/archetypes";
|
|
28
13
|
|
|
29
14
|
interface Props {
|
|
30
15
|
archetypeKey: ArchetypeKey;
|
|
31
|
-
hideLabel?: boolean;
|
|
32
16
|
}
|
|
33
17
|
|
|
34
|
-
export function Sigil({ archetypeKey
|
|
18
|
+
export function Sigil({ archetypeKey }: Props) {
|
|
35
19
|
const grid = SIGILS[archetypeKey] ?? SIGILS.optimist;
|
|
36
|
-
const archetype = ARCHETYPES[archetypeKey];
|
|
37
20
|
const cells: React.ReactElement[] = [];
|
|
38
21
|
|
|
39
22
|
for (let y = 0; y < 8; y++) {
|
|
@@ -45,49 +28,9 @@ export function Sigil({ archetypeKey, hideLabel }: Props) {
|
|
|
45
28
|
else if (c === "p") cls += " p";
|
|
46
29
|
else if (c === "g") cls += " g";
|
|
47
30
|
else if (c === "d") cls += " d";
|
|
48
|
-
cells.push(
|
|
49
|
-
<div
|
|
50
|
-
key={`${y}-${x}`}
|
|
51
|
-
className={cls}
|
|
52
|
-
style={{
|
|
53
|
-
["--cx" as string]: x,
|
|
54
|
-
["--cy" as string]: y,
|
|
55
|
-
} as React.CSSProperties}
|
|
56
|
-
/>,
|
|
57
|
-
);
|
|
31
|
+
cells.push(<div key={`${y}-${x}`} className={cls} />);
|
|
58
32
|
}
|
|
59
33
|
}
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div className="sigil-wrap" data-bare="true">
|
|
64
|
-
<div className="sigil">{cells}</div>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const indexLabel = String(archetype.index).padStart(2, "0");
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="sigil-wrap">
|
|
73
|
-
<div className="sigil-plate">
|
|
74
|
-
<span className="sigil-mark tl" aria-hidden="true" />
|
|
75
|
-
<span className="sigil-mark tr" aria-hidden="true" />
|
|
76
|
-
<span className="sigil-mark bl" aria-hidden="true" />
|
|
77
|
-
<span className="sigil-mark br" aria-hidden="true" />
|
|
78
|
-
|
|
79
|
-
<div className="sigil-strip sigil-strip--top">
|
|
80
|
-
<span className="sigil-ix">№ {indexLabel}</span>
|
|
81
|
-
<span className="sigil-coord">8×8</span>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div className="sigil">{cells}</div>
|
|
85
|
-
|
|
86
|
-
<div className="sigil-strip sigil-strip--bot">
|
|
87
|
-
<span className="sigil-strip-key">sigil</span>
|
|
88
|
-
<span className="sigil-strip-val">{archetypeKey}</span>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
35
|
+
return <div className="sigil">{cells}</div>;
|
|
93
36
|
}
|
|
@@ -1,57 +1,45 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Section 02 — STRENGTHS. "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Section 02 — STRENGTHS. "what it's great at." Calm row list:
|
|
5
|
+
* ✓ glyph · headline + sub · right-aligned metric. No card chrome,
|
|
6
|
+
* no hover backgrounds, no checkmark backdrop.
|
|
7
7
|
*/
|
|
8
8
|
import React from "react";
|
|
9
9
|
import type { Strength } from "@/src/audit/strengths";
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
strengths: Strength[];
|
|
13
|
-
totalDetectorsTriggered: number;
|
|
14
|
-
totalDetectorsAvailable: number;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
export function StrengthsSection({
|
|
18
|
-
strengths, totalDetectorsTriggered, totalDetectorsAvailable,
|
|
19
|
-
}: Props) {
|
|
15
|
+
export function StrengthsSection({ strengths }: Props) {
|
|
20
16
|
if (strengths.length === 0) return null;
|
|
21
17
|
|
|
22
18
|
return (
|
|
23
|
-
<section className="
|
|
24
|
-
<div className="
|
|
25
|
-
<
|
|
26
|
-
<span className="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</div>
|
|
30
|
-
<div className="section-meta">
|
|
31
|
-
<span className="g">●</span>{" "}
|
|
32
|
-
{totalDetectorsAvailable - totalDetectorsTriggered} of {totalDetectorsAvailable} clean
|
|
33
|
-
</div>
|
|
19
|
+
<section className="audit-sec" data-screen-label="02 Strengths">
|
|
20
|
+
<div className="audit-sec-head">
|
|
21
|
+
<span className="audit-sec-eyebrow">
|
|
22
|
+
<span className="ix">02</span>{"// strengths"}
|
|
23
|
+
</span>
|
|
24
|
+
<span className="audit-sec-meta">{strengths.length} standouts</span>
|
|
34
25
|
</div>
|
|
35
|
-
<h2 className="
|
|
26
|
+
<h2 className="audit-sec-title">what it's great at</h2>
|
|
36
27
|
|
|
37
|
-
<div className="
|
|
28
|
+
<div className="strength-list">
|
|
38
29
|
{strengths.map((s, i) => (
|
|
39
30
|
<div key={i} className="strength-row">
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
</
|
|
45
|
-
<
|
|
31
|
+
<span className="strength-check" aria-hidden="true">✓</span>
|
|
32
|
+
<span className="strength-body">
|
|
33
|
+
<span className="strength-headline">{s.headline}</span>
|
|
34
|
+
<span className="strength-detail">{s.detail}</span>
|
|
35
|
+
</span>
|
|
36
|
+
<span className="strength-metric">
|
|
46
37
|
{s.metric}
|
|
47
|
-
<span className="unit">{s.unit}</span>
|
|
48
|
-
</
|
|
38
|
+
{s.unit && <span className="unit">{s.unit}</span>}
|
|
39
|
+
</span>
|
|
49
40
|
</div>
|
|
50
41
|
))}
|
|
51
42
|
</div>
|
|
52
|
-
<div className="strengths-footer">
|
|
53
|
-
— these are your agent's defaults. keep them.
|
|
54
|
-
</div>
|
|
55
43
|
</section>
|
|
56
44
|
);
|
|
57
45
|
}
|