Skip to content

Commit 9f032cf

Browse files
authored
fix(web): align next-step Design toolbox with the composer's action menu (#4096)
* fix(web): align next-step toolbox menu * fix(web): share the design-toolbox action matcher across composer and next-step The next-step submenu had its own action matcher that ignored the matched skill's metadata, so searching by a preferred skill id/name (e.g. emilkowalski-motion) showed the skill resource row while hiding the action row it belongs to — the opposite of aligning with the composer panel. Lift the matcher into runtime/design-toolbox.ts (skill-aware, the composer's behavior) and use it from both surfaces; drop the two local copies. Regression test: searching by a preferred skill id keeps the action row visible. * fix(web): localize next-step toolbox resource matching and labels The next-step submenu filtered/rendered global skill resources with raw SkillSummary fields, so a localized query (e.g. zh-CN "创意总监") matched in the composer but not here, and rows showed the raw skill id/name instead of the localized title. Thread the locale-resolved name/description into skillMatchesQuery via a new optional `extra` argument and render localizeSkillName(locale, skill), so the next-step resource list matches and labels the same way the composer does. Regression test: a zh-CN query matches and renders the localized name. * fix(web): thread localized skill text into the shared action matcher Patch 3 taught the resource rows to match localized skill text, but the shared designToolboxActionMatchesQuery still indexed only raw skill fields, so a localized preferred-skill query (e.g. zh-CN) kept the resource row while hiding its paired follow-up action on both surfaces. Add an optional `extra` argument (parallel to skillMatchesQuery) and pass the locale-resolved name/description from both the next-step submenu and the composer panel. Regression test: a localized preferred-skill query keeps the action row visible.
1 parent e98e75f commit 9f032cf

6 files changed

Lines changed: 399 additions & 127 deletions

File tree

