Skip to content

Rebuild the landing page around what the product actually does - #7

Merged
zxch3n merged 30 commits into
mainfrom
lody/d3e959a4-3b7
Aug 15, 2026
Merged

Rebuild the landing page around what the product actually does#7
zxch3n merged 30 commits into
mainfrom
lody/d3e959a4-3b7

Conversation

@zxch3n

@zxch3n zxch3n commented Aug 15, 2026

Copy link
Copy Markdown
Member

Rebuilds site/ and reconciles every claim on it with the code, adds the
missing agent-skill commands, and gets the page deployed to Cloudflare Pages.

Live now at https://afterray.com (Direct Upload project afterray).

The page

The old page sold the architecture. Total recall. Zero upload. states a
policy, privacy took the two largest type moments and repeated itself across
six places, and "you can replay the actual audio" appeared once, inside a body
paragraph. There were also zero named scenarios anywhere.

Reordered around what a reader gets back, and around the value that actually
justifies the capture: an agent that holds your context.

  • Remember / everything. up top, with the controls — exclude, pause, delete —
    right underneath rather than four screens later.
  • The interactive recall demo moves into the hero. site/public holds a logo
    and a favicon and nothing else, so a page selling "see your own past" opened
    with nothing to look at.
  • Agents becomes the second block, so the promise cashes out immediately.
  • A three-up of things the agent can now do (finish a half-done migration,
    answer with the moment it came from, draft the standup note).
  • New Memories section. Half-hour summaries are the one feature that pays off
    without being asked, and it existed on the page only as a line of JSON.
  • CLI and Agents merge — both said "read-only, vault key stays in the daemon".
  • Privacy, the pipeline, and the spec table fold into one closer.

Nine blocks become the hero plus five. The old Rewind / Search / Skills /
Summary sections and the WebGL blackhole/ component are removed with them.

The search mock was rebuilt too: it showed two bare text fragments, one of them
an env var name, with nothing to suggest a hit is a replayable moment. It is now
a causal chain — a vague phrase, two hits with a stamp of the captured window and
the matched phrase marked in place, and an answer citing those same two moments.

Claims audited against the code

Seven were wrong or stale. Each fix cites what it was checked against:

Claim Reality
retention "ceiling" / AFTERRAY_MAX_UNSTARRED_MOMENTS the constant no longer exists; DEFAULT_STORAGE_LIMIT_BYTES is 100 GB and enforce_retention evicts oldest-first with WHERE m.is_favorite = 0
"zoom from one second out to a month" RecallView.swift zoom range is 0.4...5 against a 0.12 pt/s base — about 12.5x. Claim removed
summaries "hour by hour" SLOT_DURATION_MS is 30 minutes
"7% of the original JPEG" measured at keyint=12; the shipping default is 30, so quote the doc's 7–10% range
"exclude any app" excluded_domains landed on main, so apps and sites
models "or your own Ollama" MLX packs and an OpenAI-compatible endpoint are both selectable
"Upload: None" contradicted the above; now says nothing leaves unless you point it at a remote model

Also softened "the key lives only in the macOS Keychain" — there is a
file-keychain fallback for Developer ID helpers.

Agent skill

SKILL.md knew the original seven read commands but not slot or chat. Adds
slot day / slot history (a whole-day question is one call instead of a span
search) and chat send --conversation for follow-up turns, and extends the
do-not-run list to chat delete, slot summarize, slot backfill.

Deploy

wrangler.jsonc + npm run deploy, and a GitHub Actions workflow so pushes to
main publish while other refs get preview URLs. Cloudflare does not allow
converting a Direct Upload project to Git integration, and the domain is already
attached, so CI runs wrangler rather than recreating the project.

public/_headers caches /assets/* forever (Vite fingerprints those) but keeps
the unhashed PNGs revalidatable — otherwise an og.png fix takes a year to
propagate.

Also a real 1200x630 social card, rendered from the site's own type, replacing
the 512x512 app icon on a summary card.

Two repository secrets are needed before the workflow can run:
CLOUDFLARE_API_TOKEN (Account · Cloudflare Pages · Edit) and
CLOUDFLARE_ACCOUNT_ID.

Known gaps

  • The Download CTA still points at #download. README says V0 is unpackaged and
    source-build only, so there is nothing to link yet.
  • The page is client-rendered, so the raw HTML body is an empty <div id="root">.
    A crawler fed the live URL comes back with the title and nothing else — no
    requirements, no install path, no mention of the CLI or the skill. Prerendering,
    robots.txt, sitemap.xml and a /zh URL are all still open.

🤖 Generated with Claude Code

zxch3n and others added 30 commits August 14, 2026 20:22
A drop-in Agent Skill so Claude Code, Codex, and anything else that
reads Agent Skills can query this Mac's history through the read-only
CLI — no MCP server and no credentials. Documents the search / moment /
evidence / activity / memories / ask surface, requires --json, and
requires answers to cite clock time and app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Switch the page's primary voice to English (title, description, and the
en copy in i18n) and reorder the page so Recall lands before Privacy —
the interactive timeline is the strongest asset and now opens the body.

Rewrite the Agents section around a one-line skill install instead of
the in-app CLI toggle, rebuild the Recall stage and the search and CLI
mocks, and rework the stylesheet around the shared token scale.

Accessibility: add a skip link and a #main target, give the language
toggle a localized aria-label and an explicit type, and drop the
redundant alt text on the decorative logo. Point the GitHub CTA at the
real repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page sold the plumbing. "Total recall. Zero upload." states a policy,
and the 40-word sub spent two clauses on mechanism before saying what the
reader gets. Privacy then took the two largest type moments and repeated
itself across six places, while "you can replay the actual audio" appeared
once, inside a body paragraph.

Reorder around what people actually get back:

- Hero leads with fidelity — "Not a summary. / The moment itself." — and a
  fact strip under the CTAs absorbs the privacy claims that were scattered
  across five sections.
- Add a three-up jobs-to-be-done strip under the hero. The page previously
  named zero concrete scenarios.
- Add a Memories section. Folding the day into episodes and writing a line
  about each is the one feature that pays off without being asked, and it
  only existed on the page as a line of JSON in a CLI mock.
- Move the Recall headline above the stage, so the reader knows what they
  are dragging before they touch it.
- Merge the CLI and Agents sections. Both said "read-only, vault key stays
  in the daemon, JSON out"; they are one story and now share one row.
- Fold Privacy, the pipeline, and the spec table into a single closer.
  Privacy is the reason it is safe to say yes, not the reason to care.

Nine blocks become six.

Correct three claims that outran the code: drop "no exceptions" (an
OpenAI-compatible endpoint is a supported opt-in), replace the invented
"10/20 GB retention tiers" with the shipped behaviour (a bounded ceiling
for non-favorites, stars exempt), and stop implying a packaged free app.
Add the measured storage number from docs/hot-stills-cold-gop.md — cold
captures repack to 7% of the original JPEG — which had no place on the
site at all.

Also add Open Graph and Twitter card meta, correct the static html lang to
match the English default, give the zh recall demo the same twelve
timeline segments as en, and drop the CSS left dead by the merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The search panel showed two bare text fragments, one of them an env var
name, with no sign of what a hit actually is. Nothing looked replayable,
so the section's own promise — citations you can tap — was invisible. The
two panels were also driven by separate state: a free-text input on top, a
pair of preset buttons below, with no visible relationship between the
evidence and the answer.

Rebuild it as one causal chain. A vague phrase goes in, two hits come back
as moments — a postage-stamp of the captured window, the app and the time,
and the phrase the query landed on marked in place — and the answer below
cites those same two moments. One query now drives both panels, so the
citations cannot drift from the evidence.

Replace OCR / Transcript with the labels the app itself uses for evidence
layers, "On screen" and "Heard", which are also the ones a first-time
reader can parse. Rewrite the three example queries as things a person
would actually half-remember rather than feature names.

Extract the app glyphs into src/appIcons.tsx, now shared by the recall
stage and the search frames.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prose listed the whole CLI surface, then three bullets repeated the
same command names that the chips beside them already show, then the note
repeated the read-only claim the prose had just made. Three passes at one
idea.

Keep the idea once: install the skill, any agent can query your history.
Drop the bullets — the chips are the list. Halve the body and the note.

Shorten every sample output to a single record and drop the score field,
so the JSON stops wrapping. Terminals now scroll instead of breaking
tokens mid-word, which is what was mangling the longer lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Not a summary. The moment itself." was a negation that needed a
competitor to push against, and the page names none. With nothing to
contrast, a first-time reader gets no referent: not a summary of what,
and which moment?

Replace it with something a reader can see themselves doing, and put the
part nobody expects — that the recording has sound — on the turn:

  Watch last Tuesday.
  Hear it, too.

Drop "the actual pixels and audio" from the sub. Pixels reads as
engineering to everyone outside the trade; screen and audio is what the
thing actually is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Watch last Tuesday" demonstrated a capability nobody actually wants —
re-watching a whole day is a party trick, not a need. It answered "what
can this do" while the reader was still asking "why would I want it".

Lead with the thing people already fail at every day: they saw it, they
were told it, and they cannot get back to it.

  Find anything you've
  ever seen or heard.

"Ever" carries the scale, and "or heard" covers the half of the capture
that a screenshot tool cannot reach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Retrieval was the wrong job to lead with. A person digging through their
own history is doing work; the payoff is small and they have to want it
badly enough to look. The value is that their agent holds the context —
it was in the room, so it can act without being briefed.

  Stop briefing your agent.
  It was there.

Move the Agents section to the second block so the promise cashes out
immediately, and reorder the nav to match. Recall, memories, and search
now read as evidence for how the agent knows, rather than as the pitch.

Rewrite the three-up under the hero as things the agent can now do —
finish a half-done migration, answer with the moment it came from, draft
the standup note from the week — instead of restating features the later
sections already cover.

Shrink the hero type: a full sentence per line does not fit at 5.8rem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six rewrites of the headline did not fix "this isn't compelling", because
the words were never the problem. The hero was entirely typographic — a
sentence, two buttons, and a gradient — on a page selling the ability to
see your own past. site/public holds a logo and a favicon and nothing
else, so there was no product anywhere above the fold.

The interactive recall demo already existed; it was sitting four screens
down. Move it into the hero, where a visitor can drag the timeline and
watch the captured window change within seconds of landing.

That makes the standalone Recall section redundant, so it goes: the stage
becomes components/RecallStage, its copy survives as the caption under
the demo, and its unused headline strings are deleted rather than left
dangling. Nine blocks are now five plus the hero.

Also rewrite the hero headline in a plain register. Every prior version
was setup-then-reversal, which is ad-copy scaffolding and reads as a
pitch no matter what the words are:

  Your Mac remembers everything,
  for you and for your agent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It pointed at #download, which is the section it sits above — a control
that scrolled you to a CTA rather than doing anything. The hero and the
closer both already carry a real download button, so the nav only needs
the language toggle.

Remove the now-dead nav.download strings and the .btn-small styles it
was the only user of.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The toggle was hardcoded to two states: it said 中文 or EN and flipped
between them, so a third locale would have meant rebuilding the control.

Replace it with a globe button that opens the list of locales, each
labelled in its own language. The list renders from LANGS in i18n, so
adding a language is one entry there plus a copy block — no component
change.

The menu is a real menu: aria-haspopup and aria-expanded on the trigger,
menuitemradio with aria-checked on the options, dismissal on outside
press and on Escape, with focus returning to the trigger either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Swap the hand-drawn globe for Lucide's "languages" icon. A globe reads as
region or network as often as it reads as language; the 文/A translate
mark is what people already recognise for this control.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your Mac remembers everything" is a metaphor. A Mac does not remember,
and the sentence gave a reader no way to work out whether this is a note
app, a backup, a search tool, or something else. The one fact that makes
the rest parse — that it is continuously recording — appeared nowhere in
the hero.

State it literally instead:

  A searchable recording
  of everything you see and hear.

"See and hear" is also the accurate scope: the capture spec forbids
keystrokes, key codes, mouse paths, and click contents, so "everything
you do" would have been both wrong and needlessly alarming.

The sub now leads with the mechanism — captures your screen and the audio
around it, all day, kept on this Mac — before listing what you can do
with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Short enough to be a slogan rather than a sentence to parse, and the
promise is the whole product. Pairing it immediately with the controls
answers the reaction it provokes — records everything? — in the same
breath rather than four screens later.

  Remember
  everything.

The sub now carries the mechanism, the payoff, and the escape hatch:
records screen and audio all day, replay or search or hand to an agent,
and exclude any app you would rather it did not see. The fact strip
trades "no account, no telemetry" for "pause or delete anytime", which
is the reassurance a reader actually wants here.

Scope note: the copy says apps, not sites. Capture exclusion is
per-bundle-id (excluded_bundle_ids); there is no URL or domain filter in
the daemon, so promising site-level exclusion would have been a claim
the code cannot honour.

With the headline down to two words the type goes back up to 6.4rem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was a three-line paragraph doing six jobs at once — what it records,
replay, search, agents, app exclusion, and local-only — set in 1.05rem at
55% opacity. Nobody reads that.

Keep one sentence: what it records, and who can find it again. The
control and the guarantee move down to the fact strip, which already
exists for exactly this and reads as scannable chips rather than prose.

Also make the paragraph legible on its own terms: 1.15rem on a 31rem
measure at 72% opacity instead of 1.05rem on 34rem at 55%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The captured window was absolutely positioned at top:40% with a
translate, so a tall capture — Zoom, with four tiles — grew upward until
its title bar sat under the floating search pill. Nothing constrained it.

Lay the stage out instead of stacking it: the chrome stays pinned top and
bottom, and padding reserves those lanes so the content area cannot reach
them. The capture and the transcript are a two-column grid inside it, and
a tall capture clips rather than pushes.

The audio was also invisible. A pull-quote sat inside the mock Zoom
window, where it read as part of Zoom's UI rather than as something
AfterRay captured. Give it its own panel, labelled with the app's own
"Heard", carrying the three transcript lines nearest the playhead with
the current one lit. Dragging now moves the screen and the speech
together, which is the actual claim in the headline.

Transcript is one continuous track for the day rather than per-app,
because that is how audio capture works — the mic and system audio do not
stop when you switch windows.

Also fade the timeline edges so the sliding track looks cut rather than
broken, and drop .aw-quote, whose only user was the quote that moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The panel picked the three transcript lines nearest the playhead with no
distance bound, so parking on the 9:24am Xcode capture showed lines from
the midday meeting as if they had been heard at that moment. It also
implied AfterRay produces speech for every span, including solo work.

Scope the panel to the Zoom span under the playhead: no call, no panel,
and lines can never be borrowed from a different call. Every transcript
entry now sits inside one of those spans.

The stage drops to a single centred column when there is nothing to
transcribe, so the capture is not left beside an empty box.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things shrank it. The single-column rule capped the window at 30rem
inside an ~880px stage, and the mocks were thin — five bars of code, three
lines of notes — so the window collapsed to its content and left most of
the frame dark.

Widen the lone capture to 41rem, and pin the window body to a fixed 244px
so every app fills the same lane and switching capture no longer resizes
the stage. Fill the mocks out to match: eleven lines of code, eight of
page text, seven of notes and PR body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checked against a real render this time instead of reasoning about the
CSS, which is how the previous rounds kept missing.

The stage was capped at 940px inside a 1200px page column, so the first
screen read narrow no matter what the copy said. Give it the same column
as the headline, so its left edge lines up with the H1.

The real bug: `margin-inline: auto` on the lone capture. Auto margins make
a grid item shrink to its content rather than fill its track, so the
window collapsed to the width of its skeleton bars — a sliver adrift in a
1136px stage. Use justify-self with an explicit width instead.

Also, the Zoom capture's 2x2 tile grid overflowed the fixed body height
and clipped mid-row. Lay the participants out as a gallery strip with the
shared screen beneath, which fits and reads more like the real thing.
Panels now stretch to equal height, transcript shows four lines instead
of three, and non-current lines go from 40% to 55% opacity so they read
as speech rather than noise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every trimmed line was restating something the reader could already see.

The hero caption described the demo directly above it — the screen as it
was, the audio as it sounded — while the demo was showing exactly that.
Two sentences become one, keeping only the two facts the mock cannot
convey: the hotkey, and the zoom range.

The privacy lede was the four pillars beneath it rewritten as prose, and
then "0 bytes leave your Mac" said it a third time. Drop the lede; the
pillars carry the specifics.

The search body restated its own bullet list. The memories body described
the mock beside it, and one of its three bullets repeated the body's own
"hour by hour". The closing sub repeated the hero fact strip verbatim.

With the prose gone, 8rem section padding left the page floating, so
sections tighten to 6rem. Removed the strings and the styles that lost
their last user rather than leaving them dangling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
At the full 1200px text column the stage read as a full-bleed band across
the page. 1040px frames it as a panel instead, and the caption tucks to
the panel's edge where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	site/src/i18n.tsx
Audited the copy line by line against the merged tree. Seven claims were
wrong or stale.

Retention is a byte budget, not a count. AFTERRAY_MAX_UNSTARRED_MOMENTS
no longer exists anywhere; DEFAULT_STORAGE_LIMIT_BYTES is 100 GB, set in
Settings → General → Storage, and enforce_retention evicts oldest-first
with `WHERE m.is_favorite = 0`. The demo now says storage budget too.

The timeline does not zoom "from one second out to a month" — the range
is 0.4...5 against a 0.12 pt/s base, about 12.5x. Claim removed.

Summaries are half-hour slots, not hourly: SLOT_DURATION_MS is 30 min.
Body and the mock's spans corrected.

7% of the original JPEG was measured at keyint=12 while the shipping
default is 30, so quote the doc's honest 7–10% range instead.

Domain exclusion landed on main, so the fact strip can say apps and
sites rather than apps alone.

MLX packs and an OpenAI-compatible endpoint are both selectable now, so
the models pillar and spec row list them, and the upload row no longer
implies nothing can ever leave — it can, if you point it at a remote
model yourself.

The vault key has a file-keychain fallback for Developer ID helpers, so
"lives only in the macOS Keychain" softens to "lives in".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On a page this short the three links were decoration, and one was worse
than that: "Recall" pointed at #features, which is the hero demo already
filling the screen on load. Clicking it went nowhere.

The hero's secondary CTA had the same target and the same problem; point
it at #memories, which is an actual destination.

Nav is now the wordmark and the language switcher. Removed the copy keys
and the .nav-links styles that lost their last user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three items on space-between across a 1200px row left the tagline
floating with no relationship to either side, in a third type treatment.
The tagline explains the name, so pair them: wordmark and tagline
together on the left, copyright alone on the right.

"Local-first · Private by design" went with it — the first is the hero
eyebrow and the second is the section directly above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CLI grew a `chat` surface and a `slot` surface; the skill still only
knew the original seven read commands.

Add the two that change how an agent should work:

- `slot day` / `slot history` for whole-day questions. The daemon has
  already summarised each half-hour slot, so asking for a day is one call
  instead of searching the span moment by moment.
- `chat send --conversation` for follow-up turns, since `ask` is one-shot.

Extend the do-not-run list to match what those surfaces expose: `chat
delete` destroys a conversation, and `slot summarize` / `slot backfill`
spend model time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The preview was falling back to the 512x512 app icon on a summary card,
and og:image was relative, which some crawlers skip outright. Render a
proper 1200x630 card from the site's own type and palette, and point
every social tag at https://afterray.com/og.png so the card renders
large. Adds canonical and og:url now that the domain is known.

Pages deployment is config-driven: wrangler.jsonc names the project and
the build output, and `npm run deploy` builds then deploys.

public/_headers caches /assets/* forever, since Vite fingerprints those,
but keeps the unhashed public/ PNGs on a one-hour TTL — og.png otherwise
takes a year to propagate a fix. Plus nosniff, DENY framing, and a
same-origin-ish referrer policy.

Also correct the social description's "exclude any app" to match the
shipped app-and-domain exclusion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Pages project is Direct Upload, and Cloudflare does not allow
converting one to Git integration — the alternative it documents is
running wrangler from CI, which is what this does. Nothing about the
existing project or the live afterray.com domain has to change.

Pushes to main publish; every other ref gets a preview URL, since
--branch is what decides the environment and the project's production
branch is main. Only site/ triggers a run — the Rust and Swift trees
change far more often and have nothing to do with the marketing page.

Pin the build to the Node the site is developed against; the Pages and
Actions defaults both drift, and Vite 6 will not run on an old one.

Needs two repository secrets: CLOUDFLARE_API_TOKEN (Account · Cloudflare
Pages · Edit) and CLOUDFLARE_ACCOUNT_ID.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloudflare will build and deploy the site itself once the project is
connected to this repo, so running wrangler from Actions is redundant.

Keeping site/.node-version: Cloudflare's build image reads it too, and
its default drifts, which Vite 6 will not tolerate. `npm run deploy`
stays as a manual escape hatch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zxch3n
zxch3n merged commit b34afb5 into main Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant