| author | |
| committer | |
| log | 4749035bda13b4b6260499caedbc0d69a2f60e6f |
| tree | 2cd697819f084feb7bb33c886721500879cdf6fc |
| parent | 03950089ffa5cecf0ce731227f58551f6437495f |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 87 insertions(+), 76 deletions(-)
apps/web/src/components/ChatView.tsx+2| ... | ... | @@ -2201,6 +2201,7 @@ export default function ChatView(props: ChatViewProps) { |
| 2201 | 2201 | return { |
| 2202 | 2202 | id: `project-clone:${projectId}`, |
| 2203 | 2203 | variant: "info", |
| 2204 | compact: true, | |
| 2204 | 2205 | priority: "activity", |
| 2205 | 2206 | icon: <DownloadIcon />, |
| 2206 | 2207 | title: `Cloning ${name}`, |
| ... | ... | @@ -2224,6 +2225,7 @@ export default function ChatView(props: ChatViewProps) { |
| 2224 | 2225 | return { |
| 2225 | 2226 | id: `project-clone:${projectId}`, |
| 2226 | 2227 | variant: cancelled ? "warning" : "error", |
| 2228 | compact: true, | |
| 2227 | 2229 | icon: <DownloadIcon />, |
| 2228 | 2230 | title: cancelled ? `Cancelled cloning ${name}` : `Failed to clone ${name}`, |
| 2229 | 2231 | description: cancelled ? "Retry to bring in the repository." : activeProjectClone.error, |
apps/web/src/components/chat/ComposerBanner.tsx+1-1| ... | ... | @@ -257,7 +257,7 @@ function Actions({ className, ...props }: ComponentProps<"span">) { |
| 257 | 257 | className={cn( |
| 258 | 258 | "col-start-3 row-start-1 flex flex-wrap items-center justify-end gap-1", |
| 259 | 259 | "group-data-[composer-banner-layout=approval]/banner-row:self-center group-data-[composer-banner-layout=approval]/banner-row:gap-1.5 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:col-start-2 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:col-end-4 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:row-start-2", |
| 260 | "@max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:-ms-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:justify-start", | |
| 260 | "@max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:justify-end", | |
| 261 | 261 | "@max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:-ms-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:justify-start", |
| 262 | 262 | className, |
| 263 | 263 | )} |
apps/web/src/components/chat/ComposerBannerStack.tsx+20-15| ... | ... | @@ -13,6 +13,7 @@ export interface ComposerBannerStackItem { |
| 13 | 13 | readonly id: string; |
| 14 | 14 | readonly variant: ComposerBannerVariant; |
| 15 | 15 | readonly priority?: "urgent" | "activity" | "notice"; |
| 16 | readonly compact?: boolean; | |
| 16 | 17 | readonly icon: ReactNode; |
| 17 | 18 | readonly title: ReactNode; |
| 18 | 19 | readonly description?: ReactNode; |
| ... | ... | @@ -270,22 +271,20 @@ function ComposerBannerStackAlert({ |
| 270 | 271 | variant={item.variant} |
| 271 | 272 | density="comfortable" |
| 272 | 273 | > |
| 273 | <ComposerBanner.Row layout="wrap-actions-narrow"> | |
| 274 | <ComposerBanner.Row layout={item.compact ? "wrap-actions-narrow" : "wrap-actions"}> | |
| 274 | 275 | <ComposerBanner.Icon className="h-(--composer-banner-icon-column) self-start"> |
| 275 | 276 | {item.icon} |
| 276 | 277 | </ComposerBanner.Icon> |
| 277 | 278 | <ComposerBanner.Content className="whitespace-nowrap"> |
| 278 | <span | |
| 279 | className={cn( | |
| 280 | "min-w-0 font-medium leading-7 sm:leading-6", | |
| 281 | typeof item.title === "string" && "truncate", | |
| 282 | )} | |
| 283 | > | |
| 284 | {item.title} | |
| 285 | </span> | |
| 279 | <span className="min-w-0 truncate font-medium leading-7 sm:leading-6">{item.title}</span> | |
| 286 | 280 | {item.description ? ( |
| 287 | <> | |
| 288 | <span className="min-w-0 shrink-[9999] truncate text-muted-foreground @max-[400px]:sr-only"> | |
| 281 | <span className={item.compact ? "contents" : "flex min-w-8 flex-1 items-center gap-1"}> | |
| 282 | <span | |
| 283 | className={cn( | |
| 284 | "min-w-0 truncate text-muted-foreground", | |
| 285 | item.compact && "shrink-[9999] @max-[400px]:sr-only", | |
| 286 | )} | |
| 287 | > | |
| 289 | 288 | {item.description} |
| 290 | 289 | </span> |
| 291 | 290 | <Popover> |
| ... | ... | @@ -296,21 +295,27 @@ function ComposerBannerStackAlert({ |
| 296 | 295 | size="icon-xs" |
| 297 | 296 | variant="ghost" |
| 298 | 297 | aria-label="Show notice details" |
| 299 | className="hidden flex-none text-muted-foreground hover:text-foreground @max-[400px]:inline-flex" | |
| 298 | className={cn( | |
| 299 | "flex-none text-muted-foreground hover:text-foreground", | |
| 300 | item.compact && "hidden @max-[400px]:inline-flex", | |
| 301 | )} | |
| 300 | 302 | /> |
| 301 | 303 | } |
| 302 | 304 | > |
| 303 | 305 | <InfoIcon className="size-3.5" /> |
| 304 | 306 | </PopoverTrigger> |
| 305 | 307 | <PopoverPopup |
| 308 | aria-label="Notice details" | |
| 306 | 309 | tooltipStyle |
| 307 | 310 | side="top" |
| 308 | className="max-w-72 whitespace-normal text-pretty" | |
| 311 | className="max-w-80 whitespace-normal text-pretty wrap-anywhere" | |
| 309 | 312 | > |
| 310 | {item.description} | |
| 313 | <ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]"> | |
| 314 | {item.description} | |
| 315 | </ComposerBanner.Scroll> | |
| 311 | 316 | </PopoverPopup> |
| 312 | 317 | </Popover> |
| 313 | </> | |
| 318 | </span> | |
| 314 | 319 | ) : null} |
| 315 | 320 | </ComposerBanner.Content> |
| 316 | 321 | {item.actions || item.onDismiss ? ( |
apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx+63-59| ... | ... | @@ -228,66 +228,70 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard( |
| 228 | 228 | </ComposerBanner.Actions> |
| 229 | 229 | </CollapsibleTrigger> |
| 230 | 230 | <CollapsiblePanel> |
| 231 | <ComposerBanner.Body className="pe-1 pb-1"> | |
| 232 | <p className="text-sm text-foreground/85">{activeQuestion.question}</p> | |
| 233 | {activeQuestion.multiSelect ? ( | |
| 234 | <p className="mt-1 text-secondary-label text-xs">Select one or more options.</p> | |
| 235 | ) : null} | |
| 236 | <div className="mt-2 space-y-0.5"> | |
| 237 | {activeQuestion.options.map((option, index) => { | |
| 238 | const optionValue = option.value ?? option.label; | |
| 239 | const isOptimisticallySelected = | |
| 240 | optimisticSingleSelect?.questionId === activeQuestion.id && | |
| 241 | optimisticSingleSelect.optionValue === optionValue; | |
| 242 | const isSelected = | |
| 243 | isOptimisticallySelected || | |
| 244 | (!customAnswerActive && progress.selectedOptionValues.includes(optionValue)); | |
| 245 | const shortcutKey = index < 9 ? index + 1 : null; | |
| 246 | const className = cn( | |
| 247 | "group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-primary/25", | |
| 248 | isSelected | |
| 249 | ? "bg-muted/55 text-foreground" | |
| 250 | : "bg-transparent text-foreground/85 hover:bg-muted/30", | |
| 251 | isResponding && "opacity-50 cursor-not-allowed", | |
| 252 | !isResponding && "cursor-pointer", | |
| 253 | ); | |
| 254 | const content = ( | |
| 255 | <> | |
| 256 | <div className="min-w-0 flex-1 flex flex-col gap-0.5"> | |
| 257 | <span className="text-sm font-medium">{option.label}</span> | |
| 258 | {option.description && option.description !== option.label ? ( | |
| 259 | <span className="text-secondary-label text-[11px]">{option.description}</span> | |
| 231 | <ComposerBanner.Scroll> | |
| 232 | <ComposerBanner.Body className="pe-1 pb-1 wrap-anywhere"> | |
| 233 | <p className="text-sm text-foreground/85">{activeQuestion.question}</p> | |
| 234 | {activeQuestion.multiSelect ? ( | |
| 235 | <p className="mt-1 text-secondary-label text-xs">Select one or more options.</p> | |
| 236 | ) : null} | |
| 237 | <div className="mt-2 space-y-0.5"> | |
| 238 | {activeQuestion.options.map((option, index) => { | |
| 239 | const optionValue = option.value ?? option.label; | |
| 240 | const isOptimisticallySelected = | |
| 241 | optimisticSingleSelect?.questionId === activeQuestion.id && | |
| 242 | optimisticSingleSelect.optionValue === optionValue; | |
| 243 | const isSelected = | |
| 244 | isOptimisticallySelected || | |
| 245 | (!customAnswerActive && progress.selectedOptionValues.includes(optionValue)); | |
| 246 | const shortcutKey = index < 9 ? index + 1 : null; | |
| 247 | const className = cn( | |
| 248 | "group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-primary/25", | |
| 249 | isSelected | |
| 250 | ? "bg-muted/55 text-foreground" | |
| 251 | : "bg-transparent text-foreground/85 hover:bg-muted/30", | |
| 252 | isResponding && "opacity-50 cursor-not-allowed", | |
| 253 | !isResponding && "cursor-pointer", | |
| 254 | ); | |
| 255 | const content = ( | |
| 256 | <> | |
| 257 | <div className="min-w-0 flex-1 flex flex-col gap-0.5"> | |
| 258 | <span className="text-sm font-medium">{option.label}</span> | |
| 259 | {option.description && option.description !== option.label ? ( | |
| 260 | <span className="text-secondary-label text-[11px]"> | |
| 261 | {option.description} | |
| 262 | </span> | |
| 263 | ) : null} | |
| 264 | </div> | |
| 265 | {isSelected ? ( | |
| 266 | <CheckIcon className="size-3.5 shrink-0 text-primary" /> | |
| 267 | ) : shortcutKey !== null ? ( | |
| 268 | <kbd | |
| 269 | className={cn( | |
| 270 | "flex size-5 shrink-0 items-center justify-center text-[10px] font-medium text-muted-foreground tabular-nums", | |
| 271 | )} | |
| 272 | > | |
| 273 | {shortcutKey} | |
| 274 | </kbd> | |
| 260 | 275 | ) : null} |
| 261 | </div> | |
| 262 | {isSelected ? ( | |
| 263 | <CheckIcon className="size-3.5 shrink-0 text-primary" /> | |
| 264 | ) : shortcutKey !== null ? ( | |
| 265 | <kbd | |
| 266 | className={cn( | |
| 267 | "flex size-5 shrink-0 items-center justify-center text-[10px] font-medium text-muted-foreground tabular-nums", | |
| 268 | )} | |
| 269 | > | |
| 270 | {shortcutKey} | |
| 271 | </kbd> | |
| 272 | ) : null} | |
| 273 | </> | |
| 274 | ); | |
| 275 | return ( | |
| 276 | <button | |
| 277 | key={`${activeQuestion.id}:${optionValue}`} | |
| 278 | type="button" | |
| 279 | disabled={isResponding} | |
| 280 | onClick={() => { | |
| 281 | handleOptionSelection(activeQuestion.id, optionValue); | |
| 282 | }} | |
| 283 | className={className} | |
| 284 | > | |
| 285 | {content} | |
| 286 | </button> | |
| 287 | ); | |
| 288 | })} | |
| 289 | </div> | |
| 290 | </ComposerBanner.Body> | |
| 276 | </> | |
| 277 | ); | |
| 278 | return ( | |
| 279 | <button | |
| 280 | key={`${activeQuestion.id}:${optionValue}`} | |
| 281 | type="button" | |
| 282 | disabled={isResponding} | |
| 283 | onClick={() => { | |
| 284 | handleOptionSelection(activeQuestion.id, optionValue); | |
| 285 | }} | |
| 286 | className={className} | |
| 287 | > | |
| 288 | {content} | |
| 289 | </button> | |
| 290 | ); | |
| 291 | })} | |
| 292 | </div> | |
| 293 | </ComposerBanner.Body> | |
| 294 | </ComposerBanner.Scroll> | |
| 291 | 295 | </CollapsiblePanel> |
| 292 | 296 | </Collapsible> |
| 293 | 297 | ); |
apps/web/src/components/chat/ComposerTasksBadge.tsx+1-1| ... | ... | @@ -92,7 +92,7 @@ function TaskSummary({ |
| 92 | 92 | > |
| 93 | 93 | {progress.completedSteps}/{progress.totalSteps} |
| 94 | 94 | </ComposerBanner.Count> |
| 95 | <TaskSegments className="hidden w-20 sm:flex" steps={steps} /> | |
| 95 | <TaskSegments className="hidden w-20 @min-[560px]:flex" steps={steps} /> | |
| 96 | 96 | <ComposerBanner.ToggleIcon expanded={expanded} /> |
| 97 | 97 | </ComposerBanner.Actions> |
| 98 | 98 | </> |