+
+
+
+ );
+}
+
/* Deterministic usage wander for the traces. */
const wander = (n: number, k: number) =>
0.42 + 0.22 * Math.sin(n * 0.45 + k * 1.7) + 0.12 * Math.sin(n * 1.3 + k);
@@ -233,6 +258,18 @@ export default function FeatureCollage() {
+
+
+
+
+
+
+
+
diff --git a/src/components/nook/MoreDemos.tsx b/src/components/nook/MoreDemos.tsx
index 261c6d5..7f98a0a 100644
--- a/src/components/nook/MoreDemos.tsx
+++ b/src/components/nook/MoreDemos.tsx
@@ -10,11 +10,11 @@ import {
CARD_STROKE
} from "./IslandMock";
import { useDemoLoop } from "./useDemoLoop";
-import { Glyph, WingCapsule, type GlyphName } from "./ModuleMocks";
+import { Glyph, WingCapsule, SessionRowMock, CODEX, type GlyphName } from "./ModuleMocks";
/**
* Live demos for the breakdowns added with the 0.6–0.10 feature set:
- * terminal replies, Capture (module + Wings), and
+ * terminal replies, remote setup, Capture (module + Wings), and
* floating actions. Same ink chrome and status palette as the originals.
*/
@@ -152,6 +152,109 @@ export function TerminalReplyDemo() {
);
}
+/** RemoteMirror's progress steps, in order. */
+const SETUP_STEPS = [
+ "connecting",
+ "pushing the hook binary",
+ "configuring agents",
+ "checking the host can reach this Mac"
+];
+
+export function RemoteHostDemo() {
+ /* -1 not set up · 0…3 setting up · 4 live */
+ const [step, setStep] = createSignal(-1);
+ const [pressed, setPressed] = createSignal(false);
+ useDemoLoop(11000, [
+ [0, () => (setStep(-1), setPressed(false))],
+ [1400, () => setPressed(true)],
+ [1700, () => (setPressed(false), setStep(0))],
+ [2700, () => setStep(1)],
+ [3700, () => setStep(2)],
+ [4700, () => setStep(3)],
+ [5800, () => setStep(4)]
+ ]);
+ const live = () => step() === 4;
+ const working = () => step() >= 0 && step() < 4;
+ const chip = () =>
+ live()
+ ? { color: STATUS.ready, label: "Live" }
+ : working()
+ ? { color: "rgba(255,255,255,0.55)", label: "Setting up" }
+ : { color: "rgba(255,255,255,0.35)", label: "Not set up" };
+
+ return (
+
+ {/* the host's card in Settings ▸ Agents ▸ Remote hosts */}
+
+
+
+
+
gpu-rig
+
tailnet peer
+
+
+
+
+
+
+ {chip().label}
+
+
+ Set up
+
+ }
+ >
+
+
+ Remove
+
+
+
+
+ {working() ? SETUP_STEPS[step()] : " "}
+
+
+
+
+ {/* The host name stays visible in the session badge. */}
+
+
+
+
+
+ );
+}
+
/* ── Capture: the module, and the Wings while a recording runs ────── */
/** CapturePalette.recording and the NookPalette paper/ink pair. */
diff --git a/src/routes/nook/remote-claims.test.ts b/src/routes/nook/remote-claims.test.ts
index f15f6bb..9f51d55 100644
--- a/src/routes/nook/remote-claims.test.ts
+++ b/src/routes/nook/remote-claims.test.ts
@@ -1,39 +1,15 @@
/**
- * Regression test for the removed remote / SSH claims and the removed agent
- * count (FRE-323, FRE-359 verdict B).
- *
- * 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.
- *
- * 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.
- *
- * 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.
+ * NOOK-1038 restores remote marketing for Tailscale and authorized known hosts,
+ * per Mike's correction. FRE-323's blanket removal is no longer the contract;
+ * one-click setup is a supported scoped claim, not a promise for any SSH box.
+ * FRE-359's separate decision against agent coverage counts is unchanged.
+ * Static source audit, like agents.test.ts; browser checks cover rendered layout.
*/
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",
@@ -43,58 +19,74 @@ 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 count, no coverage claim, in any form. */
-const BANNED_COVERAGE = [
- "all thirteen",
- "thirteen coding agents",
- "13 agents",
- "every coding agent",
- "Supported 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")
.replace(/\/\*[\s\S]*?\*\//g, "")
.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(/ {
+ 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("");
+ });
+
+ 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 or agent-coverage 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");
});
});
@@ -102,9 +94,14 @@ describe("agent coverage count stays off the page (FRE-359 verdict B)", () => {
for (const file of AUDITED) {
it(`${file} states no coverage count`, () => {
const copy = copyOf(file);
- for (const phrase of BANNED_COVERAGE) {
+ for (const phrase of [
+ "all thirteen",
+ "thirteen coding agents",
+ "13 agents",
+ "every coding agent",
+ "Supported agents"
+ ])
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
- }
});
}