From eb97fa7c7fb114eadf8d9a07457be75513bb4682 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:46:48 -0300 Subject: [PATCH 1/2] feat(app): chat card primitives ui/tone.ts holds one class table per tone; ui/ChatCard.tsx has the round icon, the compact request card, the card in sections, the note fields and the settled line. The approval, site, help, plan, suggestion, routine request and question cards are built from them. --- app/src/features/browser/HelpCard.tsx | 51 ++--- app/src/features/browser/SiteCard.tsx | 52 ++--- app/src/features/chat/ApprovalCard.tsx | 64 +++--- app/src/features/chat/PlanCard.tsx | 91 ++++----- app/src/features/chat/SuggestionCard.tsx | 119 +++++------ app/src/features/questions/QuestionCard.tsx | 146 ++++++------- .../features/routines/RoutineRequestCard.tsx | 117 +++++------ app/src/ui/Badge.tsx | 41 ++++ app/src/ui/ChatCard.tsx | 193 ++++++++++++++++++ app/src/ui/tone.ts | 63 ++++++ 10 files changed, 551 insertions(+), 386 deletions(-) create mode 100644 app/src/ui/Badge.tsx create mode 100644 app/src/ui/ChatCard.tsx create mode 100644 app/src/ui/tone.ts diff --git a/app/src/features/browser/HelpCard.tsx b/app/src/features/browser/HelpCard.tsx index fd2937b4..07d038c7 100644 --- a/app/src/features/browser/HelpCard.tsx +++ b/app/src/features/browser/HelpCard.tsx @@ -3,10 +3,11 @@ // it" answer the request; once answered it shrinks to one line. import { Ban, Check, Hand, TimerOff } from "lucide-react"; -import { useContext, useId } from "react"; +import { useContext } from "react"; import { useT } from "../../i18n"; import type { ApprovalItem, Bot } from "../../lib/protocol.gen"; import { Button } from "../../ui/Button"; +import { NoteInput, RequestCard, SettledLine } from "../../ui/ChatCard"; import { useAnswer } from "../chat/useAnswer"; import { ShowBrowser } from "./showBrowser"; @@ -28,30 +29,15 @@ function read(approval: ApprovalItem): { task: string; site: string | null } { function Answered({ approval, task }: { approval: ApprovalItem; task: string }) { const words = useT().browser.help; - const line = "flex min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-sm"; switch (approval.status) { case "allowed": - return ( -

- - {words.doneLine(task)} -

- ); + return ; case "denied": return ( -

- - {words.wontLine(task)} - {approval.note && “{approval.note}”} -

+ ); default: - return ( -

- - {words.expired(task)} -

- ); + return ; } } @@ -63,23 +49,18 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot } allow: t.chat.approval.allowFailed, deny: t.chat.approval.denyFailed, }); - const noteId = useId(); const { task, site } = read(approval); if (approval.status !== "pending") { return ; } return ( -
-

- - - - {words.needs(bot.name)} -

{task}

@@ -89,15 +70,11 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot }

)}

{words.why}

