Conversation
The driver lane's "Connect this cluster" inspects the live cluster, but the header shows the Cloud button from first paint, before Radar has connected to anything. A first-run click hit the prepare endpoint's 503, surfaced as "Could not inspect this cluster", and flipped the escape link to driver-escape, so the funnel report read a missing cluster as a broken install path. While the cluster is connecting or disconnected, the footer now explains why the in-app connect is unavailable and promotes the browser wizard to the primary action, tagged utm_content=driver-unconnected so the Hub can tell the three exits apart. A 503 on prepare (connection dropped between render and click) no longer counts as a failed attempt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR Summary by QodoOffer browser setup when no cluster is connected
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
| href={driverEscapeUrl} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="whitespace-nowrap px-6 py-2.5 rounded-[10px] bg-emerald-500 hover:bg-emerald-400 text-emerald-950 text-[14px] font-bold shadow-[0_0_22px_rgba(16,185,129,0.35)] hover:shadow-[0_0_30px_rgba(16,185,129,0.5)] hover:-translate-y-px transition-all" |
There was a problem hiding this comment.
1. Browser action background stays emerald 📘 Rule violation ⚙ Maintainability
The new browser <a> uses palette-specific bg-emerald-500, hover:bg-emerald-400, and text-emerald-950 utilities plus arbitrary normal and hover shadow-[...] values instead of approved theme tokens. It renders on the promoted browser path for connecting or disconnected users when the driver lane lacks a connected cluster, tying its label, surfaces, and shadows in both interaction states to component-local styling rather than the active centralized theme.
Agent Prompt
## Issue description
Replace the browser action's hardcoded emerald background and text utilities and its arbitrary normal and hover shadow utilities with approved theme tokens.
## Issue Context
The new primary browser action appears on the promoted browser path for connecting or disconnected users when the driver lane lacks a connected cluster. Its label, normal and hover surfaces, and normal and hover shadows should derive from the active centralized theme rather than palette-specific colors or component-local raw shadow values; use approved background and text utilities and one of `shadow-theme-sm`, `shadow-theme-md`, or `shadow-theme-lg` as appropriate.
## Fix Focus Areas
- web/src/components/CloudFunnelButton.tsx[429-429]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Until the connection feed observes the drop, the footer would re-offer the in-app connect and tag the browser link driver-alt. The server's answer now wins until that feed moves or the context changes. Also fold the duplicated primary-action class string into one constant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 93da5f1. Configure here.
The connection feed holds 'connected' across short reconnects, so a 503 caught in that window would otherwise hide the in-app connect until the context changed. Clearing the override whenever the dialog opens or closes bounds it to the session that saw the error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A visible note above the footer competed with the pitch for attention. The promoted browser action now carries the reason on hover, so the footer keeps the same quiet layout as the wizard lane. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
For anyone running Radar as a binary or in-cluster, Radar itself is already in a browser, so "set up in the browser" read as "set up here". The link hands off to Radar Cloud's wizard, so say that, on the promoted button, the secondary escape link, the tooltip, and the 503 toast. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With the in-app connect unavailable, "How it works" still promised that setup runs here in the app. Hand the pitch the wizard lane in that state so it describes the path the footer actually offers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Nobody with no cluster connected expects Radar to install into one, so the "can't install the connection for you" clause answered a question that was never asked. One sentence on the situation, one on the way forward. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Someone with no cluster connected is not expecting Radar to install into one, so explaining why the in-app connect is missing adds nothing. The helper collapses to a plain availability check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ery outcome in plain words (#1770) ## Summary When someone tries the in-app Cloud connect and it doesn't end connected, the "or set up in the browser" link now tells the wizard what they're coming from, and everything a person might read along the way — the query param in their address bar, the failure kinds behind it, the failed card's headline — is written in plain words rather than internal tokens or raw errors. The dialog's copy request also names which lane rendered and where Radar runs. Radar still sends nothing on its own; every value travels only in a request or link the person triggers. ## What changed **The browser link carries the outcome.** After an in-app attempt that did not connect, the driver links append `radar_outcome=<phrase>`. The value is one of a fixed vocabulary of short snake_case phrases, shape-checked in `web/src/components/cloudConnectHandoff.ts` before it is appended and again on the Hub; never an error message, never a cluster name. Its presence is what says an attempt preceded the click. **One vocabulary, grouped by stage, each value readable without knowing the UI:** | Stage | `radar_outcome` | |---|---| | Radar inspecting the cluster (prepare HTTP class) | `radar_not_connected_to_cluster` (503), `cluster_inspect_failed` (any other status), `radar_server_unreachable` (fetch failed), `cluster_inspect_request_error` (unusable response) | | Refused (blocked plan) | `blocked_gitops_managed_install`, `blocked_preflight_checks_failed`, `blocked_unsupported_install` | | Plan shown, not started | `install_plan_canceled` | | Hub / approval (server failure kinds) | `hub_connect_request_failed`, `hub_approval_poll_failed`, `approval_rejected_in_browser`, `approval_window_expired`, `approved_but_credential_pickup_expired`, `approval_outcome_unknown`, `canceled_before_approval_page`, `canceled_after_approval` | | Installing | `helm_provision_failed`, `installed_but_tunnel_not_confirmed` | | Fallback | `failure_kind_unknown` | The server's failure kinds (`internal/server/cloud_install.go`) are these same strings. The Hub connect-request failure and the approval-poll failure are separate kinds because they differ in whether starting over is safe. **`utm_content` names the link, prefixed by lane** (`driver-*`: the in-app flow was on offer; `wizard-*`: it was not): | Link | `utm_content` | |---|---| | browser-only dialog's main button | `wizard-signup-button` | | in-cluster install link, install not identified | `wizard-install-link-unknown-install` | | in-cluster install link with namespace/release/method | `wizard-install-link-known-install` | | driver dialog footer, "or set up in the browser" | `driver-footer-browser-link` | | same destination from inside the blocked card | `driver-blocked-card-browser-link` | Previous values (`funnel-cta`, `wizard-generic`, `wizard-deeplink`, `driver-alt`/`driver-escape`, `flow-escape`) remain on rows recorded before this release. **"Try again" follows the server's `retrySafe`.** The pitch CTA reads "Try again" only when the failure the person just saw is one the server marked safe to retry — the same verdict the failed card's "Start over"/"Close" already renders. A refused plan, or a plan the person canceled, keeps "Connect this cluster…". The component carries `{ outcome, retryable }` through `exitFlow` rather than inferring retryability from the kind. **Failed cards say what happened.** The Hub connect-request failure reads "Radar couldn't reach Radar Hub, so no connection was requested." (transport, typed as `cloud.HubUnreachableError`), "Radar Hub declined the connection request (HTTP n)." (the Hub answered with a refusal), or a generic sentence; the approval-poll failure reads "Radar lost track of the connection request while waiting for approval." In every case the raw error is in the guidance's inspect block, not the headline — matching how the other failures already present. **The copy request names the dialog.** `useCloudConnectInfo` sends `?lane=driver|wizard&mode=local|in-cluster`, both closed enums, both in the query key so a lane change refetches instead of reusing the other lane's copy. It still fires only when a person opens the dialog. **Per-cluster state resets on context switch.** A blocked plan is cleared alongside the outcome when the kubeconfig context changes, so one cluster's refusal never reopens against the next. Pairs with skyhook-dev/radar-hub#248 (docs) and skyhook-dev/radar-hub-web#302 (`radar_outcome` handling). Safe to ship in any order: the Hub ignores unknown query params and older Radars send neither. ## Testing - `make tsc`; `web` vitest (`cloudConnectHandoff.test.ts`: URL shape, outcome shape guard, prepare-error classes and retryability, blocked outcomes, server kinds); `go test ./internal/server/ ./internal/cloud/` (`TestConnectRequestFailureHeadlines` pins that the raw error never appears in a headline; `cloud_connect_self_test.go` pins the link names) - Driven against a throwaway kind cluster with `RADAR_HUB_URL` pointed at a dead port and `RADAR_CLOUD_FUNNEL=on`: fresh dialog → `utm_content=driver-footer-browser-link`, no outcome, copy fetch `/api/connect/info?lane=driver&mode=local`; Connect → plan → Cancel → CTA stays "Connect this cluster…", link carries `radar_outcome=install_plan_canceled`; Connect → Continue in browser → server reports `hub_connect_request_failed` with `retrySafe: true`, card shows the written headline with the dial error in the inspect block → Start over → CTA "Try again", link carries `radar_outcome=hub_connect_request_failed` - Not exercised by hand: the blocked-plan branches and the `retrySafe: false` kinds (need a GitOps-managed Radar / an approving Hub); unit-level coverage only ## Notes - `blocked_unsupported_install` still absorbs untyped errors from `cloudinstall/plan.go` (a chart download failure lands there as a permanent-sounding refusal). Separating refusals from errors needs typed errors in the plan package — follow-up. - The blocked card's "Your Kubernetes identity can't install this" headline is imprecise for preflight failures that are ownership conflicts rather than permissions — follow-up copy fix. Follow-up to #1683. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > UX, analytics query params, and error presentation for Cloud connect; no auth or cluster mutation changes beyond clearer failure mapping. > > **Overview** > When in-app Cloud connect does not finish connected, **browser links into Radar Hub** now append a shape-checked `radar_outcome` query param (via new `cloudConnectHandoff.ts`) so the wizard knows what happened; **`utm_content` values are renamed** to name each specific link (driver footer, blocked card, wizard install deep links, signup button). The dialog tracks a **`Handoff` `{ outcome, retryable }`** instead of a bare “prepare failed” flag so **“Try again”** follows server `retrySafe` and outcomes survive modal close until kubecontext changes (blocked state clears on context switch too). > > **Server-side**, flow **failure `kind` strings** are rewritten as plain, address-bar-readable phrases (e.g. `hub_connect_request_failed`, `hub_approval_poll_failed`). **Hub connect-request errors** use `HubUnreachableError` / `HubDeclinedStatus` and `connectRequestFailure()` so cards show human headlines while raw errors stay in guidance inspect text; approval-poll transport failures are a separate kind with a clearer message. > > **Copy fetch** (`useCloudConnectInfo`) now sends closed-enums `lane` and `mode` on `/api/connect/info` and includes them in the React Query key so lane changes refetch. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 65624f8. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Nadav Erell <nadaverell@gmail.com>

