Paper theme, per-request results strip, hover readouts, audit fixes

- warm paper/ink palette with serif display type and mono figures, sparing
  oxblood accent for brand marks and the memory that matters
- entrance animations moved from JS to CSS so they never stall mid-recording
- hover any grid square or result row to read the full memory
- bottom strip now reports the selected request, falling back to the benchmark
- editorial preset nav replaces chips; brand row and status hints removed
- audit: genericize a persona detail that echoed the author's own school,
  add nosniff/referrer/permissions headers and no-store on API responses

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samarth Makhija
2026-09-19 17:00:49 -07:00
co-authored by Claude Opus 5
parent 053837b5a5
commit e31c867618
15 changed files with 544 additions and 330 deletions
+94 -28
View File
@@ -2,65 +2,131 @@
**Retrieve by relevance, not resemblance.**
Give it a situation and a list of memories. It returns only the memories that matter,
judged by TypeSafe's Jev model, one calibrated yes/no per memory, all in one request.
Give it a situation and a pile of memories. It returns only the memories that matter, judged by
[TypeSafe's Jev](https://typesafe.ai) model, one calibrated yes/no per memory, in a single request.
<!-- TODO: replace with the recorded demo GIF -->
![Jev Recall demo](docs/demo.gif)
**[Try the live demo →](https://jev-recall.vercel.app)** · replays real runs, or runs live with your own API keys.
## The problem
With LLMs, writing memories is easy. Knowing which ones to *retrieve* is the hard part.
Today you get two options, and both make you guess in advance:
- **Keyword search** works only if you already know what you're looking for.
- **Semantic search** works only if the memory that matters happens to look like the request.
That second assumption breaks constantly, because the memories that change an answer are usually
the ones phrased nothing like the question. "Order the almond tart for Saturday's dinner party"
looks nothing like "Leo has a severe tree-nut allergy," so embeddings return other pastry memories
and the assistant orders the tart.
An LLM used as a re-ranker does catch those. It reads everything and reasons about what matters.
But then you pay LLM prices and LLM latency on every single turn, so nobody runs it on every turn.
Jev is a classifier that makes that same include-or-not decision at LLM quality, for a fraction of
the cost and time. That makes it cheap enough to judge *every* memory on *every* turn.
## How it works
Every memory goes into one request's shared `state`, tagged with a short id. Each memory gets a tiny
yes/no question pointing at its id, and Jev returns a calibrated probability for each one. Because the
probabilities are calibrated, you keep everything above a threshold instead of guessing at a top-k,
and nothing is returned when nothing applies.
```python
from jev_recall import Recall
recall = Recall() # reads TYPESAFE_API_KEY
result = recall.filter(
"Book a table for Dad's 70th birthday dinner on Saturday, Oct 3. 6 of us.",
memories, # list[str] or list[Memory] or list[{"id", "text"}]
threshold=0.5, # calibrated probability; no top-k guessing
"Order the almond frangipane tart from Tartine for Saturday's dinner party.",
memories, # list[str] | list[Memory] | list[{"id", "text"}]
threshold=0.5,
)
result.texts # the relevant memories, best first (can be empty)
result.texts # the memories that matter, best first (can be empty)
result.all_scores # every memory with its probability
result.cost_usd, result.latency_s
```
Memories go into the request's state once, tagged with short ids, and each gets a tiny
yes/no question that points at its id (`mode="pointer"`, the default). Stores too big for
one request are split and sent concurrently. `afilter` is the async version.
Other modes: `"inline"` puts each memory's text in its own question, and `"shortlist"` ranks
every memory with one multiple-choice question and then verifies the top 15.
## Demo site
`web/` is a Next.js demo that races OpenAI embeddings, Claude Sonnet 5 and Jev Recall on
personal-assistant requests. It replays recorded runs by default, and visitors can run it
live with their own API keys. See [web/README.md](web/README.md).
Large stores are split across requests and sent concurrently. `afilter` is the async version.
Two other modes are available: `inline` puts each memory's text in its own question, and `shortlist`
ranks everything with one multiple-choice question and then verifies the top candidates.
## Benchmark
`bench/` holds one assistant's memory store (238 memories) and 18 requests where the
memory that matters doesn't look like the request: a quiet-period rule when drafting a
LinkedIn post, a stolen card when paying a bill, a pregnancy when booking a dive trip.
`bench/` holds one assistant's memory store of 238 fictional memories and 18 requests where the
memory that matters doesn't look like the request: a stolen card when paying a bill, a pregnancy
when booking a dive trip, a guest's nut allergy when ordering dessert.
```bash
cp .env.example .env # add TYPESAFE_API_KEY and ANTHROPIC_API_KEY
uv run --group bench python bench/run.py --systems bm25,dense,dense+rerank,openai
uv run --group bench python bench/run.py --systems jev-inline,jev-pointer,jev-shortlist
uv run --group bench python bench/run.py --systems llm:claude-haiku-4-5,llm:claude-sonnet-5,llm:claude-opus-5
```
Results land in `bench/results/`. Run on 2026-09-19 with `jev-1.13.0`:
Run on 2026-09-19 with `jev-1.13.0`. "Key memories" counts the 20 memories across those requests that
an assistant had to know about before acting.
| System | Requests passed | Key memories found | Cost per request | Latency |
|---|---|---|---|---|
| Keyword search (BM25), top 5 | 1/18 | 2/20 | local | <0.01s |
| bge-large embeddings, top 5 | 3/18 | 4/20 | local | 0.06s |
| bge-large + bge-reranker, top 5 | 1/18 | 1/20 | local | 1.2s |
| OpenAI text-embedding-3-large, top 5 | 3/18 | 4/20 | <$0.00001 | 0.4s |
| **Jev Recall, pointer mode** | **17/18** | **19/20** | **$0.00044** | **0.35s** |
| OpenAI text-embedding-3-large, top 5 | 3/18 | 4/20 | $0.000002 | 0.4s |
| **Jev Recall (pointer mode)** | **17/18** | **19/20** | **$0.00044** | **0.35s** |
| Claude Haiku 4.5 | 14/18 | 15/20 | $0.0055 | 1.4s |
| Claude Sonnet 5 | 17/18 | 19/20 | $0.020 | 7.6s |
| Claude Opus 5 | 18/18 | 20/20 | $0.049 | 7.6s |
Claude costs are without prompt caching. With the memory store cached, Sonnet 5 costs
$0.0085 and Opus 5 costs $0.019 per request.
Jev matches Sonnet 5 and is within one memory of Opus 5, at roughly 1/46 of Sonnet's cost and 20x its
speed. Scores move by less than 0.01 between runs, and the result holds at any threshold from 0.3 to 0.5.
Two caveats worth stating plainly. Claude's costs above are without prompt caching; with the memory
store cached, Sonnet 5 costs $0.0085 and Opus 5 costs $0.019 per request, so the cost gap narrows to
about 19x and 43x. And Jev's one miss is a memory that needs outside knowledge to connect ("Sam is on
an H-1B visa" rules out paid freelance work). Sonnet missed that one too.
## The demo
`web/` is the Next.js site behind [jev-recall.vercel.app](https://jev-recall.vercel.app). Three methods
race across one shared grid of all 238 memories: OpenAI embeddings, Claude Sonnet 5 and Jev Recall.
Each scanner settles on what its method picked, and a method that misses the memory that mattered says so.
```bash
cd web
pnpm install
pnpm dev
```
It replays recorded real runs by default. **Run live with your keys** accepts your own TypeSafe,
Anthropic and OpenAI keys; they stay in your browser's local storage and are passed straight through
to the provider by the site's API route, which stores and logs nothing. The deployment holds no keys
of its own. `/?present=1` autoplays once with the controls hidden, for screen recordings.
## Repo layout
| Path | What's in it |
|---|---|
| `src/jev_recall/` | the library: request building, sharding, retries, scoring |
| `tests/` | unit tests against a mocked Jev API (`uv run pytest`) |
| `bench/data/` | the 238 fictional memories and the 18 labeled requests |
| `bench/run.py` | the benchmark harness and every baseline |
| `web/` | the demo site |
## Setup
```bash
uv sync # library + dev deps
uv sync --group bench # baselines: fastembed, rank-bm25, anthropic, openai
cp .env.example .env # TYPESAFE_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY
```
A TypeSafe key is all the library itself needs. The other two are only for benchmark baselines and for
recording the demo's replays.
## License
MIT. The memories in `bench/data` describe a fictional person.
MIT. The memories in `bench/data` describe a fictional person; any resemblance to real people is coincidental.
+1 -1
View File
@@ -155,7 +155,7 @@ mom_garden | Mom's roses have won ribbons at the county fair.
sister | User's younger sister Maya lives in Brooklyn and works in publishing.
maya_visit | Maya is visiting for a week in early November.
college | User went to UCLA and studied economics.
mba | User did an MBA at Kellogg.
mba | User did an MBA a few years after college.
languages | User speaks English and conversational Spanish.
volunteering | User volunteers at the SF-Marin Food Bank once a month.
donation | User gives monthly to KQED.
+33
View File
@@ -0,0 +1,33 @@
{
"when": "2026-09-19T16:50:53",
"k": 5,
"threshold": 0.5,
"n_memories": 238,
"results": {
"openai": [
{
"scenario": "none_math",
"got": [
"parking",
"q3_okr",
"pto",
"laptop",
"alarm"
],
"found": [],
"missed": [],
"noise": [
"parking",
"q3_okr",
"pto",
"laptop",
"alarm"
],
"returned": 5,
"pass": false,
"latency_s": 0.5623054999887245,
"cost_usd": 1.17e-06
}
]
}
}
+1
View File
@@ -0,0 +1 @@
Put `demo.gif` here; the root README links to `docs/demo.gif`.
+5 -3
View File
@@ -1,8 +1,10 @@
# Jev Recall demo
A single-page demo: an assistant knows 238 things about your life, and three systems pick which
ones it should read before acting. Semantic search (OpenAI embeddings), an LLM judge
(Claude Sonnet 5) and Jev Recall run side by side.
Three methods race across one shared grid of all 238 memories: semantic search (OpenAI
embeddings), Claude Sonnet 5 as an LLM judge, and Jev Recall. Each scanner settles on what its
method picked, and a method that misses the memory that mattered says so.
Live at https://jev-recall.vercel.app
```bash
pnpm install
+56 -18
View File
@@ -1,20 +1,21 @@
@import "tailwindcss";
/* Light "scientific instrument" theme from the Shared Scanfield handoff.
Method colors are fixed across scanners, timers and result cards. */
/* Warm paper-and-ink palette: off-white stock, near-black ink, serif display,
monospace for labels and figures. Method colors are muted to match. */
:root {
--bg: #f6f7f9;
--panel: #ffffff;
--panel-2: #f8fafc;
--cell: #eceef2;
--line: rgba(15, 23, 42, 0.08);
--text: #0f172a;
--muted: #64748b;
--faint: #94a3b8;
--embeddings: #3b82f6;
--sonnet: #f97316;
--jev: #14b8a6;
--jev-ink: #0f766e;
--bg: #f1eee5;
--panel: #faf8f2;
--panel-2: #f4f1e8;
--cell: #e3dfd2;
--line: rgba(22, 21, 18, 0.14);
--text: #16150f;
--muted: #6d6758;
--faint: #9b9484;
--embeddings: #3a6ea5;
--sonnet: #c2613a;
--jev: #2f7f6b;
--jev-ink: #235f50;
--accent: #9c2f26; /* oxblood: brand marks and "the memory that matters" */
}
@theme inline {
@@ -30,8 +31,10 @@
--color-sonnet: var(--sonnet);
--color-jev: var(--jev);
--color-jev-ink: var(--jev-ink);
--font-sans: var(--font-onest);
--font-mono: var(--font-geist-mono);
--color-accent: var(--accent);
--font-sans: var(--font-serif);
--font-serif: var(--font-serif);
--font-mono: var(--font-mono);
}
html,
@@ -39,11 +42,21 @@ body {
height: 100%;
background: var(--bg);
color: var(--text);
font-family: var(--font-onest), system-ui, sans-serif;
font-family: var(--font-serif), Georgia, serif;
overflow: hidden;
}
/* Small uppercase monospace labels, as on the reference spec sheet. */
.label {
font-family: var(--font-mono), ui-monospace, monospace;
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.tabular {
font-family: var(--font-mono), ui-monospace, monospace;
font-variant-numeric: tabular-nums;
}
@@ -65,7 +78,32 @@ body {
}
}
.skeleton {
background: linear-gradient(90deg, var(--cell) 0px, #f8fafc 80px, var(--cell) 160px);
background: linear-gradient(90deg, var(--cell) 0px, var(--panel-2) 80px, var(--cell) 160px);
background-size: 400px 100%;
animation: shimmer 1.2s linear infinite;
}
/* Entrance animations run as CSS, not JS: they keep playing even when the
rAF-driven animation loop is throttled (long screen recordings, background tabs). */
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
.fade-up {
animation: fade-up 0.42s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
@keyframes pulse-dot {
50% {
opacity: 0.2;
}
}
.pulse-dot {
animation: pulse-dot 0.9s ease-in-out infinite;
}
+5 -5
View File
@@ -1,19 +1,19 @@
import type { Metadata } from "next";
import { Geist_Mono, Onest } from "next/font/google";
import { IBM_Plex_Mono, Newsreader } from "next/font/google";
import "./globals.css";
const onest = Onest({ variable: "--font-onest", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
const serif = Newsreader({ variable: "--font-serif", subsets: ["latin"], style: ["normal", "italic"] });
const mono = IBM_Plex_Mono({ variable: "--font-mono", subsets: ["latin"], weight: ["400", "500"] });
export const metadata: Metadata = {
title: "Jev Recall · See what each method finds",
title: "Jev Recall · Catches the memories semantic search misses",
description:
"An AI assistant knows 238 things about your life. Watch semantic search, Claude Sonnet 5 and Jev Recall pick which ones matter.",
};
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
<html lang="en" className={`${onest.variable} ${geistMono.variable} antialiased`}>
<html lang="en" className={`${serif.variable} ${mono.variable} antialiased`}>
<body>{children}</body>
</html>
);
+96 -59
View File
@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { Preset } from "@/data/presets";
import { METHOD_COLOR } from "@/lib/format";
import { KEY_HEADERS, SYSTEMS, type Memory, type RunResult, type SystemId } from "@/lib/types";
import { KeysDialog, useKeys } from "./KeysDialog";
import { ResultCard, type CardState } from "./ResultCard";
@@ -19,7 +20,14 @@ const IDLE: Cards = {
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// From the benchmark in bench/ (README): 238 memories, 18 requests, 20 key memories.
const BENCHMARK = { jev: "19/20", sonnet: "19/20", embeddings: "4/20", faster: "~22× faster", cost: "~1/46 cost" };
const BENCHMARK = {
requests: 18,
jev: "19/20",
sonnet: "19/20",
embeddings: "4/20",
faster: "~22× faster",
cost: "~1/46 the cost",
};
export function Demo({
memories,
@@ -171,32 +179,55 @@ export function Demo({
}),
) as Record<SystemId, Scan>;
const done = phase === "done";
const sonnetRun = cards.sonnet.result;
const jevRun = cards.jev.result;
const runStats =
done && sonnetRun && jevRun && keyIds.length > 0
? {
caption: `${keyIds.length === 1 ? "1 key memory" : `${keyIds.length} key memories`} to find`,
found: Object.fromEntries(
SYSTEMS.map(({ id }) => {
const r = cards[id].result;
const hit = r ? keyIds.filter((k) => r.ids.includes(k)).length : 0;
return [id, `${hit}/${keyIds.length}`];
}),
) as Record<SystemId, string>,
faster: `${Math.round(cards.sonnet.elapsedMs / Math.max(cards.jev.elapsedMs, 1))}× faster`,
cost: `1/${Math.round(sonnetRun.costUsd / Math.max(jevRun.costUsd, 1e-9))} the cost`,
}
: null;
const headline =
phase === "running" ? `Scanning ${memories.length} memories…` : phase === "done" ? `Scanned ${memories.length} memories` : `${memories.length} memories`;
const recordedAt = replays[preset.id]?.recordedAt;
const editable = live && !present && phase !== "running" && phase !== "typing";
return (
<Stage>
<div className="flex h-full flex-col px-10 pb-7 pt-6">
{/* Brand */}
<div className="flex h-9 items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg border-[1.5px] border-[#cbd2dc] bg-white text-[17px] font-medium text-text">
J
<div className="flex h-full flex-col px-10 pb-7 pt-8">
{/* Headline + mode controls */}
<div className="flex items-start justify-between gap-8">
<div>
<div className="mb-3 flex gap-[3px]">
<span className="h-[5px] w-[22px] rounded-full bg-embeddings" />
<span className="h-[5px] w-[22px] rounded-full bg-sonnet" />
<span className="h-[5px] w-[22px] rounded-full bg-jev" />
<span className="h-[5px] w-[9px] rounded-full bg-accent" />
</div>
<div className="text-[19px] font-medium text-text">Jev Recall</div>
<h1 className="text-[40px] font-semibold leading-[1.08] tracking-[-0.01em] text-text">
<span className="rounded-[4px] bg-jev/15 px-1.5 pb-0.5 decoration-clone">Jev Recall</span> catches the
memories semantic search{" "}
<em className="underline decoration-accent decoration-[3px] underline-offset-[6px]">misses.</em>
</h1>
<p className="mt-2 text-[19px] leading-snug text-muted">
Same answers as Claude Sonnet 5, at a fraction of the time and cost.
</p>
</div>
{!present && (
<div className="flex items-center gap-3 text-[13px]">
<span className="text-faint">
{live
? "Live with your keys"
: `Replaying a live run${recordedAt ? ` from ${new Date(recordedAt + "T12:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric" })}` : ""}`}
</span>
<div className="flex shrink-0 items-center gap-3 pt-2">
<button
onClick={() => setShowKeys(true)}
className="rounded-full border border-line bg-white px-3.5 py-1.5 text-text shadow-sm hover:border-[#cbd2dc]"
className="label rounded-full border border-line bg-panel px-3.5 py-1.5 !text-text hover:bg-panel-2"
>
{live ? "Keys" : "Run live with your keys"}
</button>
@@ -204,16 +235,36 @@ export function Demo({
)}
</div>
<h1 className="mt-2 text-[42px] font-bold leading-[1.1] tracking-[-0.02em] text-text">
See what each method <span className="text-faint">finds.</span>
</h1>
{/* Preset nav */}
{!present && (
<div className="mt-6 border-b border-line pb-2.5">
<div className="flex items-baseline gap-7">
{presets.map((p) => {
const active = p.id === preset.id && isPreset;
return (
<button
key={p.id}
onClick={() => choose(p, false)}
className={`relative pb-2.5 text-[16px] transition ${active ? "text-text" : "text-muted hover:text-text"}`}
>
{p.label}
{active && <span className="absolute inset-x-0 -bottom-[1px] h-[2px] rounded-full bg-accent" />}
</button>
);
})}
</div>
</div>
)}
{/* Request */}
<div className="mt-4 flex items-center gap-4">
<div className="flex h-[52px] w-[52px] shrink-0 items-center justify-center rounded-full border border-line bg-white text-[15px] text-muted">
You
<div className="mt-5 flex items-center gap-4">
<div className="flex h-[62px] w-[62px] shrink-0 items-center justify-center rounded-xl border border-line bg-panel text-accent">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" aria-label="Your request" role="img">
<circle cx="12" cy="8" r="3.6" stroke="currentColor" strokeWidth="1.5" />
<path d="M4.8 20c.6-3.8 3.6-6 7.2-6s6.6 2.2 7.2 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</div>
<div className="flex h-[62px] flex-1 items-center rounded-2xl border border-[#dde2e9] bg-white px-6 shadow-[0_1px_2px_rgba(15,23,42,0.04)]">
<div className="flex h-[62px] flex-1 items-center rounded-xl border border-line bg-panel px-6">
{editable ? (
<input
value={request}
@@ -237,7 +288,7 @@ export function Demo({
<button
onClick={() => run(preset, request, { type: false })}
disabled={phase === "running" || phase === "typing" || !request.trim()}
className="flex h-[62px] shrink-0 items-center gap-3 rounded-2xl border border-[#99e6d8] bg-[#d5f5ef] px-7 text-[19px] font-semibold text-text transition hover:bg-[#c3f0e7] disabled:opacity-60"
className="flex h-[62px] shrink-0 items-center gap-3 rounded-xl bg-text px-8 text-[19px] font-medium text-[#faf8f2] transition hover:opacity-90 disabled:opacity-40"
>
<svg width="14" height="16" viewBox="0 0 14 16" aria-hidden>
<path d="M1 1.5v13l12-6.5z" fill="currentColor" />
@@ -245,25 +296,6 @@ export function Demo({
Run comparison
</button>
</div>
{!present && (
<div className="ml-[68px] mt-2.5 flex gap-2">
{presets.map((p, i) => (
<button
key={p.id}
onClick={() => choose(p, false)}
className={`rounded-full border px-3 py-1 text-[13px] transition ${
p.id === preset.id && isPreset
? "border-[#cbd2dc] bg-white text-text shadow-sm"
: "border-transparent text-muted hover:text-text"
}`}
>
<span className="mr-1.5 text-faint">{i + 1}</span>
{p.label}
</button>
))}
</div>
)}
{/* Scan field + results */}
<div className="mt-4 grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_600px] gap-5">
<ScanField
@@ -291,28 +323,33 @@ export function Demo({
</div>
</div>
{/* Benchmark strip */}
<div className="mt-4 flex h-[60px] items-center rounded-2xl border border-line bg-panel px-8 text-[20px]">
{/* Results strip: this run once it finishes, the full benchmark before that */}
<div className="mt-4 flex h-[60px] items-center rounded-xl border border-line bg-panel px-8 text-[19px]">
<div className="flex items-center gap-3 pr-8">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" aria-hidden className="text-jev">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden className="text-accent">
<path d="M7 4h10v5a5 5 0 0 1-10 0V4Z M7 6H4a3 3 0 0 0 3 4 M17 6h3a3 3 0 0 1-3 4 M12 14v4 M8 20h8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className="text-text">Benchmark</span>
<span className="text-muted">(same {memories.length} memories)</span>
<span className="label !text-text">{runStats ? "This request" : "Benchmark"}</span>
<span className="text-[15px] text-muted">
{runStats ? runStats.caption : `${BENCHMARK.requests} requests, ${memories.length} memories`}
</span>
</div>
<div className="flex flex-1 items-center gap-10 border-l border-line pl-10">
<span className="text-muted">
Jev <span className="ml-2 text-[24px] font-medium text-jev">{BENCHMARK.jev}</span>
</span>
<span className="text-muted">
Sonnet <span className="ml-2 text-[24px] font-medium text-sonnet">{BENCHMARK.sonnet}</span>
</span>
<span className="text-muted">
embeddings <span className="ml-2 text-[24px] font-medium text-embeddings">{BENCHMARK.embeddings}</span>
</span>
{SYSTEMS.map(({ id }) => (
<span key={id} className="text-muted">
{id === "sonnet" ? "Sonnet" : id === "embeddings" ? "Embeddings" : "Jev"}{" "}
<span className="tabular ml-2 text-[21px]" style={{ color: METHOD_COLOR[id] }}>
{runStats ? runStats.found[id] : BENCHMARK[id]}
</span>
</span>
))}
</div>
<div className="tabular border-l border-line px-10 text-[17px] text-text">
{runStats ? runStats.faster : BENCHMARK.faster}
</div>
<div className="tabular border-l border-line pl-10 text-[17px] text-text">
{runStats ? runStats.cost : BENCHMARK.cost}
</div>
<div className="border-l border-line px-10 text-text">{BENCHMARK.faster}</div>
<div className="border-l border-line pl-10 text-text">{BENCHMARK.cost}</div>
</div>
</div>
{showKeys && <KeysDialog initial={keys} onClose={() => setShowKeys(false)} />}
+4 -4
View File
@@ -59,8 +59,8 @@ export function KeysDialog({ initial, onClose }: { initial: Keys; onClose: () =>
onClose();
};
return (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-slate-900/30 backdrop-blur-sm" onClick={() => onClose()}>
<div className="w-[560px] rounded-2xl border border-line bg-panel p-7 shadow-2xl" onClick={(e) => e.stopPropagation()}>
<div className="absolute inset-0 z-50 flex items-center justify-center bg-[#16150f]/25 backdrop-blur-sm" onClick={() => onClose()}>
<div className="w-[560px] rounded-xl border border-line bg-panel p-7 shadow-2xl" onClick={(e) => e.stopPropagation()}>
<div className="text-[22px] font-semibold">Run it live with your own keys</div>
<p className="mt-2 text-[14px] leading-relaxed text-muted">
Without keys you see recorded live runs. Add all three keys to run the presets live or type your own request.
@@ -81,7 +81,7 @@ export function KeysDialog({ initial, onClose }: { initial: Keys; onClose: () =>
value={keys[f.id]}
placeholder={f.placeholder}
onChange={(e) => setKeys({ ...keys, [f.id]: e.target.value.trim() })}
className="mt-1.5 w-full rounded-lg border border-line bg-panel-2 px-3 py-2 font-mono text-[13px] text-text outline-none focus:border-jev"
className="mt-1.5 w-full rounded-lg border border-line bg-panel-2 px-3 py-2 font-mono text-[13px] text-text outline-none focus:border-text"
/>
</label>
))}
@@ -94,7 +94,7 @@ export function KeysDialog({ initial, onClose }: { initial: Keys; onClose: () =>
<button onClick={() => done({ ...EMPTY_KEYS })} className="text-[14px] text-muted hover:text-text">
Forget keys
</button>
<button onClick={() => done(keys)} className="rounded-lg bg-text px-5 py-2 text-[14px] font-semibold text-white">
<button onClick={() => done(keys)} className="rounded-lg bg-text px-5 py-2 text-[15px] text-[#faf8f2]">
Save
</button>
</div>
+34 -27
View File
@@ -1,5 +1,4 @@
"use client";
import { AnimatePresence, motion } from "motion/react";
import { METHOD_COLOR, formatCost } from "@/lib/format";
import type { Memory, RunResult, SystemId } from "@/lib/types";
@@ -62,14 +61,14 @@ export function ResultCard({
].slice(0, ROWS);
return (
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col rounded-2xl border border-line bg-panel px-5 py-3.5">
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col rounded-xl border border-line bg-panel px-5 py-3.5">
<div className="flex items-start">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-3">
<span className="h-[18px] w-[18px] rounded-full" style={{ background: color }} />
<span className="text-[19px] font-semibold tracking-tight text-text">{name}</span>
<span className="text-[20px] font-semibold text-text">{name}</span>
</div>
<div className="ml-[30px] mt-0.5 text-[13px] text-muted">
<div className="ml-[30px] mt-0.5 text-[14px] text-muted">
{method}
{r && (
<span className="ml-2 text-text">
@@ -82,14 +81,14 @@ export function ResultCard({
</div>
<div className="flex">
<div className="w-[86px] border-l border-line pl-4">
<div className="text-[13px] text-muted">Time</div>
<div className="tabular text-[19px] text-text">
<div className="label">Time</div>
<div className="tabular mt-0.5 text-[18px] text-text">
{state.status === "idle" ? "–" : `${(state.elapsedMs / 1000).toFixed(2)}s`}
</div>
</div>
<div className="w-[118px] border-l border-line pl-4">
<div className="text-[13px] text-muted">Cost</div>
<div className="tabular text-[19px] text-text">{r ? formatCost(r.costUsd) : "–"}</div>
<div className="label">Cost</div>
<div className="tabular mt-0.5 text-[18px] text-text">{r ? formatCost(r.costUsd) : "–"}</div>
</div>
</div>
</div>
@@ -97,49 +96,57 @@ export function ResultCard({
<div className="mt-2.5 flex min-h-0 flex-1 flex-col gap-1.5">
{state.status === "running" &&
[0, 1].map((i) => <div key={i} className="skeleton h-[42px] shrink-0 rounded-xl" />)}
<AnimatePresence>
{rows.map((row, i) => {
{rows.map((row, i) => {
const score = row.kind !== "miss" ? r?.scores?.[row.id] : undefined;
const tinted = row.kind === "hit";
const full = row.kind === "miss" ? keyIds.map((k) => memoryById[k]?.text).join(" ") : memoryById[row.id]?.text;
return (
<motion.div
<div
key={`${id}-${row.id}`}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.08 + i * 0.12 }}
className="flex h-[42px] shrink-0 items-center gap-3 rounded-xl border px-3"
className="fade-up group relative flex h-[42px] shrink-0 items-center gap-3 rounded-xl border px-3"
style={{
animationDelay: `${80 + i * 120}ms`,
borderColor: tinted ? `color-mix(in srgb, ${color} 30%, transparent)` : "var(--line)",
background: tinted ? `color-mix(in srgb, ${color} 9%, white)` : row.kind === "miss" ? "var(--panel-2)" : "white",
background: tinted
? `color-mix(in srgb, ${color} 12%, var(--panel))`
: row.kind === "miss"
? "var(--panel-2)"
: "var(--panel)",
}}
>
{full && (
<div
className={`pointer-events-none absolute inset-x-0 z-50 rounded-lg border border-line bg-panel px-3 py-2 text-[14px] leading-snug text-text opacity-0 shadow-[0_8px_24px_rgba(22,21,15,0.14)] transition-opacity duration-150 group-hover:opacity-100 ${
i === 0 ? "top-[calc(100%+6px)]" : "bottom-[calc(100%+6px)]"
}`}
>
{full}
</div>
)}
{row.kind === "miss" ? <MissIcon /> : <DocIcon />}
<div className="min-w-0 flex-1">
{row.kind === "miss" ? (
<>
<div className="truncate text-[14px] text-muted">
<div className="truncate text-[15px] text-muted">
{keyLabel.charAt(0).toUpperCase() + keyLabel.slice(1)}
<span className="ml-2 text-[13px] text-faint">Doesn&apos;t find this detail.</span>
<span className="ml-2 text-[14px] text-faint">Doesn&apos;t find this detail.</span>
</div>
</>
) : (
<div className="truncate text-[14px] text-text">{memoryById[row.id]?.text}</div>
<div className="truncate text-[15px] text-text">{memoryById[row.id]?.text}</div>
)}
</div>
{score !== undefined && tinted && (
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 + i * 0.12 }}
className="tabular shrink-0 text-[14px] font-medium text-jev-ink"
<span
className="fade-up tabular shrink-0 text-[13px] text-jev-ink"
style={{ animationDelay: `${500 + i * 120}ms` }}
>
Relevance {score.toFixed(2)}
</motion.span>
</span>
)}
</motion.div>
</div>
);
})}
</AnimatePresence>
</div>
</div>
);
+60 -46
View File
@@ -1,5 +1,5 @@
"use client";
import { motion } from "motion/react";
import { useState } from "react";
import { METHOD_COLOR } from "@/lib/format";
import { SYSTEMS, type Memory, type SystemId } from "@/lib/types";
@@ -38,6 +38,7 @@ export function ScanField({
elapsedMs: number;
running: boolean;
}) {
const [hover, setHover] = useState<number | null>(null);
const index = Object.fromEntries(memories.map((m, i) => [m.id, i]));
const rows = Math.ceil(memories.length / COLS);
const width = (COLS - 1) * PITCH + CELL;
@@ -53,29 +54,26 @@ export function ScanField({
for (const id of scans[s.id].picks) (pickedBy[id] ??= []).push(s.id);
}
const hovered = hover !== null ? memories[hover] : null;
const hoverPos = hover !== null ? cellXY(hover) : null;
return (
<div className="flex h-full flex-col rounded-2xl border border-line bg-panel px-8 pb-7 pt-6">
<div className="flex h-full flex-col rounded-xl border border-line bg-panel px-8 pb-7 pt-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<span className="text-[21px] text-text">{headline}</span>
<span className="h-2 w-2 rounded-full bg-[#d8dce3]" />
<span className="tabular font-mono text-[24px] font-semibold text-text">{(elapsedMs / 1000).toFixed(2)}s</span>
{running && (
<motion.span
className="h-2 w-2 rounded-full bg-jev"
animate={{ opacity: [1, 0.2, 1] }}
transition={{ repeat: Infinity, duration: 0.9 }}
/>
)}
<span className="text-[22px] text-text">{headline}</span>
<span className="h-1.5 w-1.5 rounded-full bg-faint" />
<span className="tabular text-[23px] text-text">{(elapsedMs / 1000).toFixed(2)}s</span>
{running && <span className="pulse-dot h-1.5 w-1.5 rounded-full bg-jev" />}
</div>
<div className="flex items-center gap-2 text-[14px] text-faint">
<div className="label flex items-center gap-2">
<span className="h-2.5 w-2.5 rounded-[3px] bg-cell" />
Each square is a memory
</div>
</div>
<div className="flex flex-1 items-center justify-center">
<div className="relative" style={{ width, height }}>
<div className="relative" style={{ width, height }} onMouseLeave={() => setHover(null)}>
{/* cells */}
{memories.map((m, i) => {
const { x, y } = cellXY(i);
@@ -83,15 +81,19 @@ export function ScanField({
return (
<div
key={m.id}
title={m.text}
className="absolute rounded-[6px] transition-colors duration-500"
onMouseEnter={() => setHover(i)}
className="absolute rounded-[6px] transition-colors duration-300 hover:brightness-95"
style={{
left: x,
top: y,
width: CELL,
height: CELL,
background: isKey ? "#fff" : "var(--cell)",
boxShadow: isKey ? "0 0 0 2px var(--text)" : undefined,
background: isKey ? "var(--panel)" : "var(--cell)",
boxShadow: isKey
? "0 0 0 2px var(--accent), 0 0 12px color-mix(in srgb, var(--accent) 40%, transparent)"
: hover === i
? "0 0 0 2px var(--faint)"
: undefined,
}}
>
{pickedBy[m.id] && (
@@ -117,9 +119,9 @@ export function ScanField({
const scan = scans[id];
if (scan.status === "idle") return null;
const color = METHOD_COLOR[id];
let pos: { x: number; y: number };
let col = 0;
const sweeping = scan.status === "running";
let pos: { x: number; y: number } | null;
let col = 0;
if (sweeping) {
const p = Math.floor((scan.elapsedMs / 1000) * SWEEP_CELLS_PER_SEC) + START_ROW[id] * COLS;
const cell = p % memories.length;
@@ -127,9 +129,10 @@ export function ScanField({
col = cell % COLS;
} else {
const t = scan.target !== undefined ? index[scan.target] : undefined;
pos = t !== undefined ? cellXY(t) : { x: -999, y: -999 };
pos = t !== undefined ? cellXY(t) : null;
}
// Two scanners can settle on the same memory; nest them so both stay visible.
if (!pos) return null;
// Two scanners can settle on the same memory; nest one inside the other.
const inset = !sweeping && id === "sonnet" && scans.jev.target === scan.target ? -5 : 0;
return (
<div key={id}>
@@ -141,51 +144,62 @@ export function ScanField({
left: Math.max(0, col - TRAIL) * PITCH,
width: Math.min(col, TRAIL) * PITCH + CELL,
height: CELL,
background: `linear-gradient(90deg, transparent, color-mix(in srgb, ${color} 28%, transparent))`,
}}
/>
)}
{pos.x > -999 && (
<motion.div
className="pointer-events-none absolute rounded-[8px] border-[3px]"
initial={false}
animate={{ x: pos.x - 4 + inset, y: pos.y - 4 + inset }}
transition={sweeping ? { duration: 0 } : { type: "spring", stiffness: 140, damping: 18 }}
style={{
width: CELL + 8 - inset * 2,
height: CELL + 8 - inset * 2,
borderColor: color,
background: inset ? "transparent" : `color-mix(in srgb, ${color} 22%, transparent)`,
boxShadow: `0 0 16px color-mix(in srgb, ${color} 45%, transparent)`,
zIndex: id === "jev" ? 3 : 2,
background: `linear-gradient(90deg, transparent, color-mix(in srgb, ${color} 30%, transparent))`,
}}
/>
)}
<div
className="pointer-events-none absolute left-0 top-0 rounded-[8px] border-[3px]"
style={{
transform: `translate3d(${pos.x - 4 + inset}px, ${pos.y - 4 + inset}px, 0)`,
transition: sweeping ? "none" : "transform 0.55s cubic-bezier(0.2, 0.8, 0.2, 1)",
width: CELL + 8 - inset * 2,
height: CELL + 8 - inset * 2,
borderColor: color,
background: inset ? "transparent" : `color-mix(in srgb, ${color} 20%, transparent)`,
boxShadow: `0 0 14px color-mix(in srgb, ${color} 40%, transparent)`,
zIndex: id === "jev" ? 3 : 2,
}}
/>
</div>
);
})}
{/* hover readout */}
{hovered && hoverPos && (
<div
className="pointer-events-none absolute z-20 max-w-[420px] rounded-lg border border-line bg-panel px-3 py-2 text-[14px] leading-snug text-text shadow-[0_8px_24px_rgba(22,21,15,0.14)]"
style={{
left: Math.min(Math.max(hoverPos.x + CELL / 2, 210), width - 210),
top: hoverPos.y > 70 ? hoverPos.y - 12 : hoverPos.y + CELL + 12,
transform: `translate(-50%, ${hoverPos.y > 70 ? "-100%" : "0"})`,
}}
>
{hovered.text}
</div>
)}
{/* the memory that matters, labeled once every method has reported */}
{reveal &&
!hovered &&
(() => {
const i = index[keyIds[0]];
if (i === undefined) return null;
const { x, y } = cellXY(i);
const above = y > 60;
return (
<motion.div
initial={{ opacity: 0, y: above ? 6 : -6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="absolute z-10 whitespace-nowrap rounded-lg bg-text px-3 py-1.5 text-[14px] font-medium text-white shadow-lg"
<div
className="fade-up absolute z-10 whitespace-nowrap rounded-lg border border-accent/30 bg-accent/12 px-3 py-1.5 text-[15px] text-accent shadow-[0_6px_18px_rgba(22,21,15,0.10)]"
style={{
animationDelay: "450ms",
left: Math.min(Math.max(x + CELL / 2, 120), width - 120),
top: above ? y - 46 : y + CELL + 14,
x: "-50%",
marginLeft: "-0.5px",
transform: "translateX(-50%)",
}}
>
{keyLabel.charAt(0).toUpperCase() + keyLabel.slice(1)}
</motion.div>
</div>
);
})()}
</div>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -611,7 +611,7 @@
},
{
"id": "mba",
"text": "User did an MBA at Kellogg."
"text": "User did an MBA a few years after college."
},
{
"id": "languages",
+139 -137
View File
@@ -1,6 +1,22 @@
{
"party_tart": {
"recordedAt": "2026-09-19",
"jev": {
"ids": [
"leo_allergy",
"party_menu",
"tartine_cake"
],
"scores": {
"leo_allergy": 0.93,
"party_menu": 0.77,
"tartine_cake": 0.62
},
"latencyMs": 368.33912499999997,
"costUsd": 0.00043575,
"inputTokens": 10375,
"outputTokens": 4526
},
"embeddings": {
"ids": [
"tartine_cake",
@@ -9,58 +25,24 @@
"sam_bday_nopa",
"party_guests"
],
"latencyMs": 295.44437500000004,
"latencyMs": 193.27591699999994,
"costUsd": 0.00000221,
"inputTokens": 17,
"outputTokens": 0
},
"jev": {
"ids": [
"leo_allergy",
"party_menu",
"tartine_cake",
"card_stolen",
"amex"
],
"scores": {
"card_stolen": 0.54,
"leo_allergy": 0.93,
"party_menu": 0.77,
"tartine_cake": 0.6,
"amex": 0.5
},
"latencyMs": 338.0672919999997,
"costUsd": 0.0004356660000000001,
"inputTokens": 10373,
"outputTokens": 4526
},
"sonnet": {
"ids": [
"party_guests",
"leo_allergy",
"party_menu"
"leo_allergy"
],
"latencyMs": 4706.296709,
"costUsd": 0.017684,
"inputTokens": 7217,
"outputTokens": 325
"latencyMs": 7282.527083999999,
"costUsd": 0.019932,
"inputTokens": 7216,
"outputTokens": 550
}
},
"dad_birthday": {
"recordedAt": "2026-09-19",
"embeddings": {
"ids": [
"dad_70",
"offsite_dinner",
"sam_bday_nopa",
"dad_kokkari",
"party_guests"
],
"latencyMs": 219.18404100000043,
"costUsd": 0.0000036400000000000003,
"inputTokens": 28,
"outputTokens": 0
},
"jev": {
"ids": [
"dad_stent",
@@ -73,36 +55,90 @@
],
"scores": {
"dad_stent": 0.93,
"mom_wheelchair": 0.73,
"mom_wheelchair": 0.71,
"sober": 0.8,
"parents_wc": 0.65,
"dad_kokkari": 0.9,
"dad_kokkari": 0.89,
"sam_bday_nopa": 0.55,
"dad_70": 0.91
"dad_70": 0.9
},
"latencyMs": 321.57887499999924,
"costUsd": 0.00043617,
"inputTokens": 10385,
"latencyMs": 370.2896669999973,
"costUsd": 0.000436254,
"inputTokens": 10387,
"outputTokens": 4526
},
"embeddings": {
"ids": [
"dad_70",
"offsite_dinner",
"sam_bday_nopa",
"dad_kokkari",
"party_guests"
],
"latencyMs": 250.02366699999766,
"costUsd": 0.0000036400000000000003,
"inputTokens": 28,
"outputTokens": 0
},
"sonnet": {
"ids": [
"dad_stent",
"mom_wheelchair",
"dad_kokkari",
"dad_70",
"dad_kokkari",
"parents_wc",
"budget_flag",
"sober",
"parents_wc"
"house_closing",
"sober"
],
"latencyMs": 8792.463083000002,
"costUsd": 0.021112,
"inputTokens": 7236,
"outputTokens": 664
"latencyMs": 10199.006958999998,
"costUsd": 0.02253,
"inputTokens": 7235,
"outputTokens": 806
}
},
"belize_dive": {
"recordedAt": "2026-09-19",
"jev": {
"ids": [
"belize_dates",
"seasick",
"dive_cert",
"sam_anniv",
"pregnant",
"dramamine_brand",
"belize_blue_hole",
"budget_flag",
"biscuit_boarding",
"biscuit_anxiety",
"amex",
"vitamins",
"mom_wheelchair",
"card_stolen",
"hotel_pref"
],
"scores": {
"mom_wheelchair": 0.54,
"pregnant": 0.82,
"biscuit_anxiety": 0.61,
"budget_flag": 0.7,
"card_stolen": 0.51,
"dive_cert": 0.84,
"belize_dates": 0.93,
"seasick": 0.89,
"belize_blue_hole": 0.74,
"dramamine_brand": 0.79,
"sam_anniv": 0.84,
"amex": 0.61,
"hotel_pref": 0.51,
"biscuit_boarding": 0.67,
"vitamins": 0.6
},
"latencyMs": 352.64179100000183,
"costUsd": 0.0004356660000000001,
"inputTokens": 10373,
"outputTokens": 4526
},
"embeddings": {
"ids": [
"belize_dates",
@@ -111,51 +147,11 @@
"maya_visit",
"travel_mexico"
],
"latencyMs": 180.64829199999804,
"latencyMs": 566.6744580000013,
"costUsd": 0.0000018200000000000002,
"inputTokens": 14,
"outputTokens": 0
},
"jev": {
"ids": [
"belize_dates",
"seasick",
"dive_cert",
"pregnant",
"sam_anniv",
"dramamine_brand",
"budget_flag",
"belize_blue_hole",
"biscuit_boarding",
"amex",
"biscuit_anxiety",
"vitamins",
"card_stolen",
"hotel_pref",
"mom_wheelchair"
],
"scores": {
"mom_wheelchair": 0.53,
"pregnant": 0.83,
"biscuit_anxiety": 0.59,
"budget_flag": 0.74,
"card_stolen": 0.57,
"dive_cert": 0.87,
"belize_dates": 0.93,
"seasick": 0.9,
"belize_blue_hole": 0.72,
"dramamine_brand": 0.8,
"sam_anniv": 0.81,
"amex": 0.65,
"hotel_pref": 0.55,
"biscuit_boarding": 0.66,
"vitamins": 0.58
},
"latencyMs": 336.3340840000019,
"costUsd": 0.00043558200000000006,
"inputTokens": 10371,
"outputTokens": 4526
},
"sonnet": {
"ids": [
"belize_dates",
@@ -166,47 +162,17 @@
"belize_blue_hole",
"pregnant",
"budget_flag",
"house_closing",
"card_stolen",
"amex"
],
"latencyMs": 7694.196833000009,
"costUsd": 0.02065,
"inputTokens": 7215,
"outputTokens": 622
"latencyMs": 9425.254125000007,
"costUsd": 0.022908,
"inputTokens": 7214,
"outputTokens": 848
}
},
"tahoe_sunday": {
"recordedAt": "2026-09-19",
"jev": {
"ids": [
"biscuit_anxiety",
"car_shop",
"mom_wheelchair",
"timezone",
"subaru",
"sober",
"tahoe_cabin",
"palisades",
"farmers_market",
"babysitter"
],
"scores": {
"mom_wheelchair": 0.68,
"biscuit_anxiety": 0.81,
"car_shop": 0.72,
"sober": 0.61,
"palisades": 0.55,
"tahoe_cabin": 0.61,
"farmers_market": 0.54,
"subaru": 0.65,
"timezone": 0.68,
"babysitter": 0.51
},
"latencyMs": 296.8981669999921,
"costUsd": 0.000435792,
"inputTokens": 10376,
"outputTokens": 4526
},
"embeddings": {
"ids": [
"farmers_market",
@@ -215,27 +181,63 @@
"belize_dates",
"work_travel"
],
"latencyMs": 365.2027080000116,
"latencyMs": 276.0229580000014,
"costUsd": 0.0000026,
"inputTokens": 20,
"outputTokens": 0
},
"jev": {
"ids": [
"biscuit_anxiety",
"car_shop",
"mom_wheelchair",
"tahoe_cabin",
"timezone",
"sober",
"subaru",
"farmers_market",
"airbnb_pref",
"palisades",
"babysitter",
"budget_flag",
"biscuit_boarding"
],
"scores": {
"mom_wheelchair": 0.73,
"biscuit_anxiety": 0.83,
"budget_flag": 0.51,
"car_shop": 0.78,
"sober": 0.64,
"palisades": 0.52,
"tahoe_cabin": 0.67,
"farmers_market": 0.54,
"subaru": 0.61,
"airbnb_pref": 0.54,
"timezone": 0.66,
"babysitter": 0.52,
"biscuit_boarding": 0.51
},
"latencyMs": 324.19045899999037,
"costUsd": 0.000435876,
"inputTokens": 10378,
"outputTokens": 4526
},
"sonnet": {
"ids": [
"car_shop",
"car_rental",
"budget_flag",
"subaru",
"biscuit_anxiety",
"biscuit_walker",
"biscuit_boarding",
"budget_flag",
"car_rental",
"farmers_market",
"groceries",
"plants"
],
"latencyMs": 17339.568666000006,
"costUsd": 0.030306,
"inputTokens": 7223,
"outputTokens": 1586
"latencyMs": 15543.263249999989,
"costUsd": 0.027834,
"inputTokens": 7222,
"outputTokens": 1339
}
}
}
+14
View File
@@ -4,6 +4,20 @@ const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
devIndicators: false,
async headers() {
return [
{
source: "/:path*",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "no-referrer" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), interest-cohort=()" },
],
},
// API responses carry per-visitor key usage: never cache or store them.
{ source: "/api/:path*", headers: [{ key: "Cache-Control", value: "no-store" }] },
];
},
};
export default nextConfig;