Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Changelog

## 0.0.11-beta.3 — 2026-06-08
## 0.0.11-beta.3 — 2026-06-09

### Dependencies
- Swap the Vitest DOM environment from `happy-dom` to `jsdom` (`vitest.config.mts`, `package.json`, 15 `docs/*/testing.mdx`). happy-dom is single-maintainer and had a 2024 critical CVE; jsdom has 6 maintainers, ~7× the weekly downloads, and a perfect Snyk maintenance score. Test suite (1691 tests across 82 files) stays green on jsdom (#419).

### Features
- Make the dashboard chrome scale to fill ultrawide monitors. `.report` in `app/globals.css` swaps the fixed `max-width: 1380px; padding: 0 40px` for `max-width: clamp(720px, 96vw, 1840px); padding: 0 clamp(20px, 3vw, 56px)` — on a 2400px viewport the content shell now occupies ~1840px (~530px of empty side margin → ~280px) without letting tabular line measure get unreadably long on 4K. The audit-page override in `app/audit/audit-styles.css` matches, narrower: `clamp(720px, 92vw, 1480px)` so the archetype hero stays composed. `.archetype-frame` itself gets a `max-width: 1320px` cap with `margin: 0 auto` so the giant Bitcount headline + pink-shadowed border don't stretch past their compositional break-point on huge screens. Prose blocks (`.arch-desc`, `.arch-tagline` at 580px max-width) and side gutters keep readability tight at every step of the clamp.
- Subtle polish pass across all 5 pages — same brutalist pixel-craft aesthetic, more carefully made. `app/globals.css` gets a global `::selection` pink wash, an opt-in `:focus-visible` ring system that fires keyboard-only on every interactive element (`a`, `button`, `input`, `.btn`, `.tab`, `.btn-press`), and the existing `.btn` / `.btn-press` / `.tab` / `.panel` transitions are repointed at the `cubic-bezier(0.22, 1, 0.36, 1)` curve already used by `.audit-bar-fill` (away from `transition: all 120ms ease`, which was animating layout properties unintentionally). New: a `.btn:active` press-down, a `.btn-press:active` collapse, a `.tab::after` underline that emerges from center on hover for inactive tabs, and an opt-in `.panel.is-interactive` hover that grows the pink corner brackets from 10px → 16px. `app/projects/page.tsx` gains a section-mast row with the `━━ projects` glyph + folder counter, swaps "Projects" for the lowercased "your agent footprint." headline, and the empty state now renders a 6×6 pixel-grid "no projects" sigil with a 4px hard-offset pink shadow. `app/projects/loading.tsx` staggers its 8 skeleton rows with the same `audit-row-enter` keyframe used by the audit findings table and adds an `aria-busy` "loading…" pip in the mast. `app/project/[name]/page.tsx` migrates from the old shadcn-style `container mx-auto bg-card rounded-lg` chrome to the unified `.report` + `.section` + `.panel` shell — `<ArrowLeft />` becomes a `.btn` with the same `━━` glyph, the path / modified pair is a tight green-eyebrow `<dl>` grid, and the sessions block gets its own section-label mast. `components/navbar.tsx` tightens the slipping-through badge aria (pluralised, `role="status"`, native title tooltip). All new motion respects `prefers-reduced-motion: reduce` — `globals.css` and `audit-styles.css` each got a guard that stills the new tab underline, panel corner growth, button micro-motion, the audit terminal cursor blink, spinner step, marquee shine, and identity dot pulse for vestibular-sensitive users.

- Drop the standalone pixel icon from the top navbar (`components/navbar.tsx`) — the brand cluster is now wordmark-only. Logo resolution is also reworked: a new `useBrandLogo` hook attempts a runtime `fetch` of the remote brand URL on mount, blob-wraps the response into an object URL on success, and falls back to the bundled `/logo.svg` (served from `public/`, mirrored at `assets/logos/company/logo.svg`) on any error/non-OK status. The local fallback is also rendered as the initial state so SSR + pre-fetch frames show the brand without a flash.
- Swap the display font across the app from `Architype Stedelijk` to `Bitcount Prop Single` (the wordmark treatment used on befailproof.ai). Replaces both font binaries — `public/audit/fonts/architype-stedelijk.{woff2,ttf}` and `assets/audit/assets/fonts/architype-stedelijk.{woff2,ttf}` — with the single self-hosted static instance `bitcount-prop-single.woff2` (wght 417 + ELSH 55 baked in, so no `font-variation-settings` plumbing is needed). Both `@font-face` blocks (`app/globals.css`, `assets/audit/styles.css`) and both `--font-display` declarations are updated; the CSS variable name and fallback stack (`"VT323", "JetBrains Mono", monospace`) stay unchanged so every consumer of `var(--font-display)` picks up the new face with no further edits. Stale Architype references in the comment headers of `components/navbar.tsx`, `app/audit/_components/empty-state.tsx`, and `app/audit/_components/show-off-cta.tsx` are renamed to match.
- Pin the `/audit` report footer to the viewport bottom on the empty / running states. `ReportFooter` (`app/audit/_components/report-footer.tsx`) gains an optional `fixed` prop that adds a `report-footer--fixed` class (`position: fixed; left/right/bottom: 0; padding: 16px 32px; z-index: 10`, defined in `app/audit/audit-styles.css`); `ShellEmpty` in `audit-dashboard.tsx` passes it so the pre-run `EmptyState` and the `RunProgress` view — both short pages where the in-flow footer was floating mid-viewport and scrolling with the page — get a sticky status-bar style footer. The post-run dashboard mount is left unchanged because its long content places the footer at the document end naturally.
Expand Down
34 changes: 32 additions & 2 deletions app/audit/audit-styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,18 @@

/* ───────────────────────── audit page shell ───────────────────────── */

/* the audit page intentionally runs narrower than the global .report — the
archetype hero, findings cards, and score-share card are tuned for a
~1400px-max layout where the headline line measure stays "billboard"
tight. On a 2400px+ monitor we still want to break out of the 1180px
feel that used to leave too much black on both flanks; clamp up to
1480px and let `.archetype-frame` cap itself further so the hero doesn't
stretch past its compositional break-point. */
.report {
max-width: 1180px;
width: 100%;
max-width: clamp(720px, 92vw, 1480px);
margin: 0 auto;
padding: 0 32px;
padding: 0 clamp(20px, 3vw, 48px);
}

.section {
Expand Down Expand Up @@ -242,6 +250,11 @@

.archetype-frame {
position: relative;
/* hero composition was tuned for ~1180px max — let it grow a little but
don't let the giant Bitcount name turn into a billboard on 4K */
max-width: 1320px;
margin-left: auto;
margin-right: auto;
border: 1px solid var(--line-2);
background:
repeating-linear-gradient(0deg, rgba(228,88,125,0.025) 0 1px, transparent 1px 16px),
Expand Down Expand Up @@ -295,6 +308,9 @@
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.85); }
}
@media (prefers-reduced-motion: reduce) {
.arch-target .dot-live { animation: none; }
}
.arch-counter {
font-family: var(--font-mono); font-size: 11px;
letter-spacing: 0.18em; text-transform: uppercase; color: var(--dim);
Expand Down Expand Up @@ -1724,3 +1740,17 @@
.strength-metric { grid-column: 2; text-align: left; margin-top: 6px; }
.return-hook { padding: 28px 24px; }
}

