From e1ba305742e12ece824a48246031da54c93a21e8 Mon Sep 17 00:00:00 2001 From: Wintermute Date: Mon, 31 Aug 2026 21:21:51 -0400 Subject: [PATCH] refactor(desktop): simplify market channel listing Signed-off-by: Wintermute --- .../src/features/market/ui/MarketScreen.tsx | 532 +++++++----------- .../e2e/market-prototype-screenshots.spec.ts | 45 +- 2 files changed, 248 insertions(+), 329 deletions(-) diff --git a/desktop/src/features/market/ui/MarketScreen.tsx b/desktop/src/features/market/ui/MarketScreen.tsx index 4c74f1a2f2..5fa22e46bc 100644 --- a/desktop/src/features/market/ui/MarketScreen.tsx +++ b/desktop/src/features/market/ui/MarketScreen.tsx @@ -1,49 +1,41 @@ -import { useNavigate } from "@tanstack/react-router"; import { Bot, - Check, - CircleDollarSign, - Clock3, - LockKeyhole, MessageSquareOff, PackageCheck, Radio, - ReceiptText, - Scale, - ShieldCheck, Store, TriangleAlert, - WalletCards, } from "lucide-react"; import * as React from "react"; import { ChatHeader } from "@/features/chat/ui/ChatHeader"; import { MARKET_SCENARIOS, - MARKET_SCENARIO_IDS, type MarketActivity, + type MarketScenario, type MarketScenarioId, - type MarketTerm, } from "@/features/market/lib/marketPrototypeData"; -import { ProjectContextRail } from "@/features/projects/ui/ProjectContextRail"; -import { ProjectHomeColumn } from "@/features/projects/ui/ProjectHomeColumn"; -import { useThreadPanelWidth } from "@/shared/hooks/useThreadPanelWidth"; -import { SIDEBAR_WIDTH_MIN } from "@/shared/layout/sidebarLayout"; import { cn } from "@/shared/lib/cn"; import { UserAvatar } from "@/shared/ui/UserAvatar"; import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/shared/ui/dialog"; +import { Input } from "@/shared/ui/input"; import { useOptionalSidebar } from "@/shared/ui/sidebar"; +import { Textarea } from "@/shared/ui/textarea"; -const MARKET_CONTEXT_WIDTH_KEY = "buzz.desktop.market-context-width"; - -const SCENARIO_LABELS: Record = { - finite: "Fixed · finite", - unlimited: "Fixed · unlimited", - auction: "Auction", - tender: "Tender", - awarded: "Awarded edge", -}; +const REPRESENTING_AGENTS = [ + { name: "Forensic Finch", role: "Research & incident analysis" }, + { name: "Cartograph", role: "Repository mapping" }, + { name: "Sentinel", role: "Operations & safety" }, +] as const; const COMMERCIAL_TERM_LABELS = new Set([ "Price", @@ -55,37 +47,6 @@ const COMMERCIAL_TERM_LABELS = new Set([ "Award count", ]); -const WALLET_DETAILS: Record< - MarketScenarioId, - { account: string; balance: string; settlement: string } -> = { - finite: { - account: "escrow1report…13c8", - balance: "150 sats funded", - settlement: "1 paid · 2 reserved", - }, - unlimited: { - account: "wallet1mapper…c241", - balance: "80 sats escrowed", - settlement: "760 sats paid", - }, - auction: { - account: "escrow1strings…9f10", - balance: "600 sats funded", - settlement: "Held until award", - }, - tender: { - account: "escrow1tender…1b73", - balance: "2,000 sats funded", - settlement: "Held during selection", - }, - awarded: { - account: "escrow1tender…1b73", - balance: "1,750 sats escrowed", - settlement: "Release on signed receipt", - }, -}; - const ACTIVITY_STYLE: Record< MarketActivity["state"], { @@ -113,26 +74,9 @@ const ACTIVITY_STYLE: Record< export function MarketScreen({ scenarioId }: { scenarioId: MarketScenarioId }) { const scenario = MARKET_SCENARIOS[scenarioId]; - const navigate = useNavigate(); const sidebar = useOptionalSidebar(); - const contextWidth = useThreadPanelWidth(undefined, { - minWidthPx: SIDEBAR_WIDTH_MIN, - sessionKey: MARKET_CONTEXT_WIDTH_KEY, - }); + const [createOpen, setCreateOpen] = React.useState(false); const isTerminal = scenario.status !== "Open"; - const contractTerms = scenario.terms.filter( - (term) => !COMMERCIAL_TERM_LABELS.has(term.label), - ); - const commercialTerms = scenario.terms.filter((term) => - COMMERCIAL_TERM_LABELS.has(term.label), - ); - - const selectScenario = React.useCallback( - (nextScenario: MarketScenarioId) => { - void navigate({ to: "/market", search: { scenario: nextScenario } }); - }, - [navigate], - ); return (
- Agents participate · Humans - observe - - } channelType="stream" description={scenario.summary} leadingContent={} - statusBadge={} title={scenario.title} /> - -
-
-
-
- {scenario.eyebrow} - - {scenario.contractId} - -
-

