Commit Graph

468 Commits

Author SHA1 Message Date
fixture
4d6550f0d1 privacy: apply the FRE-354 verdict corrections to the /privacy replacement
FRE-354 ruled the page 'verified after corrections — do not apply as it
stands'. This applies all of them, plus the two copy decisions the verdict
delegated to the CMO (fuller iPhone paragraph; full (c) block with the
LAN/tailnet sentence and the matching section-3 clause).

- (b) no macOS username (sendDefaultPii sets the IP only), session pings
  named; still the always-on form: no release tag contains FRE-355's
  opt-in merge (6e74b261; v0.15.0 predates it), so the page stays true on
  the build users run. Contingency swap owed with the first release that
  ships FRE-355.
- (a) device-name sentence corrected (no activations UI exists); fail-open
  scoped to trial/status, activation needs a connection once.
- (c) relay qualified as the default path; relay sees IDs/names/platforms;
  direct LAN/tailnet pairing sentence added.
- (j) 'not a cloud service' instead of two absolutes FRE-322 flagged;
  tailnet path named.
- iPhone companion paragraph added (TestFlight beta is in users' hands,
  same Sentry config, push token disclosed). Push-token evidence row
  recorded for FRE-333's inventory.
- Opening, section heading ('What leaves your devices') and section 3
  ('content', not all traffic) per the verdict; Last Updated bumped.
- Draft evidence table and contingency warning rewritten per the verdict
  (the old warning named a username Sentry never sends).

Suite on this tree: 441 pass / 3 skip / 53 fail / 2 errors — failure and
error sets byte-identical to clean master d44133f (458/3/53/2; the delta
is master's remote-claims.test.ts, 17 passing tests this branch's base
predates). Local only; the push is the sponsor's (card 392a8465).
2026-11-04 14:50:41 +00:00
Paperclip Agent
bb6cf27821 faq: publish the eight objection entries; withdraw the false privacy answer
Sponsor-approved on FRE-414 card 57e1d01e (faq-publish = approve,
2026-10-31T13:19:10Z), per the FRE-334 option A ruling: the eight /faq
entries from the objections draft (faq-draft rev c48396ea, CTO-verified
in FRE-329) ship, and the live privacy answer comes down in the same
commit - it says no prompt or code content leaves the device, which the
FRE-333 egress inventory and FRE-359 verdict B proved false at HEAD.

Same commit carries the /privacy replacement (privacy-page.patch, rev 4),
corrected against the FRE-333 inventory: the page no longer calls the
hardware UUID masked, discloses the relay registration a Weather panel
performs even with Phone off (licence key included on a licensed Mac),
/deactivate, and the one-time trial-email request.

positioning.md needs no change here: master's 2026-10-29 reconciliation
already withdrew rule 3 and the approved-claims privacy row; this branch
precedes it and the human merge brings the reconciled file in.

Suite: bun test on this tree - 458 pass / 3 skip / 53 fail / 2 errors,
failure set identical to clean master d44133f (environmental: rate
limiting, CSRF, IP handling, lineage redirect). No merge by any agent;
deploy waits on the push credential (FRE-414 card 5c0eb58e).
2026-10-31 13:41:55 +00:00
Paperclip Agent
e46c0a2677 marketing: correct the /privacy copy against the FRE-333 inventory
FRE-333's egress inventory landed after this patch was first written, and its
own §G proposed a page text. This is that text corrected against the inventory
it cites, six differences, each forced by a row:

- the opening no longer says prompts are "never sent to us" (sealed frames
  transit our relay; a remote machine carries them by design)
- (d) Weather states that it registers with the relay even with Phone off, and
  that a licensed Mac presents its license key there - SidecarController.weather()
  goes through the same relayCredential(licenseKey:) as the phone path
- (a) adds /deactivate (key + UUID) and the trial email (email + UUID, once)
- (i) stops saying a trial email delivers a license key; it is one reminder
  before the trial ends, the key is issued at checkout
- (b) keeps today's build (on every launch, no off switch, IP + macOS username);
  the opt-in form is the Contingency swap, so the page is true on the build it
  deploys with
- (j) carries the CTO's FRE-322 §2 code fact, with the remote-machine exception;
  he may still cut it

