Skip to content

Commit 6f2f9b5

Browse files
committed
refactor(chat): pair welcome capability icons explicitly at the call site
1 parent 2777d9d commit 6f2f9b5

3 files changed

Lines changed: 37 additions & 29 deletions

File tree

apps/chat/src/components/manage-assistant.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import {
55
AssistantChatTransport,
66
useChatRuntime,
77
} from '@assistant-ui/react-ai-sdk'
8-
import { WandSparkles } from 'lucide-react'
8+
import {
9+
FilePenLineIcon,
10+
MessageSquareTextIcon,
11+
SearchIcon,
12+
WandSparkles,
13+
} from 'lucide-react'
914
import { useMemo } from 'react'
1015
import { twMerge } from 'tailwind-merge'
1116
import { useEmbeddedManageContext } from '../hooks/useEmbeddedManageContext'
@@ -17,14 +22,20 @@ import {
1722
} from '../services/manageContext'
1823
import { ChatUiProvider, useChatUi } from './chat-ui-context'
1924
import { EmbeddedSettings } from './embedded-settings'
20-
import { Thread } from './thread'
25+
import { Thread, type ThreadWelcomeCapability } from './thread'
2126

2227
const MANAGE_ASSISTANT_NAME = 'KlickerUZH Assistant'
2328
const MANAGE_ASSISTANT_WELCOME = 'Hello! How can I help you?'
24-
const MANAGE_ASSISTANT_CAPABILITIES = [
25-
'Search your courses and question pool',
26-
'Draft single-choice, multiple-choice, and free-text questions — saved to your pool only after you confirm',
27-
'Suggest improvements to question feedback',
29+
const MANAGE_ASSISTANT_CAPABILITIES: ThreadWelcomeCapability[] = [
30+
{ icon: SearchIcon, text: 'Search your courses and question pool' },
31+
{
32+
icon: FilePenLineIcon,
33+
text: 'Draft single-choice, multiple-choice, and free-text questions — saved to your pool only after you confirm',
34+
},
35+
{
36+
icon: MessageSquareTextIcon,
37+
text: 'Suggest improvements to question feedback',
38+
},
2839
]
2940
const MANAGE_ASSISTANT_LIMITS_NOTE =
3041
'Read-only for everything else — it never publishes or edits existing content.'

apps/chat/src/components/thread.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ import {
2121
ChevronDownIcon,
2222
ChevronRightIcon,
2323
CopyIcon,
24-
FilePenLineIcon,
2524
ImagePlusIcon,
26-
MessageSquareTextIcon,
2725
PencilIcon,
2826
PencilOffIcon,
2927
RefreshCwIcon,
30-
SearchIcon,
3128
SendHorizontalIcon,
3229
SquareIcon,
3330
XIcon,
@@ -80,10 +77,10 @@ type ThreadProps = {
8077
// Friendly greeting shown above the suggestions (e.g. the manage assistant).
8178
// When unset, the welcome falls back to `Ask {chatbotName}`.
8279
welcomeMessage?: string
83-
// Short capability bullets shown between the greeting and the suggestions
84-
// (e.g. the manage assistant explaining what it can help with). When
85-
// unset/empty, nothing extra is rendered.
86-
capabilities?: string[]
80+
// Short capability bullets (icon + text) shown between the greeting and the
81+
// suggestions (e.g. the manage assistant explaining what it can help with).
82+
// When unset/empty, nothing extra is rendered.
83+
capabilities?: ThreadWelcomeCapability[]
8784
// One-line note shown below the capability bullets (e.g. clarifying the
8885
// assistant's limits). Ignored when `capabilities` is unset/empty.
8986
limitsNote?: string
@@ -337,7 +334,7 @@ const ThreadWelcome: FC<{
337334
contextLabel?: string | null
338335
suggestions: ThreadSuggestion[]
339336
welcomeMessage?: string
340-
capabilities?: string[]
337+
capabilities?: ThreadWelcomeCapability[]
341338
limitsNote?: string
342339
}> = ({
343340
chatbotAvatar,
@@ -398,27 +395,26 @@ const ThreadWelcome: FC<{
398395
)
399396
}
400397

401-
// Icons for `ThreadWelcomeCapabilities`, matched to bullets by position.
402-
const CAPABILITY_ICONS = [SearchIcon, FilePenLineIcon, MessageSquareTextIcon]
398+
export type ThreadWelcomeCapability = {
399+
icon: ComponentType<{ className?: string; 'aria-hidden'?: boolean }>
400+
text: string
401+
}
403402

404403
const ThreadWelcomeCapabilities: FC<{
405-
capabilities: string[]
404+
capabilities: ThreadWelcomeCapability[]
406405
limitsNote?: string
407406
}> = ({ capabilities, limitsNote }) => (
408407
<div className="mt-3 flex w-full max-w-sm flex-col gap-1.5 text-left">
409408
<ul className="flex flex-col gap-1">
410-
{capabilities.map((capability, index) => {
411-
const Icon = CAPABILITY_ICONS[index % CAPABILITY_ICONS.length]!
412-
return (
413-
<li
414-
key={capability}
415-
className="text-muted-foreground flex items-start gap-1.5 text-xs leading-snug"
416-
>
417-
<Icon className="mt-0.5 size-3 shrink-0" />
418-
<span>{capability}</span>
419-
</li>
420-
)
421-
})}
409+
{capabilities.map(({ icon: Icon, text }, index) => (
410+
<li
411+
key={`${text}-${index}`}
412+
className="text-muted-foreground flex items-start gap-1.5 text-xs leading-snug"
413+
>
414+
<Icon aria-hidden className="mt-0.5 size-3 shrink-0" />
415+
<span>{text}</span>
416+
</li>
417+
))}
422418
</ul>
423419
{limitsNote && (
424420
<p className="text-muted-foreground/70 text-[11px] leading-snug">

project/2026-07-23-pr-5109-assistant-production-readiness-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,4 @@ agy (Gemini 3.6 Flash High, sandbox/plan mode) reviewed commit `b58112c23`. Veri
195195
- 2026-07-24: **Slice 4 done.** `BASE_MANAGE_ASSISTANT_PROMPT` hardened: any create/make/save/store/persist/add request is a persistence intent that must call `klicker_lecturer_element_create_draft_proposal`; never print a proposal or question as JSON in message text; draft-only scaffold tools are brainstorm-only prose; only an explicit do-not-save request keeps a draft in prose. Two new vitest cases lock the invariants and cover both `buildManageAssistantSystemPrompt` branches (tools available/unavailable). Eval matrix (live, standalone chat `/manage`, gpt-5.5): "Create a question about X" → card; "Save this as a draft" (after a prose draft) → card; "Add it to my pool" → card; "Make an MC question (5 options + feedback)" → prose twice on the first prompt version → per plan iterated once (added "make" to the verb list + explicit do-not-save exception) → card; "Draft one but do not save it" → prose, no card, no JSON leak (re-validated after iteration); "Store the one above" → card. Final matrix 6/6 (requirement ≥5/6 persistence + do-not-save prose). Token headroom measured via direct litellm gpt-5.5 call generating an equivalent 5-choice-MC-with-feedback JSON: 545 completion tokens (179 reasoning + ~366 payload for 1508 chars) vs `maxOutputTokens` 2048 → ~3.7x headroom, and no live proposal ever truncated — registry unchanged, no mini-commit needed. Ops note: a host-side edit of `manageAssistantRuntime.ts` mid-compile poisoned the Turbopack persistent cache (truncated parse survived touches and content changes); recovery required `devrouter stop .` + key-injected `devrouter ensure .` with `apps/chat/.next` removed while stopped — remove the cache only while the server is down. Checks: chat check green, 26 files / 124 tests. Next: Slice 4 reviews.
196196
- 2026-07-24: Slice 4 reviews closed. agy (git-show-only on `ac937b5bc`): no blocking defects — prompt internally coherent, tests cover both branches, diff strictly prompt+tests; two low findings both resolved as keep-as-is (verb-list substring is deliberate invariant locking; the no-JSON rule vs raw-technical-detail rule tension is the intended UX constraint forcing card rendering). Simplification: accepted 3 of 6 — shrank the full-sentence post-tool assertion to a short fragment, asserted shared invariants only against the tools-unavailable branch (available branch covered by earlier tests), dropped a superfluous `as const`; rejected the rest per low confidence/own recommendation. Re-verified: 26 files / 124 tests green. Next: Slice 5.
197197
- 2026-07-24: **Slice 5 done.** `Thread`/`ThreadWelcome` gained optional `capabilities?: string[]` + `limitsNote?: string`; new `ThreadWelcomeCapabilities` renders 3 icon bullets (Search/FilePenLine/MessageSquareText, matched by position) plus a more-muted one-line limits note between the greeting/contextLabel and the suggestions — nothing renders when the props are unset, so the student `[chatbotId]` path is byte-identical (Playwright `Y-chat` welcome assertion still guards it in CI). `manage-assistant.tsx` passes the 4 hardcoded EN strings per the chat app's convention. `manage.assistant.subtitle` sharpened: EN 'Manage' → 'AI assistant for your courses and question pool'; DE → 'KI-Assistent für Ihre Kurse und Ihren Fragepool' (formal Sie matches surrounding de.ts strings). Verified live: embedded drawer shows greeting → context pill → capabilities → limits note → 3 suggestions with no scrolling at drawer width, new subtitle fits the header pill on one line; standalone `/manage` tab shows the same explainer (screenshots slice5-drawer-welcome.png, slice5-standalone-welcome.png). Checks in-container: chat check + 26 files / 124 tests green, chat lint 0 errors (5 pre-existing warnings), prettier clean. Next: Slice 5 reviews.
198+
- 2026-07-24: Slice 5 reviews closed. agy (git-show-only on `2777d9d6c`): no blocking defects — student welcome DOM strictly identical (props unset → falsy gate), no 28-rem overflow risk (max-w-sm block, shrink-0 icons), no hidden behavior change; three low findings: applied `aria-hidden` on the decorative bullet icons and a collision-proof `${text}-${index}` key; the render-path unit test is deferred to Slice 8's Playwright welcome assertions (apps/chat tests are node-environment only — component rendering is explicitly mocked out per manage-proposal-card.test.ts). Simplification: commit near-minimal; accepted making the icon-bullet pairing explicit — `capabilities` is now `ThreadWelcomeCapability[]` (`{icon, text}`) paired at the call site in manage-assistant.tsx, dropping the cross-file position-matched `CAPABILITY_ICONS` array, modulo wraparound, and non-null assertion. Re-verified: chat check green, 26 files / 124 tests, standalone `/manage` welcome renders identically (slice5-standalone-after-simplify.png). Next: Slice 6.

0 commit comments

Comments
 (0)