- {scenario.summary} -

-
-
-
- + setCreateOpen(true)} + scenario={scenario} />
@@ -222,6 +127,12 @@ export function MarketScreen({ scenarioId }: { scenarioId: MarketScenarioId }) {
+ +
@@ -243,238 +154,231 @@ export function MarketScreen({ scenarioId }: { scenarioId: MarketScenarioId }) {
- - - - - -
); } -function ContractCard({ - closeAt, - mode, - terms, +function OfferCard({ + onCreate, + scenario, }: { - closeAt: string; - mode: string; - terms: MarketTerm[]; + onCreate: () => void; + scenario: MarketScenario; }) { + const author = + scenario.terms.find( + (term) => term.label === "Seller" || term.label === "Requester", + )?.value ?? "Market agent"; + const commercial = scenario.terms.find((term) => + COMMERCIAL_TERM_LABELS.has(term.label), + ); + const listingKind = scenario.eyebrow.startsWith("Offer") + ? "Agent offer" + : "Agent request"; + return ( -
-
-
-
- -

Market contract

+
+
+
+
+ + Product image
-

- Immutable signed scope · material changes require v2 -

-
- - {closeAt} - - v1 locked +
+ + {listingKind} · {scenario.mode} + +

+ {scenario.title} +

+

+ {scenario.summary} +

+
+

+ {commercial?.value ?? scenario.direction} +

+

+ {scenario.direction} +

+
+
+
+ +
+

{author}

+

+ Created this {listingKind.toLowerCase()} +

+
+
+ +
-
- -
); } -function MarketContextPanel({ - commercialTerms, - direction, - liveMetrics, - scenarioId, - status, - statusDetail, +function CreateMarketDialog({ + onOpenChange, + open, + scenario, }: { - commercialTerms: MarketTerm[]; - direction: string; - liveMetrics: MarketTerm[]; - scenarioId: MarketScenarioId; - status: "Open" | "Closed" | "Awarded" | "Fulfilled"; - statusDetail: string; + onOpenChange: (open: boolean) => void; + open: boolean; + scenario: MarketScenario; }) { - const wallet = WALLET_DETAILS[scenarioId]; + const [selectedAgent, setSelectedAgent] = React.useState( + REPRESENTING_AGENTS[0].name, + ); + return ( -
-
-
-
- -

Market context

-
- -
- -

- {direction} -

-
- -
-
+ + + + Create a market + + Publish an offer or request through an agent representing you. + + - -
- {liveMetrics.map((term) => ( -
+
+ Market type +
+
- ))} -
-
- - - {statusDetail}. Client timers never determine acceptance. - -
-
+ Offer + + Sell a product or service + + + +
+ - -
- - - - -
-
-
-
+
+ + + + + + +
+ +