diff --git a/desktop/src/__tests__/pages.test.tsx b/desktop/src/__tests__/pages.test.tsx
index f1f02e4f..570fd481 100644
--- a/desktop/src/__tests__/pages.test.tsx
+++ b/desktop/src/__tests__/pages.test.tsx
@@ -159,6 +159,22 @@ describe('Content-only pages render without errors', () => {
expect(screen.queryByText('/internal-only')).not.toBeInTheDocument()
})
+ it('EmptySession shows /goal as one command with argument hints, not pseudo subcommands', async () => {
+ vi.mocked(skillsApi.list).mockResolvedValueOnce({ skills: [] })
+
+ render()
+
+ fireEvent.change(screen.getByRole('textbox'), {
+ target: { value: '/goal', selectionStart: 5 },
+ })
+
+ expect(await screen.findAllByText('/goal')).toHaveLength(2)
+ expect(screen.getByText('[status|pause|resume|complete|clear|--tokens |]')).toBeInTheDocument()
+ expect(screen.getByText('Create or manage an autonomous completion goal')).toBeInTheDocument()
+ expect(screen.queryByText('/goal status')).not.toBeInTheDocument()
+ expect(screen.queryByText('/goal --tokens')).not.toBeInTheDocument()
+ })
+
it('EmptySession renders mascot and composer', async () => {
let container!: HTMLElement
await act(async () => {
diff --git a/desktop/src/components/chat/ChatInput.tsx b/desktop/src/components/chat/ChatInput.tsx
index 579e7b26..54d90a28 100644
--- a/desktop/src/components/chat/ChatInput.tsx
+++ b/desktop/src/components/chat/ChatInput.tsx
@@ -887,12 +887,12 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
: 'hover:bg-[var(--color-surface-hover)]'
}`}
>
-
-
+
+
/{command.name}
{command.argumentHint ? (
-
+
{command.argumentHint}
) : null}
diff --git a/desktop/src/components/chat/composerUtils.test.ts b/desktop/src/components/chat/composerUtils.test.ts
index 70fd0f26..acc14e5b 100644
--- a/desktop/src/components/chat/composerUtils.test.ts
+++ b/desktop/src/components/chat/composerUtils.test.ts
@@ -18,9 +18,9 @@ describe('composerUtils', () => {
expect(findSlashToken('/ review', 8)).toBeNull()
})
- it('keeps /goal argument completion active for one subcommand token', () => {
- expect(findSlashToken('/goal ', 6)).toEqual({ start: 0, filter: 'goal ' })
- expect(findSlashToken('/goal sta', 9)).toEqual({ start: 0, filter: 'goal sta' })
+ it('closes slash completion once /goal arguments start', () => {
+ expect(findSlashToken('/goal ', 6)).toBeNull()
+ expect(findSlashToken('/goal sta', 9)).toBeNull()
expect(findSlashToken('/goal build app', 15)).toBeNull()
})
@@ -82,28 +82,22 @@ describe('composerUtils', () => {
)
})
- it('suggests /goal subcommands after the /goal argument separator', () => {
- expect(
- filterSlashCommands(mergeSlashCommands([]), 'goal ').map((command) => command.name),
- ).toEqual([
- 'goal status',
- 'goal pause',
- 'goal resume',
- 'goal complete',
- 'goal clear',
- 'goal --tokens',
- ])
+ it('keeps /goal as a single command with argument hints instead of pseudo subcommands', () => {
+ const commands = filterSlashCommands(mergeSlashCommands([]), 'goal')
expect(
- filterSlashCommands(mergeSlashCommands([]), 'goal s').map((command) => command.name),
- ).toEqual(['goal status'])
+ commands.map((command) => command.name),
+ ).toEqual(['goal'])
+ expect(commands[0]).toMatchObject({
+ description: 'Create or manage an autonomous completion goal',
+ argumentHint: '[status|pause|resume|complete|clear|--tokens |]',
+ })
+ expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal status')
+ expect(mergeSlashCommands([]).map((command) => command.name)).not.toContain('goal --tokens')
})
- it('replaces a /goal subcommand fragment with the selected subcommand', () => {
- expect(replaceSlashCommand('/goal sta', 9, 'goal status')).toEqual({
- value: '/goal status ',
- cursorPos: 13,
- })
+ it('does not replace /goal arguments as slash command fragments', () => {
+ expect(replaceSlashCommand('/goal sta', 9, 'goal status')).toBeNull()
})
it('ranks slash command name matches before broad description matches', () => {
diff --git a/desktop/src/components/chat/composerUtils.ts b/desktop/src/components/chat/composerUtils.ts
index e804495c..b35c36a4 100644
--- a/desktop/src/components/chat/composerUtils.ts
+++ b/desktop/src/components/chat/composerUtils.ts
@@ -19,19 +19,6 @@ export const SLASH_COMMAND_ALIASES = [
{ name: 'plugins', target: 'plugin' },
] as const
-export const GOAL_SLASH_SUBCOMMANDS = [
- { name: 'goal status', description: 'Show the current goal status' },
- { name: 'goal pause', description: 'Pause the active goal loop' },
- { name: 'goal resume', description: 'Resume the active goal loop' },
- { name: 'goal complete', description: 'Mark the active goal complete' },
- { name: 'goal clear', description: 'Clear the active goal' },
- {
- name: 'goal --tokens',
- description: 'Create a goal with a token budget',
- argumentHint: ' ',
- },
-] as const
-
export const FALLBACK_SLASH_COMMANDS = [
...PANEL_SLASH_COMMANDS,
...SETTINGS_SLASH_COMMANDS.map(({ name, description }) => ({ name, description })),
@@ -42,7 +29,6 @@ export const FALLBACK_SLASH_COMMANDS = [
description: 'Create or manage an autonomous completion goal',
argumentHint: '[status|pause|resume|complete|clear|--tokens |]',
},
- ...GOAL_SLASH_SUBCOMMANDS,
{ name: 'review', description: 'Review code changes' },
{ name: 'commit', description: 'Create a git commit' },
{ name: 'pr', description: 'Create a pull request' },
@@ -168,7 +154,7 @@ export function findSlashTrigger(value: string, cursorPos: number): SlashTrigger
const filter = textBeforeCursor.slice(slashPos + 1)
if (filter.includes('\n')) return null
- if (/\s/.test(filter) && !/^goal\s+[^\s]*$/i.test(filter)) return null
+ if (/\s/.test(filter)) return null
return { slashPos, filter }
}
diff --git a/desktop/src/pages/EmptySession.tsx b/desktop/src/pages/EmptySession.tsx
index a2ba8154..3ba97704 100644
--- a/desktop/src/pages/EmptySession.tsx
+++ b/desktop/src/pages/EmptySession.tsx
@@ -640,7 +640,14 @@ export function EmptySession() {
index === slashSelectedIndex ? 'bg-[var(--color-surface-hover)]' : 'hover:bg-[var(--color-surface-hover)]'
}`}
>
- /{command.name}
+
+ /{command.name}
+ {command.argumentHint ? (
+
+ {command.argumentHint}
+
+ ) : null}
+
{command.description}
))}