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
|
@@ -3,30 +3,31 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Top-level client wrapper for /audit.
|
|
5
5
|
*
|
|
6
|
-
* Composes the personality report: classify the agent into one of
|
|
7
|
-
* archetypes, derive a score
|
|
8
|
-
* ShowOff + Strengths + Score (with leaderboard) + Findings + Policies
|
|
9
|
-
* + Return-loop CTA.
|
|
6
|
+
* Composes the calm personality report: classify the agent into one of
|
|
7
|
+
* 8 archetypes, derive a score, and render the 5-section flow:
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 01 AuditPoster — single-screen shareable poster
|
|
10
|
+
* 02 StrengthsSection — what it's great at
|
|
11
|
+
* 03 QuirksSection — what slipped through
|
|
12
|
+
* 04 HowToImproveSection — install / configure
|
|
13
|
+
* 05 ComeBackBetterSection — reminder + perks
|
|
14
|
+
*
|
|
15
|
+
* Empty / running states fall back to EmptyState and RunProgress.
|
|
13
16
|
*/
|
|
14
17
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
18
|
import { getAuditResultAction } from "@/app/actions/get-audit-result";
|
|
16
19
|
import type { AuditResult, RunAuditOptions } from "@/src/audit/types";
|
|
17
20
|
import { classifyAgent } from "@/src/audit/archetypes";
|
|
18
|
-
import {
|
|
21
|
+
import { deriveScore, gradeFor, projectedScore } from "@/src/audit/scoring";
|
|
19
22
|
import { deriveStrengths } from "@/src/audit/strengths";
|
|
20
23
|
import { deriveFindings } from "@/src/audit/findings";
|
|
21
24
|
import { usePostHog } from "@/contexts/PostHogContext";
|
|
22
25
|
|
|
23
|
-
import {
|
|
24
|
-
import { ShareDock } from "./share-dock";
|
|
26
|
+
import { AuditPoster } from "./audit-poster";
|
|
25
27
|
import { StrengthsSection } from "./strengths-section";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { ReturnSection } from "./return-section";
|
|
28
|
+
import { QuirksSection } from "./quirks-section";
|
|
29
|
+
import { HowToImproveSection } from "./how-to-improve-section";
|
|
30
|
+
import { ComeBackBetterSection } from "./come-back-better-section";
|
|
30
31
|
import { ReportFooter } from "./report-footer";
|
|
31
32
|
import { EmptyState } from "./empty-state";
|
|
32
33
|
import { RunProgress } from "./run-progress";
|
|
@@ -58,11 +59,6 @@ interface Props {
|
|
|
58
59
|
totalCatalogSize: number;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function inferWindow(params: RunAuditOptions | undefined): string {
|
|
62
|
-
if (!params?.since) return "all time";
|
|
63
|
-
return params.since;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
62
|
function inferProjectName(result: AuditResult, override?: string): string {
|
|
67
63
|
if (override && override.trim()) return override;
|
|
68
64
|
// Pick the cwd that appears in the most examples — proxy for "your
|
|
@@ -106,7 +102,7 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
|
|
|
106
102
|
* soft-refreshes the dashboard cache on success — no full page reload. */
|
|
107
103
|
const startRerun = useCallback(async (source: RerunSource) => {
|
|
108
104
|
if (running) return;
|
|
109
|
-
capture("audit_rerun_clicked", { source, since: "
|
|
105
|
+
capture("audit_rerun_clicked", { source, since: "all" });
|
|
110
106
|
setRunning(true);
|
|
111
107
|
setRerunStatus({ kind: "running", startedAt: Date.now() });
|
|
112
108
|
try {
|
|
@@ -116,7 +112,7 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
|
|
|
116
112
|
// success; the prior cache survives a failed run so the report doesn't vanish.
|
|
117
113
|
// The empty-state first-run (empty-state.tsx) deliberately stays on the
|
|
118
114
|
// fast cached path — it's a first scan, not a re-audit.
|
|
119
|
-
await triggerRun({ cli: [], since: "
|
|
115
|
+
await triggerRun({ cli: [], since: "all", noCache: true });
|
|
120
116
|
await refreshFromCache();
|
|
121
117
|
setRerunStatus({ kind: "idle" });
|
|
122
118
|
} catch (err) {
|
|
@@ -124,7 +120,7 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
|
|
|
124
120
|
capture("audit_rerun_failed", {
|
|
125
121
|
kind,
|
|
126
122
|
source,
|
|
127
|
-
since: "
|
|
123
|
+
since: "all",
|
|
128
124
|
cli_filter: "all",
|
|
129
125
|
});
|
|
130
126
|
setRerunStatus({ kind: "failed", reason: kind, failedAt: Date.now() });
|
|
@@ -249,9 +245,7 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
|
|
|
249
245
|
<MainReport
|
|
250
246
|
result={result}
|
|
251
247
|
cachedAt={cachedAt}
|
|
252
|
-
params={params}
|
|
253
248
|
projectFromUrl={projectFromUrl}
|
|
254
|
-
totalCatalogSize={totalCatalogSize}
|
|
255
249
|
isRunning={running}
|
|
256
250
|
rerunStatus={rerunStatus}
|
|
257
251
|
onRerun={(source) => startRerun(source)}
|
|
@@ -263,9 +257,7 @@ export function AuditDashboard({ initial, projectFromUrl, totalCatalogSize }: Pr
|
|
|
263
257
|
interface MainReportProps {
|
|
264
258
|
result: AuditResult;
|
|
265
259
|
cachedAt: string | null;
|
|
266
|
-
params: RunAuditOptions | undefined;
|
|
267
260
|
projectFromUrl?: string;
|
|
268
|
-
totalCatalogSize: number;
|
|
269
261
|
isRunning: boolean;
|
|
270
262
|
rerunStatus: RerunStatus;
|
|
271
263
|
onRerun: (source: RerunSource) => void;
|
|
@@ -275,9 +267,7 @@ interface MainReportProps {
|
|
|
275
267
|
function MainReport({
|
|
276
268
|
result,
|
|
277
269
|
cachedAt,
|
|
278
|
-
params,
|
|
279
270
|
projectFromUrl,
|
|
280
|
-
totalCatalogSize,
|
|
281
271
|
isRunning,
|
|
282
272
|
rerunStatus,
|
|
283
273
|
onRerun,
|
|
@@ -294,14 +284,10 @@ function MainReport({
|
|
|
294
284
|
const projectedGrade = gradeFor(projected);
|
|
295
285
|
const strengths = useMemo(() => deriveStrengths(result), [result]);
|
|
296
286
|
const findings = useMemo(() => deriveFindings(result), [result]);
|
|
297
|
-
// Renamed from `window` to avoid shadowing the browser global — any
|
|
298
|
-
// future `window.*` reference added inside MainReport would silently
|
|
299
|
-
// bind to a string and crash at runtime.
|
|
300
|
-
const scopeWindow = inferWindow(params);
|
|
301
287
|
|
|
302
|
-
// One pass over result.results
|
|
303
|
-
//
|
|
304
|
-
//
|
|
288
|
+
// One pass over result.results: detectors triggered + missing prescribed
|
|
289
|
+
// policies. Both feed PostHog instrumentation; `missing` also feeds the
|
|
290
|
+
// poster's share-text template.
|
|
305
291
|
const { detectorsTriggered, missing } = useMemo(() => {
|
|
306
292
|
let detectorsTriggered = 0;
|
|
307
293
|
let missing = 0;
|
|
@@ -340,54 +326,37 @@ function MainReport({
|
|
|
340
326
|
detectorsTriggered,
|
|
341
327
|
]);
|
|
342
328
|
|
|
343
|
-
/**
|
|
344
|
-
const
|
|
329
|
+
/** Poster ref — captured to PNG by the poster's share buttons. */
|
|
330
|
+
const posterRef = useRef<HTMLDivElement>(null);
|
|
345
331
|
|
|
346
332
|
return (
|
|
347
333
|
<div className="app">
|
|
348
334
|
<AuditProgressStrip status={rerunStatus} onDismiss={onDismissRerun} />
|
|
349
|
-
<div className="scanline-overlay" />
|
|
350
335
|
<div className="app-shell">
|
|
351
336
|
<div className="report">
|
|
352
|
-
<
|
|
353
|
-
ref={
|
|
337
|
+
<AuditPoster
|
|
338
|
+
ref={posterRef}
|
|
354
339
|
archetypeKey={classification.archetype}
|
|
355
|
-
secondaryKey={classification.secondary}
|
|
356
|
-
toolCalls={result.eventsScanned ?? 0}
|
|
357
|
-
sessions={result.transcripts.scanned}
|
|
358
|
-
window={scopeWindow}
|
|
359
340
|
seed={classification.variantSeed}
|
|
360
|
-
/>
|
|
361
|
-
<StrengthsSection
|
|
362
|
-
strengths={strengths}
|
|
363
|
-
totalDetectorsTriggered={detectorsTriggered}
|
|
364
|
-
totalDetectorsAvailable={totalCatalogSize}
|
|
365
|
-
/>
|
|
366
|
-
<ScoreSection
|
|
367
|
-
result={result}
|
|
368
341
|
score={score}
|
|
369
342
|
grade={grade}
|
|
370
|
-
|
|
371
|
-
|
|
343
|
+
missing={missing}
|
|
344
|
+
auditedAt={cachedAt ?? new Date().toISOString()}
|
|
372
345
|
/>
|
|
373
|
-
<
|
|
346
|
+
<StrengthsSection strengths={strengths} />
|
|
347
|
+
<QuirksSection findings={findings} />
|
|
348
|
+
<HowToImproveSection
|
|
374
349
|
result={result}
|
|
350
|
+
projected={projected}
|
|
351
|
+
projectedGrade={projectedGrade}
|
|
352
|
+
/>
|
|
353
|
+
<ComeBackBetterSection
|
|
375
354
|
isRunning={isRunning}
|
|
376
355
|
onRerun={() => onRerun("return_section")}
|
|
377
356
|
/>
|
|
378
|
-
<FindingsSection findings={findings} />
|
|
379
|
-
<PoliciesSection result={result} projected={projected} projectedGrade={projectedGrade} />
|
|
380
357
|
</div>
|
|
381
358
|
<ReportFooter cachedAt={cachedAt} />
|
|
382
359
|
</div>
|
|
383
|
-
<ShareDock
|
|
384
|
-
frameRef={identityFrameRef}
|
|
385
|
-
archetypeKey={classification.archetype}
|
|
386
|
-
seed={classification.variantSeed}
|
|
387
|
-
score={score}
|
|
388
|
-
grade={grade}
|
|
389
|
-
missing={missing}
|
|
390
|
-
/>
|
|
391
360
|
</div>
|
|
392
361
|
);
|
|
393
362
|
}
|
|
@@ -408,7 +377,6 @@ function ShellEmpty({ running, mode = "no-cache", rerunStatus, onDismissRerun, o
|
|
|
408
377
|
return (
|
|
409
378
|
<div className="app">
|
|
410
379
|
<AuditProgressStrip status={rerunStatus} onDismiss={onDismissRerun} />
|
|
411
|
-
<div className="scanline-overlay" />
|
|
412
380
|
<div className="app-shell">
|
|
413
381
|
<div className="report">
|
|
414
382
|
{running ? (
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section 01 — AUDIT POSTER. The single-screen shareable above-the-fold
|
|
5
|
+
* artifact. Replaces the old IdentitySection + ScoreSection + ShareDock
|
|
6
|
+
* triumvirate.
|
|
7
|
+
*
|
|
8
|
+
* Layout (inside the PNG capture region):
|
|
9
|
+
*
|
|
10
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
* │ ▣ failproof_ai · audit № 01 of 08 · audited <date> │
|
|
12
|
+
* ├─────────────────────────────────────────────────────────────┤
|
|
13
|
+
* │ │
|
|
14
|
+
* │ <score>/100 the optimist ▓░▓░▓░▓░ │
|
|
15
|
+
* │ <rank> pace · conviction · forgetful ░▓░▓░▓░▓ │
|
|
16
|
+
* │ // only N% of agents are this archetype │
|
|
17
|
+
* │ │
|
|
18
|
+
* ├─────────────────────────────────────────────────────────────┤
|
|
19
|
+
* │ audit yours → failproof.ai │
|
|
20
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
21
|
+
*
|
|
22
|
+
* Outside the capture region: three share buttons + scroll hint.
|
|
23
|
+
*/
|
|
24
|
+
import React, { forwardRef, useMemo, useState } from "react";
|
|
25
|
+
import { pickArchetypeVariant, type ArchetypeKey } from "@/src/audit/archetypes";
|
|
26
|
+
import { type Grade } from "@/src/audit/scoring";
|
|
27
|
+
import { getArchetypeRarityPct, getScoreRank } from "@/src/audit/social-proof";
|
|
28
|
+
import { copyOrDownloadCard, downloadCard, shareCardNative, shareCardToastMessage } from "@/lib/share-card";
|
|
29
|
+
import { toast } from "@/app/components/toast";
|
|
30
|
+
import { usePostHog } from "@/contexts/PostHogContext";
|
|
31
|
+
import { Sigil } from "./sigil";
|
|
32
|
+
import { X_TEMPLATES, LI_TEMPLATES, pickTemplate, type ShareCtx } from "./share-templates";
|
|
33
|
+
|
|
34
|
+
const SITE_URL = "https://befailproof.ai";
|
|
35
|
+
const X_INTENT = (text: string) =>
|
|
36
|
+
`https://x.com/intent/tweet?text=${encodeURIComponent(text)}`;
|
|
37
|
+
const LI_INTENT = (text: string) =>
|
|
38
|
+
`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(SITE_URL)}&summary=${encodeURIComponent(text)}`;
|
|
39
|
+
|
|
40
|
+
interface Props {
|
|
41
|
+
archetypeKey: ArchetypeKey;
|
|
42
|
+
/** Stable seed for variant selection (project name is the natural fit). */
|
|
43
|
+
seed: string;
|
|
44
|
+
score: number;
|
|
45
|
+
grade: Grade;
|
|
46
|
+
/** Count of unenabled prescribed policies — passed to the share-text
|
|
47
|
+
* templates, not rendered on the poster itself. */
|
|
48
|
+
missing: number;
|
|
49
|
+
/** Audit timestamp (ISO string from cache). Rendered as ISO date in the
|
|
50
|
+
* top-right meta. UTC to keep the poster timezone-stable across shares. */
|
|
51
|
+
auditedAt: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const AuditPoster = forwardRef<HTMLDivElement, Props>(function AuditPoster(
|
|
55
|
+
{ archetypeKey, seed, score, grade, missing, auditedAt }: Props,
|
|
56
|
+
posterRef,
|
|
57
|
+
) {
|
|
58
|
+
const archetype = useMemo(
|
|
59
|
+
() => pickArchetypeVariant(archetypeKey, seed),
|
|
60
|
+
[archetypeKey, seed],
|
|
61
|
+
);
|
|
62
|
+
const rarityPct = getArchetypeRarityPct(archetypeKey);
|
|
63
|
+
const scoreRank = getScoreRank(score);
|
|
64
|
+
const indexLabel = String(archetype.index).padStart(2, "0");
|
|
65
|
+
const auditedDate = useMemo(() => formatAuditedDate(auditedAt), [auditedAt]);
|
|
66
|
+
|
|
67
|
+
const { capture } = usePostHog();
|
|
68
|
+
const [busy, setBusy] = useState<null | "x" | "linkedin" | "download">(null);
|
|
69
|
+
|
|
70
|
+
const captureCardBlob = async (): Promise<Blob | null> => {
|
|
71
|
+
const node = (posterRef as React.MutableRefObject<HTMLDivElement | null>)?.current;
|
|
72
|
+
if (!node) return null;
|
|
73
|
+
// Capture via html-to-image instead of html2canvas. The former
|
|
74
|
+
// serializes the DOM into an SVG <foreignObject> and rasterizes
|
|
75
|
+
// it through the browser's native rendering engine — so dashed
|
|
76
|
+
// borders, the SVG logo, gradients, and font metrics render
|
|
77
|
+
// exactly as they do on screen. html2canvas reimplements CSS
|
|
78
|
+
// in JS and was producing broken dashes and a stray pink square
|
|
79
|
+
// on the logo's mask.
|
|
80
|
+
if (typeof document !== "undefined" && document.fonts?.ready) await document.fonts.ready;
|
|
81
|
+
await new Promise<void>((r) => requestAnimationFrame(() => r()));
|
|
82
|
+
// Clone the poster into an off-screen container with a fixed width
|
|
83
|
+
// so html-to-image captures a self-contained subtree. The live
|
|
84
|
+
// .poster sits inside a flex column (.poster-section's flex: 1)
|
|
85
|
+
// and uses margin: 0 auto for centering — capturing it directly
|
|
86
|
+
// inherits that parent context, which shifts content within an
|
|
87
|
+
// oversized canvas. The clone has no such context.
|
|
88
|
+
const liveRect = node.getBoundingClientRect();
|
|
89
|
+
const captureWidth = Math.round(liveRect.width);
|
|
90
|
+
const captureHeight = Math.round(liveRect.height);
|
|
91
|
+
|
|
92
|
+
const wrapper = document.createElement("div");
|
|
93
|
+
wrapper.style.cssText = [
|
|
94
|
+
"position: fixed",
|
|
95
|
+
"left: -10000px",
|
|
96
|
+
"top: 0",
|
|
97
|
+
`width: ${captureWidth}px`,
|
|
98
|
+
`height: ${captureHeight}px`,
|
|
99
|
+
"padding: 0",
|
|
100
|
+
"margin: 0",
|
|
101
|
+
"background: var(--bg)",
|
|
102
|
+
"z-index: -1",
|
|
103
|
+
"pointer-events: none",
|
|
104
|
+
].join(";");
|
|
105
|
+
|
|
106
|
+
const clone = node.cloneNode(true) as HTMLElement;
|
|
107
|
+
clone.style.cssText += [
|
|
108
|
+
"",
|
|
109
|
+
`width: ${captureWidth}px`,
|
|
110
|
+
`height: ${captureHeight}px`,
|
|
111
|
+
"max-width: none",
|
|
112
|
+
"min-width: 0",
|
|
113
|
+
"flex: 0 0 auto",
|
|
114
|
+
"margin: 0",
|
|
115
|
+
].join(";");
|
|
116
|
+
|
|
117
|
+
wrapper.appendChild(clone);
|
|
118
|
+
document.body.appendChild(wrapper);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const { toBlob } = await import("html-to-image");
|
|
122
|
+
return await toBlob(clone, {
|
|
123
|
+
backgroundColor: "#0e0e11",
|
|
124
|
+
pixelRatio: 2,
|
|
125
|
+
cacheBust: true,
|
|
126
|
+
width: captureWidth,
|
|
127
|
+
height: captureHeight,
|
|
128
|
+
});
|
|
129
|
+
} finally {
|
|
130
|
+
wrapper.remove();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const filenameFor = (channel: "x" | "linkedin" | "download") =>
|
|
135
|
+
`failproofai-${channel}-${grade.toLowerCase()}-${score}.png`;
|
|
136
|
+
|
|
137
|
+
const handleShare = async (channel: "x" | "linkedin" | "download") => {
|
|
138
|
+
if (busy) return;
|
|
139
|
+
setBusy(channel);
|
|
140
|
+
capture("audit_card_share_clicked", {
|
|
141
|
+
channel,
|
|
142
|
+
source: "poster",
|
|
143
|
+
score,
|
|
144
|
+
grade,
|
|
145
|
+
missing_policies: missing,
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
const blob = await captureCardBlob().catch(() => null);
|
|
149
|
+
if (!blob) {
|
|
150
|
+
capture("audit_card_capture_completed", {
|
|
151
|
+
trigger: channel === "download" ? "download" : `share_${channel}`,
|
|
152
|
+
status: "error",
|
|
153
|
+
image_method: "failed",
|
|
154
|
+
source: "poster",
|
|
155
|
+
});
|
|
156
|
+
toast(shareCardToastMessage("failed"));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (channel === "download") {
|
|
161
|
+
const ok = downloadCard(blob, filenameFor(channel));
|
|
162
|
+
const method = ok ? "download" : "failed";
|
|
163
|
+
capture("audit_card_capture_completed", {
|
|
164
|
+
trigger: "download",
|
|
165
|
+
status: ok ? "success" : "error",
|
|
166
|
+
image_method: method,
|
|
167
|
+
source: "poster",
|
|
168
|
+
});
|
|
169
|
+
toast(shareCardToastMessage(method));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const shareCtx: ShareCtx = {
|
|
174
|
+
score,
|
|
175
|
+
arch: archetype.name.toLowerCase(),
|
|
176
|
+
grade,
|
|
177
|
+
missing,
|
|
178
|
+
};
|
|
179
|
+
const shareText = channel === "x"
|
|
180
|
+
? pickTemplate(X_TEMPLATES, seed, shareCtx)
|
|
181
|
+
: pickTemplate(LI_TEMPLATES, seed, shareCtx);
|
|
182
|
+
const native = await shareCardNative(blob, filenameFor(channel), shareText);
|
|
183
|
+
if (native) {
|
|
184
|
+
capture("audit_card_capture_completed", {
|
|
185
|
+
trigger: `share_${channel}`,
|
|
186
|
+
status: "success",
|
|
187
|
+
image_method: "native",
|
|
188
|
+
source: "poster",
|
|
189
|
+
});
|
|
190
|
+
toast(shareCardToastMessage("native"));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const fallbackMethod = await copyOrDownloadCard(blob, filenameFor(channel));
|
|
195
|
+
capture("audit_card_capture_completed", {
|
|
196
|
+
trigger: `share_${channel}`,
|
|
197
|
+
status: fallbackMethod === "failed" ? "error" : "success",
|
|
198
|
+
image_method: fallbackMethod,
|
|
199
|
+
source: "poster",
|
|
200
|
+
});
|
|
201
|
+
toast(shareCardToastMessage(fallbackMethod));
|
|
202
|
+
const intent = channel === "x" ? X_INTENT(shareText) : LI_INTENT(shareText);
|
|
203
|
+
globalThis.open(intent, "_blank", "noopener,noreferrer");
|
|
204
|
+
} finally {
|
|
205
|
+
setBusy(null);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<section className="poster-section" data-screen-label="01 Poster">
|
|
211
|
+
<div className="poster" ref={posterRef}>
|
|
212
|
+
<header className="poster-head">
|
|
213
|
+
<span className="poster-wordmark">
|
|
214
|
+
<img
|
|
215
|
+
src="/logo.svg"
|
|
216
|
+
alt="failproof_ai"
|
|
217
|
+
className="poster-logo"
|
|
218
|
+
/>
|
|
219
|
+
<span className="poster-sep">·</span> audit
|
|
220
|
+
</span>
|
|
221
|
+
<span className="poster-meta">
|
|
222
|
+
<span className="poster-ix">№ {indexLabel}</span>
|
|
223
|
+
<span className="of"> of 08</span>
|
|
224
|
+
<span className="poster-sep"> · </span>
|
|
225
|
+
audited {auditedDate}
|
|
226
|
+
</span>
|
|
227
|
+
</header>
|
|
228
|
+
|
|
229
|
+
<div className="poster-body">
|
|
230
|
+
{/* Sigil — visual anchor at the top of the centered stack */}
|
|
231
|
+
<div className="poster-sigil">
|
|
232
|
+
<Sigil archetypeKey={archetypeKey} />
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Persona block — name + keywords + rarity, centered */}
|
|
236
|
+
<div className="poster-persona">
|
|
237
|
+
<h1 className="persona-name">{archetype.name}</h1>
|
|
238
|
+
<div className="persona-keywords">
|
|
239
|
+
{archetype.keywords.map((k, i) => (
|
|
240
|
+
<React.Fragment key={k}>
|
|
241
|
+
<span className={`kw kw-${i}`}>{k}</span>
|
|
242
|
+
{i < archetype.keywords.length - 1 && (
|
|
243
|
+
<span className="kw-sep">·</span>
|
|
244
|
+
)}
|
|
245
|
+
</React.Fragment>
|
|
246
|
+
))}
|
|
247
|
+
</div>
|
|
248
|
+
{typeof rarityPct === "number" && (
|
|
249
|
+
<div className="persona-rarity">
|
|
250
|
+
<span className="lbl">{"// only"}</span>{" "}
|
|
251
|
+
<span className="pct">{rarityPct}%</span>{" "}
|
|
252
|
+
<span className="lbl">of agents are this archetype</span>
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
{/* Score block — heroic number + rank pill on the same baseline */}
|
|
258
|
+
<div className="poster-score">
|
|
259
|
+
<span className="score-n">{score}</span>
|
|
260
|
+
<span className="score-of">/100</span>
|
|
261
|
+
<span className="score-rank">{scoreRank}</span>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<footer className="poster-foot">
|
|
266
|
+
<span className="poster-cta">
|
|
267
|
+
audit yours <span className="arrow">→</span> failproof.ai
|
|
268
|
+
</span>
|
|
269
|
+
</footer>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="poster-share-row">
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
className="poster-share-btn"
|
|
276
|
+
onClick={() => handleShare("x")}
|
|
277
|
+
disabled={busy !== null}
|
|
278
|
+
>
|
|
279
|
+
<span className="mark" aria-hidden="true">𝕏</span>
|
|
280
|
+
{busy === "x" ? "rendering…" : "post your archetype"}
|
|
281
|
+
</button>
|
|
282
|
+
<button
|
|
283
|
+
type="button"
|
|
284
|
+
className="poster-share-btn"
|
|
285
|
+
onClick={() => handleShare("linkedin")}
|
|
286
|
+
disabled={busy !== null}
|
|
287
|
+
>
|
|
288
|
+
<span className="mark" aria-hidden="true">in</span>
|
|
289
|
+
{busy === "linkedin" ? "rendering…" : "share on linkedin"}
|
|
290
|
+
</button>
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
className="poster-share-btn"
|
|
294
|
+
onClick={() => handleShare("download")}
|
|
295
|
+
disabled={busy !== null}
|
|
296
|
+
>
|
|
297
|
+
<span className="mark" aria-hidden="true">↓</span>
|
|
298
|
+
{busy === "download" ? "rendering…" : "download poster"}
|
|
299
|
+
</button>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div className="poster-scroll-hint" aria-hidden="true">
|
|
303
|
+
scroll for full report <span className="arrow">↓</span>
|
|
304
|
+
</div>
|
|
305
|
+
</section>
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
/** UTC ISO date (YYYY-MM-DD) so the poster's date stays timezone-stable
|
|
310
|
+
* across the geographies it gets shared into. */
|
|
311
|
+
function formatAuditedDate(iso: string): string {
|
|
312
|
+
try {
|
|
313
|
+
const d = new Date(iso);
|
|
314
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
315
|
+
const y = d.getUTCFullYear();
|
|
316
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
317
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
318
|
+
return `${y}-${m}-${day}`;
|
|
319
|
+
} catch {
|
|
320
|
+
return iso;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -32,7 +32,7 @@ interface Props {
|
|
|
32
32
|
const ERROR_COPY: Record<RerunError["kind"], string> = {
|
|
33
33
|
timeout: "audit took too long — try again",
|
|
34
34
|
network: "network hiccup — check your connection",
|
|
35
|
-
post_failed: "audit failed
|
|
35
|
+
post_failed: "audit failed — try again",
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export function formatElapsed(ms: number): string {
|