fix: stop dimming setup card bodies

Clack's `p.note` defaults to `format: e => styleText("dim", e)`, which
fades note bodies regardless of the project's stated readability stance
(see comment on `dimWrap` in setup/lib/theme.ts: "prose renders at the
terminal's regular weight"). The dim styling makes body copy hard to
read on dark terminals and visibly washes out brand-colored segments
embedded in cards (e.g. the chip + bold heading rows).

Add a `note()` helper in setup/lib/theme.ts that wraps `p.note` with a
pass-through formatter, and route every setup-flow `p.note` call site
through it: setup/auto.ts, every setup/channels/*.ts adapter, and the
two setup/lib/claude-* helpers.

Pre-styled segments (brandBold, brandChip, formatPairingCard,
formatCodeCard) now render at full strength instead of being faded
alongside surrounding prose.
This commit is contained in:
exe.dev user
2026-04-29 10:20:10 +00:00
parent ede6c01da8
commit 9c8f680ca8
11 changed files with 62 additions and 43 deletions

View File

@@ -52,7 +52,7 @@ import { claudeCliAvailable, resolveTimezoneViaClaude } from './lib/tz-from-clau
import * as setupLog from './logs.js';
import { ensureAnswer, fail, runQuietChild, runQuietStep } from './lib/runner.js';
import { emit as phEmit } from './lib/diagnostics.js';
import { brandBold, brandChip, dimWrap, fitToWidth, wrapForGutter } from './lib/theme.js';
import { brandBold, brandChip, dimWrap, fitToWidth, note, wrapForGutter } from './lib/theme.js';
import { isValidTimezone } from '../src/timezone.js';
const CLI_AGENT_NAME = 'Terminal Agent';
@@ -435,7 +435,7 @@ async function main(): Promise<void> {
);
}
if (notes.length > 0) {
p.note(notes.join('\n'), "What's left");
note(notes.join('\n'), "What's left");
}
// "What's left" is a soft failure — we don't abort like fail(), but the
// user is still stuck and a fix is exactly what claude-assist is for.
@@ -467,11 +467,11 @@ async function main(): Promise<void> {
];
const labelWidth = Math.max(...rows.map(([l]) => l.length));
const nextSteps = rows.map(([l, c]) => `${k.cyan(l.padEnd(labelWidth))} ${c}`).join('\n');
p.note(nextSteps, 'Try these');
note(nextSteps, 'Try these');
// Always-on warning goes before the "check your DMs" directive so the
// caveat doesn't land after the user's already looked away at their phone.
p.note(
note(
wrapForGutter(
"NanoClaw runs on this machine. It's only reachable while this computer is on and connected to the internet. For always-on availability, run it on a cloud VM — or keep this machine awake.",
6,
@@ -488,7 +488,7 @@ async function main(): Promise<void> {
// that the welcome-message signal was too easy to miss. Use p.note so it
// renders with a visible box, cyan-bold the directive line, and put it
// as the last thing before outro.
p.note(`${brandBold('→')} ${k.bold(`Check your ${dmTarget} — your assistant is saying hi.`)}`, 'Go say hi');
note(`${brandBold('→')} ${k.bold(`Check your ${dmTarget} — your assistant is saying hi.`)}`, 'Go say hi');
p.outro(k.green("You're set."));
} else {
p.outro(k.green("You're ready! Chat with `pnpm run chat hi`."));
@@ -567,7 +567,7 @@ function renderPingFailureNote(result: PingResult): void {
'No reply from your assistant within 30 seconds. Check `logs/nanoclaw.log` for clues, then try `pnpm run chat hi`.',
6,
);
p.note(body, 'Skipping the first chat');
note(body, 'Skipping the first chat');
}
/**
@@ -582,7 +582,7 @@ function renderPingFailureNote(result: PingResult): void {
* clearly optional.
*/
async function runFirstChat(): Promise<void> {
p.note(
note(
wrapForGutter(
[
'Your assistant runs in a sandbox on this machine.',