Skip to content

feat(config-nudge): distinguish install/auth state in cli_login nudge cards#1633

Merged
wpfleger96 merged 11 commits into
mainfrom
duncan/doctor-cta-nudge
Jul 8, 2026
Merged

feat(config-nudge): distinguish install/auth state in cli_login nudge cards#1633
wpfleger96 merged 11 commits into
mainfrom
duncan/doctor-cta-nudge

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

The config nudge card that appears when a managed agent needs CLI login treated every non-ready state the same way — a single generic "needs login" message with no path to fixing it. A user whose adapter or CLI binary isn't installed got the same copy as a user who just needs to run tool login, and neither had a direct affordance to open Doctor where the actual fix lives.

This distinguishes the four availability states and gives each a path to resolution.

Backend

cli_login_requirements reuses the existing classify_runtime logic to stamp a granular AcpAvailabilityStatus (available / adapter_missing / cli_missing / not_installed) onto each Requirement::CliLogin. The login probe only runs when the runtime is Available — for the other states, probing a missing/broken binary is meaningless, so it's skipped and the state is reported directly.

The setup payload crosses a process boundary: desktop serializes it into BUZZ_ACP_SETUP_PAYLOAD, and buzz-acp deserializes it, renders the nudge, and re-serializes it into the buzz:config-nudge sentinel the frontend parses. buzz-acp carries a local AcpAvailabilityStatus mirror enum on its RequirementPayload::CliLogin so the field survives that round-trip intact. Boundary tests assert the field survives desktop → buzz-acp SetupPayload → sentinel for all four states.

Codex setup_copy uses plain codex login — the --with-api-key flag was over-prescriptive for the common ChatGPT OAuth path.

Frontend

  • Per-state card copy so the message matches the actual problem (adapter missing vs CLI missing vs not installed vs logged-out-but-ready).
  • Two routing modes based on card contents:
    • All cli_login: card click routes to Doctor; card-level "Open Doctor →" label in AttachmentActions confirms the action at rest. No per-row CTAs.
    • Mixed requirements: card click opens Edit Agent as a card-level fallback. Each row carries its own inline CTA at a shared right edge so the action is clearly paired with its requirement — cli_login rows get "Open Doctor →", env_key / normalized_field rows get "Edit Agent →". The AttachmentActions column is omitted on mixed cards.
  • All per-row CTAs use relative z-20 (above the AttachmentTrigger overlay) and stopPropagation (no double-fire), and text-muted-foreground color (no false "danger" signal).
  • Always-visible CTAs plus cursor-pointer / hover-lift affordances so the card reads as actionable.

Card routing uses useAppShell() rather than threading a new prop through ProfilePanelContext — every render site already has AppShell in scope. A console.warn guard surfaces null context on surfaces without AppShell.

Tests

New cli_login_requirements integration tests are host-portable: POSIX-specific true/false stand-ins are replaced with std::env::current_exe() (always present, resolves via absolute-path check) so the tests pass on the windows-latest CI shard.

Stack

Merges before #1448 (global agent config defaults), which is parked awaiting manual E2E.

wpfleger96 pushed a commit that referenced this pull request Jul 8, 2026
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 5 commits July 8, 2026 12:16
… cards

Splits the collapsed Requirement::CliLogin into four distinct failure
states using the existing AcpAvailabilityStatus classifier from Doctor
(classify_runtime in discovery.rs). The nudge card now shows actionable
per-state copy and a deep-link CTA to Settings → Doctor instead of
routing every harness issue through Edit Agent.

Backend (Rust):
- readiness.rs: rework cli_login_requirements to call classify_runtime
  before deciding whether to probe. States: Available → run probe;
  AdapterMissing/CliMissing/NotInstalled → skip probe, emit CliLogin
  with the precise state. availability field added to Requirement::CliLogin.
- discovery.rs: make find_command and classify_runtime pub(crate) for
  reuse in readiness.rs.
- types.rs: add Deserialize to AcpAvailabilityStatus derive.
- runtime.rs: include availability in the cli_login serde JSON payload.
- Tests: 4 new unit tests covering each arm + updated codex_not_ready
  test to use make_cli_runtime stub (function now requires a runtime arg).

Frontend (TS):
- configNudge.ts: add required availability field to cli_login union arm.
  Type guard rejects old-format payloads without the field.
- config-nudge-attachment.tsx: per-state copy via cliLoginMessage();
  (A) pure cli_login card routes whole card to Doctor; (B) mixed card
  routes card to Edit Agent + each cli_login row gets inline Open Doctor
  CTA with stopPropagation. Always-visible CTA label replaces opacity-0
  fade-in hint. cursor-pointer + hover:shadow-sm affordances.
