Skip to content

feat(tui): v3 foundation quick wins — dead enum + /help + Tab autocomplete#712

Merged
kelsonpw merged 1 commit into
mainfrom
feat/tui-v3-foundation-quick-wins
May 15, 2026
Merged

feat(tui): v3 foundation quick wins — dead enum + /help + Tab autocomplete#712
kelsonpw merged 1 commit into
mainfrom
feat/tui-v3-foundation-quick-wins

Conversation

@kelsonpw
Copy link
Copy Markdown
Collaborator

@kelsonpw kelsonpw commented May 11, 2026

Summary

First slice of the TUI v3 redesign consensus from a 6-subagent audit. Lowest-risk, ship-able-now wins. Each commit is atomic so a reviewer can bisect.

Changes

  1. refactor(tui): remove unused Screen.Options enum + null registration (04e42ee1)
    The Options member of Screen was registered to a null component and referenced nowhere else. Dead enum + null component clutter the surface. Drops the screen-registry from 24 → 23 entries.

  2. feat(tui): register /help slash command — docs canonical, registry was missing (1e1571bd)
    `CLAUDE.md` documents `/help` as canonical, but the registry in `console-commands.ts` was missing it. Users typing `/help` got an empty result — the worst kind of broken because the command looks unsupported when it should be the first thing a new user reaches for. Adds a dispatch case that prints every other slash command + a key-bind cheatsheet via `setCommandFeedback`. Regression test asserts `/help` is in the registry and not `requiresIdle`.

  3. `feat(tui): / autocompletes slash command to longest common prefix` (`bb289e8b`)
    Bash/zsh/fish convention: Tab extends a partial command to the LCP of matches. The wizard's slash palette swallowed Tab (`SlashCommandInput.tsx:118`) — only Enter could select. Now `Tab` extends the input to `longestCommonPrefix(filtered.map(c => c.cmd))` in slash mode. Outside slash mode Tab is still a no-op (parent owns it). `longestCommonPrefix` exported with 7 unit tests covering empty / single / divergent / identity / real-world (`/d` → `/d` for debug vs diagnostics).

Test plan

  • `pnpm exec tsc --noEmit` — clean
  • `pnpm exec eslint src/ --cache --max-warnings=0` — clean (one pre-existing warning in `EventPlanFullScreen.test.tsx:9` is unrelated, present on `main`)
  • `pnpm exec vitest run --pool=forks --maxWorkers=1 src/ui/tui/` — 801/801 passing (7 new tests added)
  • Manual: type `/he`, expect `/help` filled in; press Enter, expect categorized command list rendered

Out of scope (follow-up PRs)

This is the foundation slice. The audit surfaced bigger wins that warrant their own PRs:

  • Consolidation — SignupEmail+ToS+SignupFullName into a single CreateAccount screen
  • AccountConfirm extraction from AuthScreen
  • JourneyStepper rebucketing ("Auth" + DataSetup separation)
  • OutroScreen rules-of-hooks fix (useEffect after early-return)
  • Always-include Events tab on RunScreen (mid-run reindex bug)
  • Delete InlineEventPlan + ConditionalTips from RunScreen
  • McpScreen copy reframe (lead with value prop, not unfound clients)
  • Slack demotion from main flow to OutroScreen picker
  • TUI delight pass — gradient wordmark on Outro, picker selection flash, three-mood spinner, direction-aware dissolves, typewriter file path, DataIngestionCheck sparkline, Snake easter-egg buffer, voice-driven copy

🤖 Generated with Claude Code


Note

Low Risk
Low risk cleanup that only removes dead screen identifiers/mappings; no behavior changes expected unless something external referenced the deleted enum value.

Overview
Removes the unused Screen.Options entry from the Screen enum and deletes its null component mapping from screen-registry.tsx, reducing the registered screen surface area.

Reviewed by Cursor Bugbot for commit c2c12e2. Bugbot is set up for automated code reviews on this repo. Configure here.