apps/web/src/components/AssistantMessage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ interface Props {
310310
// tests that don't wire chat send).
311311
onArtifactShare?: (fileName: string) => void;
312312
// Featured design-toolbox follow-up rows on the "next step" card. Seeding the
313-
// composer with an action / opening the full toolbox both route through the
313+
// composer with an action / opening the toolbox both route through the
314314
// composer; see ChatPane's composer ref wiring.
315315
onToolboxAction?: (id: DesignToolboxActionId) => void;
316316
onPickSkill?: (skillId: string) => void;
@@ -352,8 +352,8 @@ const ASSISTANT_MESSAGE_COMPARED_PROPS: Array<keyof Props> = [
352352
'suppressDirectionForms',
353353
'hasDesignSystemContext',
354354
// Memoized + stable from ChatPane; compared so a late skill-list load
355-
// refreshes the featured next-step rows' `@skill` hover detail and the full
356-
// More → Design toolbox skill list.
355+
// refreshes the featured next-step rows' `@skill` hover detail and the
356+
// More → Design toolbox global resources.
357357
'toolboxSkillNames',
358358
'nextStepSkills',
359359
// Live streaming tool input changes identity on every `tool_input_delta`.

apps/web/src/components/ChatComposer.tsx

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
DESIGN_TOOLBOX_ACTIONS,
6060
designToolboxActionBadge,
6161
designToolboxActionDescription,
62+
designToolboxActionMatchesQuery,
6263
designToolboxActionTitle,
6364
findDesignToolboxSkill,
6465
getDesignToolboxAction,
@@ -3359,10 +3360,17 @@ function DesignToolboxPanel({
33593360
);
33603361
const visibleActions = useMemo(
33613362
() =>
3362-
actions.filter((action) =>
3363-
designToolboxActionMatchesQuery(action, query, findDesignToolboxSkill(action, skills), t),
3364-
),
3365-
[actions, query, skills, t],
3363+
actions.filter((action) => {
3364+
const skill = findDesignToolboxSkill(action, skills);
3365+
return designToolboxActionMatchesQuery(
3366+
action,
3367+
query,
3368+
skill,
3369+
t,
3370+
skill ? [localizeSkillName(locale, skill), localizeSkillDescription(locale, skill)] : [],
3371+
);
3372+
}),
3373+
[actions, query, skills, locale, t],
33663374
);
33673375
const visibleResources = useMemo(
33683376
() => {
@@ -3947,29 +3955,6 @@ function designToolboxResourceIsActive(
39473955
}
39483956

39493957

3950-
function designToolboxActionMatchesQuery(
3951-
action: DesignToolboxAction,
3952-
query: string,
3953-
skill: SkillSummary | null,
3954-
t: TranslateFn,
3955-
): boolean {
3956-
const q = query.trim().toLowerCase();
3957-
if (!q) return true;
3958-
return [
3959-
designToolboxActionTitle(action, t),
3960-
designToolboxActionBadge(action, t),
3961-
designToolboxActionDescription(action, t),
3962-
...action.searchTerms,
3963-
skill?.id ?? '',
3964-
skill?.name ?? '',
3965-
skill?.description ?? '',
3966-
skill?.category ?? '',
3967-
]
3968-
.join(' ')
3969-
.toLowerCase()
3970-
.includes(q);
3971-
}
3972-
39733958
function isDesignToolboxSkill(skill: SkillSummary): boolean {
39743959
const category = skill.category ?? '';
39753960
if (

apps/web/src/components/NextStepActions.module.css

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -240,50 +240,36 @@
240240
.flyoutMenu {
241241
width: 200px;
242242
}
243-
.flyoutSkills {
244-
width: 260px;
245-
max-height: min(60vh, 360px);
243+
.flyoutToolbox {
244+
width: 300px;
245+
max-height: min(70vh, 500px);
246+
padding: 10px;
246247
}
247248

248-
.flyoutRow {
249+
.toolboxFlyoutTitle {
249250
display: flex;
250251
align-items: center;
251252
gap: 8px;
252-
width: 100%;
253-
padding: 6px 8px;
254-
font-size: 13px;
253+
padding: 2px 4px 8px;
254+
font-size: 14px;
255+
font-weight: 650;
255256
color: var(--text-default);
256-
background: transparent;
257-
border: 1px solid transparent;
258-
border-radius: 8px;
259-
cursor: pointer;
260-
text-align: left;
261-
transition:
262-
background 140ms cubic-bezier(0.23, 1, 0.32, 1),
263-
border-color 140ms cubic-bezier(0.23, 1, 0.32, 1);
264-
}
265-
.flyoutRow:hover:not(:disabled),
266-
.flyoutRow[aria-expanded='true'] {
267-
background: var(--accent-tint);
268-
border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
269257
}
270-
.flyoutRow:focus-visible {
271-
outline: 2px solid var(--accent);
272-
outline-offset: 1px;
273-
}
274-
.flyoutRow:disabled {
275-
opacity: 0.6;
276-
cursor: not-allowed;
258+
259+
.toolboxFlyoutTitle svg {
260+
color: var(--accent-strong);
277261
}
278262

279263
.flyoutSearch {
280264
display: flex;
281265
align-items: center;
282-
gap: 6px;
283-
padding: 4px 8px;
284-
margin-bottom: 4px;
285-
border-bottom: 1px solid var(--border-soft, var(--border));
266+
gap: 8px;
267+
padding: 7px 9px;
268+
margin-bottom: 8px;
269+
border: 1px solid var(--border-strong, var(--border));
270+
border-radius: 10px;
286271
color: var(--text-muted);
272+
background: var(--bg-default);
287273
}
288274
.flyoutSearch input {
289275
flex: 1 1 auto;
@@ -298,8 +284,46 @@
298284
overflow-y: auto;
299285
scrollbar-width: thin;
300286
}
287+
.flyoutSectionLabel {
288+
padding: 8px 8px 4px;
289+
font-size: 11px;
290+
font-weight: 650;
291+
letter-spacing: 0.02em;
292+
color: var(--text-muted);
293+
}
301294
.flyoutEmpty {
302295
padding: 8px;
303296
font-size: 12px;
304297
color: var(--text-muted);
305298
}
299+
300+
.flyoutRow {
301+
display: flex;
302+
align-items: center;
303+
gap: 8px;
304+
width: 100%;
305+
padding: 6px 8px;
306+
font-size: 13px;
307+
color: var(--text-default);
308+
background: transparent;
309+
border: 1px solid transparent;
310+
border-radius: 8px;
311+
cursor: pointer;
312+
text-align: left;
313+
transition:
314+
background 140ms cubic-bezier(0.23, 1, 0.32, 1),
315+
border-color 140ms cubic-bezier(0.23, 1, 0.32, 1);
316+
}
317+
.flyoutRow:hover:not(:disabled),
318+
.flyoutRow[aria-expanded='true'] {
319+
background: var(--accent-tint);
320+
border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
321+
}
322+
.flyoutRow:focus-visible {
323+
outline: 2px solid var(--accent);
324+
outline-offset: 1px;
325+
}
326+
.flyoutRow:disabled {
327+
opacity: 0.6;
328+
cursor: not-allowed;
329+
}

0 commit comments

Comments
 (0)