- AppShellContext.tsx/AppShell.tsx: onOpenSettings added to context so
  config-nudge-attachment can deep-link to Doctor via useAppShell().
- configNudge.test.mjs: update cli_login fixtures to include availability,
  add test pinning rejection of old-format payloads.
- check-file-sizes.mjs: bump readiness.rs override to 1370.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The cli_login sentinel the FE parses was missing the `availability`
field because `RequirementPayload::CliLogin` in setup_mode.rs did not
have the field — serde silently dropped it during deserialization of the
desktop-emitted JSON, so nudge_body() re-serialized a struct that never
had it.

Add an `AcpAvailabilityStatus` mirror enum (snake_case, Serialize +
Deserialize) and add `availability` to `RequirementPayload::CliLogin`.
The field now round-trips desktop → buzz-acp → sentinel → FE intact.

Add 4 boundary tests that exercise the full desktop→from_raw_env_value
→nudge_body→sentinel extraction path for each availability state.

Update `instruction()` prose fallback to name the specific install gap
(adapter missing / CLI missing / not installed) for non-card clients
instead of always emitting the login instruction.

FE MINOR: replace the inline availability union in configNudge.ts with
the shared `AcpAvailabilityStatus` import from api/types.ts. The runtime
validator still enumerates wire literals explicitly (correct); the type
annotation now uses the shared type (no more drift point).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Replace review-thread reference with a description of the invariant
being guarded.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Captures the four availability states the Doctor-CTA nudge card can
render in: available (logged-out), not_installed, adapter_missing, and
cli_missing. Also covers the mixed-requirements case showing the inline
per-row Doctor CTA alongside the card-level Edit Agent trigger.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…login cards

On pure allCliLogin cards the card-level trigger already routes to Doctor
("Open Doctor →" in AttachmentActions). The per-row "Open Doctor →" button
in the cli_login RequirementRow was additionally rendering for each row,
producing two identical Doctor CTAs visible at once.

Thread showRowDoctorCta={!allCliLogin} into RequirementRow and guard the
inline button behind it. Mixed cards (card CTA = "Edit Agent →") are
unchanged — each cli_login row still renders its own inline Doctor CTA.
Pure cli_login cards now show exactly one "Open Doctor →" (the card action).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Jul 8, 2026
@wpfleger96 wpfleger96 force-pushed the duncan/doctor-cta-nudge branch from 71548f9 to 05b3d72 Compare July 8, 2026 16:21
#1640 persona-env-vars-refresh rebase added live-persona env merge path
changes that grew readiness.rs by 4 lines beyond the existing override.
Availability-classification growth, not generic debt.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Q1: change codex setup_copy from 'codex login --with-api-key' to plain
'codex login'. The --with-api-key flag is wrong for the majority of
users (ChatGPT OAuth) and over-prescribes the login method. Updated
all 8 sites: the production line in readiness.rs, the doc-comment,
test comment, three Rust fixtures, and two TS fixtures in
configNudge.test.mjs.

Q2: fix Windows CI portability for cli_login_requirements tests.
The four new tests used POSIX 'true'/'false' as stand-in binaries
which are not guaranteed on the Windows/Git-for-Windows PATH. Replace
with std::env::current_exe() via a new present_binary_str() helper
that returns the absolute path of the running test binary (always
present, resolves via path.exists() regardless of platform). Success
probe uses --list (exits 0 on all platforms); failure probe uses an
unrecognized flag (libtest exits non-zero for unknown flags on all
platforms). Bump readiness.rs file-size override 1374 to 1403.

Q3: fix config-nudge card visual issues in mixed-card layout.
(a) Row CTA was styled text-destructive (red), making the navigation
button look like a danger action. Changed to text-muted-foreground to
match the card-level CTA color.
(b) AttachmentActions now has items-end self-end so the 'Edit Agent'
pill bottom-anchors in its panel rather than vertically centering
alongside requirement rows, which was causing false visual pairing
with the per-row 'Open Doctor' button.

Nit: extract openDoctor() helper in ConfigNudgeCard and add a
console.warn guard so onOpenSettings null is not a silent dead click
on surfaces without AppShell context.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Jul 8, 2026
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 2 commits July 8, 2026 14:59
The mixed-card layout placed 'Open Doctor →' inside AttachmentContent
(after the cli_login row text) and 'Edit Agent →' in a separate
AttachmentActions column. Two different flex columns have two different
right edges, so the CTAs could never line up vertically and the visual
pairing between each CTA and its requirement was unclear.

Fix: every requirement row on a mixed card now owns its CTA, right-
aligned to a shared edge via the same 'justify-between' / 'flex-1 span
+ shrink-0 button' pattern already used by cli_login rows.

