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