From 4c6b79dc44fe8c6349a775c59e80d0fad715ccce Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 22 Aug 2026 11:16:28 +0200 Subject: [PATCH] feat(app): refine agent profile creation (#3680) Keep profiles as the richer primitive while restoring the familiar star cue and clearer guidance for agent selection. Account for borders in shared field geometry so controls preserve their declared heights across form consumers. --- .../app/e2e/support/helpers/agent-profiles.ts | 15 ++++++++--- .../internal/agent-profile-glyph.tsx | 4 +-- .../components/ui/control-geometry.test.ts | 27 +++++++++++-------- .../app/src/components/ui/control-geometry.ts | 23 ++++++++++++---- packages/app/src/components/ui/form-field.tsx | 2 +- packages/app/src/i18n/resources/ar.ts | 4 +-- packages/app/src/i18n/resources/en.ts | 5 ++-- packages/app/src/i18n/resources/es.ts | 5 ++-- packages/app/src/i18n/resources/fr.ts | 5 ++-- packages/app/src/i18n/resources/ja.ts | 5 ++-- packages/app/src/i18n/resources/ko.ts | 5 ++-- packages/app/src/i18n/resources/pt-BR.ts | 5 ++-- packages/app/src/i18n/resources/ru.ts | 5 ++-- packages/app/src/i18n/resources/zh-CN.ts | 4 +-- 14 files changed, 66 insertions(+), 48 deletions(-) diff --git a/packages/app/e2e/support/helpers/agent-profiles.ts b/packages/app/e2e/support/helpers/agent-profiles.ts index 59deb8863..d9bd9c404 100644 --- a/packages/app/e2e/support/helpers/agent-profiles.ts +++ b/packages/app/e2e/support/helpers/agent-profiles.ts @@ -275,7 +275,7 @@ async function fillAgentProfileForm(page: Page, draft: AgentProfileDraft): Promi await chooseSelectFieldOption(page, { field: "mode", option: draft.mode }); } if (draft.notes !== undefined) { - await modal.getByRole("textbox", { name: "Notes for agents", exact: true }).fill(draft.notes); + await modal.getByRole("textbox", { name: "When to use", exact: true }).fill(draft.notes); } } @@ -329,11 +329,18 @@ export async function expectAgentProfileForm( const modal = editModal(page); await expect(modal).toBeVisible({ timeout: 30_000 }); await expect(modal.getByRole("textbox", { name: "Name", exact: true })).toHaveValue(name); + const notesHint = modal.getByTestId("agent-profile-notes-field-hint"); + await expect(notesHint).toHaveText( + "Helps agents choose this profile when starting another agent.", + ); + expect(await notesHint.evaluate((element) => getComputedStyle(element).webkitLineClamp)).not.toBe( + "1", + ); for (const [field, value] of Object.entries(expected)) { if (field === "notes") { - await expect( - modal.getByRole("textbox", { name: "Notes for agents", exact: true }), - ).toHaveValue(value); + await expect(modal.getByRole("textbox", { name: "When to use", exact: true })).toHaveValue( + value, + ); continue; } await expect( diff --git a/packages/app/src/agent-profiles/internal/agent-profile-glyph.tsx b/packages/app/src/agent-profiles/internal/agent-profile-glyph.tsx index e9795614e..35ff3c099 100644 --- a/packages/app/src/agent-profiles/internal/agent-profile-glyph.tsx +++ b/packages/app/src/agent-profiles/internal/agent-profile-glyph.tsx @@ -1,7 +1,6 @@ import { withUnistyles } from "react-native-unistyles"; import { BookOpen, - Bot, Boxes, Brain, Bug, @@ -27,6 +26,7 @@ import { Server, Shield, Sparkles, + Star, Terminal, TestTube, Wrench, @@ -42,7 +42,7 @@ import { } from "./profile-appearance"; /** Drawn when a profile names no icon, and as the "default" cell in the picker grid. */ -const ThemedDefaultIcon = withUnistyles(Bot); +const ThemedDefaultIcon = withUnistyles(Star); /** * `withUnistyles` has to wrap each icon once at module scope, so the registry diff --git a/packages/app/src/components/ui/control-geometry.test.ts b/packages/app/src/components/ui/control-geometry.test.ts index d62455aef..5f4a653e5 100644 --- a/packages/app/src/components/ui/control-geometry.test.ts +++ b/packages/app/src/components/ui/control-geometry.test.ts @@ -86,23 +86,28 @@ describe("control geometry", () => { expect(geometry.formTextInputMd.lineHeight).toBe(20); }); - it("derives field padding from line height without changing the control height", () => { + it("derives field padding from content and border without changing the control height", () => { const geometry = createControlGeometry(theme); + const borderWidth = theme.borderWidth[1]; expect(geometry.fieldControlSm.minHeight).toBe(32); - expect(geometry.fieldControlSm.paddingVertical).toBe(6); - expect(geometry.fieldTextSm.lineHeight + geometry.fieldControlSm.paddingVertical * 2).toBe( - geometry.fieldControlSm.minHeight, - ); + expect(geometry.fieldControlSm.paddingVertical).toBe(5); + expect( + geometry.fieldTextSm.lineHeight + + geometry.fieldControlSm.paddingVertical * 2 + + borderWidth * 2, + ).toBe(geometry.fieldControlSm.minHeight); expect(geometry.fieldControlMd.minHeight).toBe(44); - expect(geometry.fieldControlMd.paddingVertical).toBe(12); - expect(geometry.fieldTextMd.lineHeight + geometry.fieldControlMd.paddingVertical * 2).toBe( - geometry.fieldControlMd.minHeight, - ); + expect(geometry.fieldControlMd.paddingVertical).toBe(11); + expect( + geometry.fieldTextMd.lineHeight + + geometry.fieldControlMd.paddingVertical * 2 + + borderWidth * 2, + ).toBe(geometry.fieldControlMd.minHeight); - expect(geometry.formTextInputSm.paddingVertical).toBe(6); - expect(geometry.formTextInputMd.paddingVertical).toBe(12); + expect(geometry.formTextInputSm.paddingVertical).toBe(5); + expect(geometry.formTextInputMd.paddingVertical).toBe(11); }); it("keeps segmented controls ghost with button-radius segments in a button-sized track", () => { diff --git a/packages/app/src/components/ui/control-geometry.ts b/packages/app/src/components/ui/control-geometry.ts index 526f52862..0538fa40f 100644 --- a/packages/app/src/components/ui/control-geometry.ts +++ b/packages/app/src/components/ui/control-geometry.ts @@ -78,8 +78,12 @@ function fieldLineHeight(fontSize: number): number { return Math.round(fontSize * FIELD_TEXT_LINE_HEIGHT_RATIO); } -function fieldVerticalPadding(controlHeight: number, lineHeight: number): number { - return (controlHeight - lineHeight) / 2; +function fieldVerticalPadding( + controlHeight: number, + lineHeight: number, + borderWidth: number, +): number { + return (controlHeight - lineHeight - borderWidth * 2) / 2; } export function getControlInteractionPhase( @@ -111,18 +115,27 @@ export function resolveControlInteractionStyles( } export function createControlGeometry(theme: Theme) { + const controlBorderWidth = theme.borderWidth[1]; const fieldTextSmLineHeight = fieldLineHeight(theme.fontSize.base); const fieldTextMdLineHeight = fieldLineHeight(theme.fontSize.base); const fieldControlSm = { minHeight: CONTROL_HEIGHTS.compact, paddingHorizontal: theme.spacing[3], - paddingVertical: fieldVerticalPadding(CONTROL_HEIGHTS.compact, fieldTextSmLineHeight), + paddingVertical: fieldVerticalPadding( + CONTROL_HEIGHTS.compact, + fieldTextSmLineHeight, + controlBorderWidth, + ), borderRadius: theme.borderRadius.md, }; const fieldControlMd = { minHeight: CONTROL_HEIGHTS.field, paddingHorizontal: theme.spacing[4], - paddingVertical: fieldVerticalPadding(CONTROL_HEIGHTS.field, fieldTextMdLineHeight), + paddingVertical: fieldVerticalPadding( + CONTROL_HEIGHTS.field, + fieldTextMdLineHeight, + controlBorderWidth, + ), borderRadius: theme.borderRadius.lg, }; const fieldTextSm = { @@ -182,7 +195,7 @@ export function createControlGeometry(theme: Theme) { fieldTextSm, fieldTextMd, controlRest: { - borderWidth: theme.borderWidth[1], + borderWidth: controlBorderWidth, borderColor: "transparent", outlineWidth: 0, outlineColor: "transparent", diff --git a/packages/app/src/components/ui/form-field.tsx b/packages/app/src/components/ui/form-field.tsx index 4df8136d0..d7c5356f0 100644 --- a/packages/app/src/components/ui/form-field.tsx +++ b/packages/app/src/components/ui/form-field.tsx @@ -45,7 +45,7 @@ export function Field({ label, children, hint, error, testID }: FieldProps) { } if (hint) { return ( - + {hint} ); diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 1e0cf7f75..114629e26 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -2343,9 +2343,9 @@ export const ar: TranslationResources = { featuresLabel: "السمات", featureCount: "{{count}} سمة", featureCountOne: "{{count}} سمة", - notesLabel: "ملاحظات للوكلاء", + notesLabel: "متى يُستخدم", notesPlaceholder: "استخدمه لعمل الواجهة — المكونات والتخطيط ورموز التصميم.", - notesHint: 'تُعاد بواسطة أداة MCP "list_profiles". اكتبها كتعليمة لوكيل آخر.', + notesHint: "يساعد الوكلاء على اختيار ملف التعريف هذا عند بدء وكيل آخر.", save: "حفظ", saving: "جارٍ الحفظ...", remove: "إزالة", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 72c190c88..3beebc278 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -2410,10 +2410,9 @@ export const en = { featuresLabel: "Features", featureCount: "{{count}} features", featureCountOne: "{{count}} feature", - notesLabel: "Notes for agents", + notesLabel: "When to use", notesPlaceholder: "Use for UI work — components, layout and design tokens.", - notesHint: - "Returned by the list_profiles MCP tool. Write it as an instruction to another agent.", + notesHint: "Helps agents choose this profile when starting another agent.", save: "Save", saving: "Saving...", remove: "Remove", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 4eadca32d..9be4b5bbd 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -2397,10 +2397,9 @@ export const es: TranslationResources = { featuresLabel: "Características", featureCount: "{{count}} características", featureCountOne: "{{count}} característica", - notesLabel: "Notas para los agentes", + notesLabel: "Cuándo usarlo", notesPlaceholder: "Úselo para trabajo de UI: componentes, diseño y tokens de diseño.", - notesHint: - 'Devuelto por la herramienta MCP "list_profiles". Escríbalo como una instrucción para otro agente.', + notesHint: "Ayuda a los agentes a elegir este perfil al iniciar otro agente.", save: "Guardar", saving: "Guardando...", remove: "Eliminar", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index ef312200a..b5c54a0d5 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -2404,11 +2404,10 @@ export const fr: TranslationResources = { featuresLabel: "Caractéristiques", featureCount: "{{count}} caractéristiques", featureCountOne: "{{count}} caractéristique", - notesLabel: "Notes pour les agents", + notesLabel: "Quand l’utiliser", notesPlaceholder: "À utiliser pour le travail sur l'UI — composants, mise en page et tokens de design.", - notesHint: - "Renvoyé par l'outil MCP « list_profiles ». Rédigez-le comme une instruction destinée à un autre agent.", + notesHint: "Aide les agents à choisir ce profil lorsqu’ils démarrent un autre agent.", save: "Enregistrer", saving: "Enregistrement...", remove: "Supprimer", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index 57cf19418..0feb4869a 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -2366,10 +2366,9 @@ export const ja: TranslationResources = { featuresLabel: "機能", featureCount: "{{count}}個の機能", featureCountOne: "{{count}}個の機能", - notesLabel: "エージェントへのメモ", + notesLabel: "使用する場面", notesPlaceholder: "UI作業に使用 — コンポーネント、レイアウト、デザイントークンなど。", - notesHint: - "list_profiles MCPツールによって返されます。他のエージェントへの指示として記述してください。", + notesHint: "別のエージェントを起動するとき、このプロファイルを選ぶために使用されます。", save: "保存", saving: "保存中...", remove: "削除", diff --git a/packages/app/src/i18n/resources/ko.ts b/packages/app/src/i18n/resources/ko.ts index 537d724dc..e70a966ad 100644 --- a/packages/app/src/i18n/resources/ko.ts +++ b/packages/app/src/i18n/resources/ko.ts @@ -2355,10 +2355,9 @@ export const ko: TranslationResources = { featuresLabel: "기능", featureCount: "기능 {{count}}개", featureCountOne: "기능 {{count}}개", - notesLabel: "에이전트를 위한 메모", + notesLabel: "사용 시점", notesPlaceholder: "UI 작업에 사용 — 컴포넌트, 레이아웃, 디자인 토큰.", - notesHint: - "list_profiles MCP 도구가 반환합니다. 다른 에이전트에게 전달할 지침으로 작성하세요.", + notesHint: "에이전트가 다른 에이전트를 시작할 때 이 프로필을 선택하는 데 사용됩니다.", save: "저장", saving: "저장하는 중...", remove: "제거", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index a65d05391..409875100 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -2382,10 +2382,9 @@ export const ptBR: TranslationResources = { featuresLabel: "Recursos", featureCount: "{{count}} recursos", featureCountOne: "{{count}} recurso", - notesLabel: "Notas para os agentes", + notesLabel: "Quando usar", notesPlaceholder: "Use para trabalho de UI — componentes, layout e tokens de design.", - notesHint: - 'Retornado pela ferramenta MCP "list_profiles". Escreva como uma instrução para outro agente.', + notesHint: "Ajuda os agentes a escolher este perfil ao iniciar outro agente.", save: "Salvar", saving: "Salvando...", remove: "Remover", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 6a4e719e7..25b94e024 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -2387,10 +2387,9 @@ export const ru: TranslationResources = { featuresLabel: "Функции", featureCount: "Функции: {{count}}", featureCountOne: "{{count}} функция", - notesLabel: "Заметки для агентов", + notesLabel: "Когда использовать", notesPlaceholder: "Используйте для работы над UI — компоненты, макет и токены дизайна.", - notesHint: - "MCP-инструмент «list_profiles» возвращает эти заметки. Сформулируйте их как инструкцию для другого агента.", + notesHint: "Помогает агентам выбрать этот профиль при запуске другого агента.", save: "Сохранить", saving: "Сохранение...", remove: "Удалить", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index 3fb546b88..6fcc2e1d0 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -2317,9 +2317,9 @@ export const zhCN: TranslationResources = { featuresLabel: "Features", featureCount: "{{count}} 个 Feature", featureCountOne: "{{count}} 个 Feature", - notesLabel: "给 Agent 的说明", + notesLabel: "适用场景", notesPlaceholder: "用于 UI 工作 — 组件、布局和设计 token。", - notesHint: "由 list_profiles MCP 工具返回。请将其写成给另一个 Agent 的指令。", + notesHint: "帮助 Agent 在启动另一个 Agent 时选择此配置。", save: "保存", saving: "保存中...", remove: "移除",