diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15c5fe63..123ddb4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 — `` becomes a `.btn` with the same `━━` glyph, the path / modified pair is a tight green-eyebrow `
` 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.
diff --git a/app/audit/audit-styles.css b/app/audit/audit-styles.css
index 7041c893..9534734c 100644
--- a/app/audit/audit-styles.css
+++ b/app/audit/audit-styles.css
@@ -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 {
@@ -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),
@@ -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);
@@ -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; }
+}
diff --git a/app/globals.css b/app/globals.css
index ba5b9139..feced63a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -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);
@@ -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 {
@@ -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 {
@@ -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;
@@ -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) ───────────────────────── */
@@ -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;
+ }
}
diff --git a/app/project/[name]/page.tsx b/app/project/[name]/page.tsx
index 0a1aa092..918c7fbd 100644
--- a/app/project/[name]/page.tsx
+++ b/app/project/[name]/page.tsx
@@ -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";
@@ -115,50 +114,101 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
const displayPath = claudeExists && claudeProjectPath ? claudeProjectPath : canonicalRoot;
return (
-
-
- Session files will appear here once they are created.
-
-
- ) : (
-
+
+
+ path
+
+
{displayPath}
+ {lastModifiedFormatted && (
+ <>
+
+ modified
+
+
{lastModifiedFormatted}
+ >
)}
+
+
+
+
+ ━━ sessions
+
-
+
+ {sessionFiles.length === 0 ? (
+
+
+ no .jsonl files found in this project.
+
+
+ session files will appear here once they are created.
+
+
+ ) : (
+
+
+
+ )}
+
);
}
diff --git a/app/projects/loading.tsx b/app/projects/loading.tsx
index a8804495..4fea943b 100644
--- a/app/projects/loading.tsx
+++ b/app/projects/loading.tsx
@@ -1,18 +1,35 @@
/** Skeleton loading UI for the projects page — audit-styled to match
- * the dashed `.panel` chrome of the loaded state. */
+ * the dashed `.panel` chrome of the loaded state. Staggered fade-in on the
+ * rows gives the skeleton its own rhythm rather than every bar blinking in
+ * lockstep. */
export default function ProjectsLoading() {
return (
+