Commit Graph

465 Commits

Author SHA1 Message Date
6ff31346ff site: restore the remote SSH copy — HELD, do not merge (FRE-997, remote half)
DO NOT MERGE OR DEPLOY THIS BRANCH. It is the held half of FRE-997, split out
of a336a6c at the Code Review Engineer's changes-requested verdict and the
Chief of Staff's 2026-11-03 launch hold (comment 2ccad154), and it is stacked
on `freno-dev/FRE-997-count-18` so its own diff stays remote-only.

The copy it restores was pulled by FRE-323 (c1c498e, c718370). FRE-997's
argument is that those removals were checked against claims the copy never
made: hosts come from `~/.ssh/config` and Tailscale (`RemoteHostDiscovery`),
the one-time `ssh-copy-id` is a prerequisite the row hands the user as a
command (`RemoteHostStatus.fixCommand`), the per-host credential is minted and
copied over (`RemoteMirror`), and a remote host mirrors this Mac's integrations
(`RemoteMirrorReconcile`). Two things in the old copy were genuinely wrong and
are fixed rather than restored: the "every hooked agent" overstatement (Muse,
Cline and Amp are `.unsupported` for remote and are named) and the count.

It may land only when all of these are recorded on FRE-997:

1. a CTO verdict that the one-click flow was run on the shipping build;
2. footage of a remote agent joining the island;
3. the Chief of Staff revising the FRE-346 §3 launch gate and taking the
   public-claim call to the sponsor.

Until then the live site keeps the FRE-323 state. The restore condition this
commit deletes (`marketing/drafts/remote-ssh/copy.md`) is the gate being cited:
FRE-205 is todo/unassigned, FRE-320 is a verdict task, and no recording exists.

- The Remote collage tile and its mock.
- The "Any SSH box, one click away" breakdown with its RemoteHostDemo.
- The "One-click SSH setup" chip.
- The /agents remote paragraph and the Cline/Amp "this Mac only" rows.
- The approvals bullet names the three local-only agents instead of claiming
  every hooked agent.
- remote-claims.test.ts guards the restored copy plus the two overstatements
  that must not return.
- positioning.md moves remote SSH to publishable with its backing.

Gate on this branch, this run: `bun test src/routes/nook` 17 pass / 0 fail;
`bun run build` succeeds. Green tests do not lift the hold.
2026-11-04 00:30:21 +00:00
25ec84ceb4 site: state the agent count as eighteen (FRE-997, count half)
FRE-359 verdict B dropped the agent count instead of correcting it: the page
said thirteen while `AgentSpec.all` held eighteen, and the /agents table listed
13 of those rows. The count is a fact, not a claim, so this states it and makes
the table match the registry again.

- Landing island bullet and the marquee chip say 18 agents.
- /agents: title/description/ogTitle/H1 at eighteen, and the five missing rows
  are on the page — Kimi, GitHub Copilot CLI, Crush, Cursor, Amp — with events
  and gating taken from hook/setup.go, hook/normalize.go,
  hook/normalize_hosts.go, Sources/NookCore/Agents/HookConfigInstaller.swift
  and plugins/amp/index.ts. All five are Observe (kimi/copilot PreToolUse and
  PermissionRequest fire before the host's own check; Crush documents one
  event; a silent Cursor permission hook blocks; Amp's plugin API has no
  permission-wait event).
- agents.test.ts asserts the roster as an equality with the 18-name registry
  in registry order again, plus the count, instead of a subset check.
- remote-claims.test.ts keeps every FRE-323 remote removal guard and the parked
  copy, and drops only the two completeness bans the corrected count
  contradicts ("every coding agent", "Supported agents") — the ban existed
  because the page claimed the set and listed 13 of 18.
- positioning.md: the coverage row is publishable at 18.

This is the count/roster half of FRE-997 only. The remote-SSH restore is held
by the Chief of Staff against FRE-346 §3 and is not in this commit: no remote
tile, breakdown, demo, chip, /agents remote paragraph or drafts deletion here.

