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:
@@ -275,7 +275,7 @@ async function fillAgentProfileForm(page: Page, draft: AgentProfileDraft): Promi
|
|||||||
await chooseSelectFieldOption(page, { field: "mode", option: draft.mode });
|
await chooseSelectFieldOption(page, { field: "mode", option: draft.mode });
|
||||||
}
|
}
|
||||||
if (draft.notes !== undefined) {
|
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);
|
const modal = editModal(page);
|
||||||
await expect(modal).toBeVisible({ timeout: 30_000 });
|
await expect(modal).toBeVisible({ timeout: 30_000 });
|
||||||
await expect(modal.getByRole("textbox", { name: "Name", exact: true })).toHaveValue(name);
|
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)) {
|
for (const [field, value] of Object.entries(expected)) {
|
||||||
if (field === "notes") {
|
if (field === "notes") {
|
||||||
await expect(
|
await expect(modal.getByRole("textbox", { name: "When to use", exact: true })).toHaveValue(
|
||||||
modal.getByRole("textbox", { name: "Notes for agents", exact: true }),
|
value,
|
||||||
).toHaveValue(value);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { withUnistyles } from "react-native-unistyles";
|
import { withUnistyles } from "react-native-unistyles";
|
||||||
import {
|
import {
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Bot,
|
|
||||||
Boxes,
|
Boxes,
|
||||||
Brain,
|
Brain,
|
||||||
Bug,
|
Bug,
|
||||||
@@ -27,6 +26,7 @@ import {
|
|||||||
Server,
|
Server,
|
||||||
Shield,
|
Shield,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
|
Star,
|
||||||
Terminal,
|
Terminal,
|
||||||
TestTube,
|
TestTube,
|
||||||
Wrench,
|
Wrench,
|
||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
} from "./profile-appearance";
|
} from "./profile-appearance";
|
||||||
|
|
||||||
/** Drawn when a profile names no icon, and as the "default" cell in the picker grid. */
|
/** 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
|
* `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);
|
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 geometry = createControlGeometry(theme);
|
||||||
|
const borderWidth = theme.borderWidth[1];
|
||||||
|
|
||||||
expect(geometry.fieldControlSm.minHeight).toBe(32);
|
expect(geometry.fieldControlSm.minHeight).toBe(32);
|
||||||
expect(geometry.fieldControlSm.paddingVertical).toBe(6);
|
expect(geometry.fieldControlSm.paddingVertical).toBe(5);
|
||||||
expect(geometry.fieldTextSm.lineHeight + geometry.fieldControlSm.paddingVertical * 2).toBe(
|
expect(
|
||||||
geometry.fieldControlSm.minHeight,
|
geometry.fieldTextSm.lineHeight +
|
||||||
);
|
geometry.fieldControlSm.paddingVertical * 2 +
|
||||||
|
borderWidth * 2,
|
||||||
|
).toBe(geometry.fieldControlSm.minHeight);
|
||||||
|
|
||||||
expect(geometry.fieldControlMd.minHeight).toBe(44);
|
expect(geometry.fieldControlMd.minHeight).toBe(44);
|
||||||
expect(geometry.fieldControlMd.paddingVertical).toBe(12);
|
expect(geometry.fieldControlMd.paddingVertical).toBe(11);
|
||||||
expect(geometry.fieldTextMd.lineHeight + geometry.fieldControlMd.paddingVertical * 2).toBe(
|
expect(
|
||||||
geometry.fieldControlMd.minHeight,
|
geometry.fieldTextMd.lineHeight +
|
||||||
);
|
geometry.fieldControlMd.paddingVertical * 2 +
|
||||||
|
borderWidth * 2,
|
||||||
|
).toBe(geometry.fieldControlMd.minHeight);
|
||||||
|
|
||||||
expect(geometry.formTextInputSm.paddingVertical).toBe(6);
|
expect(geometry.formTextInputSm.paddingVertical).toBe(5);
|
||||||
expect(geometry.formTextInputMd.paddingVertical).toBe(12);
|
expect(geometry.formTextInputMd.paddingVertical).toBe(11);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps segmented controls ghost with button-radius segments in a button-sized track", () => {
|
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);
|
return Math.round(fontSize * FIELD_TEXT_LINE_HEIGHT_RATIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldVerticalPadding(controlHeight: number, lineHeight: number): number {
|
function fieldVerticalPadding(
|
||||||
return (controlHeight - lineHeight) / 2;
|
controlHeight: number,
|
||||||
|
lineHeight: number,
|
||||||
|
borderWidth: number,
|
||||||
|
): number {
|
||||||
|
return (controlHeight - lineHeight - borderWidth * 2) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getControlInteractionPhase(
|
export function getControlInteractionPhase(
|
||||||
@@ -111,18 +115,27 @@ export function resolveControlInteractionStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createControlGeometry(theme: Theme) {
|
export function createControlGeometry(theme: Theme) {
|
||||||
|
const controlBorderWidth = theme.borderWidth[1];
|
||||||
const fieldTextSmLineHeight = fieldLineHeight(theme.fontSize.base);
|
const fieldTextSmLineHeight = fieldLineHeight(theme.fontSize.base);
|
||||||
const fieldTextMdLineHeight = fieldLineHeight(theme.fontSize.base);
|
const fieldTextMdLineHeight = fieldLineHeight(theme.fontSize.base);
|
||||||
const fieldControlSm = {
|
const fieldControlSm = {
|
||||||
minHeight: CONTROL_HEIGHTS.compact,
|
minHeight: CONTROL_HEIGHTS.compact,
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: fieldVerticalPadding(CONTROL_HEIGHTS.compact, fieldTextSmLineHeight),
|
paddingVertical: fieldVerticalPadding(
|
||||||
|
CONTROL_HEIGHTS.compact,
|
||||||
|
fieldTextSmLineHeight,
|
||||||
|
controlBorderWidth,
|
||||||
|
),
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
};
|
};
|
||||||
const fieldControlMd = {
|
const fieldControlMd = {
|
||||||
minHeight: CONTROL_HEIGHTS.field,
|
minHeight: CONTROL_HEIGHTS.field,
|
||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingVertical: fieldVerticalPadding(CONTROL_HEIGHTS.field, fieldTextMdLineHeight),
|
paddingVertical: fieldVerticalPadding(
|
||||||
|
CONTROL_HEIGHTS.field,
|
||||||
|
fieldTextMdLineHeight,
|
||||||
|
controlBorderWidth,
|
||||||
|
),
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
};
|
};
|
||||||
const fieldTextSm = {
|
const fieldTextSm = {
|
||||||
@@ -182,7 +195,7 @@ export function createControlGeometry(theme: Theme) {
|
|||||||
fieldTextSm,
|
fieldTextSm,
|
||||||
fieldTextMd,
|
fieldTextMd,
|
||||||
controlRest: {
|
controlRest: {
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: controlBorderWidth,
|
||||||
borderColor: "transparent",
|
borderColor: "transparent",
|
||||||
outlineWidth: 0,
|
outlineWidth: 0,
|
||||||
outlineColor: "transparent",
|
outlineColor: "transparent",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function Field({ label, children, hint, error, testID }: FieldProps) {
|
|||||||
}
|
}
|
||||||
if (hint) {
|
if (hint) {
|
||||||
return (
|
return (
|
||||||
<Text numberOfLines={1} style={styles.hintText} testID={hintTestID}>
|
<Text style={styles.hintText} testID={hintTestID}>
|
||||||
{hint}
|
{hint}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2343,9 +2343,9 @@ export const ar: TranslationResources = {
|
|||||||
featuresLabel: "السمات",
|
featuresLabel: "السمات",
|
||||||
featureCount: "{{count}} سمة",
|
featureCount: "{{count}} سمة",
|
||||||
featureCountOne: "{{count}} سمة",
|
featureCountOne: "{{count}} سمة",
|
||||||
notesLabel: "ملاحظات للوكلاء",
|
notesLabel: "متى يُستخدم",
|
||||||
notesPlaceholder: "استخدمه لعمل الواجهة — المكونات والتخطيط ورموز التصميم.",
|
notesPlaceholder: "استخدمه لعمل الواجهة — المكونات والتخطيط ورموز التصميم.",
|
||||||
notesHint: 'تُعاد بواسطة أداة MCP "list_profiles". اكتبها كتعليمة لوكيل آخر.',
|
notesHint: "يساعد الوكلاء على اختيار ملف التعريف هذا عند بدء وكيل آخر.",
|
||||||
save: "حفظ",
|
save: "حفظ",
|
||||||
saving: "جارٍ الحفظ...",
|
saving: "جارٍ الحفظ...",
|
||||||
remove: "إزالة",
|
remove: "إزالة",
|
||||||
|
|||||||
@@ -2410,10 +2410,9 @@ export const en = {
|
|||||||
featuresLabel: "Features",
|
featuresLabel: "Features",
|
||||||
featureCount: "{{count}} features",
|
featureCount: "{{count}} features",
|
||||||
featureCountOne: "{{count}} feature",
|
featureCountOne: "{{count}} feature",
|
||||||
notesLabel: "Notes for agents",
|
notesLabel: "When to use",
|
||||||
notesPlaceholder: "Use for UI work — components, layout and design tokens.",
|
notesPlaceholder: "Use for UI work — components, layout and design tokens.",
|
||||||
notesHint:
|
notesHint: "Helps agents choose this profile when starting another agent.",
|
||||||
"Returned by the list_profiles MCP tool. Write it as an instruction to another agent.",
|
|
||||||
save: "Save",
|
save: "Save",
|
||||||
saving: "Saving...",
|
saving: "Saving...",
|
||||||
remove: "Remove",
|
remove: "Remove",
|
||||||
|
|||||||
@@ -2397,10 +2397,9 @@ export const es: TranslationResources = {
|
|||||||
featuresLabel: "Características",
|
featuresLabel: "Características",
|
||||||
featureCount: "{{count}} características",
|
featureCount: "{{count}} características",
|
||||||
featureCountOne: "{{count}} característica",
|
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.",
|
notesPlaceholder: "Úselo para trabajo de UI: componentes, diseño y tokens de diseño.",
|
||||||
notesHint:
|
notesHint: "Ayuda a los agentes a elegir este perfil al iniciar otro agente.",
|
||||||
'Devuelto por la herramienta MCP "list_profiles". Escríbalo como una instrucción para otro agente.',
|
|
||||||
save: "Guardar",
|
save: "Guardar",
|
||||||
saving: "Guardando...",
|
saving: "Guardando...",
|
||||||
remove: "Eliminar",
|
remove: "Eliminar",
|
||||||
|
|||||||
@@ -2404,11 +2404,10 @@ export const fr: TranslationResources = {
|
|||||||
featuresLabel: "Caractéristiques",
|
featuresLabel: "Caractéristiques",
|
||||||
featureCount: "{{count}} caractéristiques",
|
featureCount: "{{count}} caractéristiques",
|
||||||
featureCountOne: "{{count}} caractéristique",
|
featureCountOne: "{{count}} caractéristique",
|
||||||
notesLabel: "Notes pour les agents",
|
notesLabel: "Quand l’utiliser",
|
||||||
notesPlaceholder:
|
notesPlaceholder:
|
||||||
"À utiliser pour le travail sur l'UI — composants, mise en page et tokens de design.",
|
"À utiliser pour le travail sur l'UI — composants, mise en page et tokens de design.",
|
||||||
notesHint:
|
notesHint: "Aide les agents à choisir ce profil lorsqu’ils démarrent un autre agent.",
|
||||||
"Renvoyé par l'outil MCP « list_profiles ». Rédigez-le comme une instruction destinée à un autre agent.",
|
|
||||||
save: "Enregistrer",
|
save: "Enregistrer",
|
||||||
saving: "Enregistrement...",
|
saving: "Enregistrement...",
|
||||||
remove: "Supprimer",
|
remove: "Supprimer",
|
||||||
|
|||||||
@@ -2366,10 +2366,9 @@ export const ja: TranslationResources = {
|
|||||||
featuresLabel: "機能",
|
featuresLabel: "機能",
|
||||||
featureCount: "{{count}}個の機能",
|
featureCount: "{{count}}個の機能",
|
||||||
featureCountOne: "{{count}}個の機能",
|
featureCountOne: "{{count}}個の機能",
|
||||||
notesLabel: "エージェントへのメモ",
|
notesLabel: "使用する場面",
|
||||||
notesPlaceholder: "UI作業に使用 — コンポーネント、レイアウト、デザイントークンなど。",
|
notesPlaceholder: "UI作業に使用 — コンポーネント、レイアウト、デザイントークンなど。",
|
||||||
notesHint:
|
notesHint: "別のエージェントを起動するとき、このプロファイルを選ぶために使用されます。",
|
||||||
"list_profiles MCPツールによって返されます。他のエージェントへの指示として記述してください。",
|
|
||||||
save: "保存",
|
save: "保存",
|
||||||
saving: "保存中...",
|
saving: "保存中...",
|
||||||
remove: "削除",
|
remove: "削除",
|
||||||
|
|||||||
@@ -2355,10 +2355,9 @@ export const ko: TranslationResources = {
|
|||||||
featuresLabel: "기능",
|
featuresLabel: "기능",
|
||||||
featureCount: "기능 {{count}}개",
|
featureCount: "기능 {{count}}개",
|
||||||
featureCountOne: "기능 {{count}}개",
|
featureCountOne: "기능 {{count}}개",
|
||||||
notesLabel: "에이전트를 위한 메모",
|
notesLabel: "사용 시점",
|
||||||
notesPlaceholder: "UI 작업에 사용 — 컴포넌트, 레이아웃, 디자인 토큰.",
|
notesPlaceholder: "UI 작업에 사용 — 컴포넌트, 레이아웃, 디자인 토큰.",
|
||||||
notesHint:
|
notesHint: "에이전트가 다른 에이전트를 시작할 때 이 프로필을 선택하는 데 사용됩니다.",
|
||||||
"list_profiles MCP 도구가 반환합니다. 다른 에이전트에게 전달할 지침으로 작성하세요.",
|
|
||||||
save: "저장",
|
save: "저장",
|
||||||
saving: "저장하는 중...",
|
saving: "저장하는 중...",
|
||||||
remove: "제거",
|
remove: "제거",
|
||||||
|
|||||||
@@ -2382,10 +2382,9 @@ export const ptBR: TranslationResources = {
|
|||||||
featuresLabel: "Recursos",
|
featuresLabel: "Recursos",
|
||||||
featureCount: "{{count}} recursos",
|
featureCount: "{{count}} recursos",
|
||||||
featureCountOne: "{{count}} recurso",
|
featureCountOne: "{{count}} recurso",
|
||||||
notesLabel: "Notas para os agentes",
|
notesLabel: "Quando usar",
|
||||||
notesPlaceholder: "Use para trabalho de UI — componentes, layout e tokens de design.",
|
notesPlaceholder: "Use para trabalho de UI — componentes, layout e tokens de design.",
|
||||||
notesHint:
|
notesHint: "Ajuda os agentes a escolher este perfil ao iniciar outro agente.",
|
||||||
'Retornado pela ferramenta MCP "list_profiles". Escreva como uma instrução para outro agente.',
|
|
||||||
save: "Salvar",
|
save: "Salvar",
|
||||||
saving: "Salvando...",
|
saving: "Salvando...",
|
||||||
remove: "Remover",
|
remove: "Remover",
|
||||||
|
|||||||
@@ -2387,10 +2387,9 @@ export const ru: TranslationResources = {
|
|||||||
featuresLabel: "Функции",
|
featuresLabel: "Функции",
|
||||||
featureCount: "Функции: {{count}}",
|
featureCount: "Функции: {{count}}",
|
||||||
featureCountOne: "{{count}} функция",
|
featureCountOne: "{{count}} функция",
|
||||||
notesLabel: "Заметки для агентов",
|
notesLabel: "Когда использовать",
|
||||||
notesPlaceholder: "Используйте для работы над UI — компоненты, макет и токены дизайна.",
|
notesPlaceholder: "Используйте для работы над UI — компоненты, макет и токены дизайна.",
|
||||||
notesHint:
|
notesHint: "Помогает агентам выбрать этот профиль при запуске другого агента.",
|
||||||
"MCP-инструмент «list_profiles» возвращает эти заметки. Сформулируйте их как инструкцию для другого агента.",
|
|
||||||
save: "Сохранить",
|
save: "Сохранить",
|
||||||
saving: "Сохранение...",
|
saving: "Сохранение...",
|
||||||
remove: "Удалить",
|
remove: "Удалить",
|
||||||
|
|||||||
@@ -2317,9 +2317,9 @@ export const zhCN: TranslationResources = {
|
|||||||
featuresLabel: "Features",
|
featuresLabel: "Features",
|
||||||
featureCount: "{{count}} 个 Feature",
|
featureCount: "{{count}} 个 Feature",
|
||||||
featureCountOne: "{{count}} 个 Feature",
|
featureCountOne: "{{count}} 个 Feature",
|
||||||
notesLabel: "给 Agent 的说明",
|
notesLabel: "适用场景",
|
||||||
notesPlaceholder: "用于 UI 工作 — 组件、布局和设计 token。",
|
notesPlaceholder: "用于 UI 工作 — 组件、布局和设计 token。",
|
||||||
notesHint: "由 list_profiles MCP 工具返回。请将其写成给另一个 Agent 的指令。",
|
notesHint: "帮助 Agent 在启动另一个 Agent 时选择此配置。",
|
||||||
save: "保存",
|
save: "保存",
|
||||||
saving: "保存中...",
|
saving: "保存中...",
|
||||||
remove: "移除",
|
remove: "移除",
|
||||||
|
|||||||
Reference in New Issue
Block a user