@kelsonpw kelsonpw requested a review from a team as a code owner May 11, 2026 05:29
@kelsonpw kelsonpw force-pushed the feat/tui-v3-foundation-quick-wins branch from bb289e8 to c6dc278 Compare May 11, 2026 05:33
@kelsonpw kelsonpw changed the base branch from main to feat/v2-tui-redesign May 11, 2026 05:33
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Duplicate /help switch case shadows better implementation
    • Removed the first inline /help case (line 104) from the switch statement so the better getHelpText(runActive) handler is now reachable.
  • ✅ Fixed: Duplicate /help entry in COMMANDS array
    • Removed the second duplicate /help entry from the COMMANDS array, leaving the single entry at the top with description 'List every slash command and what it does'.
  • ✅ Fixed: Duplicate longestCommonPrefix already exists in PathInput
    • Replaced the duplicate implementation in SlashCommandInput.tsx with an import from PathInput.tsx, re-exporting it for existing consumers.

Create PR

Or push these changes by commenting:

@cursor push 2e3a993307
Preview (2e3a993307)
diff --git a/src/ui/tui/components/ConsoleView.tsx b/src/ui/tui/components/ConsoleView.tsx
--- a/src/ui/tui/components/ConsoleView.tsx
+++ b/src/ui/tui/components/ConsoleView.tsx
@@ -101,21 +101,6 @@
   }
 
   switch (cmd) {
-    case '/help': {
-      // CLAUDE.md documents `/help` as canonical but the registry was
-      // missing it — users typed `/help` and got nothing. Render a
-      // categorized list of every slash command + its description.
-      const lines = ['Slash commands:', ''];
-      for (const c of COMMANDS) {
-        if (c.cmd === '/help') continue;
-        const idleNote = c.requiresIdle ? ' [paused during runs]' : '';
-        lines.push(`  ${c.cmd.padEnd(18)} ${c.desc}${idleNote}`);
-      }
-      lines.push('');
-      lines.push('Key bindings: [/] commands · [Tab] ask · [Esc] back · [Ctrl+C] quit');
-      store.setCommandFeedback(lines.join('\n'), 30_000);
-      break;
-    }
     case '/region':
       store.setRegionForced();
       break;
@@ -799,8 +784,8 @@
                   ? eventPlanPromptShowing
                     ? 'Finish the plan above ([Y]/[S]/[F]) — / and Tab resume after.'
                     : visibleHistory.length > 0
-                      ? 'Press / for commands · Tab to ask · Esc to hide answer'
-                      : 'Press / for commands or Tab to ask a question'
+                    ? 'Press / for commands · Tab to ask · Esc to hide answer'
+                    : 'Press / for commands or Tab to ask a question'
                   : 'Press / for commands'}
               </Text>
             )}

diff --git a/src/ui/tui/console-commands.ts b/src/ui/tui/console-commands.ts
--- a/src/ui/tui/console-commands.ts
+++ b/src/ui/tui/console-commands.ts
@@ -70,10 +70,6 @@
     cmd: '/status',
     desc: 'Show orchestration status: pending choices, verifications, MCP capabilities, next action',
   },
-  {
-    cmd: '/help',
-    desc: 'List slash commands with one-line descriptions',
-  },
   { cmd: '/snake', desc: 'Play Snake' },
   { cmd: '/exit', desc: 'Exit the wizard' },
 ];

diff --git a/src/ui/tui/primitives/SlashCommandInput.tsx b/src/ui/tui/primitives/SlashCommandInput.tsx
--- a/src/ui/tui/primitives/SlashCommandInput.tsx
+++ b/src/ui/tui/primitives/SlashCommandInput.tsx
@@ -10,7 +10,10 @@
 import { Box, Text, useInput } from 'ink';
 import { useState, useEffect } from 'react';
 import { Colors, Icons } from '../styles.js';
+import { longestCommonPrefix } from '../components/PathInput.js';
 
+export { longestCommonPrefix };
+
 export interface SlashCommand {
   cmd: string;
   desc: string;
@@ -33,26 +36,6 @@
   );
 }
 
