forked from Mike/freno-dev
FRE-1695: reconcile the GitHub deploy line into Gitea master
Production builds GitHub `mikefreno/freno-dev`; agents merge into Gitea `Mike/freno-dev`. Around merge-basedd368d2the two masters diverged: Gitea was 12 commits ahead (FRE-371, FRE-366, FRE-202, FRE-997, FRE-1670), GitHub 2 ahead (NOOK-1037 landing refresh, NOOK-1038 scoped-remote restore). Every Gitea merge sincedd368d2was therefore undeployed. This merge brings the GitHub line into Gitea so the two become one line, with the Gitea master as the fast-forward ancestor of GitHub master. No content is dropped from either side. Conflicts and how they were resolved (only two, both copy contracts): - `src/components/nook/FeatureBreakdowns.tsx`: took NOOK-1038's refreshed island bullets and kept FRE-997's "18 agents" bullet, because the roster on /agents lists all eighteen (agents.test.ts asserts equality). - `src/routes/nook/remote-claims.test.ts`: rewrote as one file that holds both contracts — NOOK-1038 supersedes FRE-323's blanket remote hold (scoped one-click setup for Tailscale and authorized known hosts is a supported claim; blanket promises stay banned), and FRE-997's coverage correction stands (18 is publishable, stale thirteens stay banned). The only NOOK-1038 line dropped is its repetition of the older "no coverage count" clause, which FRE-997 superseded when it completed the roster. `marketing/positioning.md` already records both rows side by side. Tests: bun test src/routes/nook -> 38 pass, 0 fail. Full suite matches the pre-merge baseline failure set (see docs/deploy-source.md).
This commit is contained in:
@@ -59,9 +59,9 @@ export default function NookLanding() {
|
||||
<PageHead
|
||||
title="Agent control for your Mac"
|
||||
ogTitle="The Nook — agent control for your Mac"
|
||||
description="The Nook — a native macOS utility for coding-agent orchestration, fan and thermal control."
|
||||
description="Monitor coding agents, respond to permission requests, and manage your Mac\'s cooling, battery, and audio with The Nook."
|
||||
ogImage={`https://${site().domain}/nook/og.png`}
|
||||
ogDescription="A one-time-purchase macOS app for coding-agent orchestration, fan control, and thermal insight."
|
||||
ogDescription="A macOS app for coding agents, fan control, battery charging, and per-app audio. Includes a 14-day trial and a one-time license."
|
||||
/>
|
||||
|
||||
<SubdomainHeader />
|
||||
@@ -96,11 +96,11 @@ export default function NookLanding() {
|
||||
The Nook
|
||||
</div>
|
||||
<h1 class="text-text mb-4 text-4xl font-bold tracking-tight sm:text-5xl">
|
||||
Your Mac and Agents, under control
|
||||
Manage your coding agents and your Mac
|
||||
</h1>
|
||||
<p class="text-subtext0 mb-2 max-w-xl text-xl">
|
||||
Agent orchestration, fan and thermal control in a beautiful native
|
||||
UI.
|
||||
Follow your agents, respond to requests, and manage cooling,
|
||||
charging, and audio from the top of your screen.
|
||||
</p>
|
||||
<p class="text-subtext1 mb-8 text-sm">
|
||||
macOS 14.2+ · 14-day free trial · up to 3 devices
|
||||
@@ -145,7 +145,7 @@ export default function NookLanding() {
|
||||
In action
|
||||
</p>
|
||||
<h2 class="text-text mb-16 text-center text-3xl font-bold">
|
||||
Built for the way you actually run agents
|
||||
What you can do with The Nook
|
||||
</h2>
|
||||
<FeatureBreakdowns />
|
||||
</div>
|
||||
@@ -164,10 +164,9 @@ export default function NookLanding() {
|
||||
Try it free for 14 days
|
||||
</h2>
|
||||
<p class="text-subtext0 mx-auto mb-10 max-w-2xl leading-relaxed">
|
||||
Orchestrate your coding agents, take control of your fans and
|
||||
battery, give every app its own volume, capture your screen, and
|
||||
keep your day in the notch. When the trial ends, unlock everything
|
||||
with a single one-time payment — no subscription, ever.
|
||||
The trial includes every module. After 14 days, a one-time
|
||||
purchase lets you keep using The Nook on up to three of your Macs.
|
||||
There is no subscription.
|
||||
</p>
|
||||
<div class="flex flex-col items-center justify-center gap-4 sm:flex-row sm:space-x-4">
|
||||
<Button
|
||||
@@ -187,7 +186,7 @@ export default function NookLanding() {
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-subtext1 mt-6 text-xs">
|
||||
Beta pricing — full price will be $15. One license covers up to 3 of
|
||||
The beta price is $10. The regular price will be $15. One license covers up to 3 of
|
||||
your own Macs.
|
||||
</p>
|
||||
</div>
|
||||
@@ -229,8 +228,7 @@ export default function NookLanding() {
|
||||
</form>
|
||||
<Show when={resendSent()}>
|
||||
<p class="text-subtext1 mt-4 text-center text-sm">
|
||||
Check your inbox — if a license is registered to that email, your
|
||||
key is on its way.
|
||||
If a license is registered to that email, we'll send you the key.
|
||||
</p>
|
||||
</Show>
|
||||
<Show when={resendError()}>
|
||||
|
||||
78
src/routes/nook/landing.test.ts
Normal file
78
src/routes/nook/landing.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const root = join(import.meta.dir, "../../..");
|
||||
const read = (path: string) => readFileSync(join(root, path), "utf8");
|
||||
const landingCopy = [
|
||||
"src/routes/nook/index.tsx",
|
||||
...[
|
||||
"FeatureCollage",
|
||||
"FeatureBreakdowns",
|
||||
"IslandExtras",
|
||||
"PhoneBeta",
|
||||
"MoreDemos",
|
||||
"UtilityDemos"
|
||||
].map((name) => `src/components/nook/${name}.tsx`)
|
||||
]
|
||||
.map(read)
|
||||
.join("\n");
|
||||
|
||||
// Like remote-claims.test.ts, this pins an editorial decision, not a tone score.
|
||||
describe("Nook landing copy (NOOK-1037)", () => {
|
||||
for (const phrase of [
|
||||
"Your fleet, in your pocket",
|
||||
"Timers that stack",
|
||||
"Volume and brightness, under the notch",
|
||||
"The sky, in the slot",
|
||||
"Storage, measured",
|
||||
"Wall power, on your terms",
|
||||
"Fans that answer to you, not the scheduler",
|
||||
"Your whole fleet, in one pixel strip",
|
||||
"A hearth that tells the truth",
|
||||
"A lens that stays asleep",
|
||||
"Your buttons, orbiting the island",
|
||||
"Silence it or max it",
|
||||
"Sleep when you say",
|
||||
"Panels you shape"
|
||||
]) {
|
||||
it(`replaces “${phrase}”`, () => {
|
||||
expect(landingCopy).not.toContain(phrase);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Source anatomy checks supplement the rendered browser checks.
|
||||
const demos = read("src/components/nook/UtilityDemos.tsx");
|
||||
|
||||
describe("current utility demo anatomy (NOOK-1037)", () => {
|
||||
it("shows the three calendar slot states and calendar-colored agenda rows", () => {
|
||||
for (const text of ["ON AIR", "Work", "Thu, Nov 12", "Join meeting"])
|
||||
expect(demos).toContain(text);
|
||||
expect(demos).toMatch(/>\s*IN\s*</);
|
||||
});
|
||||
|
||||
it("shows the power circuit and current battery controls", () => {
|
||||
for (const text of [
|
||||
"System draw",
|
||||
"Adapter",
|
||||
"Mac",
|
||||
"Overheat protection",
|
||||
"Charges past the limit; off on unplug"
|
||||
])
|
||||
expect(demos).toContain(text);
|
||||
expect(demos).not.toContain("the pack stays young longer");
|
||||
});
|
||||
|
||||
it("shows master routing, app percentages, pin and hide controls", () => {
|
||||
for (const text of [
|
||||
"System sounds",
|
||||
"Microphone",
|
||||
"Output volume",
|
||||
"Pin app",
|
||||
"Hide app",
|
||||
"Default"
|
||||
])
|
||||
expect(demos).toContain(text);
|
||||
});
|
||||
});
|
||||
@@ -1,49 +1,32 @@
|
||||
/**
|
||||
* Regression test for the removed remote / SSH claims and the removed agent
|
||||
* count (FRE-323, FRE-359 verdict B).
|
||||
* Two page contracts live in this file, and they are reconciled on purpose.
|
||||
*
|
||||
* FRE-323 took the remote story off the site because it was unverified and
|
||||
* demo-gated (FRE-315, FRE-320, FRE-322 §3 correction 4): the "one click
|
||||
* installs the nook-hook" flow is app-UI-only, needs a hand-edited
|
||||
* `~/.ssh/config` for any host not already in ssh config or on the tailnet,
|
||||
* needs a one-time `ssh-copy-id` because the app runs ssh with
|
||||
* `BatchMode=yes`, and has no manual fallback for minting the per-host
|
||||
* credential. FRE-359 verdict B removed the coverage count for the same
|
||||
* reason: the /agents table lists 13 of the registry's 18 rows.
|
||||
* NOOK-1038 (2026-11-09, Mike's correction) supersedes FRE-323's blanket remote
|
||||
* hold: one-click setup is a supported scoped claim for Tailscale peers and
|
||||
* authorized known hosts, so the Remote cards and the setup demo belong on the
|
||||
* page. Blanket authorization promises stay out.
|
||||
*
|
||||
* FRE-997 corrects the second half without touching the first. The count was
|
||||
* stale, not unverifiable: `AgentSpec.all` holds eighteen, /agents now lists
|
||||
* all eighteen and `agents.test.ts` asserts that roster as an equality again.
|
||||
* So the two completeness phrasings FRE-359 banned ("every coding agent",
|
||||
* "Supported agents") no longer rest on a 13-of-18 gap — the ban existed
|
||||
* because the page claimed the set and listed two thirds of it. They are out
|
||||
* of `BANNED_COVERAGE` on this branch for that reason alone; the stale counts
|
||||
* stay banned, and every FRE-323 remote sentence stays banned, because this
|
||||
* branch still carries the FRE-323 site state.
|
||||
* FRE-997 (2026-11-04) restored the agent count. The page listed thirteen of the
|
||||
* registry's eighteen rows, so FRE-359 verdict B dropped the count instead of
|
||||
* correcting it; FRE-997 put all eighteen rows on /agents and made the roster an
|
||||
* equality check again (`agents.test.ts`), so the count is publishable at 18 and
|
||||
* the stale counts stay banned.
|
||||
*
|
||||
* The copy was not thrown away — it is parked in
|
||||
* `marketing/drafts/remote-ssh/` with the restore gate, and this test asserts
|
||||
* it is still there so nobody deletes the parked story as stale. It goes back
|
||||
* on the site only when the one-click install ships and is recorded.
|
||||
* The two lines met in the FRE-1695 reconciliation (Gitea was 12 commits ahead,
|
||||
* GitHub 2). NOOK-1038's file repeated FRE-359's older "no coverage count"
|
||||
* clause; that clause is superseded by FRE-997's landed correction and is the
|
||||
* one line dropped here. Everything else from NOOK-1038 is taken verbatim, and
|
||||
* `marketing/positioning.md` records both decisions side by side (coverage row:
|
||||
* publishable at 18; remote row: approved, scoped). See `docs/deploy-source.md`.
|
||||
*
|
||||
* STATIC SOURCE AUDIT, the same pattern as `agents.test.ts` and
|
||||
* `downloads.test.ts` (SolidJS does not render under `bun:test`).
|
||||
*
|
||||
* Deliberate exceptions, not audited here — both were weighed with the Chief
|
||||
* of Staff at the go/no-go and left in place on purpose:
|
||||
* - `src/routes/nook/agents.tsx` Muse row: "This Mac only — remote hosts skip
|
||||
* it" is a limitation disclosure, not a capability claim.
|
||||
* - `src/components/nook/IslandExtras.tsx` "Tailscale hosts" chip: adjacent to
|
||||
* the removed remote story, but tailnet host lookup does exist. Raised on
|
||||
* FRE-323 rather than widened into this correction.
|
||||
*/
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const ROOT = join(import.meta.dir, "../../..");
|
||||
|
||||
/** Shipped page and component source. Tests and drafts may quote the old copy. */
|
||||
const AUDITED = [
|
||||
"src/routes/nook/index.tsx",
|
||||
"src/routes/nook/agents.tsx",
|
||||
@@ -53,29 +36,6 @@ const AUDITED = [
|
||||
"src/components/nook/IslandExtras.tsx"
|
||||
];
|
||||
|
||||
/** Every sentence FRE-323 ordered off the site. */
|
||||
const BANNED_REMOTE_CLAIMS = [
|
||||
"Agents on other machines",
|
||||
"one click installs the nook-hook",
|
||||
"Any SSH box",
|
||||
"Works across every hooked agent, local or remote",
|
||||
"Remote sessions are ordinary rows",
|
||||
"One-click SSH setup",
|
||||
"remote machines over SSH",
|
||||
"this Mac or remote machines"
|
||||
];
|
||||
|
||||
/** FRE-359 verdict B: no stale count, in any form.
|
||||
*
|
||||
* "every coding agent" and "Supported agents" left this list with FRE-997:
|
||||
* the page now lists all eighteen registry rows and `agents.test.ts` asserts
|
||||
* that equality, so the completeness claim is backed rather than incomplete. */
|
||||
const BANNED_COVERAGE = [
|
||||
"all thirteen",
|
||||
"thirteen coding agents",
|
||||
"13 agents"
|
||||
];
|
||||
|
||||
/** User-facing copy only: comments are not copy and may name the old wording. */
|
||||
function copyOf(file: string): string {
|
||||
return readFileSync(join(ROOT, file), "utf8")
|
||||
@@ -83,44 +43,83 @@ function copyOf(file: string): string {
|
||||
.replace(/^\s*\/\/.*$/gm, "");
|
||||
}
|
||||
|
||||
describe("remote claims stay off the site (FRE-323)", () => {
|
||||
describe("remote marketing reflects the supported setup (NOOK-1038)", () => {
|
||||
it("fills the collage's missing sixth grid slot with host-labelled remote sessions", () => {
|
||||
const collage = copyOf("src/components/nook/FeatureCollage.tsx");
|
||||
// The hero spans two columns; four regular cards fill the other slots.
|
||||
expect(collage.match(/<CollageCell\b/g)).toHaveLength(4);
|
||||
expect(collage).toContain('kicker="Remote"');
|
||||
expect(collage).toContain("One-click setup for Tailscale and authorized known hosts");
|
||||
expect(collage).toContain('host="build-box"');
|
||||
expect(collage).toContain('host="gpu-rig"');
|
||||
});
|
||||
|
||||
it("restores the detailed setup section with both discovery sources", () => {
|
||||
const breakdowns = copyOf("src/components/nook/FeatureBreakdowns.tsx");
|
||||
expect(breakdowns).toContain('kicker="Remote"');
|
||||
expect(breakdowns).toContain('title="Set up remote agents in one click"');
|
||||
expect(breakdowns).toContain("Tailscale peer or an authorized host from your SSH config");
|
||||
expect(breakdowns).toContain("<RemoteHostDemo />");
|
||||
});
|
||||
|
||||
it("demonstrates setup progressing to Live and a host-labelled session", () => {
|
||||
const demos = copyOf("src/components/nook/MoreDemos.tsx");
|
||||
for (const label of [
|
||||
"connecting",
|
||||
"pushing the hook binary",
|
||||
"configuring agents",
|
||||
"checking the host can reach this Mac",
|
||||
"Live",
|
||||
"Set up"
|
||||
])
|
||||
expect(demos).toContain(label);
|
||||
expect(demos).toContain('host="gpu-rig"');
|
||||
});
|
||||
|
||||
for (const file of AUDITED) {
|
||||
it(`${file} does not restore a removed remote sentence`, () => {
|
||||
const copy = copyOf(file);
|
||||
for (const phrase of BANNED_REMOTE_CLAIMS) {
|
||||
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
|
||||
}
|
||||
it(`${file} makes no blanket authorization promise`, () => {
|
||||
const copy = copyOf(file).toLowerCase();
|
||||
for (const phrase of [
|
||||
"Any SSH box",
|
||||
"Works across every hooked agent, local or remote",
|
||||
"no SSH authorization needed"
|
||||
])
|
||||
expect(copy).not.toContain(phrase.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
it("/agents no longer offers remote sessions", () => {
|
||||
const copy = copyOf("src/routes/nook/agents.tsx");
|
||||
expect(copy).not.toContain("remote sessions report into the same grid");
|
||||
expect(copy).not.toContain("on this Mac or on remote");
|
||||
});
|
||||
|
||||
it("the parked copy still exists, so restoring the story is a revert and not a rewrite", () => {
|
||||
const parked = readFileSync(join(ROOT, "marketing/drafts/remote-ssh/copy.md"), "utf8");
|
||||
for (const phrase of ["Agents on other machines", "Any SSH box, one click away"]) {
|
||||
expect(parked).toContain(phrase);
|
||||
it("records the current decision without deleting the historical copy", () => {
|
||||
for (const file of [
|
||||
"marketing/drafts/remote-ssh/copy.md",
|
||||
"marketing/drafts/remote-ssh/rationale.md",
|
||||
"marketing/positioning.md"
|
||||
]) {
|
||||
const text = readFileSync(join(ROOT, file), "utf8");
|
||||
expect(text).toContain("NOOK-1038");
|
||||
expect(text).toContain("Tailscale and authorized known hosts");
|
||||
}
|
||||
expect(readFileSync(join(ROOT, "marketing/drafts/remote-ssh/rationale.md"), "utf8")).toContain(
|
||||
"FRE-320"
|
||||
);
|
||||
expect(
|
||||
readFileSync(join(ROOT, "marketing/drafts/remote-ssh/copy.md"), "utf8")
|
||||
).toContain("Any SSH box, one click away");
|
||||
});
|
||||
});
|
||||
|
||||
describe("the agent count is corrected, not dropped (FRE-997)", () => {
|
||||
/** FRE-359 verdict B, minus the phrasings FRE-997 re-grounded in the full roster. */
|
||||
const BANNED_COVERAGE = ["all thirteen", "thirteen coding agents", "13 agents"];
|
||||
|
||||
for (const file of AUDITED) {
|
||||
it(`${file} states no stale count`, () => {
|
||||
const copy = copyOf(file);
|
||||
for (const phrase of BANNED_COVERAGE) {
|
||||
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
|
||||
}
|
||||
const copy = copyOf(file).toLowerCase();
|
||||
for (const phrase of BANNED_COVERAGE) expect(copy).not.toContain(phrase);
|
||||
});
|
||||
}
|
||||
|
||||
it("/agents states the registry's size", () => {
|
||||
expect(copyOf("src/routes/nook/agents.tsx")).toContain("Supported agents");
|
||||
});
|
||||
|
||||
it("the landing copy states the count the roster actually has", () => {
|
||||
expect(copyOf("src/components/nook/FeatureBreakdowns.tsx")).toContain("18 agents");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user