Rebuild the landing page around what the product actually does - #7
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds
site/and reconciles every claim on it with the code, adds themissing 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 apolicy, 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.
site/publicholds a logoand a favicon and nothing else, so a page selling "see your own past" opened
with nothing to look at.
answer with the moment it came from, draft the standup note).
without being asked, and it existed on the page only as a line of JSON.
Nine blocks become the hero plus five. The old
Rewind/Search/Skills/Summarysections and the WebGLblackhole/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:
AFTERRAY_MAX_UNSTARRED_MOMENTSDEFAULT_STORAGE_LIMIT_BYTESis 100 GB andenforce_retentionevicts oldest-first withWHERE m.is_favorite = 0RecallView.swiftzoom range is0.4...5against a 0.12 pt/s base — about 12.5x. Claim removedSLOT_DURATION_MSis 30 minuteskeyint=12; the shipping default is 30, so quote the doc's 7–10% rangeexcluded_domainslanded on main, so apps and sitesAlso softened "the key lives only in the macOS Keychain" — there is a
file-keychain fallback for Developer ID helpers.
Agent skill
SKILL.mdknew the original seven read commands but notslotorchat. Addsslot day/slot history(a whole-day question is one call instead of a spansearch) and
chat send --conversationfor follow-up turns, and extends thedo-not-run list to
chat delete,slot summarize,slot backfill.Deploy
wrangler.jsonc+npm run deploy, and a GitHub Actions workflow so pushes tomain 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/_headerscaches/assets/*forever (Vite fingerprints those) but keepsthe 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) andCLOUDFLARE_ACCOUNT_ID.Known gaps
#download. README says V0 is unpackaged andsource-build only, so there is nothing to link yet.
<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.xmland a/zhURL are all still open.🤖 Generated with Claude Code