Gate: `bun test src/routes/nook` 18 pass / 0 fail; `bun run build` succeeds.
2026-11-04 00:21:35 +00:00
dd368d21e7 smarter-remote: publish the app's privacy policy and terms of use
A Smarter Remote's iOS paywall sells an auto-renewing subscription, and
App Review 3.1.2 requires it to link a privacy policy and terms of use.
No such pages existed. They now live on the main site at
/smarter-remote/privacy and /smarter-remote/terms, so no new subdomain
or DNS change is needed. The policy describes what the app actually
does: local-network control, pairing data kept on the phone, purchases
through Apple and RevenueCat, and QR sharing phone to phone.

Tests: bun test shows the same 52 pre-existing failures with and
without the pages; tsc reports nothing in the new files.

TVR-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:34:18 -05:00
Paperclip Agent
d44133fcb3 site: guard the removed remote claim and agent count against return (FRE-323, FRE-359) 2026-10-29 14:38:31 +00:00
Freno CMO
578ee58f9c marketing: the remote correction is committed, not deployed
copy.md said the sentences were "removed from the live site" and
rationale.md said "the landing page now says only what the product can back
today". Neither is true: nook.freno.me still serves all of them (HTTP 200,
re-fetched 2026-10-29T14:20Z) because the push is blocked on a write
credential (card 82d1e1b7). Both files now say committed-not-deployed, and
positioning.md's excluded-count row records FRE-359 verdict B and where the
count was removed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 14:22:12 +00:00
Freno CMO
d2c2e6a4ee agents: drop the count and the coverage claim (FRE-359 verdict B)
FRE-359's verdict is B: the page's thirteen is the registry's first thirteen
of eighteen rows, so the count, "every coding agent The Nook supports" and
"Supported agents" all overstate or understate the app. The permitted
sentences are the CTO's, verbatim.

- agents.tsx: head description, ogTitle and H1 paragraph replaced with the
  verdict's sentences; title/H1 "Supported agents" -> "Agent coverage".
- agents.test.ts: the stale NOOK-336 guard asserted "all thirteen" and
  the 13-name roster, so it passed in lockstep with the page. It now asserts
  no count of any form, no completeness phrase, and that the rows are a
  subset of the 18-name registry list (copied 2026-10-29, @ f4258034) in
  registry order. The table still lists 13 rows; regenerating it from the
  registry is its own ticket, and the guard no longer pretends otherwise.
- FeatureBreakdowns.tsx / IslandExtras.tsx: the landing "13 agents:" bullet
  and the "13 agents" marquee chip lose the count; the chip becomes
  "Agents wired in".

bun test src/routes/nook/agents.test.ts: 4 pass. Server security and appcast
suites fail in this checkout on missing env (Environment validation failed /
mainDBConnection), unrelated to these files.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 14:22:11 +00:00
Paperclip Agent
c718370674 /agents: take the remote-session sentence off the page
FRE-323 removed the remote claim from the landing page and missed the second
page that carries it. /agents said 'Agents can run on this Mac or on remote
machines over SSH — remote sessions report into the same grid', which is the
same unverified claim (FRE-315 §1, FRE-320), in front of the same launch
traffic. Removed; the paragraph now ends at 'the agent keeps its own prompting'.

Not deployed: same read-only credential as c1c498e.
2026-10-29 10:20:13 +00:00
Paperclip Agent
210a7e6e75 marketing: reconcile positioning.md with the CTO verdicts and the price decision
The source-of-truth file still sold claims the board has since ruled on:

- 'thirteen agents' as a public coverage count — FRE-322 §1 counts 18 specs,
  9 of which can raise an approval card; no count is publishable (FRE-203 plan-b).
- 'Price rises to 5 at 1.0' — withdrawn by the Chief of Staff on 2026-10-29
  04:01; price stays $10 and is not a launch asset.
- 'macOS 14+' — the app cannot start below 14.2 (881190d, site 134f41e).
- Approved-claims table carried no launch sentence at all, and no row for the
  remote-SSH hold or the banned absolute privacy sentences.
- Pointed at the-nook/docs/marketing-strategy.md, which no longer exists.

Now: the Plan B claim set, a per-claim status table with verdict pointers,
FRE-354's corrections to the hook/bridge sentence and the Sentry disclosure,
the iPhone TestFlight state, and an explicit excluded/banned list.
2026-10-29 10:19:14 +00:00
Paperclip Agent
c1c498e37f site: remove the unproven remote claim from the Nook landing page
FRE-323. The one-click remote install is app-UI-only and needs hand-edited ~/.ssh/config plus a one-time ssh-copy-id (FRE-320); FRE-315 and FRE-322 hold the claim unverified and demo-gated.