-/**
- * Returns the longest common prefix shared by every input string.
- * Used by Tab autocomplete to extend a partial slash command as far as
- * the input is unambiguous — bash/zsh/fish convention.
- *
- * Exported for unit testing.
- */
-export function longestCommonPrefix(strings: readonly string[]): string {
-  if (strings.length === 0) return '';
-  if (strings.length === 1) return strings[0];
-  let prefix = strings[0];
-  for (let i = 1; i < strings.length; i++) {
-    while (!strings[i].startsWith(prefix)) {
-      prefix = prefix.slice(0, -1);
-      if (prefix === '') return '';
-    }
-  }
-  return prefix;
-}
-
 interface SlashCommandInputProps {
   commands?: SlashCommand[];
   isActive: boolean;

You can send follow-ups to the cloud agent here.

Comment thread src/ui/tui/components/ConsoleView.tsx Outdated
Comment thread src/ui/tui/console-commands.ts
Comment thread src/ui/tui/primitives/SlashCommandInput.tsx Outdated
@kelsonpw kelsonpw force-pushed the feat/tui-v3-foundation-quick-wins branch from e993e40 to 764ff8a Compare May 11, 2026 05:43
@kelsonpw kelsonpw changed the base branch from feat/v2-tui-redesign to main May 13, 2026 23:54
@kelsonpw kelsonpw force-pushed the feat/tui-v3-foundation-quick-wins branch from 764ff8a to 14d9f97 Compare May 13, 2026 23:54
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Tab autocomplete LCP polluted by description-matched commands
    • Filtered the LCP computation to only include commands where c.cmd prefix-matches the input query, excluding description-only matches that would collapse the common prefix.

Create PR

Or push these changes by commenting:

@cursor push 4464ee8b9d
Preview (4464ee8b9d)
diff --git a/src/ui/tui/primitives/SlashCommandInput.tsx b/src/ui/tui/primitives/SlashCommandInput.tsx
--- a/src/ui/tui/primitives/SlashCommandInput.tsx
+++ b/src/ui/tui/primitives/SlashCommandInput.tsx
@@ -143,7 +143,11 @@
         // there's nothing to complete (filtered.length === 0) or when
         // the input is already at the LCP.
         if (isSlashMode && filtered.length > 0) {
-          const lcp = longestCommonPrefix(filtered.map((c) => c.cmd));
+          const prefixMatches = filtered.filter((c) =>
+            c.cmd.slice(1).startsWith(query),
+          );
+          if (prefixMatches.length === 0) return;
+          const lcp = longestCommonPrefix(prefixMatches.map((c) => c.cmd));
           if (lcp.length > value.length) {
             setValue(lcp);
             setSelectedIndex(0);

You can send follow-ups to the cloud agent here.

Comment thread src/ui/tui/primitives/SlashCommandInput.tsx Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: /help renders as single text blob, loses per-line formatting
    • Changed lines.join('\n') to lines so the /help handler passes the array directly to setCommandFeedback, matching the pattern used by /debug and /diagnostics for proper per-line rendering.

Create PR

Or push these changes by commenting:

@cursor push 96aa9384b6
Preview (96aa9384b6)
diff --git a/src/ui/tui/components/ConsoleView.tsx b/src/ui/tui/components/ConsoleView.tsx
--- a/src/ui/tui/components/ConsoleView.tsx
+++ b/src/ui/tui/components/ConsoleView.tsx
@@ -111,7 +111,7 @@
       }
       lines.push('');
       lines.push('Key bindings: [/] commands · [Tab] ask · [Esc] back · [Ctrl+C] quit');
-      store.setCommandFeedback(lines.join('\n'), 30_000);
+      store.setCommandFeedback(lines, 30_000);
       break;
     }
     case '/region':

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 32e449a. Configure here.

Comment thread src/ui/tui/components/ConsoleView.tsx Outdated
The `Options` enum member in `Screen` (src/ui/tui/flows.ts:36) is
registered to a `null` component in `screen-registry.tsx:85` and
has zero references anywhere else in the codebase. Confirmed with
\`grep -rn 'Screen.Options' src/\` — only hit is the null
registration site. Dead enum + null component clutter the surface;
removing them drops the screen count from 24 → 23 and surfaces a
real path to "everything in the registry is a real screen."

No tests reference the value. Type-check + lint stay green.
@kelsonpw kelsonpw force-pushed the feat/tui-v3-foundation-quick-wins branch from 7558fe3 to c2c12e2 Compare May 14, 2026 22:01
@kelsonpw kelsonpw merged commit faf62e7 into main May 15, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant