From 7625e0c1366279786125cc91adaa48c3e6929e75 Mon Sep 17 00:00:00 2001 From: "Bob (CC)" Date: Tue, 28 Jul 2026 15:39:45 +0200 Subject: [PATCH 1/3] feat(radar): add native Radar screen (status, sources, findings, inert controls) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Smallest visible slice of AI Radar (issue 9854ad5f) inside Buzz itself: a preview-gated sidebar entry (Settings -> Experiments, off by default, matching Pulse/Projects) showing the real current state ("waiting for X access"), last/next scan, watched sources, and a findings feed where each item is tagged "Buzz update" vs "X watch" per Chief's direction. Scan now / Pause / Resume / Edit sources controls are present but inert (toast explains they're not wired up yet) — Scotty connects them to the real collector/status contract next. Sources and findings show clearly-labeled example data so the full shape is visible before real data exists; status stays honest (no fake "running"). Seam for the next handoff: desktop/src/features/radar/hooks/useRadarStatus.ts Signed-off-by: Bob (CC) --- desktop/src/app/AppShell.helpers.ts | 10 +- desktop/src/app/AppShell.tsx | 2 + .../src/app/navigation/useAppNavigation.ts | 12 + desktop/src/app/routeTree.gen.ts | 21 ++ desktop/src/app/routes.ts | 1 + desktop/src/app/routes/radar.tsx | 25 ++ .../features/radar/hooks/useRadarStatus.ts | 80 ++++++ desktop/src/features/radar/lib/radarTypes.ts | 31 +++ desktop/src/features/radar/ui/RadarScreen.tsx | 18 ++ desktop/src/features/radar/ui/RadarView.tsx | 263 ++++++++++++++++++ .../src/features/sidebar/ui/AppSidebar.tsx | 6 +- .../sidebar/ui/AppSidebarPinnedHeader.tsx | 28 +- preview-features.json | 6 + 13 files changed, 499 insertions(+), 4 deletions(-) create mode 100644 desktop/src/app/routes/radar.tsx create mode 100644 desktop/src/features/radar/hooks/useRadarStatus.ts create mode 100644 desktop/src/features/radar/lib/radarTypes.ts create mode 100644 desktop/src/features/radar/ui/RadarScreen.tsx create mode 100644 desktop/src/features/radar/ui/RadarView.tsx diff --git a/desktop/src/app/AppShell.helpers.ts b/desktop/src/app/AppShell.helpers.ts index b0ce894931..770e08130a 100644 --- a/desktop/src/app/AppShell.helpers.ts +++ b/desktop/src/app/AppShell.helpers.ts @@ -9,7 +9,8 @@ export type AppView = | "agents" | "workflows" | "pulse" - | "projects"; + | "projects" + | "radar"; const WINDOW_DRAG_HANDLE_HEIGHT = 44; const TAURI_DRAG_REGION_ATTR = "data-tauri-drag-region"; @@ -153,6 +154,13 @@ export function deriveShellRoute(pathname: string): { }; } + if (pathname === "/radar") { + return { + selectedChannelId: null, + selectedView: "radar", + }; + } + return { selectedChannelId: null, selectedView: "home", diff --git a/desktop/src/app/AppShell.tsx b/desktop/src/app/AppShell.tsx index 877cd948ad..cce7c7abec 100644 --- a/desktop/src/app/AppShell.tsx +++ b/desktop/src/app/AppShell.tsx @@ -129,6 +129,7 @@ export function AppShell() { goNewMessage, goProjects, goPulse, + goRadar, goSettings, goWorkflows, closeSettings, @@ -881,6 +882,7 @@ export function AppShell() { onSelectHome={() => void goHome()} onSelectProjects={() => void goProjects()} onSelectPulse={() => void goPulse()} + onSelectRadar={() => void goRadar()} onSelectSettings={handleOpenSettings} onSelectWorkflows={() => void goWorkflows()} onSetPresenceStatus={(status) => diff --git a/desktop/src/app/navigation/useAppNavigation.ts b/desktop/src/app/navigation/useAppNavigation.ts index f928970610..2bc2bb8e7e 100644 --- a/desktop/src/app/navigation/useAppNavigation.ts +++ b/desktop/src/app/navigation/useAppNavigation.ts @@ -79,6 +79,17 @@ export function useAppNavigation() { [commitNavigation], ); + const goRadar = React.useCallback( + (behavior?: NavigationBehavior) => + commitNavigation( + { + to: "/radar", + }, + behavior, + ), + [commitNavigation], + ); + const goProjects = React.useCallback( (behavior?: NavigationBehavior) => commitNavigation( @@ -303,6 +314,7 @@ export function useAppNavigation() { goProject, goProjects, goPulse, + goRadar, goSettings, goWorkflow, goWorkflows, diff --git a/desktop/src/app/routeTree.gen.ts b/desktop/src/app/routeTree.gen.ts index 2bc2c8ddb6..1bbd4a2471 100644 --- a/desktop/src/app/routeTree.gen.ts +++ b/desktop/src/app/routeTree.gen.ts @@ -8,6 +8,7 @@ import { Route as rootRouteImport } from "./routes/root"; import { Route as workflowsRouteImport } from "./routes/workflows"; import { Route as settingsRouteImport } from "./routes/settings"; import { Route as remindersRouteImport } from "./routes/reminders"; +import { Route as radarRouteImport } from "./routes/radar"; import { Route as pulseRouteImport } from "./routes/pulse"; import { Route as projectsRouteImport } from "./routes/projects"; import { Route as agentsRouteImport } from "./routes/agents"; @@ -33,6 +34,11 @@ const remindersRoute = remindersRouteImport.update({ path: "/reminders", getParentRoute: () => rootRouteImport, } as any); +const radarRoute = radarRouteImport.update({ + id: "/radar", + path: "/radar", + getParentRoute: () => rootRouteImport, +} as any); const pulseRoute = pulseRouteImport.update({ id: "/pulse", path: "/pulse", @@ -85,6 +91,7 @@ export interface FileRoutesByFullPath { "/agents": typeof agentsRoute; "/projects": typeof projectsRoute; "/pulse": typeof pulseRoute; + "/radar": typeof radarRoute; "/reminders": typeof remindersRoute; "/settings": typeof settingsRoute; "/workflows": typeof workflowsRoute; @@ -99,6 +106,7 @@ export interface FileRoutesByTo { "/agents": typeof agentsRoute; "/projects": typeof projectsRoute; "/pulse": typeof pulseRoute; + "/radar": typeof radarRoute; "/reminders": typeof remindersRoute; "/settings": typeof settingsRoute; "/workflows": typeof workflowsRoute; @@ -114,6 +122,7 @@ export interface FileRoutesById { "/agents": typeof agentsRoute; "/projects": typeof projectsRoute; "/pulse": typeof pulseRoute; + "/radar": typeof radarRoute; "/reminders": typeof remindersRoute; "/settings": typeof settingsRoute; "/workflows": typeof workflowsRoute; @@ -130,6 +139,7 @@ export interface FileRouteTypes { | "/agents" | "/projects" | "/pulse" + | "/radar" | "/reminders" | "/settings" | "/workflows" @@ -144,6 +154,7 @@ export interface FileRouteTypes { | "/agents" | "/projects" | "/pulse" + | "/radar" | "/reminders" | "/settings" | "/workflows" @@ -158,6 +169,7 @@ export interface FileRouteTypes { | "/agents" | "/projects" | "/pulse" + | "/radar" | "/reminders" | "/settings" | "/workflows" @@ -173,6 +185,7 @@ export interface RootRouteChildren { agentsRoute: typeof agentsRoute; projectsRoute: typeof projectsRoute; pulseRoute: typeof pulseRoute; + radarRoute: typeof radarRoute; remindersRoute: typeof remindersRoute; settingsRoute: typeof settingsRoute; workflowsRoute: typeof workflowsRoute; @@ -206,6 +219,13 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof remindersRouteImport; parentRoute: typeof rootRouteImport; }; + "/radar": { + id: "/radar"; + path: "/radar"; + fullPath: "/radar"; + preLoaderRoute: typeof radarRouteImport; + parentRoute: typeof rootRouteImport; + }; "/pulse": { id: "/pulse"; path: "/pulse"; @@ -277,6 +297,7 @@ const rootRouteChildren: RootRouteChildren = { agentsRoute: agentsRoute, projectsRoute: projectsRoute, pulseRoute: pulseRoute, + radarRoute: radarRoute, remindersRoute: remindersRoute, settingsRoute: settingsRoute, workflowsRoute: workflowsRoute, diff --git a/desktop/src/app/routes.ts b/desktop/src/app/routes.ts index f5c6938e11..333295d035 100644 --- a/desktop/src/app/routes.ts +++ b/desktop/src/app/routes.ts @@ -4,6 +4,7 @@ export const routes = rootRoute("root.tsx", [ index("index.tsx"), route("/agents", "agents.tsx"), route("/pulse", "pulse.tsx"), + route("/radar", "radar.tsx"), route("/reminders", "reminders.tsx"), route("/settings", "settings.tsx"), route("/workflows", "workflows.tsx"), diff --git a/desktop/src/app/routes/radar.tsx b/desktop/src/app/routes/radar.tsx new file mode 100644 index 0000000000..c023ea48d4 --- /dev/null +++ b/desktop/src/app/routes/radar.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { createFileRoute } from "@tanstack/react-router"; + +import { usePreviewFeatureWarning } from "@/shared/features"; +import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback"; + +const RadarScreen = React.lazy(async () => { + const module = await import("@/features/radar/ui/RadarScreen"); + return { default: module.RadarScreen }; +}); + +export const Route = createFileRoute("/radar")({ + component: RadarRouteComponent, +}); + +function RadarRouteComponent() { + usePreviewFeatureWarning("radar"); + return ( + } + > + + + ); +} diff --git a/desktop/src/features/radar/hooks/useRadarStatus.ts b/desktop/src/features/radar/hooks/useRadarStatus.ts new file mode 100644 index 0000000000..dfd2ee9e7e --- /dev/null +++ b/desktop/src/features/radar/hooks/useRadarStatus.ts @@ -0,0 +1,80 @@ +import * as React from "react"; + +import type { RadarSnapshot } from "@/features/radar/lib/radarTypes"; + +// Bob's slice ends here: this is the seam Scotty wires to the real +// collector/status contract (Buzz events or a narrow local API — see +// issue 9854ad5f). The status block is genuinely "waiting for X access" — +// that's today's real state, not a placeholder. Sources and findings below +// ARE placeholders (per Chief's direction) so the full shape of the screen +// is visible before Scotty wires either source in; the UI marks them as +// examples so they're never mistaken for live data. +async function fetchRadarSnapshot(): Promise { + return { + status: { + state: "waiting_for_x_access", + lastScanAt: null, + nextScanAt: null, + lastError: null, + }, + sources: [ + { id: "example-source-1", label: "@AnthropicAI (X account)" }, + { id: "example-source-2", label: 'search: "buzz nostr"' }, + ], + findings: [ + { + id: "example-finding-1", + url: "https://github.com/block/buzz/releases", + summary: "Buzz shipped use-limited invite links", + whyItMatters: + "Makes it safer to share an invite link publicly without it being reused indefinitely.", + chiefsTake: "Worth turning on for any channel we link from outside Buzz.", + foundAt: "2026-07-27T12:00:00.000Z", + source: "buzz_update", + }, + { + id: "example-finding-2", + url: "https://x.com/example/status/0", + summary: "An X account discusses a new agent-orchestration pattern", + whyItMatters: + "Relevant to how Buzz agents hand off work to each other.", + chiefsTake: null, + foundAt: "2026-07-27T09:00:00.000Z", + source: "x_watch", + }, + ], + }; +} + +export function useRadarStatus(): { + snapshot: RadarSnapshot | null; + error: string | null; + refresh: () => void; +} { + const [snapshot, setSnapshot] = React.useState(null); + const [error, setError] = React.useState(null); + + const fetchOnce = React.useCallback(() => { + let cancelled = false; + (async () => { + try { + const value = await fetchRadarSnapshot(); + if (!cancelled) { + setSnapshot(value); + setError(null); + } + } catch (err) { + if (!cancelled) { + setError(err instanceof Error ? err.message : String(err)); + } + } + })(); + return () => { + cancelled = true; + }; + }, []); + + React.useEffect(() => fetchOnce(), [fetchOnce]); + + return { snapshot, error, refresh: fetchOnce }; +} diff --git a/desktop/src/features/radar/lib/radarTypes.ts b/desktop/src/features/radar/lib/radarTypes.ts new file mode 100644 index 0000000000..b2c577602d --- /dev/null +++ b/desktop/src/features/radar/lib/radarTypes.ts @@ -0,0 +1,31 @@ +export type RadarRunState = "running" | "paused" | "waiting_for_x_access"; + +export type RadarStatus = { + state: RadarRunState; + lastScanAt: string | null; + nextScanAt: string | null; + lastError: string | null; +}; + +export type RadarSource = { + id: string; + label: string; +}; + +export type RadarFindingSource = "buzz_update" | "x_watch"; + +export type RadarFinding = { + id: string; + url: string; + summary: string; + whyItMatters: string; + chiefsTake: string | null; + foundAt: string; + source: RadarFindingSource; +}; + +export type RadarSnapshot = { + status: RadarStatus; + sources: RadarSource[]; + findings: RadarFinding[]; +}; diff --git a/desktop/src/features/radar/ui/RadarScreen.tsx b/desktop/src/features/radar/ui/RadarScreen.tsx new file mode 100644 index 0000000000..6d8480a7a2 --- /dev/null +++ b/desktop/src/features/radar/ui/RadarScreen.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; + +import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback"; + +const RadarView = React.lazy(async () => { + const module = await import("@/features/radar/ui/RadarView"); + return { default: module.RadarView }; +}); + +export function RadarScreen() { + return ( +
+ }> + + +
+ ); +} diff --git a/desktop/src/features/radar/ui/RadarView.tsx b/desktop/src/features/radar/ui/RadarView.tsx new file mode 100644 index 0000000000..056b24af67 --- /dev/null +++ b/desktop/src/features/radar/ui/RadarView.tsx @@ -0,0 +1,263 @@ +import { Pause, Play, Radar as RadarIcon, RefreshCw } from "lucide-react"; +import { toast } from "sonner"; + +import { useRadarStatus } from "@/features/radar/hooks/useRadarStatus"; +import type { + RadarFinding, + RadarFindingSource, + RadarRunState, + RadarSource, +} from "@/features/radar/lib/radarTypes"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; +import { Card } from "@/shared/ui/card"; +import { Skeleton } from "@/shared/ui/skeleton"; + +// Scotty wires these buttons up to the real collector. Until then, every +// control is visibly present but honestly inert. +function notWiredYet(action: string) { + toast.info(`${action} isn't wired up yet — that's Scotty's part, next.`); +} + +const FINDING_SOURCE_COPY: Record< + RadarFindingSource, + { label: string; variant: "secondary" | "info" } +> = { + buzz_update: { label: "Buzz update", variant: "secondary" }, + x_watch: { label: "X watch", variant: "info" }, +}; + +const STATE_COPY: Record< + RadarRunState, + { label: string; variant: "success" | "secondary" | "warning"; blurb: string } +> = { + running: { + label: "Running", + variant: "success", + blurb: "Radar is actively scanning its watched sources.", + }, + paused: { + label: "Paused", + variant: "secondary", + blurb: "Radar is paused. Resume it to pick scanning back up.", + }, + waiting_for_x_access: { + label: "Waiting for X access", + variant: "warning", + blurb: + "Radar is safely paused because no enrolled X app is connected yet. It will start scanning once one is.", + }, +}; + +function formatTimestamp(value: string | null): string { + if (!value) return "—"; + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) return "—"; + return parsed.toLocaleString(); +} + +function StatusCard({ + state, + lastScanAt, + nextScanAt, + lastError, + onRefresh, + isRefreshing, +}: { + state: RadarRunState; + lastScanAt: string | null; + nextScanAt: string | null; + lastError: string | null; + onRefresh: () => void; + isRefreshing: boolean; +}) { + const copy = STATE_COPY[state]; + return ( + +
+
+ + {copy.label} +
+ +
+

{copy.blurb}

+
+ Last scan: {formatTimestamp(lastScanAt)} + Next scan: {formatTimestamp(nextScanAt)} +
+ {lastError ? ( +

{lastError}

+ ) : null} +
+ + +
+
+ ); +} + +function SourcesSection({ sources }: { sources: RadarSource[] }) { + return ( +
+
+

+ Watched sources +

+ +
+ {sources.length === 0 ? ( + + No sources configured yet. + + ) : ( +
+

+ Example — Scotty connects the real watch list next. +

+ {sources.map((source) => ( + + {source.label} + + ))} +
+ )} +
+ ); +} + +function FindingsSection({ findings }: { findings: RadarFinding[] }) { + return ( +
+

+ Recent finds +

+ {findings.length === 0 ? ( + + Nothing yet — finds will show up here after the first scan. + + ) : ( +
+

+ Example — real finds replace these once Scotty wires the sources. +

+ {findings.map((finding) => { + const sourceCopy = FINDING_SOURCE_COPY[finding.source]; + return ( + +
+ + {sourceCopy.label} + +
+ + {finding.summary} + +

+ {finding.whyItMatters} +

+ {finding.chiefsTake ? ( +

+ Chief's take: {finding.chiefsTake} +

+ ) : null} +
+ ); + })} +
+ )} +
+ ); +} + +function RadarViewSkeleton() { + return ( +
+ + + + + + + + + +
+ ); +} + +export function RadarView() { + const { snapshot, error, refresh } = useRadarStatus(); + + return ( +
+
+

Radar

+
+ + {error ? ( + + Couldn't load Radar status: {error} + + ) : null} + + {!snapshot ? ( + + ) : ( +
+ + + +
+ )} +
+ ); +} diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index 55f467f215..2b8466038b 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -105,7 +105,8 @@ type AppSidebarProps = { | "agents" | "workflows" | "pulse" - | "projects"; + | "projects" + | "radar"; unreadChannelCounts: ReadonlyMap; unreadChannelIds: ReadonlySet; communities: Community[]; @@ -145,6 +146,7 @@ type AppSidebarProps = { onSelectAgents: () => void; onSelectProjects: () => void; onSelectPulse: () => void; + onSelectRadar: () => void; onSelectWorkflows: () => void; onSelectHome: () => void; onSelectChannel: (channelId: string) => void; @@ -214,6 +216,7 @@ export function AppSidebar({ onSelectAgents, onSelectProjects, onSelectPulse, + onSelectRadar, onSelectWorkflows, onSelectHome, onSelectChannel, @@ -612,6 +615,7 @@ export function AppSidebar({ onSelectHome={onSelectHome} onSelectProjects={onSelectProjects} onSelectPulse={onSelectPulse} + onSelectRadar={onSelectRadar} onSelectWorkflows={onSelectWorkflows} selectedView={selectedView} /> diff --git a/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx b/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx index 95a0a47ef1..d3186600dd 100644 --- a/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx @@ -1,4 +1,11 @@ -import { Activity, Bell, Bot, FolderGit2, Zap } from "lucide-react"; +import { + Activity, + Bell, + Bot, + FolderGit2, + Radar as RadarIcon, + Zap, +} from "lucide-react"; import { TopbarSearch } from "@/features/search/ui/TopbarSearch"; import { FeatureGate } from "@/shared/features"; @@ -19,7 +26,8 @@ type SidebarSelectedView = | "agents" | "workflows" | "pulse" - | "projects"; + | "projects" + | "radar"; type AppSidebarPinnedHeaderProps = { channelLabels: Record; @@ -41,6 +49,7 @@ type AppSidebarPrimaryMenuProps = { onSelectHome: () => void; onSelectProjects: () => void; onSelectPulse: () => void; + onSelectRadar: () => void; onSelectWorkflows: () => void; selectedView: SidebarSelectedView; }; @@ -86,6 +95,7 @@ export function AppSidebarPrimaryMenu({ onSelectHome, onSelectProjects, onSelectPulse, + onSelectRadar, onSelectWorkflows, selectedView, }: AppSidebarPrimaryMenuProps) { @@ -129,6 +139,20 @@ export function AppSidebarPrimaryMenu({ + + + + + Radar + + + Date: Wed, 29 Jul 2026 04:15:50 +0200 Subject: [PATCH 2/3] feat(radar): show live Buzz updates Signed-off-by: Atlas (CO) --- desktop/src/app/routes/radar.tsx | 4 +- .../features/radar/hooks/useRadarStatus.ts | 104 ++++++++++++------ desktop/src/features/radar/ui/RadarView.tsx | 24 +--- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/desktop/src/app/routes/radar.tsx b/desktop/src/app/routes/radar.tsx index c023ea48d4..4b51391d39 100644 --- a/desktop/src/app/routes/radar.tsx +++ b/desktop/src/app/routes/radar.tsx @@ -16,9 +16,7 @@ export const Route = createFileRoute("/radar")({ function RadarRouteComponent() { usePreviewFeatureWarning("radar"); return ( - } - > + }> ); diff --git a/desktop/src/features/radar/hooks/useRadarStatus.ts b/desktop/src/features/radar/hooks/useRadarStatus.ts index dfd2ee9e7e..eb211765fa 100644 --- a/desktop/src/features/radar/hooks/useRadarStatus.ts +++ b/desktop/src/features/radar/hooks/useRadarStatus.ts @@ -1,48 +1,86 @@ import * as React from "react"; -import type { RadarSnapshot } from "@/features/radar/lib/radarTypes"; +import type { + RadarFinding, + RadarSnapshot, +} from "@/features/radar/lib/radarTypes"; + +const BUZZ_COMMITS_URL = + "https://api.github.com/repos/dschwartzAI/buzz/commits?sha=main&per_page=20"; + +type GithubCommit = { + sha?: string; + html_url?: string; + commit?: { + message?: string; + author?: { date?: string | null }; + }; +}; + +function firstUsefulSentence(value: string | null | undefined): string | null { + const normalized = value + ?.replace(//g, "") + .replace(/[#*_`>\r\n]+/g, " ") + .replace(/\s+/g, " ") + .trim(); + if (!normalized) return null; + const sentence = normalized.match(/^.*?[.!?](?:\s|$)/)?.[0] ?? normalized; + return sentence.slice(0, 240); +} + +function findingsFromMainCommits(value: unknown): RadarFinding[] { + if (!Array.isArray(value)) return []; + + return value.flatMap((candidate) => { + const entry = candidate as GithubCommit; + const message = entry.commit?.message?.trim(); + const title = message?.split("\n", 1)[0]?.trim(); + const date = entry.commit?.author?.date; + if ( + typeof entry.sha !== "string" || + typeof entry.html_url !== "string" || + !title || + typeof date !== "string" + ) { + return []; + } + + const detail = message?.slice(title.length).trim(); + return [ + { + id: `buzz-commit-${entry.sha}`, + url: entry.html_url, + summary: title, + whyItMatters: + firstUsefulSentence(detail) ?? "This shipped on Buzz's main branch.", + chiefsTake: null, + foundAt: date, + source: "buzz_update" as const, + }, + ]; + }); +} -// Bob's slice ends here: this is the seam Scotty wires to the real -// collector/status contract (Buzz events or a narrow local API — see -// issue 9854ad5f). The status block is genuinely "waiting for X access" — -// that's today's real state, not a placeholder. Sources and findings below -// ARE placeholders (per Chief's direction) so the full shape of the screen -// is visible before Scotty wires either source in; the UI marks them as -// examples so they're never mistaken for live data. async function fetchRadarSnapshot(): Promise { + const response = await fetch(BUZZ_COMMITS_URL, { + headers: { Accept: "application/vnd.github+json" }, + }); + if (!response.ok) { + throw new Error(`Buzz updates returned HTTP ${response.status}`); + } + return { status: { state: "waiting_for_x_access", - lastScanAt: null, + lastScanAt: new Date().toISOString(), nextScanAt: null, lastError: null, }, sources: [ - { id: "example-source-1", label: "@AnthropicAI (X account)" }, - { id: "example-source-2", label: 'search: "buzz nostr"' }, - ], - findings: [ - { - id: "example-finding-1", - url: "https://github.com/block/buzz/releases", - summary: "Buzz shipped use-limited invite links", - whyItMatters: - "Makes it safer to share an invite link publicly without it being reused indefinitely.", - chiefsTake: "Worth turning on for any channel we link from outside Buzz.", - foundAt: "2026-07-27T12:00:00.000Z", - source: "buzz_update", - }, - { - id: "example-finding-2", - url: "https://x.com/example/status/0", - summary: "An X account discusses a new agent-orchestration pattern", - whyItMatters: - "Relevant to how Buzz agents hand off work to each other.", - chiefsTake: null, - foundAt: "2026-07-27T09:00:00.000Z", - source: "x_watch", - }, + { id: "buzz-main", label: "Buzz updates merged to main" }, + { id: "x-watch", label: "X watch (waiting for enrolled API access)" }, ], + findings: findingsFromMainCommits(await response.json()), }; } diff --git a/desktop/src/features/radar/ui/RadarView.tsx b/desktop/src/features/radar/ui/RadarView.tsx index 056b24af67..7b4094b229 100644 --- a/desktop/src/features/radar/ui/RadarView.tsx +++ b/desktop/src/features/radar/ui/RadarView.tsx @@ -13,10 +13,10 @@ import { Button } from "@/shared/ui/button"; import { Card } from "@/shared/ui/card"; import { Skeleton } from "@/shared/ui/skeleton"; -// Scotty wires these buttons up to the real collector. Until then, every -// control is visibly present but honestly inert. function notWiredYet(action: string) { - toast.info(`${action} isn't wired up yet — that's Scotty's part, next.`); + toast.info( + `${action} needs the VPS collector bridge, which is not connected yet.`, + ); } const FINDING_SOURCE_COPY: Record< @@ -96,9 +96,7 @@ function StatusCard({ Last scan: {formatTimestamp(lastScanAt)} Next scan: {formatTimestamp(nextScanAt)} - {lastError ? ( -

{lastError}

- ) : null} + {lastError ?

{lastError}

: null}