- - setNote(event.target.value)} + onChange={setNote} placeholder={t.chat.approval.notePlaceholder} - className="mt-3 h-9 w-full rounded-xl border border-line-strong bg-canvas px-3 text-sm outline-none placeholder:text-muted focus:border-muted" />
{showBrowser && ( @@ -117,6 +94,6 @@ export function HelpCard({ approval, bot }: { approval: ApprovalItem; bot: Bot } {words.wontDo}
-
+ ); } diff --git a/app/src/features/browser/SiteCard.tsx b/app/src/features/browser/SiteCard.tsx index d2a074e8..dd962230 100644 --- a/app/src/features/browser/SiteCard.tsx +++ b/app/src/features/browser/SiteCard.tsx @@ -2,10 +2,10 @@ // address, and Allow or Deny. Once answered it shrinks to one line. import { Ban, Check, Globe, TimerOff } from "lucide-react"; -import { useId } from "react"; import { useT } from "../../i18n"; import type { ApprovalItem, Bot } from "../../lib/protocol.gen"; import { Button } from "../../ui/Button"; +import { NoteInput, RequestCard, SettledLine } from "../../ui/ChatCard"; import { useAnswer } from "../chat/useAnswer"; /** The request the daemon opens from the browser tools. */ @@ -23,30 +23,15 @@ function addressOf(input: string): string | null { function Answered({ approval, site }: { approval: ApprovalItem; site: string }) { const words = useT().browser.site; - const line = "flex min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-sm"; switch (approval.status) { case "allowed": - return ( -

- - {words.allowed(site)} -

- ); + return ; case "denied": return ( -

- - {words.denied(site)} - {approval.note && “{approval.note}”} -

+ ); default: - return ( -

- - {words.expired(site)} -

- ); + return ; } } @@ -57,7 +42,6 @@ export function SiteCard({ approval, bot }: { approval: ApprovalItem; bot: Bot } allow: t.chat.approval.allowFailed, deny: t.chat.approval.denyFailed, }); - const noteId = useId(); const site = approval.summary; if (approval.status !== "pending") { return ; @@ -65,33 +49,27 @@ export function SiteCard({ approval, bot }: { approval: ApprovalItem; bot: Bot } const url = addressOf(approval.input); return ( -
-

- - - + {words.wants(bot.name)} {site} -

+ } + > {url && (

{url}

)}

{words.why}

- - setNote(event.target.value)} + onChange={setNote} placeholder={t.chat.approval.notePlaceholder} - className="mt-3 h-9 w-full rounded-xl border border-line-strong bg-canvas px-3 text-sm outline-none placeholder:text-muted focus:border-muted" />
-
+ ); } diff --git a/app/src/features/chat/ApprovalCard.tsx b/app/src/features/chat/ApprovalCard.tsx index 5af4c107..1536d882 100644 --- a/app/src/features/chat/ApprovalCard.tsx +++ b/app/src/features/chat/ApprovalCard.tsx @@ -2,10 +2,10 @@ // and Deny. Once answered it shrinks to one line. import { Ban, Check, CheckCheck, Hand, TimerOff } from "lucide-react"; -import { useId } from "react"; import { useT } from "../../i18n"; import type { AllowScope, ApprovalItem, Bot } from "../../lib/protocol.gen"; import { Button } from "../../ui/Button"; +import { NoteInput, RequestCard, SettledLine } from "../../ui/ChatCard"; import { Details } from "../../ui/Details"; import { HELP_TOOL, HelpCard } from "../browser/HelpCard"; import { SITE_TOOL, SiteCard } from "../browser/SiteCard"; @@ -29,35 +29,28 @@ function About({ approval }: { approval: ApprovalItem }) { function Answered({ approval }: { approval: ApprovalItem }) { const t = useT(); const label = toolAction(approval.toolName, t.tools); - const line = "flex min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-sm"; switch (approval.status) { case "allowed": return ( -

- - {t.chat.approval.allowed(label)} - -

+ } + /> ); case "denied": return ( -

- - {t.chat.approval.denied(label)} - {approval.note ? ( - {`“${approval.note}”`} - ) : ( - - )} -

+ } + /> ); default: - return ( -

- - {t.chat.approval.expired(label)} -

- ); + return ; } } @@ -134,23 +127,18 @@ function ToolApproval({ approval, bot }: { approval: ApprovalItem; bot: Bot }) { allow: t.chat.approval.allowFailed, deny: t.chat.approval.denyFailed, }); - const noteId = useId(); if (approval.status !== "pending") { return ; } const label = toolAction(approval.toolName, t.tools); return ( -
-

- - - - {t.chat.approval.wants(bot.name, label)} -

{isCommand(approval.toolName) ? ( ) : ( @@ -165,15 +153,11 @@ function ToolApproval({ approval, bot }: { approval: ApprovalItem; bot: Bot }) { )} - - setNote(event.target.value)} + onChange={setNote} placeholder={t.chat.approval.notePlaceholder} - className="mt-3 h-9 w-full rounded-xl border border-line-strong bg-canvas px-3 text-sm outline-none placeholder:text-muted focus:border-muted" />
-
+ ); } diff --git a/app/src/features/chat/PlanCard.tsx b/app/src/features/chat/PlanCard.tsx index 44157ecd..e37252ab 100644 --- a/app/src/features/chat/PlanCard.tsx +++ b/app/src/features/chat/PlanCard.tsx @@ -2,11 +2,11 @@ // it as the ExitPlanMode tool, with the plan. Approving lets the bot work; // sending it back keeps it planning, with what should change. -import { Check, ChevronRight, ListTodo, PencilLine, TimerOff } from "lucide-react"; -import { useId } from "react"; +import { Check, ListTodo, PencilLine, TimerOff } from "lucide-react"; import { useT } from "../../i18n"; import type { ApprovalItem, Bot } from "../../lib/protocol.gen"; import { Button } from "../../ui/Button"; +import { NoteArea, SectionCard, SettledLine } from "../../ui/ChatCard"; import { Markdown } from "./Markdown"; import { useAnswer } from "./useAnswer"; @@ -34,77 +34,58 @@ export function PlanCard({ approval, bot }: { approval: ApprovalItem; bot: Bot } allow: p.approveFailed, deny: p.keepFailed, }); - const noteId = useId(); const plan = planText(approval.input); if (approval.status !== "pending") { return ; } return ( -
+ +
+ + +
+ + } > -

- - - - {p.ready(bot.name)} -

-
- -