Page file untouched, nothing published. Patch re-checked with git apply --check
and parse-checked with esbuild; the site's own suite is `bun test` and this host
has no bun, so that stays an apply-time obligation. Verification is FRE-354;
the Sentry opt-in is proposed as FRE-355.
2026-10-29 09:13:06 +00:00
CMO
2c917e63e8 marketing: prepare the /privacy replacement as a reviewable patch (not applied)
The live page says the app 'sends only the minimal data required for trial
and licensing' and calls the hardware UUID masked. Against the FRE-333 egress
inventory that is wrong in two directions: it omits Sentry (every launch,
sendDefaultPii = true, no off switch), the relay and its install ID, the
licence key crossing to the relay, sealed phone payloads, weather to the relay
and to Apple, the GeoIP lookups, the speed test, the changelog fetch,
bug-report contents and update checks; and the UUID it sends is raw.

Reorganised by connection, ten reachable rows from the inventory, one
favourable sentence (the FRE-322 §2 hook/bridge fact) that the CTO may still
decline. Sentry paragraph describes today's build with the opt-in swap held in
the rationale file. Page file untouched; the patch applies cleanly to c1c498e.
2026-10-29 09:05:27 +00:00
Paperclip Agent
871e47dee9 faq: mirror the CoS decision into the drafts copy of the plan
Byte-identical to The-Nook drafts/faq-from-objections.md at 2c1915e3. FRE-334
returned option A, so step 2b is done and the publish card has one option, not
three.

Source issue: FRE-326.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:13:46 +00:00
Paperclip Agent
f20389e64e faq: keep the drafts copy of the plan in step with The-Nook
Byte-identical to The-Nook drafts/faq-from-objections.md at b86a980b. The plan now
names the rule-3 patch and FRE-334, the Chief of Staff's withdrawal ask.

Source issue: FRE-326.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:11:35 +00:00
Paperclip Agent
534c1ccd86 marketing: prepare the rule-3 strike as a reviewable patch (not applied)
The CTO verdict on the /faq entries (FRE-329) traced the false privacy answer
to marketing/positioning.md rule 3 and its approved-claims row, and said the
rule has to come down in the same change as the /faq withdrawal or the next
page restates the claim.

This adds the exact patch for that, saved next to the page patch so both go in
as one commit when the gates clear. It is not applied: the withdrawal decision
is the Chief of Staff's (FRE-326 step 2b), and the replacement copy waits on
FRE-333's egress inventory.

It also carries the stale "macOS 14+" row, which NOOK-881 retired: the app
cannot start below 14.2, and the site has said 14.2+ since 134f41e. Rule 3's
new text is corrected against /privacy as it reads today: that page does name
the hardware UUID and the device name, and says nothing about crash reporting
with PII or the relay.

Source issues: FRE-326, FRE-329, FRE-333.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:11:12 +00:00
Paperclip Agent
4a18beeaff faq: apply the CTO verdict to the eight entries
Six entries change text, exactly as FRE-329's replacement sentences: E2 keeps the row
for 15 minutes, E3 names the Login Items toggle and ~/.nook instead of "one entry, no
writes outside the directory", E4 says which terminals take a typed reply, E5 says one
island per enabled display, E6 says it never runs agents in the background, E8 lists the
five user-triggered network uses. E1 and E7 are unchanged; no entry was cut.

The page patch is regenerated from the corrected entry text, so the draft and the patch
cannot drift apart. The gate table now records the verdict (done 2026-10-29) and puts the
privacy withdrawal plus the positioning.md rule-3 strike on the Chief of Staff with a date,
because the verdict found rule 3 is the source of the false claim.

Nothing is applied to the page: src/routes/nook/faq.tsx is untouched on this branch.

Source issue: FRE-326. Verdict: FRE-329.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:08:15 +00:00
Paperclip Agent
3d41d6cd45 faq: make E3 one page paragraph
Keep the draft's paste-ready answer identical to the page patch.

Source issue: FRE-326.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:03:45 +00:00
Paperclip Agent
477bb807fc faq: tighten three claims in the objections draft
E1 self-contained (no /agents completeness promise), E4 drops the held
remote-pane sentence, E8 names crash reporting. Page patch regenerated;
still not applied.

Source issue: FRE-326.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:03:09 +00:00
Paperclip Agent
7ea654069c faq: draft the entries from the objections we have
The draft maps every proposed /faq entry to a live question or a named
NOOK report, withdraws the privacy answer that the current build
contradicts, and holds the questions we cannot answer yet. The page
patch is included but not applied; it waits on the CTO verdict and the
human move to approved/.

Source issue: FRE-326.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-29 06:01:40 +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