fix(chat): address review nits

- Namespace hourglass-flip class + keyframes to ai-chat-hourglass-flip
  so the :global rule can't pick up unrelated classes elsewhere.
- Rename contextPickerRow snippet to selectedContextBadges since it
  now only renders badges (the picker moved to the controls row).
- Restore the @ picker in the message-editing surface via a new
  showInlinePicker prop on AIChatInput, opted into by AIChatMessage.
  AIChatDisplay still renders its own picker in the controls row.
This commit is contained in:
Guilhem Lemouel
2026-05-20 15:37:48 +02:00
parent 894b76b957
commit 9032cc1e2d
3 changed files with 56 additions and 9 deletions
@@ -330,7 +330,7 @@
class="inline-flex items-center gap-1.5 text-2xs text-accent"
aria-label="Waiting for your input"
>
<Hourglass class="w-3 h-3 hourglass-flip" />
<Hourglass class="w-3 h-3 ai-chat-hourglass-flip" />
Waiting for your input
</span>
{:else}
@@ -536,11 +536,11 @@
/* Hourglass flips every 4s with long rests at each upright position.
`:global` because the class is applied to a child component's root
(Lucide SVG) and Svelte scoped CSS otherwise wouldn't match it. */
:global(.hourglass-flip) {
animation: hourglass-flip 4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
:global(.ai-chat-hourglass-flip) {
animation: ai-chat-hourglass-flip 4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
transform-origin: center;
}
@keyframes hourglass-flip {
@keyframes ai-chat-hourglass-flip {
0%,
35% {
transform: rotate(0deg);
@@ -1,5 +1,6 @@
<script lang="ts">
import AppAvailableContextList from './AppAvailableContextList.svelte'
import AvailableContextList from './AvailableContextList.svelte'
import ContextElementBadge from './ContextElementBadge.svelte'
import ContextTextarea from './ContextTextarea.svelte'
import autosize from '$lib/autosize'
@@ -9,6 +10,7 @@
import { twMerge } from 'tailwind-merge'
import { tick, untrack, type Snippet } from 'svelte'
import Portal from '$lib/components/Portal.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import { zIndexes } from '$lib/zIndexes'
import { ArrowUp, Square } from 'lucide-svelte'
import { Button } from '$lib/components/common'
@@ -38,6 +40,12 @@
loading?: boolean
// Called when the user clicks Stop. Defaults to `aiChatManager.cancel()`.
onCancel?: () => void
// Render an inline `@` picker button above the textarea. Off by
// default because the main chat surface renders its own picker in
// `AIChatDisplay`'s controls row; opt-in for the message-editing
// surface (`AIChatMessage`) which mounts `AIChatInput` standalone
// without surrounding controls.
showInlinePicker?: boolean
}
let {
@@ -56,7 +64,8 @@
bottomRightSnippet,
onKeyDown = undefined,
loading,
onCancel
onCancel,
showInlinePicker = false
}: Props = $props()
// GLOBAL-mode suggestion pool. We pick one at mount-time so each new
@@ -449,9 +458,46 @@
/>
{/snippet}
{#snippet contextPickerRow()}
{#if selectedContext.length > 0}
{#snippet selectedContextBadges()}
{#if showInlinePicker || selectedContext.length > 0}
<div class="flex flex-row flex-wrap items-center gap-1 mb-1">
{#if showInlinePicker}
<Popover>
{#snippet trigger()}
<div
class="text-primary text-xs flex flex-row items-center font-normal border px-1 rounded-lg hover:bg-surface-hover bg-surface"
title="Add context"
>
@
</div>
{/snippet}
{#snippet content({ close })}
{#if aiChatManager.mode === AIMode.APP}
<AppAvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
void addContextToSelection(element)
close()
}}
/>
{:else}
<AvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
void addContextToSelection(element)
close()
}}
onSelectWorkspaceItem={(element) => {
void addContextToSelection(element)
close()
}}
/>
{/if}
{/snippet}
</Popover>
{/if}
{#each selectedContext as element (element.type + '-' + element.title)}
<ContextElementBadge
contextElement={element}
@@ -480,7 +526,7 @@
>
{#if isContextEnabledMode}
{#if showContext}
{@render contextPickerRow()}
{@render selectedContextBadges()}
{/if}
<div class="relative">
<ContextTextarea
@@ -508,7 +554,7 @@
</div>
{:else if aiChatManager.mode === AIMode.APP}
{#if showContext}
{@render contextPickerRow()}
{@render selectedContextBadges()}
{/if}
<div class={twMerge('relative w-full scroll-pb-2', className)}>
<textarea
@@ -64,6 +64,7 @@
bind:selectedContext
initialInstructions={message.content}
{editingMessageIndex}
showInlinePicker
onClickOutside={() => (editingMessageIndex = null)}
onKeyDown={(e) => {
if (e.key === 'Escape') {