- cli_login rows → 'Open Doctor →' (unchanged, routes to Doctor)
- env_key rows → new per-row 'Edit Agent →' button
- normalized_field rows → new per-row 'Edit Agent →' button

All per-row CTAs: relative z-20 (above AttachmentTrigger overlay),
stopPropagation (no double-fire), text-muted-foreground (no regression
to red). Extract openEditAgent() helper called by both the card-level
fallback trigger and the new per-row handler.

AttachmentActions on mixed cards is removed (per-row CTAs replace it).
All-cli_login cards keep the card-level 'Open Doctor →' in
AttachmentActions — single destination, no alignment issue, unchanged.

Replace showRowDoctorCta prop with allCliLogin to uniformly gate all
per-row CTA types from one boolean.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…odel

The test-03 doc-comment and inline assertions still described the old
card-level CTA shape ('card-level CTA says Edit Agent'). Update to
describe the per-row model: each requirement row owns its right-aligned
CTA sharing one edge. Assertions unchanged — they still verify both
'Open Doctor' and 'Edit Agent' are visible, which remains correct
for the new layout.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Jul 8, 2026
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 2 commits July 8, 2026 15:28
Doctor only installs the harness/ACP adapter — it has no auth functionality.
Pointing an installed-but-unauthenticated user at Doctor is a misleading
dead-end; the setup_copy already carries the exact login command.

Gate: availability === "available" cli_login requirements never get a
Doctor CTA or Doctor routing in any position.

- Add isAuthOnly() helper: true when all requirements are cli_login with
  availability === "available"
- authOnly cards: drop AttachmentTrigger, AttachmentActions, and the
  cursor-pointer/hover-shadow affordance — purely informational
- Per-row Doctor button in RequirementRow: gate on
  !allCliLogin && req.availability !== "available"
- Update ConfigNudgeCard doc-comment with (A-auth) case
- e2e test-01: rename to informational state, flip assertion to
  toHaveCount(0) for "Open Doctor →"

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The isAllCliLogin helper comment and the handleOpen (A) branch comment
both said the card routes to Doctor unconditionally for all-cli_login
cards. That was true before the auth-only suppression but is now stale:
auth-only cards (all cli_login rows availability === 'available') never
mount AttachmentTrigger so handleOpen never fires for them. Narrow both
comments to reflect that Doctor routing only applies to non-authOnly
install-state all-cli_login cards. No behavior change.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

01 — Auth-only cli_login card (informational, no CTA)

Installed-but-needs-auth cli_login card: shows the run \claude auth login`` login command as instruction — NO "Open Doctor →" deep-link, no whole-card routing, no pointer/hover affordance.

01-cli-login-available-informational

02 — cli_login not-installed state

cli_login requirement with availability: "not_installed" — "Open Doctor →" CTA present, whole-card Doctor routing active.

02-cli-login-not-installed-state

03 — Mixed requirements (adapter_missing + env_key)

Mixed card: cli_login row with availability: "adapter_missing" shows per-row "Open Doctor →"; env_key row shows "Edit Agent →"; both right-aligned to the same edge.

03-mixed-requirements-inline-doctor-cta

04 — cli_login cli-missing state

cli_login requirement with availability: "cli_missing" — "Open Doctor →" CTA present, whole-card Doctor routing active.

04-cli-login-cli-missing-state

wpfleger96 pushed a commit that referenced this pull request Jul 8, 2026
@wpfleger96 wpfleger96 merged commit 4f1a487 into main Jul 8, 2026
29 checks passed
@wpfleger96 wpfleger96 deleted the duncan/doctor-cta-nudge branch July 8, 2026 20:00
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
…#1633

#1633 added AcpAvailabilityStatus to the types import in readiness.rs
alongside our GlobalAgentConfig import, bringing the merged line count
to 1405 (gate) vs the prior 1403 cap. Ratchet to 1408.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
…#1633

#1633 added AcpAvailabilityStatus to the types import in readiness.rs
alongside our GlobalAgentConfig import, bringing the merged line count
to 1405 (gate) vs the prior 1403 cap. Ratchet to 1408.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
…#1633

#1633 added AcpAvailabilityStatus to the types import in readiness.rs
alongside our GlobalAgentConfig import, bringing the merged line count
to 1405 (gate) vs the prior 1403 cap. Ratchet to 1408.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
…#1633

#1633 added AcpAvailabilityStatus to the types import in readiness.rs
alongside our GlobalAgentConfig import, bringing the merged line count
to 1405 (gate) vs the prior 1403 cap. Ratchet to 1408.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
…#1633

#1633 added AcpAvailabilityStatus to the types import in readiness.rs
alongside our GlobalAgentConfig import, bringing the merged line count
to 1405 (gate) vs the prior 1403 cap. Ratchet to 1408.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant