site: restore scoped one-click remote setup
[gpt-6.1-sol] A blanket restriction on remote claims hid the working setup flow for Tailscale and authorized known hosts and left the opening grid unbalanced. Restore the host-labelled session card and setup demo, and make the copy safeguards reflect the supported scope rather than deny the feature. Tests: 4 restoration checks fail without the fix; 38 Nook tests pass; bun run build; Playwright at 1440/768/375px in both themes NOOK-1038
This commit is contained in:
@@ -1,13 +1,8 @@
|
|||||||
# Remote SSH copy — HELD, do not publish
|
# Remote SSH copy — historical unqualified wording
|
||||||
|
|
||||||
**Status:** removed from the `freno-dev` source on 2026-10-29 in the FRE-323
|
[gpt-6.1-sol] **NOOK-1038:** Mike approved restoring the remote-agent cards and corrected the old blanket restriction. One-click setup works for **Tailscale and authorized known hosts**. The current page describes that scope; this file preserves the old unqualified wording for reference, not for verbatim reuse.
|
||||||
correction, and **not yet off the live site** — the deploy is blocked on a write
|
|
||||||
credential to `mikefreno/freno-dev` (card `82d1e1b7`). `nook.freno.me` still
|
[gpt-6.1-sol] The earlier FRE-323 removal treated SSH authorization needed by a new host as though it prevented one-click setup for hosts already authorized. Current evidence is `RemoteHostCatalog.discover`, `RemoteHostsController.setUp`, `RemoteMirror.apply`, and the existing `RemoteHostsControllerLiveTests.setUpLeavesItLive` test. No new live-host test run is claimed by this website change.
|
||||||
serves every sentence below until that push or a Vercel deploy lands.
|
|
||||||
**Owner:** CMO. **Verified by:** CTO, on the FRE-320/FRE-205 work.
|
|
||||||
**Restore only when:** FRE-320/FRE-205 ship a genuinely one-click install, the CTO
|
|
||||||
verifies that flow, and a recording shows a remote agent joining the island.
|
|
||||||
Until then this file is the only home for these sentences.
|
|
||||||
|
|
||||||
## What was removed
|
## What was removed
|
||||||
|
|
||||||
@@ -62,18 +57,8 @@ Source: `src/components/nook/IslandExtras.tsx` (removed 2026-10-29).
|
|||||||
One-click SSH setup
|
One-click SSH setup
|
||||||
```
|
```
|
||||||
|
|
||||||
## What replaced it
|
## Current restoration
|
||||||
|
|
||||||
Nothing. The Remote tile and section were removed rather than replaced; the
|
[gpt-6.1-sol] NOOK-1038 restores the collage's Remote tile, host-labelled session examples, and the detailed setup section. The collage says: “One-click setup for Tailscale and authorized known hosts.” The detailed section shows choosing a discovered host, clicking Set up, installation progress, and Live status.
|
||||||
existing Approvals cell already carries the verified local claim:
|
|
||||||
|
|
||||||
> Approve an agent's tool call from your Mac's notch, and the agent carries on.
|
[gpt-6.1-sol] The historical “Any SSH box” claim, universal agent-coverage bullet, and unqualified setup chip above are not restored. Discovery and an existing authorization path are not the same as granting access to a new machine. This does not limit or deny the working one-click flow for authorized hosts.
|
||||||
|
|
||||||
## Restore checklist
|
|
||||||
|
|
||||||
1. FRE-320/FRE-205 have shipped the one-click install and removed the
|
|
||||||
`~/.ssh/config` / `ssh-copy-id` friction described in FRE-320.
|
|
||||||
2. The CTO has verified the shipping flow and the evidence is on FRE-320.
|
|
||||||
3. A recording exists showing a remote agent joining the island.
|
|
||||||
4. CMO restores the copy above, then owns the launch plan for it — with an
|
|
||||||
owner and date per item — before anything goes back on the site.
|
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
# Why the remote claim is parked
|
# Remote setup marketing decision
|
||||||
|
|
||||||
The site was selling a setup path a buyer does not get.
|
[gpt-6.1-sol] **NOOK-1038 supersedes the blanket hold.** Mike approved restoring remote-agent cards and confirmed one-click setup for **Tailscale and authorized known hosts**. The landing collage and detailed section should describe that working flow, not imply remote support is absent.
|
||||||
|
|
||||||
FRE-320 measured today's actual path (2026-10-29): the one-click flow is
|
## Why it was removed
|
||||||
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-315 and FRE-322 §3 correction 4 marked the remote claim
|
|
||||||
unverified and demo-gated. The CTO named it the largest launch risk because it
|
|
||||||
was already in front of buyers.
|
|
||||||
|
|
||||||
So the `freno-dev` source now says only what the product can back today: local
|
[gpt-6.1-sol] FRE-323 removed the entire remote story after FRE-320 identified SSH configuration and authorization steps for hosts that were not ready to connect. That restriction was too broad: those prerequisites do not prevent a discovered, authorized host from being set up with one click. The historical unqualified copy remains in `copy.md` so the change in scope is explicit.
|
||||||
agents, the card, and the notch. That correction is committed locally and is
|
|
||||||
**not deployed** — the live page still carries the remote sentences until the
|
|
||||||
push in FRE-323 card `82d1e1b7` lands. The remote story is not deleted — it is
|
|
||||||
parked here with the FRE-320/FRE-205 work so it can return the day the one-click
|
|
||||||
install ships and is recorded.
|
|
||||||
|
|
||||||
## What has to be true before it returns
|
## Current evidence and scope
|
||||||
|
|
||||||
| Requirement | Owner | Date |
|
[gpt-6.1-sol] `RemoteHostCatalog.discover` reads SSH config and Tailscale peers. `RemoteHostsController.setUp` binds the bridge and calls `RemoteMirror.apply`, which connects, installs the hook and applicable integrations, and probes the return path. The existing `RemoteHostsControllerLiveTests.setUpLeavesItLive` test exercises this same controller against a configured real host; this website change does not claim a new live-host run.
|
||||||
|---|---|---|
|
|
||||||
| One-click install ships without hand-edited `~/.ssh/config` or `ssh-copy-id` | FRE-320/FRE-205 engineering | When FRE-320/FRE-205 ships |
|
|
||||||
| CTO verifies the shipping flow and records the evidence on FRE-320 | CTO | Same day, before any copy moves |
|
|
||||||
| A recording shows a remote agent joining the island | Sponsor/CMO | Before restore |
|
|
||||||
| Restore copy and rebuild the launch plan with an owner + date per item | CMO | Day the three rows above are true |
|
|
||||||
|
|
||||||
Nothing in this folder is publishable. `marketing/approved/` remains the only
|
[gpt-6.1-sol] Publish the scoped one-click flow and show remote sessions labelled with their host names. Do not replace it with a “manual setup only” disclaimer. Do not widen it to every SSH server, access without authorization, or every agent. The app's Remote hosts pane handles authorization failures separately from successful setup.
|
||||||
place a human publishes from.
|
|
||||||
|
|||||||
@@ -113,11 +113,13 @@ one thing forbidden: say "TestFlight beta" when the phone exists in a sentence a
|
|||||||
all, and never claim approvals-from-phone as a shipped capability without its own
|
all, and never claim approvals-from-phone as a shipped capability without its own
|
||||||
CTO verdict — no "(coming soon)" parentheticals either way.
|
CTO verdict — no "(coming soon)" parentheticals either way.
|
||||||
|
|
||||||
Remote SSH is **held**, not approved: the one-click install has not
|
[gpt-6.1-sol] **NOOK-1038:** Mike approved restoring remote-agent marketing.
|
||||||
been verified end to end ([FRE-315] §1) and the shipped path needs hand-edited
|
One-click setup works for **Tailscale and authorized known hosts**. Describe
|
||||||
`~/.ssh/config` and a one-time `ssh-copy-id` ([FRE-320]). Its copy stays parked in
|
selecting a discovered host and clicking Set up; show host-labelled sessions in
|
||||||
`marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow **and** a
|
the island. `RemoteHostsController.setUp` runs installation and a return-path
|
||||||
recording shows a remote agent joining the notch.
|
probe. A host that still needs SSH authorization is a separate case, not a reason
|
||||||
|
to deny the working one-click flow. `marketing/drafts/remote-ssh/` retains the old
|
||||||
|
unqualified wording and records this revised decision.
|
||||||
|
|
||||||
### 5. Humans hold every account
|
### 5. Humans hold every account
|
||||||
|
|
||||||
@@ -138,7 +140,7 @@ Each row names the evidence, not a feeling. Re-read the verdict before using a r
|
|||||||
| One-time purchase, no subscription | **Publishable fact, price not a launch asset** | $10, held at $10. No dated rise, no discount framing, no pricing copy. |
|
| One-time purchase, no subscription | **Publishable fact, price not a launch asset** | $10, held at $10. No dated rise, no discount framing, no pricing copy. |
|
||||||
| Agent coverage numbers ("thirteen agents", "8 agents", "all your agents") | **Excluded — verdict B, count removed** | [FRE-359] verdict B (2026-10-29): the page's 13 rows are the registry's first 13 of 18, so no number is publishable and `every` / `all` / `the agents The Nook supports` are banned. Removed from `agents.tsx`, the landing island section and the marquee. [FRE-322] §1 matrix for the sets. |
|
| Agent coverage numbers ("thirteen agents", "8 agents", "all your agents") | **Excluded — verdict B, count removed** | [FRE-359] verdict B (2026-10-29): the page's 13 rows are the registry's first 13 of 18, so no number is publishable and `every` / `all` / `the agents The Nook supports` are banned. Removed from `agents.tsx`, the landing island section and the marquee. [FRE-322] §1 matrix for the sets. |
|
||||||
| macOS 14.2 or later | **Publishable fact** | The app cannot start below 14.2 (`freno-dev` `881190d`, site `134f41e`). "macOS 14+" is false and was fixed on the site. |
|
| macOS 14.2 or later | **Publishable fact** | The app cannot start below 14.2 (`freno-dev` `881190d`, site `134f41e`). "macOS 14+" is false and was fixed on the site. |
|
||||||
| Remote agents over SSH, one click to install | **Held** | [FRE-315] §1, [FRE-320], [FRE-323]. Needs a verified one-click install plus a recording. |
|
| One-click remote setup for Tailscale and authorized known hosts | **Approved by Mike — NOOK-1038** | [gpt-6.1-sol] Restores the scoped flow: host discovery, `RemoteHostsController.setUp`, `RemoteMirror.apply`, and host-labelled sessions. Supersedes the blanket FRE-323 hold, not the requirement for SSH authorization. |
|
||||||
| "Nothing leaves your Mac", "no telemetry", "local-only", "no account" | **False today — banned** | Sentry unconditional, licence check with hardware fingerprint ([FRE-322] §2.4). |
|
| "Nothing leaves your Mac", "no telemetry", "local-only", "no account" | **False today — banned** | Sentry unconditional, licence check with hardware fingerprint ([FRE-322] §2.4). |
|
||||||
| Approvals from the iPhone or Apple Watch | **No verdict — not a launch claim** | The companion is a TestFlight beta ([FRE-354]); the approval-from-phone claim has no CTO verdict and stays out of launch copy. |
|
| Approvals from the iPhone or Apple Watch | **No verdict — not a launch claim** | The companion is a TestFlight beta ([FRE-354]); the approval-from-phone claim has no CTO verdict and stays out of launch copy. |
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useDemoLoop } from "./useDemoLoop";
|
|||||||
import { BatteryDemo, CalendarDemo, MixerDemo } from "./UtilityDemos";
|
import { BatteryDemo, CalendarDemo, MixerDemo } from "./UtilityDemos";
|
||||||
import {
|
import {
|
||||||
TerminalReplyDemo,
|
TerminalReplyDemo,
|
||||||
|
RemoteHostDemo,
|
||||||
CaptureDemo,
|
CaptureDemo,
|
||||||
FloatingActionsDemo
|
FloatingActionsDemo
|
||||||
} from "./MoreDemos";
|
} from "./MoreDemos";
|
||||||
@@ -579,6 +580,21 @@ export default function FeatureBreakdowns() {
|
|||||||
<TerminalReplyDemo />
|
<TerminalReplyDemo />
|
||||||
</BreakdownSection>
|
</BreakdownSection>
|
||||||
|
|
||||||
|
<BreakdownSection
|
||||||
|
kicker="Remote"
|
||||||
|
title="Set up remote agents in one click"
|
||||||
|
body="Choose a Tailscale peer or an authorized host from your SSH config and click Set up. The Nook installs its integrations and checks that the host can reach your Mac."
|
||||||
|
bullets={[
|
||||||
|
"Local and remote sessions in the same island, labelled by host",
|
||||||
|
"Remote integrations follow the agents installed on this Mac",
|
||||||
|
"Direct connections over Tailscale, or an SSH tunnel for other hosts",
|
||||||
|
"Uses the host\'s existing SSH authorization"
|
||||||
|
]}
|
||||||
|
reversed
|
||||||
|
>
|
||||||
|
<RemoteHostDemo />
|
||||||
|
</BreakdownSection>
|
||||||
|
|
||||||
<BreakdownSection
|
<BreakdownSection
|
||||||
kicker="The campfire"
|
kicker="The campfire"
|
||||||
title="Agent status in the campfire"
|
title="Agent status in the campfire"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js";
|
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js";
|
||||||
import { IslandPill, STATUS, CALM, CRITICAL, ModuleCard } from "./IslandMock";
|
import { IslandPill, STATUS, CALM, CRITICAL, ModuleCard } from "./IslandMock";
|
||||||
import { ApprovalCardMock, DieLine, CLAUDE } from "./ModuleMocks";
|
import { ApprovalCardMock, SessionRowMock, DieLine, CLAUDE, CODEX } from "./ModuleMocks";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Feature collage: five cells sold as the app's actual UI. The hero cell
|
* Feature collage: five cells sold as the app's actual UI. The hero cell
|
||||||
@@ -99,6 +99,31 @@ function FansPanelMock() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RemoteMock() {
|
||||||
|
return (
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<SessionRowMock
|
||||||
|
title="api · main"
|
||||||
|
prompt="fix the flaky test"
|
||||||
|
state="Running pytest -x…"
|
||||||
|
status="running"
|
||||||
|
agent={CLAUDE}
|
||||||
|
host="build-box"
|
||||||
|
age="4m"
|
||||||
|
/>
|
||||||
|
<SessionRowMock
|
||||||
|
title="trainer · fp16"
|
||||||
|
prompt="benchmark fp16"
|
||||||
|
state="Done · replied 1m ago"
|
||||||
|
status="ready"
|
||||||
|
agent={CODEX}
|
||||||
|
host="gpu-rig"
|
||||||
|
age="12m"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* Deterministic usage wander for the traces. */
|
/* Deterministic usage wander for the traces. */
|
||||||
const wander = (n: number, k: number) =>
|
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);
|
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() {
|
|||||||
</div>
|
</div>
|
||||||
</InkStage>
|
</InkStage>
|
||||||
</CollageCell>
|
</CollageCell>
|
||||||
|
|
||||||
|
<CollageCell
|
||||||
|
kicker="Remote"
|
||||||
|
title="Agents on other machines"
|
||||||
|
body="One-click setup for Tailscale and authorized known hosts. Remote sessions appear in the island with the host name beside the agent."
|
||||||
|
>
|
||||||
|
<InkStage class="w-full">
|
||||||
|
<div class="w-full min-w-0 px-3" role="img" aria-label="Remote agent sessions on build-box and gpu-rig">
|
||||||
|
<RemoteMock />
|
||||||
|
</div>
|
||||||
|
</InkStage>
|
||||||
|
</CollageCell>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-subtext1 mt-6 text-center text-xs">
|
<p class="text-subtext1 mt-6 text-center text-xs">
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import {
|
|||||||
CARD_STROKE
|
CARD_STROKE
|
||||||
} from "./IslandMock";
|
} from "./IslandMock";
|
||||||
import { useDemoLoop } from "./useDemoLoop";
|
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:
|
* 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.
|
* 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 (
|
||||||
|
<div class="relative mx-auto w-full max-w-[360px] space-y-3" role="img" aria-label="One-click remote setup preview for an authorized Tailscale host">
|
||||||
|
{/* the host's card in Settings ▸ Agents ▸ Remote hosts */}
|
||||||
|
<ModuleCard>
|
||||||
|
<div class="space-y-2 text-left">
|
||||||
|
<div class="flex items-center gap-2.5">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="text-[13px] font-semibold text-white">gpu-rig</p>
|
||||||
|
<p class="truncate text-[11px] text-white/45">tailnet peer</p>
|
||||||
|
</div>
|
||||||
|
<Show when={working()}>
|
||||||
|
<span class="h-3 w-3 animate-spin rounded-full border-[1.5px] border-white/25 border-t-white/80" />
|
||||||
|
</Show>
|
||||||
|
<span class="flex items-center gap-1.5 text-[10.5px] font-medium text-white/85">
|
||||||
|
<span
|
||||||
|
class="h-[7px] w-[7px] rounded-full transition-colors duration-300"
|
||||||
|
style={{ background: chip().color }}
|
||||||
|
/>
|
||||||
|
{chip().label}
|
||||||
|
</span>
|
||||||
|
<Show
|
||||||
|
when={live()}
|
||||||
|
fallback={
|
||||||
|
<span
|
||||||
|
class="rounded-md px-2.5 py-1 text-[11px] font-semibold text-white transition-all duration-200"
|
||||||
|
style={{
|
||||||
|
background: ACCENT,
|
||||||
|
opacity: working() ? 0 : 1,
|
||||||
|
transform: pressed() ? "scale(0.92)" : "scale(1)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Set up
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Glyph name="refresh" size={11} class="text-white/50" />
|
||||||
|
<span class="rounded-md bg-white/10 px-2 py-1 text-[11px] font-semibold text-[#ff6961]">
|
||||||
|
Remove
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<p class="min-h-8 text-[11px] text-white/50">
|
||||||
|
{working() ? SETUP_STEPS[step()] : " "}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ModuleCard>
|
||||||
|
|
||||||
|
{/* The host name stays visible in the session badge. */}
|
||||||
|
<div
|
||||||
|
class="transition-all duration-500"
|
||||||
|
style={{
|
||||||
|
opacity: live() ? 1 : 0,
|
||||||
|
transform: live() ? "translateY(0)" : "translateY(6px)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SessionRowMock
|
||||||
|
title="trainer · fp16"
|
||||||
|
prompt="benchmark fp16"
|
||||||
|
state="Running python bench.py…"
|
||||||
|
status="running"
|
||||||
|
agent={CODEX}
|
||||||
|
host="gpu-rig"
|
||||||
|
age="now"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<FakeCursor
|
||||||
|
phase={pressed() ? "press" : step() === -1 ? "idle" : "live"}
|
||||||
|
at={{ x: 88, y: 16 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Capture: the module, and the Wings while a recording runs ────── */
|
/* ── Capture: the module, and the Wings while a recording runs ────── */
|
||||||
|
|
||||||
/** CapturePalette.recording and the NookPalette paper/ink pair. */
|
/** CapturePalette.recording and the NookPalette paper/ink pair. */
|
||||||
|
|||||||
@@ -1,39 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* Regression test for the removed remote / SSH claims and the removed agent
|
* NOOK-1038 restores remote marketing for Tailscale and authorized known hosts,
|
||||||
* count (FRE-323, FRE-359 verdict B).
|
* 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-323 took the remote story off the site because it was unverified and
|
* FRE-359's separate decision against agent coverage counts is unchanged.
|
||||||
* demo-gated (FRE-315, FRE-320, FRE-322 §3 correction 4): the "one click
|
* Static source audit, like agents.test.ts; browser checks cover rendered layout.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
import { describe, it, expect } from "bun:test";
|
import { describe, it, expect } from "bun:test";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
const ROOT = join(import.meta.dir, "../../..");
|
const ROOT = join(import.meta.dir, "../../..");
|
||||||
|
|
||||||
/** Shipped page and component source. Tests and drafts may quote the old copy. */
|
|
||||||
const AUDITED = [
|
const AUDITED = [
|
||||||
"src/routes/nook/index.tsx",
|
"src/routes/nook/index.tsx",
|
||||||
"src/routes/nook/agents.tsx",
|
"src/routes/nook/agents.tsx",
|
||||||
@@ -43,58 +19,74 @@ const AUDITED = [
|
|||||||
"src/components/nook/IslandExtras.tsx"
|
"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 {
|
function copyOf(file: string): string {
|
||||||
return readFileSync(join(ROOT, file), "utf8")
|
return readFileSync(join(ROOT, file), "utf8")
|
||||||
.replace(/\/\*[\s\S]*?\*\//g, "")
|
.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||||
.replace(/^\s*\/\/.*$/gm, "");
|
.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) {
|
for (const file of AUDITED) {
|
||||||
it(`${file} does not restore a removed remote sentence`, () => {
|
it(`${file} makes no blanket authorization or agent-coverage promise`, () => {
|
||||||
const copy = copyOf(file);
|
const copy = copyOf(file).toLowerCase();
|
||||||
for (const phrase of BANNED_REMOTE_CLAIMS) {
|
for (const phrase of [
|
||||||
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
|
"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", () => {
|
it("records the current decision without deleting the historical copy", () => {
|
||||||
const copy = copyOf("src/routes/nook/agents.tsx");
|
for (const file of [
|
||||||
expect(copy).not.toContain("remote sessions report into the same grid");
|
"marketing/drafts/remote-ssh/copy.md",
|
||||||
expect(copy).not.toContain("on this Mac or on remote");
|
"marketing/drafts/remote-ssh/rationale.md",
|
||||||
});
|
"marketing/positioning.md"
|
||||||
|
]) {
|
||||||
it("the parked copy still exists, so restoring the story is a revert and not a rewrite", () => {
|
const text = readFileSync(join(ROOT, file), "utf8");
|
||||||
const parked = readFileSync(join(ROOT, "marketing/drafts/remote-ssh/copy.md"), "utf8");
|
expect(text).toContain("NOOK-1038");
|
||||||
for (const phrase of ["Agents on other machines", "Any SSH box, one click away"]) {
|
expect(text).toContain("Tailscale and authorized known hosts");
|
||||||
expect(parked).toContain(phrase);
|
|
||||||
}
|
}
|
||||||
expect(readFileSync(join(ROOT, "marketing/drafts/remote-ssh/rationale.md"), "utf8")).toContain(
|
expect(
|
||||||
"FRE-320"
|
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) {
|
for (const file of AUDITED) {
|
||||||
it(`${file} states no coverage count`, () => {
|
it(`${file} states no coverage count`, () => {
|
||||||
const copy = copyOf(file);
|
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());
|
expect(copy.toLowerCase()).not.toContain(phrase.toLowerCase());
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user