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.
This commit is contained in:
Mohamed Boudra
2026-08-22 11:16:28 +02:00
committed by GitHub
parent a687c42f33
commit 4c6b79dc44
14 changed files with 66 additions and 48 deletions
@@ -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(
@@ -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
@@ -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", () => {
@@ -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",
@@ -45,7 +45,7 @@ export function Field({ label, children, hint, error, testID }: FieldProps) {
}
if (hint) {
return (
<Text numberOfLines={1} style={styles.hintText} testID={hintTestID}>
<Text style={styles.hintText} testID={hintTestID}>
{hint}
</Text>
);
+2 -2
View File
@@ -2343,9 +2343,9 @@ export const ar: TranslationResources = {
featuresLabel: "السمات",
featureCount: "{{count}} سمة",
featureCountOne: "{{count}} سمة",
notesLabel: "ملاحظات للوكلاء",
notesLabel: "متى يُستخدم",
notesPlaceholder: "استخدمه لعمل الواجهة — المكونات والتخطيط ورموز التصميم.",
notesHint: 'تُعاد بواسطة أداة MCP "list_profiles". اكتبها كتعليمة لوكيل آخر.',
notesHint: "يساعد الوكلاء على اختيار ملف التعريف هذا عند بدء وكيل آخر.",
save: "حفظ",
saving: "جارٍ الحفظ...",
remove: "إزالة",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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: "削除",
+2 -3
View File
@@ -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: "제거",
+2 -3
View File
@@ -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",
+2 -3
View File
@@ -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: "Удалить",
+2 -2
View File
@@ -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: "移除",