Why
The Cloud dialog's driver lane ("Connect this cluster…") inspects the live cluster, but the header renders the Cloud button from first paint, before Radar has connected to anything. A first-run user who clicks it immediately hits the prepare endpoint's 503, sees "Could not inspect this cluster for Cloud connect", and the escape link flips to
utm_content=driver-escape. The daily funnel report then reads "no cluster yet" as "the in-app install path broke".This is exactly what happened with the first funnel-attributed org creation this week: six seconds between the dialog opening on a fresh desktop install and the Hub signup landing via
driver-escape, with no connect request ever reaching the Hub.What changes
connectingordisconnected, the driver-lane footer replaces the in-app Connect button with "Set up in the browser" as the primary action. No explanation is attached: someone with no cluster connected is not expecting Radar to install into one. The footer keeps the same quiet layout as the wizard lane, and "How it works" describes the Cloud-side path in that state.utm_content=driver-unconnected, so the Hub can tell "prefers the browser" (driver-alt), "in-app path broke" (driver-escape) and "no cluster to connect" apart. The existing two values keep their meaning.connectedfeed until the feed moves, the context changes, or the dialog is reopened.cloudFunnelState.tswith unit tests.No Go changes. The embedded Hub build hides this button entirely, so library consumers are unaffected.
Follow-up outside this repo
Wherever the daily radar-stats report interprets
utm_contentvalues, adddriver-unconnectedso it is not reported as unknown.Verified
tscclean,vitest run src914/914.…&utm_content=driver-unconnected.🤖 Generated with Claude Code
Note
Low Risk
UI-only Cloud funnel and analytics tagging; no auth, API, or data-path changes beyond clearer prepare error handling.
Overview
Fixes the driver-lane Cloud modal so users without a live cluster are not pushed into Connect this cluster… only to hit prepare 503 and mis-tagged
driver-escapeanalytics.Connection-aware footer: While Radar is
connectingordisconnected, the primary CTA becomes Continue in Radar Cloud; in-app connect returns once connected. Pitch copy temporarily follows the wizard lane when connect is unavailable so it does not promise an on-cluster install.503 handling: Prepare 503 sets
serverReportedNoCluster, shows a connect-first toast, exits the flow without Try again, and treats the cluster as disconnected until the connection feed, kube context, or modal lifecycle clears the flag.Analytics: Hub signup links use a new
utm_content=driver-unconnectedviacloudFunnelStatehelpers (effectiveConnectionState,driverEscapeContent,driverConnectUnavailableNote), with unit tests.Reviewed by Cursor Bugbot for commit c9153e3. Bugbot is set up for automated code reviews on this repo. Configure here.