- drop the Remote tile and its mock from the feature collage
- drop the "Any SSH box, one click away" breakdown, its demo and bullets
- drop "Works across every hooked agent, local or remote"
- drop the "One-click SSH setup" chip
- move the copy to marketing/drafts/remote-ssh/ and mark remote held in marketing/positioning.md

No replacement claim was added. Deploy waits on a push credential for the GitHub repo Vercel builds.
2026-10-29 05:55:26 +00:00
134f41eac4 Merge branch 'NOOK-881' — the landing page, FAQ and downloads list said macOS 14, but the app cannot start below 14.2: a CoreAudio overlay symbol its mixer uses only exists from 14.2, and the old bundle floor let LaunchServices launch it on machines that could never run it. The FAQ now names the audio tap API rather than the observation framework, and the downloads list's 14.6+ becomes 14.2+. InputHalo and Gaze keep their own floors. 2026-10-23 13:28:47 -04:00
881190d1d4 nook: say macOS 14.2, which is what the app actually needs
The landing page, the FAQ and the downloads list still promised macOS 14. The
app cannot start there: dyld refuses it on 14.0 and 14.1 because a
CoreAudio overlay symbol its mixer uses only exists from 14.2, and the old
floor in its bundle had LaunchServices launching it on machines that could
never run it (NOOK-881).

The FAQ answer named the observation framework as the reason for 14; the
binding reason is the per-app audio tap API, which Apple shipped in 14.2. The
downloads list said 14.6+, which was already stricter than the truth.

InputHalo and Gaze still read 14.6+; their floors are their own and this
change leaves them alone.