/* reduced-motion: silence the ambient terminal motion (blinking cursor,
spinner, marquee shine, status pulse) so vestibular-sensitive users get a
still UI. Functional progress (.running-bar-fill width) still updates;
only the decorative loops are stopped. */
@media (prefers-reduced-motion: reduce) {
.running-cursor,
.running-stage-spin,
.running-bar-fill::after,
.auth-status-pill .dot {
animation: none;
}
.running-bar-fill { transition: none; }
}
111 changes: 103 additions & 8 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,35 @@ select { color-scheme: dark; }
select option { background-color: var(--bg-2); color: var(--ink); }
input[type="date"] { color-scheme: dark; }

/* selection — pink wash ties prose snippets and code-like fragments to the
accent without redesigning anything; used app-wide. */
::selection {
background: var(--accent-pink-bg);
color: var(--accent-light);
}
::-moz-selection {
background: var(--accent-pink-bg);
color: var(--accent-light);
}

/* focus-visible — keyboard users get a clear pink ring on every interactive
element. Pointer users see nothing different (`:focus-visible` only fires
on keyboard-driven focus). Bracketed outline + outline-offset matches the
pixel-craft chrome (no rounded glow). */
:where(a, button, [role="button"], input, textarea, select, summary, [tabindex]):focus-visible {
outline: 1px solid var(--accent-pink);
outline-offset: 2px;
}
.btn:focus-visible,
.tab:focus-visible {
outline: 1px solid var(--accent-pink);
outline-offset: 3px;
}
.btn-press:focus-visible {
outline: 1px solid var(--accent-pink);
outline-offset: 4px;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
Expand Down Expand Up @@ -250,16 +279,32 @@ input[type="date"] { color-scheme: dark; }
padding: 7px 12px;
font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.04em;
border: 1px solid var(--line-2); background: transparent; color: var(--ink);
transition: all 120ms ease; white-space: nowrap;
/* limit transitioned props so width/height/font don't animate, and use the
same cubic-bezier curve as .audit-bar-fill for a coherent motion feel */
transition:
color 140ms cubic-bezier(0.22, 1, 0.36, 1),
background-color 140ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 140ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 140ms cubic-bezier(0.22, 1, 0.36, 1),
transform 140ms cubic-bezier(0.22, 1, 0.36, 1);
white-space: nowrap;
}
.btn:hover { border-color: var(--ink); background: rgba(255,255,255,0.03); }
.btn:active { transform: translateY(1px); }
.btn[aria-disabled="true"], .btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn[aria-disabled="true"]:hover, .btn:disabled:hover { border-color: var(--line-2); background: transparent; }
.btn-primary { border-color: var(--accent-pink); color: var(--accent-pink); }
.btn-primary:hover { background: var(--accent-pink); color: var(--bg); }
.btn-press {
box-shadow: 4px 4px 0 0 var(--accent-pink-shadow);
transition: box-shadow 120ms, transform 120ms;
transition:
box-shadow 140ms cubic-bezier(0.22, 1, 0.36, 1),
transform 140ms cubic-bezier(0.22, 1, 0.36, 1),
background-color 140ms cubic-bezier(0.22, 1, 0.36, 1),
color 140ms cubic-bezier(0.22, 1, 0.36, 1);
}
.btn-press:hover { box-shadow: 2px 2px 0 0 var(--accent-pink-shadow); transform: translate(2px, 2px); }
.btn-press:active { box-shadow: 0 0 0 0 var(--accent-pink-shadow); transform: translate(4px, 4px); }

/* primary tab strip — used by both the audit page and the policies/projects nav rows */
.tabs {
Expand All @@ -269,26 +314,49 @@ input[type="date"] { color-scheme: dark; }
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
position: relative;
display: inline-flex; align-items: center; gap: 8px;
padding: 12px 16px;
font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.04em;
color: var(--ink-2);
border-bottom: 1px solid transparent; margin-bottom: -1px;
transition: color 120ms, border-color 120ms; white-space: nowrap;
transition:
color 160ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 160ms cubic-bezier(0.22, 1, 0.36, 1);
white-space: nowrap;
background: transparent;
}
.tab:hover { color: var(--ink); }
/* faint underline preview on hover — emerges from center on inactive tabs;
active state keeps the existing sharp full-width pink line. */
.tab::after {
content: "";
position: absolute;
left: 16px; right: 16px; bottom: -1px;
height: 1px;
background: var(--accent-pink);
transform: scaleX(0);
transform-origin: center;
opacity: 0.4;
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}
.tab:hover::after { transform: scaleX(1); }
.tab.is-active { color: var(--accent-pink); border-bottom-color: var(--accent-pink); }
.tab.is-active::after { display: none; }

/* ───────────────────────── canonical page chrome ───────────────────────── */

/* the canonical content shell. Scales to fill ultrawide monitors without
collapsing on mobile or letting prose lines get unreadable. The cap at
1840px keeps the line measure for tabular pages (policies activity,
sessions list) comfortably below "scan back to start of next row" length
on a 2560px display. Side gutter is clamped 20→56px so the chrome breathes
on big screens without crowding small ones. */
.report {
max-width: 1380px;
width: 100%;
max-width: clamp(720px, 96vw, 1840px);
margin: 0 auto;
padding: 0 40px;
}
@media (max-width: 720px) {
.report { padding: 0 20px; }
padding: 0 clamp(20px, 3vw, 56px);
}

