editor: drop redundant two-mode fork on Add connection

The ledger's "How Pulse collects data" explainer already teaches the
Platform API / Pulse Unified Agent split. Repeating that split as a
choose-your-path screen when the user clicks Add connection was a dead
decision step — the subtitle ("Paste an address. Pulse detects the
product…") already commits to API-first, then the body below
contradicted it with a 50/50 fork between API and Agent.

Collapse the fallback to: one address probe (the primary path), the
existing manual-type picker under "Enter credentials manually", and the
agent install as a one-line contextual offramp beneath ("Setting up
bare-metal Linux, Unraid, or FreeBSD…? Install the Unified Agent on a
host."). The agent CTA is still there — it's just not presented as an
equal-weight peer to the probe.

Tests adjusted: the former "two cards mirroring the explainer" case is
replaced with "leads with address probe; agent install is an offramp,"
which pins the same buttons but explicitly forbids a "Platform API"
card header from reappearing. Architecture snapshot simplified to pin
only the shape-level anchors (AddressProbeStep import, manual-type
list, agent offramp CTA), not copy details.
This commit is contained in:
rcourtman
2026-04-20 10:31:59 +01:00
parent e1e4b7f3a7
commit c4142ae115
3 changed files with 42 additions and 54 deletions
@@ -76,64 +76,47 @@ export const ConnectionEditor: Component<ConnectionEditorProps> = (props) => {
<Show
when={showCredentialSlot()}
fallback={
<div class="space-y-4 p-4">
<section class="space-y-3 rounded-md border border-border bg-surface-alt/30 p-3">
<div>
<div class="space-y-5 p-4">
<AddressProbeStep
state={state}
onSelectCandidate={chooseCandidate}
onChooseManually={() => setManualPickerOpen((v) => !v)}
/>
<Show when={manualPickerOpen()}>
<div class="space-y-2 rounded-md border border-border bg-surface p-3">
<div class="text-xs font-semibold uppercase tracking-wide text-muted">
Platform API
</div>
<div class="text-[11px] text-muted">
Proxmox VE / PBS / PMG, VMware, TrueNAS
Choose Platform API type manually
</div>
<ul class="divide-y divide-border rounded-md border border-border">
{manualOptions().map((type) => (
<li>
<button
type="button"
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm text-base-content transition-colors hover:bg-surface-hover"
onClick={() => chooseManualType(type)}
>
<span>{CONNECTION_TYPE_LABELS[type] ?? type}</span>
<span class="text-xs text-muted">{type}</span>
</button>
</li>
))}
</ul>
</div>
</Show>
<AddressProbeStep
state={state}
onSelectCandidate={chooseCandidate}
onChooseManually={() => setManualPickerOpen((v) => !v)}
/>
<Show when={manualPickerOpen()}>
<div class="space-y-2 rounded-md border border-border bg-surface p-3">
<div class="text-xs font-semibold uppercase tracking-wide text-muted">
Choose Platform API type manually
</div>
<ul class="divide-y divide-border rounded-md border border-border">
{manualOptions().map((type) => (
<li>
<button
type="button"
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm text-base-content transition-colors hover:bg-surface-hover"
onClick={() => chooseManualType(type)}
>
<span>{CONNECTION_TYPE_LABELS[type] ?? type}</span>
<span class="text-xs text-muted">{type}</span>
</button>
</li>
))}
</ul>
</div>
</Show>
</section>
<section class="space-y-3 rounded-md border border-blue-200 bg-blue-50/40 p-3 dark:border-blue-900 dark:bg-blue-950/20">
<div>
<div class="text-xs font-semibold uppercase tracking-wide text-muted">
Pulse Unified Agent
</div>
<div class="text-[11px] text-muted">
Host-level metrics on Proxmox / VMware / TrueNAS, or the only path on
bare-metal Linux, Unraid, FreeBSD.
</div>
</div>
<div class="border-t border-border pt-3 text-xs text-muted">
Setting up bare-metal Linux, Unraid, or FreeBSD, or adding host-level
metrics (CPU/disk temps, SMART, power) on Proxmox, VMware, or TrueNAS?{' '}
<button
type="button"
onClick={() => chooseManualType('agent')}
class="inline-flex items-center rounded-md border border-blue-600 bg-blue-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-500"
class="font-medium text-blue-600 underline underline-offset-2 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
Install the Unified Agent on a host
</button>
</section>
.
</div>
</div>
}
>
@@ -94,7 +94,7 @@ describe('ConnectionEditor', () => {
expect(lastCall.candidate).toBeNull();
});
it('surfaces Platform API and Pulse Unified Agent as the two add-paths, mirroring the explainer', () => {
it('leads with the address probe; agent install is an offramp, not a peer fork', () => {
render(() => (
<ConnectionEditor
renderCredentialSlot={() => <div />}
@@ -102,8 +102,15 @@ describe('ConnectionEditor', () => {
/>
));
expect(screen.getByText('Platform API')).toBeInTheDocument();
expect(screen.getByText('Pulse Unified Agent')).toBeInTheDocument();
// Primary path is probe-the-address. The explainer on the ledger already
// taught the user the two-mode split, so the editor must not restate it
// as a second decision screen — no "Platform API" card header here.
expect(screen.getByRole('button', { name: /probe address/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /enter credentials manually/i })).toBeInTheDocument();
expect(screen.queryByText('Platform API')).toBeNull();
// Agent install is still reachable, but as a contextual offramp beneath
// the probe, not as an equal-weight card.
expect(
screen.getByRole('button', { name: /install the unified agent on a host/i }),
).toBeInTheDocument();
@@ -77,10 +77,8 @@ describe('settings architecture guardrails', () => {
it('keeps probe-first connection setup and inline node credentials on the shared editor model', () => {
expect(connectionEditorSource).toContain("import { AddressProbeStep } from './AddressProbeStep';");
expect(connectionEditorSource).toContain('const DEFAULT_MANUAL_TYPES: ConnectionType[] =');
expect(connectionEditorSource).toContain('Platform API');
expect(connectionEditorSource).toContain('Pulse Unified Agent');
expect(connectionEditorSource).toContain('Install the Unified Agent on a host');
expect(connectionEditorSource).toContain('<AddressProbeStep');
expect(connectionEditorSource).toContain('Install the Unified Agent on a host');
expect(connectionEditorSource).not.toContain('NodeModal');
expect(addressProbeStepSource).toContain('Probe address');