Skip to content

refactor(core): reorganize UI into component library#121

Open
zrosenbauer wants to merge 14 commits intomainfrom
feat/component-lib
Open

refactor(core): reorganize UI into component library#121
zrosenbauer wants to merge 14 commits intomainfrom
feat/component-lib

Conversation

@zrosenbauer
Copy link
Copy Markdown
Member

Summary

  • Reorganize flat UI directory into prompts/, display/, and layout/ subdirectories
  • Add new prompt components: Autocomplete, GroupMultiSelect, PathInput, SelectKey
  • Add new display components: Alert, ProgressBar, Spinner, StatusMessage
  • Extract screen/ module from ui/ with provider, context, and output store
  • Consolidate output rendering into a single output.tsx file
  • Add shared theme module for consistent styling

Test plan

  • pnpm check passes (typecheck + lint + format)
  • pnpm test passes
  • Existing stories viewer works with updated import paths
  • New component stories render correctly

zrosenbauer and others added 2 commits March 27, 2026 18:53
…ayout modules

Move UI components from flat structure into organized subdirectories:
- prompts/ for user input components (Confirm, Select, TextInput, etc.)
- display/ for presentational components
- layout/ for structural components (FullScreen, ScrollArea, Tabs)
- Consolidate output module into single file
- Add screen module with provider and context exports

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
oss-kidd Ignored Ignored Preview Mar 30, 2026 1:04am

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 57f1abb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@kidd-cli/core Minor
@kidd-cli/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

zrosenbauer and others added 2 commits March 27, 2026 19:01
The reduce with a void accumulator was semantically incorrect for
side-effect iteration. Revert to map which is the idiomatic choice
in this codebase.

Co-Authored-By: Claude <noreply@anthropic.com>
Merge origin/main into feat/component-lib, resolving conflicts in:
- packages/core/src/index.ts: keep expanded type exports from main + ScreenContext from branch
- packages/core/src/ui/output.tsx: adopt cancelled/error spinner states from main using theme abstractions

Co-Authored-By: Claude <noreply@anthropic.com>
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 27, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing feat/component-lib (57f1abb) with main (4395330)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Remove all backwards compatibility for the deprecated `spinner` option
on `cli()`, `createContext()`, and `createTestContext()`. Consumers must
migrate to `status` — no deprecation period, hard break.

- Remove `spinner` from CliOptions, RuntimeOptions, CreateContextOptions,
  TestContextOptions and all pass-through call sites
- Remove compat `resolveStatus` logic that wrapped spinner in Status
- Remove "backward compatibility" comment on screen re-exports
- Delete deprecated-spinner test case
- Clean up unused Spinner type imports

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

This PR reorganizes the public UI export surface by introducing new barrel modules (ui/display/index.ts, ui/layout/index.ts, ui/prompts/index.ts, screen/index.ts) and adding local implementations of terminal UI components previously sourced from @inkjs/ui. The changes include new prompt components (TextInput, PasswordInput, PathInput, Autocomplete, Confirm, SelectKey, MultiSelect, GroupMultiSelect), new display components (Alert, ProgressBar, StatusMessage, ErrorMessage), removal of old re-export wrappers, a new centralized theme module, and corresponding Storybook story configurations. Import paths across ~15 files are updated to reference the reorganized module structure, and a module resolver patch is added to the story importer.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: reorganizing the UI into a component library structure with new subdirectories and components.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of structural reorganization, new components added, and the test plan to validate the changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/component-lib

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/context/create-context.ts (1)

25-33: ⚠️ Potential issue | 🟡 Minor

Remove stale Spinner override claim from CreateContextOptions docs.

Line 30-Line 31 still document direct Spinner injection, but this option was removed from CreateContextOptions. The API docs are now inaccurate.

🛠️ Suggested doc fix
- * custom {`@link` Log}, {`@link` Prompts}, {`@link` Status}, and {`@link` Spinner}
- * implementations; when omitted, default `@clack/prompts`-backed instances
+ * custom {`@link` Log}, {`@link` Prompts}, and {`@link` Status}
+ * implementations; when omitted, default `@clack/prompts`-backed instances
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/context/create-context.ts` around lines 25 - 33, The JSDoc
for CreateContextOptions incorrectly claims a direct Spinner override; update
the documentation in create-context.ts (the CreateContextOptions docs used by
createContext / CommandContext) to remove the stale "Spinner" injection mention
and only list the actual supported overrides (e.g., Log, Prompts, Status),
ensuring the doc text matches the current CreateContextOptions shape and
available overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/designs/component-library.md`:
- Line 77: Add language identifiers to all fenced code blocks in the component
library doc (the directory structure and visual example blocks) so linting
recognizes them; specifically update each triple-backtick block (the directory
structure example and other plaintext examples around the visual examples) to
start with ```text instead of ```; ensure every fenced block between the noted
examples includes the `text` language tag.

