deploy: reconcile the GitHub line into master so merges reach production (FRE-1695) #9

Merged
agent-cto merged 4 commits from freno-dev/FRE-1695-reconcile-github-deploy-line into master 2026-11-09 17:18:40 -05:00
7 changed files with 250 additions and 127 deletions
Showing only changes of commit 4e412ede8e - Show all commits

View File

@@ -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
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
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.
[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.
[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.
## What was removed
@@ -62,18 +57,8 @@ Source: `src/components/nook/IslandExtras.tsx` (removed 2026-10-29).
One-click SSH setup
```
## What replaced it
## Current restoration
Nothing. The Remote tile and section were removed rather than replaced; the
existing Approvals cell already carries the verified local claim:
[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.
> Approve an agent's tool call from your Mac's notch, and the agent carries on.
## 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.
[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.

View File

@@ -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
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.
## Why it was removed
So the `freno-dev` source now says only what the product can back today: local
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.
[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.
## What has to be true before it returns
## Current evidence and scope
| Requirement | Owner | Date |
|---|---|---|
| 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 |
[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.
Nothing in this folder is publishable. `marketing/approved/` remains the only
place a human publishes from.
[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.

View File

@@ -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
CTO verdict — no "(coming soon)" parentheticals either way.
Remote SSH is **held**, not approved: the one-click install has not
been verified end to end ([FRE-315] §1) and the shipped path needs hand-edited
`~/.ssh/config` and a one-time `ssh-copy-id` ([FRE-320]). Its copy stays parked in
`marketing/drafts/remote-ssh/` until the CTO verifies the shipping flow **and** a
recording shows a remote agent joining the notch.
[gpt-6.1-sol] **NOOK-1038:** Mike approved restoring remote-agent marketing.
One-click setup works for **Tailscale and authorized known hosts**. Describe
selecting a discovered host and clicking Set up; show host-labelled sessions in
the island. `RemoteHostsController.setUp` runs installation and a return-path
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
@@ -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. |
| 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. |
| 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). |
| 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. |

View File

@@ -26,6 +26,7 @@ import { useDemoLoop } from "./useDemoLoop";
import { BatteryDemo, CalendarDemo, MixerDemo } from "./UtilityDemos";
import {
TerminalReplyDemo,
RemoteHostDemo,
CaptureDemo,
FloatingActionsDemo
} from "./MoreDemos";
@@ -579,6 +580,21 @@ export default function FeatureBreakdowns() {
<TerminalReplyDemo />
</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
kicker="The campfire"
title="Agent status in the campfire"

View File

@@ -1,6 +1,6 @@
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js";
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
@@ -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. */
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() {
</div>
</InkStage>
</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>
<p class="text-subtext1 mt-6 text-center text-xs">

View File

@@ -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 (
<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 ────── */
/** CapturePalette.recording and the NookPalette paper/ink pair. */

View File

@@ -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(/<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 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());
}
});
}