-
- Enter
-
+
{
fireEvent.change(screen.getByLabelText('执行目标'), {
target: { value: 'target-local-edge-1' },
});
- expect(screen.getByText('Agent @Builder')).toBeInTheDocument();
- expect(screen.getByText('目标 Alpha Desktop')).toBeInTheDocument();
- expect(screen.getByText('需填写内容')).toBeInTheDocument();
+ expect(screen.getByText('@Builder')).toBeInTheDocument();
+ expect(screen.getByLabelText('执行目标')).toHaveValue('target-local-edge-1');
+ expect(screen.getByRole('button', { name: '启动 Agent 任务' })).toBeDisabled();
fireEvent.change(screen.getByRole('textbox', { name: '输入框' }), {
target: { value: 'Start the Web main chain' },
});
- expect(screen.getByText('任务就绪')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '启动 Agent 任务' })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: '启动 Agent 任务' }));
await waitFor(() => {
@@ -291,9 +291,7 @@ describe('AgentHubWorkbench', () => {
target: { value: 'Run the remote task' },
});
- expect(screen.getByText('Agent @Builder')).toBeInTheDocument();
- expect(screen.getByText('目标未选')).toBeInTheDocument();
- expect(screen.getByText('需填写内容')).toBeInTheDocument();
+ expect(screen.getByLabelText('执行目标')).toHaveValue('');
expect(screen.getAllByRole('status').find((el) => el.textContent?.includes('请先选择执行目标'))).toHaveTextContent('请先选择执行目标再开始。');
expect(screen.getByRole('button', { name: '启动 Agent 任务' })).toBeDisabled();
expect(platform.submittedIntents).toEqual([]);
diff --git a/app/workbench/src/unifiedComposerHelpers.test.ts b/app/workbench/src/unifiedComposerHelpers.test.ts
index ea0facee9..997bec396 100644
--- a/app/workbench/src/unifiedComposerHelpers.test.ts
+++ b/app/workbench/src/unifiedComposerHelpers.test.ts
@@ -12,14 +12,12 @@ import {
formatComposerDataModeLabel,
formatComposerTargetStateLabel,
formatQuotePreview,
- formatSelectedAgentLabel,
isAttachmentUploadFailed,
isAttachmentUploadInFlight,
isAttachmentUploading,
isExecutionTargetStillValid,
isImageAttachment,
isTargetSelectionRequired,
- mainchainTaskState,
partitionAttachmentsBySize,
resolveTargetStatus,
shouldSubmitComposerKey,
@@ -145,12 +143,10 @@ describe('unifiedComposerHelpers', () => {
})).toBe(true);
});
- it('derives composer chrome labels and task readiness', () => {
- expect(formatSelectedAgentLabel(baseComposer.mentions)).toBe('@Builder');
+ it('derives composer status and target validity', () => {
expect(formatQuotePreview({ author: 'Ada', text: 'x'.repeat(80) })).toBe(
`Ada: ${'x'.repeat(60)}`,
);
- expect(mainchainTaskState({ composer: baseComposer, targetSelected: true })).toBe('ready');
expect(isExecutionTargetStillValid([{ id: 't1', label: 'A' }], 't2')).toBe(false);
expect(isImageAttachment({
id: 'a1',
@@ -177,11 +173,9 @@ describe('unifiedComposerHelpers', () => {
expect(derived.targetSelectionRequired).toBe(true);
expect(derived.targetSelected).toBe(false);
expect(derived.submitDisabled).toBe(true);
- expect(derived.selectedAgentLabel).toBe('@Builder');
expect(derived.availableMentionOptions).toHaveLength(1);
expect(derived.statusItems).toContain('数据:真实数据');
expect(derived.statusItems).toContain('请先选择执行目标再开始。');
- expect(derived.mainchainTask).toBe('draft required');
});
describe('attachment size limit', () => {
diff --git a/app/workbench/src/unifiedComposerHelpers.ts b/app/workbench/src/unifiedComposerHelpers.ts
index 81a9090f8..1ea2ba108 100644
--- a/app/workbench/src/unifiedComposerHelpers.ts
+++ b/app/workbench/src/unifiedComposerHelpers.ts
@@ -93,17 +93,6 @@ function isExecutionTargetSelected(
return !targetSelectionRequired || executionTargetId.trim().length > 0;
}
-function resolveSelectedTargetLabel(
- executionTargets: ComposerExecutionTarget[] | undefined,
- executionTargetId: string,
-): string | undefined {
- return executionTargets?.find((target) => target.id === executionTargetId)?.label;
-}
-
-export function formatSelectedAgentLabel(mentions: ComposerMention[]): string {
- return mentions.map((mention) => `@${mention.label}`).join(', ');
-}
-
export function resolveTargetStatus(params: {
targetSelectionRequired: boolean;
executionTargetId: string;
@@ -187,14 +176,6 @@ function isComposerSubmitDisabled(params: {
return !canSubmitComposer(composer) || isSubmitting || !targetSelected;
}
-export function mainchainTaskState(params: {
- composer: ComposerState;
- targetSelected: boolean;
-}): 'ready' | 'draft required' {
- const { composer, targetSelected } = params;
- return canSubmitComposer(composer) && targetSelected ? 'ready' : 'draft required';
-}
-
export function isExecutionTargetStillValid(
executionTargets: ComposerExecutionTarget[] | undefined,
executionTargetId: string,
@@ -298,12 +279,9 @@ export interface UnifiedComposerDerivedState {
targetSelectionRequired: boolean;
targetSelected: boolean;
submitDisabled: boolean;
- selectedTargetLabel: string | undefined;
- selectedAgentLabel: string;
targetStatus: string | undefined;
availableMentionOptions: ComposerMention[];
statusItems: string[];
- mainchainTask: 'ready' | 'draft required';
}
export function deriveUnifiedComposerState(params: {
@@ -339,11 +317,8 @@ export function deriveUnifiedComposerState(params: {
isSubmitting,
targetSelected,
}),
- selectedTargetLabel: resolveSelectedTargetLabel(executionTargets, executionTargetId),
- selectedAgentLabel: formatSelectedAgentLabel(composer.mentions),
targetStatus,
availableMentionOptions: filterAvailableMentionOptions(mentionableAgents, composer.mentions),
statusItems: buildComposerStatusItems({ status, targetStatus }),
- mainchainTask: mainchainTaskState({ composer, targetSelected }),
};
}
diff --git a/app/workbench/src/unifiedComposerHostHelpers.test.ts b/app/workbench/src/unifiedComposerHostHelpers.test.ts
index 2f51fefef..4a381a162 100644
--- a/app/workbench/src/unifiedComposerHostHelpers.test.ts
+++ b/app/workbench/src/unifiedComposerHostHelpers.test.ts
@@ -92,7 +92,6 @@ describe('unifiedComposerHostHelpers', () => {
replyBar: false,
quoteBar: false,
mentionChips: false,
- mainchainStrip: false,
attachmentBar: false,
agentPicker: false,
targetPicker: false,
@@ -146,7 +145,6 @@ describe('unifiedComposerHostHelpers', () => {
expect(model.replyTo?.author).toBe('Ada');
expect(model.quote?.text).toBe('quoted');
expect(model.mentions).toHaveLength(1);
- expect(model.mainchain?.mainchainTask).toBe('ready');
expect(model.attachment?.uploadProgresses?.a1?.percent).toBe(10);
expect(model.agentOptions).toHaveLength(1);
expect(model.targetPicker?.executionTargetId).toBe('t1');
@@ -171,11 +169,9 @@ describe('unifiedComposerHostHelpers', () => {
expect(vm.targetSelectionRequired).toBe(true);
expect(vm.targetSelected).toBe(false);
expect(vm.submitDisabled).toBe(true);
- expect(vm.selectedAgentLabel).toBe('@Builder');
expect(vm.availableMentionOptions).toHaveLength(1);
expect(vm.statusItems).toContain('数据:真实数据');
expect(vm.statusItems).toContain('请先选择执行目标再开始。');
- expect(vm.mainchainTask).toBe('draft required');
expect(vm.hasMentions).toBe(true);
expect(vm.inputPlaceholder).toBe('发消息给 AgentHub');
expect(vm.fileAccept).toBe('image/*');
diff --git a/app/workbench/src/unifiedComposerHostHelpers.ts b/app/workbench/src/unifiedComposerHostHelpers.ts
index f2ef40c14..bbfe9386a 100644
--- a/app/workbench/src/unifiedComposerHostHelpers.ts
+++ b/app/workbench/src/unifiedComposerHostHelpers.ts
@@ -19,7 +19,6 @@ export type {
UnifiedComposerProps,
ComposerChromeVisibility,
ComposerHostRuntimeDefaults,
- ComposerHostMainchainModel,
ComposerHostAttachmentModel,
ComposerHostTargetPickerModel,
ComposerHostChromeModel,
diff --git a/app/workbench/src/unifiedComposerHostViewModel.test.ts b/app/workbench/src/unifiedComposerHostViewModel.test.ts
index 8f8b1b17c..dc0ef3d2f 100644
--- a/app/workbench/src/unifiedComposerHostViewModel.test.ts
+++ b/app/workbench/src/unifiedComposerHostViewModel.test.ts
@@ -76,7 +76,6 @@ describe('unifiedComposerHostViewModel', () => {
replyBar: false,
quoteBar: false,
mentionChips: false,
- mainchainStrip: false,
attachmentBar: false,
agentPicker: false,
targetPicker: false,
@@ -130,7 +129,6 @@ describe('unifiedComposerHostViewModel', () => {
expect(model.replyTo?.author).toBe('Ada');
expect(model.quote?.text).toBe('quoted');
expect(model.mentions).toHaveLength(1);
- expect(model.mainchain?.mainchainTask).toBe('ready');
expect(model.attachment?.uploadProgresses?.a1?.percent).toBe(10);
expect(model.agentOptions).toHaveLength(1);
expect(model.targetPicker?.executionTargetId).toBe('t1');
@@ -155,7 +153,6 @@ describe('unifiedComposerHostViewModel', () => {
expect(vm.targetSelectionRequired).toBe(true);
expect(vm.targetSelected).toBe(false);
expect(vm.submitDisabled).toBe(true);
- expect(vm.selectedAgentLabel).toBe('@Builder');
expect(vm.availableMentionOptions).toHaveLength(1);
expect(vm.hasMentions).toBe(true);
expect(vm.inputPlaceholder).toBe('发消息给 AgentHub');
diff --git a/app/workbench/src/unifiedComposerHostViewModel.ts b/app/workbench/src/unifiedComposerHostViewModel.ts
index 71bcbeb82..428925af5 100644
--- a/app/workbench/src/unifiedComposerHostViewModel.ts
+++ b/app/workbench/src/unifiedComposerHostViewModel.ts
@@ -77,7 +77,6 @@ export interface ComposerChromeVisibility {
replyBar: boolean;
quoteBar: boolean;
mentionChips: boolean;
- mainchainStrip: boolean;
attachmentBar: boolean;
agentPicker: boolean;
targetPicker: boolean;
@@ -92,13 +91,6 @@ export interface ComposerHostRuntimeDefaults {
hasNativePicker: boolean;
}
-export interface ComposerHostMainchainModel {
- mainchainTask: 'ready' | 'draft required';
- selectedAgentLabel: string;
- selectedTargetLabel: string | undefined;
- targetSelected: boolean;
-}
-
export interface ComposerHostAttachmentModel {
attachments: ComposerAttachment[];
uploadProgresses: Record | undefined;
@@ -113,7 +105,6 @@ export interface ComposerHostChromeModel {
replyTo: ReplyToContext | null;
quote: QuoteContext | null;
mentions: ComposerMention[] | null;
- mainchain: ComposerHostMainchainModel | null;
attachment: ComposerHostAttachmentModel | null;
agentOptions: ComposerMention[] | null;
targetPicker: ComposerHostTargetPickerModel | null;
@@ -185,8 +176,6 @@ export function deriveComposerChromeVisibility(params: {
replyBar: Boolean(composer.replyTo),
quoteBar: Boolean(composer.quote),
mentionChips: hasMentions,
- /* Show mainchain when mentions or targets exist so Agent/目标/任务 always readable. */
- mainchainStrip: hasMentions || hasExecutionTargets,
attachmentBar: composer.attachments.length > 0,
agentPicker: mentionableAgentsCount > 0,
targetPicker: hasExecutionTargets,
@@ -219,14 +208,6 @@ export function buildComposerHostChromeModel(params: {
replyTo: chrome.replyBar && composer.replyTo ? composer.replyTo : null,
quote: chrome.quoteBar && composer.quote ? composer.quote : null,
mentions: chrome.mentionChips ? composer.mentions : null,
- mainchain: chrome.mainchainStrip
- ? {
- mainchainTask: derived.mainchainTask,
- selectedAgentLabel: derived.selectedAgentLabel,
- selectedTargetLabel: derived.selectedTargetLabel,
- targetSelected: derived.targetSelected,
- }
- : null,
attachment: chrome.attachmentBar
? {
attachments: composer.attachments,
diff --git a/docs/architecture/07-design-system-ssot.md b/docs/architecture/07-design-system-ssot.md
index 0f2ae00ed..fce02a88b 100644
--- a/docs/architecture/07-design-system-ssot.md
+++ b/docs/architecture/07-design-system-ssot.md
@@ -39,7 +39,7 @@ Entry CSS load: desktop/web `main.tsx` imports surface `styles/{tokens,themes,pr
8. **Design token gate**:修改 `app/shared/src/styles/` 或 `app/shared/src/designTokens.ts` 后,运行 `pnpm --dir app --filter @agenthub/shared test`;`designTokens.test.ts`、`styles/tokens-base.test.ts`、`styles/surfaceStyles.test.ts` 必须全绿。
9. **Shared component acceptance**:新组件验收见 [component-acceptance.md](../component-acceptance.md)。
10. **New chat surfaces** use the vendored shadcn primitives (`Message`, `Bubble`, `InputGroup`, `Attachment`) plus Tailwind utilities whose colors/radii resolve from `shadcn.css`. Do not add a second palette or a component-local radius scale.
-11. **Preflight stays off during migration.** `shadcn.css` imports Tailwind theme + utilities only; enabling Preflight would reset legacy CSS-Module surfaces. Remove this exception only in the change that retires the last migrated module.
+11. **Preflight stays off during migration.** `shadcn.css` imports Tailwind theme + utilities only; enabling Preflight would reset legacy CSS-Module surfaces. Remove this exception only in the change that retires the last migrated module. The hand-rolled reset in `tokens-base.css` therefore lives inside `@layer base`: an unlayered `*, *::before, *::after { margin: 0; padding: 0 }` wins over every `@layer utilities` rule and silently kills `m-*`, `mx-auto` and `space-*`.
12. **Flat chrome, glass overlays.** Workbench chrome (rail, sidebar, header, inspector, composer) uses flat `--background` / `--surface-dim` surfaces and 9–18% foreground hairlines. Frosted glass remains for transient overlays and previews, not for the persistent shell.
## 3. Legitimate surface glue (not forks)