In `@packages/core/src/ui/display/alert.tsx`:
- Line 79: The component coerces ReactNode to a string via String(children)
which yields "[object Object]" for JSX; change the prop type so
AlertProps.children is string (not ReactNode), update any usages/prop types that
reference AlertProps and the component signature, and then use children directly
when building contentStr (referencing contentStr and the component that reads
AlertProps.children) to ensure only text is accepted and rendered correctly.

In `@packages/core/src/ui/display/progress-bar.tsx`:
- Line 67: The calculation of ratio in the ProgressBar component can divide by
zero; update the ratio computation (the const ratio in progress-bar.tsx /
ProgressBar) to guard against max === 0 (or non-positive max) before
dividing—e.g., if max is zero or <= 0 treat ratio as 0 (or clamp appropriately),
otherwise compute Math.min(1, Math.max(0, value / max)); apply the same guard
anywhere else that uses value/max.

In `@packages/core/src/ui/display/status-message.tsx`:
- Line 59: The Text element in StatusMessage is coercing children with
String(children) which breaks ReactNode rendering; update the Text rendering in
the StatusMessage component to output children as a ReactNode instead of
string-coercing it (preserve the intended leading space by rendering an explicit
space node or spacing prop and then render children directly). Locate the Text
line that currently reads <Text>{` ${String(children)}`}</Text> and replace it
so it renders a literal space (if needed) and the children ReactNode without
calling String(), ensuring complex children (JSX, elements) render correctly.

In `@packages/core/src/ui/prompts/autocomplete.tsx`:
- Around line 131-139: The Delete key is currently treated the same as
Backspace; update the key handling so key.backspace removes the character before
the cursor (keep using removeCharAt(search, cursorOffset - 1), decrement
cursorOffset and setFocusIndex(0)), while key.delete removes the character at
the cursor (call removeCharAt(search, cursorOffset) only when cursorOffset <
search.length, do not decrement cursorOffset, and still call setSearch and
setFocusIndex(0)); ensure both branches return after handling. Reference the
current handlers around key.backspace / key.delete, removeCharAt, setSearch,
setCursorOffset, and setFocusIndex.

In `@packages/core/src/ui/prompts/group-multi-select.tsx`:
- Around line 245-255: The moveFocus function can land on a disabled option at
the list boundary; update moveFocus(current: number, direction: number, items:
readonly FlatItem[]) to scan in the given direction until it finds an enabled
option and return that index, but if the scan reaches out-of-bounds without
finding any enabled option, return the original current index instead of the
last in-bounds index. Use the existing symbols (current, direction, items,
FlatItem, item.kind === 'option' and item.option.disabled) to implement a loop
that advances next while skipping disabled options and stops with current if no
enabled candidate is found.

In `@packages/core/src/ui/prompts/multi-select.tsx`:
- Around line 141-151: The OptionRow elements use option.label as the React key
which can collide; update the key in the map that renders OptionRow (the block
passing option, indicator, isFocused, isSelected, isDisabled) to use a stable
unique identifier such as option.value (or if value may not be unique, a
combined key like `${option.value}-${index}` or an explicit id field) instead of
option.label so React reconciliation is deterministic; locate the map that
renders OptionRow (references: OptionRow, option.label, focusedIndex) and
replace the key accordingly.