Tests: bun test src/routes/nook (2 pass); bunx tsc --noEmit reports nothing in
the three files changed (the checkout's 286 pre-existing errors are elsewhere).

NOOK-881
2026-10-23 13:28:45 -04:00
f492132bf2 Merge branch 'MISC-48' — download tracking across every door 2026-10-22 12:59:50 -04:00
a524f23be2 downloads: track every door and surface them on /analytics
The analytics page could not answer "how many downloads, of which
product". Three of the four download doors recorded nothing: the tRPC
click proxy only left the generic request row, whose path is the raw URL
with the asset buried in `?batch=1&input={"asset_name":...}`, and the S3
file route that serves Sparkle updates and the Nook app logged nothing
at all. The one door that did log (the Nook redirect) kept a private copy
of the logVisit/enrich/flush sequence, under its own path.

All doors now go through trackDownload, which writes one row per event
under /api/downloads/<asset> and flushes it inside the request — a click
is low-volume and serverless is not guaranteed to run the 30s batch
timer after the response. The file route maps its prefix allowlist to
asset keys, so a version bump stays one asset rather than one per
release. getAnalyticsSummary groups those rows into a downloads block and
the freno.me panel renders it under the overview cards.

History is recovered to the degree it exists: the Nook door's rows and
the tRPC clicks still carry enough to derive from (asset name in the URL).
scripts/backfill-downloads.ts inserts those derived rows with a
`backfill-<source id>` primary key and INSERT OR IGNORE, leaving the
originals untouched, so re-running is a no-op. Run against the live table
it added 66 rows: gaze 42, thenook 19, inputhalo 4, lineage 1.

Tests: new backfill suite (derivation, visitor detail carried over,
idempotency, empty history) and summariseDownloads cases; the Nook route
test now pins the canonical path, and the NOOK-337 allowlist audit reads
the map keys instead of quoted strings. Red-checked by stubbing the
downloads block back to zeros — the summary case fails, then passes.
Full suite matches master.

Co-Authored-By: nw-flash <noreply@nw-flash.local>
MISC-48
2026-10-22 12:59:48 -04:00
0bf15e528d Merge branch 'NOOK-565' 2026-10-17 16:11:43 -04:00
a48326fd1d nook: decode hex apostrophe entities in release notes
Settings > Changelog and /changelog showed literal &#x27; wherever a
bullet had an apostrophe: embed_release_notes.py escapes appcast
descriptions with Python's html.escape, which prefers the hex form for
apostrophes, and decodeEntities only knew the decimal &#39; — so the
entity survived into releases.json and was rendered as-is. Add &#x27;
(and &apos;) to the replacement table.

Tests: new sectionsFromHtml case pinning html.escape-style output;
fails without the fix, full suite otherwise matches master.

Co-Authored-By: glm-5.3-flash <noreply@z.ai>
NOOK-565
2026-10-17 16:11:40 -04:00
ac63057ef7 Merge branch 'NOOK-353' — agents page location cleanup 2026-10-14 10:02:09 -04:00
f6d6d442de site: drop path references from the Nook agents page
The Nook's config, hook binary, plugins and state all live under
~/.nook now, so per-agent filesystem paths on the /agents cards were
both stale and repetitive — every plugin is stored canonically under
~/.nook/plugins and only linked out to each agent's own config dir.
The wiring copy now describes the mechanism, not the location.

The card review also surfaced drift against hook/setup.go, plugins/*
and AgentRegistry: OpenClaw listed AgentResponse, an event renamed to
Stop in NOOK-298, and several cards missed events their integrations
actually emit (SessionError on halted sessions, AgentStart on pi/omp,
PermissionRequest/Resolved on omp, QuestionAsked on hermes, and
Codex's SessionEnd/PreToolUse/PostToolUse). Codex's gating now notes
its user-input questions are answered from a card too.

Tests: bun test (505 pass, 3 skip, 1 pre-existing queryAuditLogs
failure identical to master); tsc --noEmit diagnostics identical to
master.

NOOK-353

Co-Authored-By: kimi-k3-flex <noreply@freno.me>
2026-10-14 10:01:52 -04:00
14a61fde9b Merge branch 'NOOK-336' — NOOK-336 Muse on the agents page 2026-10-13 23:23:44 -04:00
b4e68765fe nook: list Muse on the agents page
nook.freno.me/agents listed twelve agents and said "twelve" in its meta
description, OG title and intro, while the app's registry (AgentSpec.all)
gained Muse in 0.10.0 as its thirteenth. The page mirrors that registry
by hand from another repo, so nothing flagged the drift.

Adds the Muse card from its plugin manifest and registry row: the plugin
body at ~/.nook/plugins/muse installed through Muse's own plugins CLI,
its fourteen hook events, approvals answerable from a card (every tool
call under Verify), and this Mac only since remote hosts skip it. The
positioning claims table and copy move to thirteen with it.

Tests: src/routes/nook/agents.test.ts pins the roster to the registry's
names in order (red on master: Muse missing) and every stated count to
the number of entries (red when the intro says twelve again).

NOOK-336

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:23:43 -04:00
dabfeada3b Merge branch 'NOOK-334' — NOOK-334 landing page feature set 2026-10-13 23:07:14 -04:00
9508aee8e1 nook: show the full feature set on the landing page
The landing page sold eight features from around 0.6 and summed up the
rest in one line, while 0.6–0.10 shipped most of the island's modules. It
also said twelve agents (Muse made thirteen) and marked phone approvals
"coming soon" although the iPhone companion is in TestFlight beta.

Adds live demos for terminal replies, the volume mixer, Capture with its
Wings, and floating actions; a bento of module tiles behind a two-way
feature marquee; and an iPhone/Watch beta section whose request form
posts the same email-only report the app's Settings > Phone card sends.
The approval flow, remote sessions and setup, timer, temperatures and
Capture mocks are rebuilt from the app's own views, so the site shows
what the island actually draws.

Tests: report.test.ts pins the landing page's beta request against the
/report schema (red when the schema demands a machine snapshot); nook and
subdomain suites pass.

NOOK-334

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:07:10 -04:00
1a182e53fe downloads: serve Nook delta files the Sparkle updater fetches
The v0.10.0 publish stopped at the enclosure check: the appcast names
Nook17-9.delta / Nook17-10.delta, and the proxy 400'd both with
'Invalid file format' while the TheNook DMG passed. Sparkle names delta
files after the app bundle, not the DMG, so the prefix allowlist needs
'Nook' alongside 'TheNook'. Both deltas sat in S3 the whole time; the
appcast was never published and the live one still serves 0.9.0.

Adds a static source-audit regression test in the repo's downloads.test
pattern; proven red against the unfixed allowlist.

Tests: bun test src/routes/api/downloads/filename.test.ts

NOOK-337

Co-Authored-By: glm-5.3-flash <noreply@neuralwatt.com>
2026-10-13 22:17:27 -04:00
6898186df5 nessa: retire the API copy now served by nessa-api.freno.me
The Nessa API (nessaDb.*, nessa.community.*, Clerk auth and the Clerk user
webhook) lives in the Nessa repo's server/ on pan. This copy never served
the app: RemoteAPIClient wraps every input in {"input": …}, which the stock
tRPC adapter here reads as the input itself (queries 404, writes no-op).
Clerk's webhook already targets nessa-api.freno.me.

Drops the routers, nessa-auth, the webhook, NessaConnectionFactory, the
NESSA_* env and the now-unused @clerk/backend and svix. Nessa's marketing,
privacy and deletion pages stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:54:14 -04:00
9ff095a82c nook: fill release notes the appcast lost from the archive
0.8.0 and 0.7.0 rendered as a version header over an empty row on
/changelog and in releases.json, the feed the macOS app's Changelog pane
renders. Older still, 0.6.0/0.6.2/0.6.3 were gone from the page outright.

Both come from one gap. The appcast is the only live source and it is not a
store: it holds just the DMGs the release machine keeps, and
generate_appcast drops the description of any rebuilt item whose DMG ships
no notes file of its own — so a release it still lists can arrive with
nothing to show, and one it has aged out of vanishes. The checked-in
archive was meant to cover that, but it stopped at 0.5.0, and mergeReleases
skipped an archived version whenever the appcast mentioned it, so it could
never heal a row that existed and was blank.

The archive now carries every release that shipped notes, 0.9.0 down to
0.2.0, and withArchivedNotes fills an item only where the feed has nothing,
leaving a published description alone. /changelog and releases.json run the
same rule rather than two copies, so the page and the feed cannot disagree
about what a release said. 0.6.1 and 0.3.1 stay absent by design — they
shipped with no notes written.

Tests: bun test src/routes/api/the-nook/ (51 pass; the 4 new cases fail on
this branch's parent); bun test unchanged at 49 failures, all in
src/server/security, which needs an .env a worktree does not carry.
Proved end to end against the live appcast as published by the 0.9.0
release: 11 releases, all with sections.

NOOK-305

Co-Authored-By: qwen3.8-flash <noreply@freno.me>
2026-10-13 00:19:05 -04:00
be90b3d068 nook release changelog api available 2026-10-11 10:19:49 -04:00
cbe56390f8 remove build numbers 2026-10-11 08:26:33 -04:00
26c37c1862 simplified 2026-10-11 08:25:29 -04:00
7108f77bf9 pvp: make the blood token ledger server-authoritative
The PvP_Characters table tallied wins and losses but held no token
balance and no purchase endpoint: earnings were client-reported
(winners credited themselves locally), so verifying only a spend
would have been security theater — a cheater could mint tokens by
self-reporting battle wins.

The server is now the ledger authority (FRE-142): registerCharacter
seeds the balance from the client cache exactly once and reconciles
it on every sync; battleResult requires the verified Lineage JWT
bearer, rejects non-participants, and credits the winner via the
reward formula ported from the client (computed from both registered
rows); a new purchase mutation checks cost, balance, and cooldown in
one conditional UPDATE scoped to the caller-owned row, so the spend
is atomic under concurrency and rate limiting survives cold starts.
Schema changes ship as an idempotent pvp-schema bootstrap
(pvpTokens, ownerUserId, lastPurchaseAt), mirroring the
analytics-schema pattern.

Client-side counterpart shipped in the lineage repo; design record
in its docs/security/pvp-server-side-purchases.md.

Tests: pvp-schema (DDL + legacy-column migration + idempotency),
pvp-rewards (formula parity with the client port, cost table),
pvp-ledger (seed-once reconciliation, owner binding, participant
verification, atomic spend, cooldown, refusal reasons) — 31 new tests
against in-memory SQLite; suite 572 pass (1 pre-existing audit
date-filter failure, unrelated).
2026-10-06 10:56:29 -04:00
687df016a6 nook: defer the SSR stream until the appcast resolves on /changelog
Every cold origin render of /changelog died with ERR_HTTP_HEADERS_SENT:
the pending createAsync at the top of NookChangelogPage suspended the
whole component before <EdgeCacheHeaders> rendered, the shell flushed
while the S3 appcast fetch was in flight, and the boundary re-render
then called setResponseHeader on an already-sent response. Warm renders
hid it because the appcast's 5-minute module cache resolved in a
microtask; fresh Vercel isolates and cache expiry hit the crash, which
is why refreshes and direct navigation failed while in-app Links worked.

deferStream holds the stream until the releases resolve, so HttpHeader
always runs pre-flush. The S3 latency lands on origin renders only,
which the page's CDN-Cache-Control s-maxage=300 already keeps rare.

Tests: local prod-build cold-start loop (build + curl /nook/changelog),
red 3/3 before, green 5/5 after; stack identical to the prod trace.

Fixes FRE-112
2026-10-05 12:40:44 -04:00
92af52dfc9 better changelog capture 2026-10-05 11:05:24 -04:00
53f1413966 nook: render the full release history on /changelog
The appcast retains only recent items, so releases before 0.6.0 had no
surface. The five older sections (0.5.0 back to the first widely
distributed 0.2.0) were recovered from the changelog file's git
history (The-Nook 1ad8a69 / db165cd), converted to the same HTML the
embed step produces, and checked in as an archive the page merges
behind the appcast items — appcast stays authoritative, archive fills
the gaps. 0.3.1 and 0.6.1 shipped without written notes and are absent
by design. Also fixes the accordion snapping 0.6.3 back open when all
sections were collapsed: after any click the state is a version or an
explicit none sentinel, never the initial first-open default.

Tests: tsc clean; dev render shows all eight releases with notes in
the DOM.

Issue: FRE-13
2026-10-05 09:51:08 -04:00
6f41436591 nook: add an appcast upload helper
The release pipeline had no in-repo path to put a corrected appcast
back on S3 (the backfill of 0.6.0/0.6.2 notes needed one). Reads the
same bucket env the appcast route does; refuses to upload unless every
expected release item is present.

Tests: used for the production backfill; endpoint re-verified after.
2026-10-05 09:43:11 -04:00
0b128f6639 nook: claim lifecycle sends so concurrent runs cannot double-mail
The lifecycle job marked rows with an unconditional UPDATE before
sending, so two overlapping invocations (cron tick + manual hit, or
retry) both passed the SELECT, both ran the UPDATE — re-writing the
same value — and both sent the same email. Real duplicates went out to
licensees. Rows are now claimed with a conditional UPDATE (... AND
asked_at IS NULL), and the send happens only when the claim reports
rowsAffected 1; a racing invocation claims nothing and sends nothing.

Tests: lifecycle suite gains a lost-claim case; 36 nook API tests pass.

Issue: FRE-11
2026-10-05 09:38:48 -04:00
37a39b59fe nook: say when a release carries no notes
Releases that shipped before notes were embedded in the appcast
(0.6.2 and earlier) have empty descriptions, so expanding their row
rendered a blank expanse that read as broken. Show a muted "no notes
recorded" line instead; the appcast embed step now runs every release,
so history accumulates from here on.

Tests: vinxi build clean; tsc clean.

Issue: FRE-13
2026-10-05 09:32:22 -04:00
ba6b478320 nook: collapse changelog history into a fixed-height scroller
A long appcast made the page grow with every release and pushed
historical versions far below the fold. Versions now collapse to a
header row inside a fixed-height vertical panel (max-h 70vh): expanding
one never shifts the page, and history is browsed by scrolling inside
it. The newest release starts open; notes stay mounted and toggle via
hidden rather than unmounting, so crawlers still see every entry.

Tests: vinxi build clean; tsc clean.

Issue: FRE-13
2026-10-05 09:27:30 -04:00
35986239c1 nook: dispatch /agents, /changelog and /faq at the top level
The new subdomain pages 404ed in production: the vercel.json host
rewrites do not fire — SSR sees /agents, never /nook/agents — so pages
that only exist under src/routes/nook/* are unreachable from the
subdomain. Every pre-existing subdomain surface survives on top-level
host-dispatch routes (index, privacy, checkout, deletion), which is why
only the new pages broke. Give the three pages the same dispatch,
falling back to 404 on other sites.

Tests: full bun test run unchanged (539 pass); vinxi build clean.

Issue: FRE-12
2026-10-04 23:33:11 -04:00
b41cf83879 marketing: add the scaffolding and positioning source of truth
One voice, one place, so agent-drafted assets stop inventing framing
per artifact. positioning.md carries the wedge, the category-noun rule
(freno-dev/AGENTS.md competitor ban), claims each backed by code, the
honest privacy scoping and the voice/banned-phrasings list. The tree
adds calendar.json, per-channel briefs, drafts → pending → approved →
posted, metrics and listening — with the hard rule up front: approved/
is the only thing a human ever publishes from, nothing posts itself.

Issue: FRE-17
2026-10-04 23:22:28 -04:00
f66fd43f62 nook: capture trial email and run the two lifecycle emails
Trials were anonymous — a 14-day trial you cannot talk to. trials gains
an email column (idempotent ALTER), POST /api/the-nook/trial-email
attaches the optional first-launch prompt's answer, and the address
lands in a Brevo list when NOOK_TRIAL_LIST_ID is set. The lifecycle
emails follow: a D+11 trial expiry reminder with a buy link, a purchase
thank-you from the Stripe webhook, and scheduled testimonial (D+3) and
review (D+14) asks via the daily /api/the-nook/lifecycle cron. Rows are
marked before sending so retries never double-send; trial addresses
that already bought get no buy reminder; the endpoint requires a
Bearer secret (CRON_SECRET / NOOK_LIFECYCLE_SECRET) and fails closed
when unconfigured — an open trigger would email real users. All sends
are best-effort and logged, never thrown.

Tests: trial-email (3) and lifecycle (6) suites; full run 539 pass.

Issues: FRE-10, FRE-11
2026-10-04 23:22:24 -04:00
421fb946fc nook: add /agents, /changelog and /faq
Three organic surfaces, each answering an objection at the moment it
bites. /agents lists all twelve agents with each hook's exact event
surface (mirrors AgentSpec.all + hook/setup.go + the plugins) and what
a card can do — allow/deny or observe. /changelog renders the appcast
HTML the Sparkle updater already publishes, so release notes have one
source of truth. /faq answers privacy (scoped honestly: no prompt or
code content leaves the device), the notch question, battery,
permissions, the macOS 14 floor and offline use — no refund question,
per decision: the trial is the answer. Nav gains the three links.

Tests: appcast parser suite (5 tests) covers CDATA, escaped HTML,
Version-prefix titles and garbage input.

Issues: FRE-12, FRE-13, FRE-14
2026-10-04 23:22:09 -04:00
dc959d971b nook: fix the landing title, isolate /blog, grow the sitemap
The landing page spent its strongest on-page signal on the word "Home";
the title now carries the positioning line and the OG title carries the
full brand phrase. /blog on a product subdomain served the personal blog
in dev (vinxi ignores the vercel.json rewrites that protect production),
splitting the brand and crawl equity — subdomain hits now render the
shared 404. /agents, /changelog and /faq join the sitemap registry so
they are indexed as they land.

Tests: bun test (539 pass); vinxi build clean, no new tsc errors.

Issues: FRE-8, FRE-15, FRE-9
2026-10-04 23:22:04 -04:00
44bf21a868 fix pause error 2026-10-04 08:48:32 -04:00
dd63da30c4 og image 2026-10-03 15:01:23 -04:00
2fb5097b85 update package name 2026-10-03 11:04:37 -04:00
96e401ecbb nook marketing - utm, analytics, etc 2026-10-03 11:03:26 -04:00
3fe3516b9e nook helper 2026-09-22 22:22:13 -04:00
bef8f8414b utilization reduction 2026-09-22 16:38:43 -04:00
f3e9cdf0bd fix sentry + note email delivery 2026-09-06 13:53:49 -04:00
7ecc0f68d7 increase width cap 2026-09-06 01:08:52 -04:00
00a26a9e49 resized+compressed 2026-09-06 01:06:58 -04:00
163c68fcb8 new with temp demo 2026-09-05 20:02:10 -04:00