.section {
Expand Down Expand Up @@ -347,6 +415,15 @@ input[type="date"] { color-scheme: dark; }
border: 1px solid var(--line-2);
background: var(--bg-2);
padding: 28px;
transition:
border-color 200ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.panel::before,
.panel::after {
transition: width 200ms cubic-bezier(0.22, 1, 0.36, 1),
height 200ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.panel::before {
content: ""; position: absolute; top: -1px; left: -1px;
Expand All @@ -360,6 +437,13 @@ input[type="date"] { color-scheme: dark; }
border-bottom: 1px solid var(--accent-pink);
border-right: 1px solid var(--accent-pink);
}
/* opt-in interactive flavour: hovering grows the corner brackets and softens
the panel border. Apply to clickable card-like panels (e.g. project rows). */
.panel.is-interactive { cursor: pointer; }
.panel.is-interactive:hover { border-color: var(--line); }
.panel.is-interactive:hover::before,
.panel.is-interactive:hover::after { width: 16px; height: 16px; }
.panel.is-interactive:focus-within { border-color: var(--accent-pink-soft); }

/* ───────────────────────── animations (preserved from previous globals) ───────────────────────── */

Expand Down Expand Up @@ -407,4 +491,15 @@ input[type="date"] { color-scheme: dark; }
animation: none;
opacity: 1;
}
.section-h-dot {
animation: none;
}
.tab::after,
.panel,
.panel::before,
.panel::after,
.btn,
.btn-press {
transition: none;
}
}
128 changes: 89 additions & 39 deletions app/project/[name]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { notFound } from "next/navigation";
import { existsSync } from "fs";
import { stat } from "fs/promises";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import { formatDate } from "@/lib/format-date";
import SessionsList from "@/app/components/sessions-list";

Expand Down Expand Up @@ -115,50 +114,101 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
const displayPath = claudeExists && claudeProjectPath ? claudeProjectPath : canonicalRoot;

return (
<main className="min-h-screen bg-background">
<div className="container mx-auto p-8">
<Link
href="/projects"
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground mb-6 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Projects</span>
</Link>

<div className="mb-8">
<h1 className="text-4xl font-bold text-foreground mb-2 break-words break-all">
{canonicalRoot}
</h1>
<div className="space-y-1">
<p className="text-muted-foreground">
<span className="font-medium">Path:</span> {displayPath}
</p>
{lastModifiedFormatted && (
<p className="text-muted-foreground">
<span className="font-medium">Modified:</span> {lastModifiedFormatted}
</p>
)}
<main className="report">
<section className="section" data-screen-label="project">
<div className="section-mast">
<Link
href="/projects"
className="btn"
style={{ fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase" }}
aria-label="Back to projects"
>
<span style={{ color: "var(--accent-pink)", letterSpacing: "-2px" }}>━━</span>
back to projects
</Link>
<div className="section-meta">
<span className="g">●</span> {sessionFiles.length} session{sessionFiles.length === 1 ? "" : "s"}
</div>
</div>

{/* Sessions Section */}
<div className="bg-card text-card-foreground rounded-lg border border-border p-6 shadow-sm">
<h2 className="text-2xl font-semibold mb-4">Sessions</h2>
<h1
className="section-h"
style={{ textTransform: "none", marginBottom: 18, wordBreak: "break-word" }}
>
{canonicalRoot}
</h1>

{sessionFiles.length === 0 ? (
<div className="text-center py-8">
<p className="text-muted-foreground mb-2">
No .jsonl files found in this project.
</p>
<p className="text-sm text-muted-foreground">
Session files will appear here once they are created.
</p>
</div>
) : (
<Suspense><SessionsList files={sessionFiles} projectName={name} /></Suspense>
<dl
style={{
display: "grid",
gridTemplateColumns: "auto 1fr",
gap: "8px 18px",
margin: "0 0 36px",
fontFamily: "var(--font-mono)",
fontSize: 12,
color: "var(--ink-2)",
}}
>
<dt
style={{
color: "var(--accent-green)",
letterSpacing: "0.2em",
textTransform: "uppercase",
fontSize: 10,
alignSelf: "center",
}}
>
path
</dt>
<dd style={{ margin: 0, wordBreak: "break-all" }}>{displayPath}</dd>
{lastModifiedFormatted && (
<>
<dt
style={{
color: "var(--accent-green)",
letterSpacing: "0.2em",
textTransform: "uppercase",
fontSize: 10,
alignSelf: "center",
}}
>
modified
</dt>
<dd style={{ margin: 0 }}>{lastModifiedFormatted}</dd>
</>
)}
</dl>

<div className="section-mast" style={{ marginBottom: 18 }}>
<div className="section-label">
<span className="glyph">━━</span> sessions
</div>
</div>
</div>

{sessionFiles.length === 0 ? (
<div
className="panel"
style={{ textAlign: "center", padding: "48px 32px" }}
>
<p style={{ color: "var(--ink-2)", marginBottom: 8 }}>
no .jsonl files found in this project.
</p>
<p
style={{
color: "var(--dim)",
fontSize: 12,
letterSpacing: "0.05em",
}}
>
session files will appear here once they are created.
</p>
</div>
) : (
<div className="panel" style={{ padding: 0 }}>
<Suspense><SessionsList files={sessionFiles} projectName={name} /></Suspense>
</div>
)}
</section>
</main>
);
}
Loading