In `@packages/core/src/ui/prompts/navigation.ts`:
- Around line 98-110: The exported function resolveInitialIndex currently
accepts two positional parameters; change its signature to use a single object
parameter with destructured properties (e.g., { options, defaultValue }: {
options: readonly PromptOption<TValue>[], defaultValue?: TValue }) and update
all callers (such as select.tsx where it's invoked) to pass an object instead of
two args; preserve the generic TValue and return type and ensure type
annotations for PromptOption and optional defaultValue remain correct.

---

Outside diff comments:
In `@packages/core/src/context/create-context.ts`:
- Around line 25-33: The JSDoc for CreateContextOptions incorrectly claims a
direct Spinner override; update the documentation in create-context.ts (the
CreateContextOptions docs used by createContext / CommandContext) to remove the
stale "Spinner" injection mention and only list the actual supported overrides
(e.g., Log, Prompts, Status), ensuring the doc text matches the current
CreateContextOptions shape and available overrides.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ee727fa-2e1a-4614-ac26-6d045471dfe4

📥 Commits

Reviewing files that changed from the base of the PR and between b3049e5 and c581c9d.

⛔ Files ignored due to path filters (1)
  • .changeset/reorganize-ui-components.md is excluded by !.changeset/**
📒 Files selected for processing (81)
  • docs/designs/component-library.md
  • examples/diagnostic-output/package.json
  • packages/core/src/cli.ts
  • packages/core/src/context/create-context.ts
  • packages/core/src/index.ts
  • packages/core/src/runtime/runtime.ts
  • packages/core/src/runtime/types.ts
  • packages/core/src/screen/index.ts
  • packages/core/src/screen/output/index.ts
  • packages/core/src/screen/output/screen-log.ts
  • packages/core/src/screen/output/screen-report.ts
  • packages/core/src/screen/output/screen-spinner.ts
  • packages/core/src/screen/output/store-key.ts
  • packages/core/src/screen/output/store.ts
  • packages/core/src/screen/output/types.ts
  • packages/core/src/screen/output/use-output-store.ts
  • packages/core/src/screen/provider.tsx
  • packages/core/src/screen/screen.test.ts
  • packages/core/src/screen/screen.tsx
  • packages/core/src/stories/decorators.tsx
  • packages/core/src/stories/viewer/components/field-control.tsx
  • packages/core/src/stories/viewer/components/preview.tsx
  • packages/core/src/stories/viewer/components/props-editor.tsx
  • packages/core/src/stories/viewer/components/sidebar.tsx
  • packages/core/src/stories/viewer/stories-app.tsx
  • packages/core/src/stories/viewer/stories-check.tsx
  • packages/core/src/test/context.test.ts
  • packages/core/src/test/context.ts
  • packages/core/src/test/types.ts
  • packages/core/src/types/cli.ts
  • packages/core/src/ui/confirm.tsx
  • packages/core/src/ui/display/alert.stories.tsx
  • packages/core/src/ui/display/alert.tsx
  • packages/core/src/ui/display/error-message.tsx
  • packages/core/src/ui/display/index.ts
  • packages/core/src/ui/display/progress-bar.stories.tsx
  • packages/core/src/ui/display/progress-bar.tsx
  • packages/core/src/ui/display/spinner.stories.tsx
  • packages/core/src/ui/display/spinner.tsx
  • packages/core/src/ui/display/status-message.stories.tsx
  • packages/core/src/ui/display/status-message.tsx
  • packages/core/src/ui/index.ts
  • packages/core/src/ui/layout/fullscreen.test.ts
  • packages/core/src/ui/layout/fullscreen.tsx
  • packages/core/src/ui/layout/index.ts
  • packages/core/src/ui/layout/scroll-area.tsx
  • packages/core/src/ui/layout/tabs.tsx
  • packages/core/src/ui/layout/use-size.test.ts
  • packages/core/src/ui/layout/use-size.tsx
  • packages/core/src/ui/multi-select.tsx
  • packages/core/src/ui/output.tsx
  • packages/core/src/ui/password-input.tsx
  • packages/core/src/ui/prompts/autocomplete.stories.tsx
  • packages/core/src/ui/prompts/autocomplete.tsx
  • packages/core/src/ui/prompts/confirm.stories.tsx
  • packages/core/src/ui/prompts/confirm.tsx
  • packages/core/src/ui/prompts/cursor-value.tsx
  • packages/core/src/ui/prompts/group-multi-select.stories.tsx
  • packages/core/src/ui/prompts/group-multi-select.tsx
  • packages/core/src/ui/prompts/index.ts
  • packages/core/src/ui/prompts/input-state.ts
  • packages/core/src/ui/prompts/multi-select.stories.tsx
  • packages/core/src/ui/prompts/multi-select.tsx
  • packages/core/src/ui/prompts/navigation.ts
  • packages/core/src/ui/prompts/option-row.tsx
  • packages/core/src/ui/prompts/password-input.stories.tsx
  • packages/core/src/ui/prompts/password-input.tsx
  • packages/core/src/ui/prompts/path-input.stories.tsx
  • packages/core/src/ui/prompts/path-input.tsx
  • packages/core/src/ui/prompts/select-key.stories.tsx
  • packages/core/src/ui/prompts/select-key.tsx
  • packages/core/src/ui/prompts/select.stories.tsx
  • packages/core/src/ui/prompts/select.tsx
  • packages/core/src/ui/prompts/string-utils.ts
  • packages/core/src/ui/prompts/text-input.stories.tsx
  • packages/core/src/ui/prompts/text-input.tsx
  • packages/core/src/ui/prompts/types.ts
  • packages/core/src/ui/select.tsx
  • packages/core/src/ui/spinner.tsx
  • packages/core/src/ui/text-input.tsx
  • packages/core/src/ui/theme.ts
💤 Files with no reviewable changes (9)
  • packages/core/src/runtime/runtime.ts
  • packages/core/src/ui/multi-select.tsx
  • packages/core/src/ui/spinner.tsx
  • packages/core/src/ui/select.tsx
  • packages/core/src/cli.ts
  • packages/core/src/ui/text-input.tsx
  • packages/core/src/ui/confirm.tsx
  • packages/core/src/screen/output/index.ts
  • packages/core/src/ui/password-input.tsx

- Add language identifiers to fenced code blocks in component-library.md
- Restrict Alert and StatusMessage children to string (prevents [object Object])
- Guard ProgressBar against division by zero when max is 0
- Fix Delete key in Autocomplete to remove char at cursor (not before)
- Prevent focus landing on disabled option at boundary in GroupMultiSelect
- Use option.value as React key in MultiSelect to avoid collisions
- Refactor resolveInitialIndex to use object destructuring per conventions

Co-Authored-By: Claude <noreply@anthropic.com>
zrosenbauer and others added 4 commits March 29, 2026 19:34
Co-Authored-By: Claude <noreply@anthropic.com>
- Add @kidd-cli/cli as root devDependency for direct `kidd stories` access
- Patch Module._resolveFilename in story importer to handle TypeScript
  ESM-style .js -> .ts/.tsx extension mapping (same strategy as tsx/ts-node)
- Replace strikethrough on disabled options with gray color (matches clack)
- Add (disabled) text fallback when colors are not supported
- Use gray + dim for disabled option hints

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/core/src/ui/prompts/select.tsx (1)

115-125: ⚠️ Potential issue | 🟠 Major

Use a stable key to avoid reconciliation bugs.

At Line 124, key={option.label} can collide when labels repeat. That can misrender focus/selection state between rows.

Proposed fix
           <OptionRow
-            key={option.label}
+            key={String(option.value)}
             option={option}
             indicator={indicator}
             isFocused={index === focusedIndex}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/ui/prompts/select.tsx` around lines 115 - 125, The list
uses option.label as the React key in the options.map rendering (OptionRow)
which can collide for duplicate labels; change the key to a stable unique
identifier such as option.id or, if no id exists, use the map index only as a
fallback (e.g., key={option.id ?? `option-${index}`}) so each OptionRow has a
stable, unique key and avoids reconciliation/focus/selection bugs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/designs/component-library.md`:
- Around line 645-650: The fenced code block containing the dependency graph
(the block with "foundation", "├── prompts-core", "├── prompts-extended", "└──
display") is missing a language identifier; update that fenced block to use the
plaintext language identifier by changing the opening fence from ``` to ```text
so it matches other plaintext examples in the doc.

In `@packages/core/src/ui/prompts/autocomplete.tsx`:
- Around line 103-106: When handling the down-arrow case in the autocomplete key
handler, guard against an empty filtered list so focusIndex isn't set to -1:
check filtered.length > 0 before computing next and calling setFocusIndex; if
filtered is empty, do nothing (or keep focusIndex at -1/0 per desired behavior).
Update the block that uses key.downArrow, Math.min(filtered.length - 1,
focusIndex + 1), setFocusIndex and the focused = filtered[next] access to only
run when filtered.length > 0 to avoid negative indices and out-of-bounds access.

In `@packages/core/src/ui/prompts/group-multi-select.tsx`:
- Around line 332-337: The key generator itemKey for FlatItem can produce
collisions when two options in the same group have identical labels; update
itemKey to use a stable, unique identifier (e.g., option.value) for option keys
instead of option.label, ensuring option.value is stringified if necessary;
change the option branch in itemKey to generate
`option-${groupName}-${String(option.value)}` and verify the FlatItem/option
types (and any generic TValue) support or coerce to a string to avoid runtime
key collisions.

---

Duplicate comments:
In `@packages/core/src/ui/prompts/select.tsx`:
- Around line 115-125: The list uses option.label as the React key in the
options.map rendering (OptionRow) which can collide for duplicate labels; change
the key to a stable unique identifier such as option.id or, if no id exists, use
the map index only as a fallback (e.g., key={option.id ?? `option-${index}`}) so
each OptionRow has a stable, unique key and avoids
reconciliation/focus/selection bugs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0b29016-2307-4cc0-868c-7d15296e2688

📥 Commits

Reviewing files that changed from the base of the PR and between c581c9d and 2ceb33d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (17)
  • codspeed.yml
  • docs/designs/component-library.md
  • examples/tui/package.json
  • package.json
  • packages/core/src/stories/importer.ts
  • packages/core/src/stories/viewer/components/preview.tsx
  • packages/core/src/stories/viewer/stories-app.tsx
  • packages/core/src/ui/display/alert.tsx
  • packages/core/src/ui/display/progress-bar.tsx
  • packages/core/src/ui/display/status-message.tsx
  • packages/core/src/ui/index.ts
  • packages/core/src/ui/prompts/autocomplete.tsx
  • packages/core/src/ui/prompts/group-multi-select.tsx
  • packages/core/src/ui/prompts/multi-select.tsx
  • packages/core/src/ui/prompts/navigation.ts
  • packages/core/src/ui/prompts/option-row.tsx
  • packages/core/src/ui/prompts/select.tsx

zrosenbauer and others added 2 commits March 29, 2026 20:42
Static test/story data doesn't need to be frozen — it's already
a const binding that nothing mutates.

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove docs/designs/component-library.md design doc
- Guard focus movement in autocomplete when filtered list is empty
- Fix key collision in group-multi-select by using option.value instead of label

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/ui/prompts/autocomplete.tsx`:
- Around line 93-104: The up-arrow branch for the autocomplete currently only
clamps the lower bound, so if focusIndex is greater than filtered.length - 1 the
new index stays out of range; update the calculation of next in that branch (the
code that sets focusIndex via setFocusIndex) to clamp both ends using
filtered.length - 1 (e.g., compute next = Math.max(0, Math.min(filtered.length -
1, focusIndex - 1))) and then use that next when calling setFocusIndex and
onChange (referencing focusIndex, filtered, setFocusIndex, and onChange).

In `@packages/core/src/ui/prompts/group-multi-select.tsx`:
- Around line 75-80: The focusIndex state is initialized once and can become
out-of-bounds when flatItems (from buildFlatItems using options and
selectableGroups) changes; add a reconciliation effect that runs when flatItems
changes to clamp or reset focusIndex via setFocusIndex to a valid entry (e.g.,
min(current, flatItems.length - 1)) and prefer the first enabled/selectable item
(skip disabled rows) so keyboard navigation and initial focus never point at a
removed or disabled item; reference the flatItems variable,
focusIndex/setFocusIndex state, and the buildFlatItems/selectableGroups logic
when implementing this fix.
- Around line 84-88: The early return in the useInput handler prevents Enter
handling when flatItems is empty; update the handler in useInput (the callback
referencing flatItems) so it does not unconditionally return on flatItems.length
=== 0 — instead only skip navigation/selection logic for empty lists but still
allow the Enter/Return branch to run so non-required prompts can submit [] and
required prompts can run validation; adjust the conditional to check key.name
(e.g., 'return'/'enter') and only bypass other key handling when flatItems is
empty while preserving Enter processing.
- Around line 90-122: Replace the sequential if-returns in the input handler
with a ts-pattern match() dispatch so control flow is explicit and conforms to
the style guide: use match({ key, input }) (or match(key) and match on input
where appropriate) to branch on upArrow, downArrow, input === ' ' (space) and
return; in each arm call the same side-effecting helpers
(setFocusIndex(moveFocus(...)), toggleItem -> setSelected -> setError(undefined)
-> onChange, and onSubmit) and preserve the required validation (required &&
selected.length === 0) and early returns inside the matching arm; update imports
to include match from 'ts-pattern' and keep references to setFocusIndex,
moveFocus, toggleItem, setSelected, onChange, onSubmit unchanged so the behavior
remains identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7de35f6a-2277-4ead-adf9-0f09866ebbf6

📥 Commits

Reviewing files that changed from the base of the PR and between 695fa54 and 4c9f5af.

📒 Files selected for processing (2)
  • packages/core/src/ui/prompts/autocomplete.tsx
  • packages/core/src/ui/prompts/group-multi-select.tsx

zrosenbauer and others added 2 commits March 29, 2026 20:59
Co-Authored-By: Claude <noreply@anthropic.com>
- Clamp focusIndex on upArrow in autocomplete when filtered list shrinks
- Sync focusIndex with flatItems length in group-multi-select via useEffect
- Move Enter handling before empty list guard so non-required prompts can submit []

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer
Copy link
Copy Markdown
Member Author

Re: using match() for useInput dispatch — all prompt components in this PR (select, multi-select, autocomplete, select-key, path-input, etc.) use guard clause if/early-return for input handlers. This is idiomatic for React/Ink useInput callbacks where each branch performs side effects and returns. Converting to match() would require wrapping side effects in closures with no readability gain. The ts-pattern requirement targets data-driven conditional logic, not imperative event dispatch.

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