Skip to content

feat(core): React/Ink render support and UI components#91

Merged
zrosenbauer merged 24 commits intomainfrom
feat/middleware-refactor-render-support
Mar 23, 2026
Merged

feat(core): React/Ink render support and UI components#91
zrosenbauer merged 24 commits intomainfrom
feat/middleware-refactor-render-support

Conversation

@zrosenbauer
Copy link
Member

@zrosenbauer zrosenbauer commented Mar 23, 2026

Summary

Stacked on #92 (middleware refactor).

  • Add render as an alternative to handler on command definitions for React/Ink TUI support
  • Add .tsx/.jsx to autoloader valid extensions
  • Export Ink primitives (Box, Text, useApp, useInput, etc.) and @inkjs/ui component wrappers from @kidd-cli/core/ui
  • Add RenderFn and RenderProps types
  • Runtime render path: when command.render is defined, it is called instead of handler
  • Add kitchen-sink example with temp TUI (deploy.tsx) and persistent TUI (dashboard.tsx)
  • Add contributing/standards/typescript/components.md for React/Ink conventions

Test plan

  • pnpm check passes (typecheck + lint + format)
  • pnpm test passes (711 core tests, all CLI tests, 66 integration tests)
  • All examples build correctly
  • Manual test: cd examples/kitchen-sink && pnpm dev -- deploy
  • Manual test: cd examples/kitchen-sink && pnpm dev -- dashboard

@changeset-bot
Copy link

changeset-bot bot commented Mar 23, 2026

🦋 Changeset detected

Latest commit: 7c36e8e

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

This PR includes changesets to release 3 packages
Name Type
@kidd-cli/core Minor
@kidd-cli/bundler Patch
@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

@vercel
Copy link

vercel bot commented Mar 23, 2026

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

Project Deployment Actions Updated (UTC)
oss-kidd Error Error Mar 23, 2026 9:32pm

Request Review

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 23, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing feat/middleware-refactor-render-support (7c36e8e) with main (a01cf00)

Open in CodSpeed

zrosenbauer and others added 4 commits March 23, 2026 14:41
Move logger, spinner, and prompts off base Context into a `logger()`
middleware providing a unified `ctx.log` API. Extract diagnostics into
a `report()` middleware with `ctx.report`.

- New `@kidd-cli/core/logger` export with `logger()` middleware
- New `@kidd-cli/core/report` export with `report()` middleware
- Context no longer ships logger/spinner/prompts by default
- All CLI commands and examples updated to `ctx.log.*` API
- Test utilities updated with `mockLog()` helper
- Rename formatTally → formatSummary

Co-Authored-By: Claude <noreply@anthropic.com>
…ents

Move logger, spinner, and prompts off base Context into a `logger()` middleware
that provides a unified `ctx.log` API. Extract diagnostics into a `report()`
middleware with `ctx.report`. Add `render` as an alternative to `handler` on
commands for React/Ink-based TUI support. Export Ink component wrappers from
`@kidd-cli/core/ui`.

Breaking changes:
- `ctx.logger` removed — use `logger()` middleware, access via `ctx.log`
- `ctx.spinner` removed — use `ctx.log.spinner('msg')` (creates + starts)
- `ctx.prompts` removed — prompts are flat on `ctx.log`
- `CliLogger` type removed — replaced by `Log` interface
- `TallyInput` renamed to `SummaryInput`
- `formatTally` renamed to `formatSummary`

New exports:
- `@kidd-cli/core/logger` — logger() middleware factory
- `@kidd-cli/core/report` — report() middleware factory
- `@kidd-cli/core/ui` — Ink component wrappers (Spinner, Select, etc.)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Remove middleware changeset (already on feat/middleware-refactor).
Add render/UI-specific changeset.

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer zrosenbauer force-pushed the feat/middleware-refactor-render-support branch from 7b64b10 to 03cb0b1 Compare March 23, 2026 18:42
@coderabbitai
Copy link

coderabbitai bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

This PR introduces a screen-based TUI command pattern to Kidd CLI, enabling React/Ink components as command renderers. It adds a comprehensive UI module structure with component re-exports from ink and @inkjs/ui, a screen() factory for defining TUI commands, a KiddProvider with context hooks, and runtime support for render-based commands alongside handler-based commands. The changes extend command types to support an optional render property, update the command registration and execution paths to handle screen commands, add support for .tsx and .jsx file extensions, and include component standards documentation and kitchen-sink example projects.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(core): React/Ink render support and UI components' accurately describes the main changes: adding render support and UI exports from core.
Description check ✅ Passed The description directly relates to the changeset by explaining the render feature, UI exports, autoloader updates, and example additions in detail.
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/middleware-refactor-render-support

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

@zrosenbauer zrosenbauer changed the title feat(packages/core): middleware refactor + render support + UI components feat(core): React/Ink render support and UI components Mar 23, 2026
@zrosenbauer zrosenbauer changed the base branch from main to feat/middleware-refactor March 23, 2026 18:42
Add missing exports: measureElement, useIsScreenReaderEnabled,
kittyFlags, kittyModifiers, and all remaining prop types.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The bundler's scan-commands mirrored the runtime autoloader's extension
list but was missing .tsx and .jsx. This caused kidd build/dev to skip
React/Ink command files during static autoloader generation.

Co-Authored-By: Claude <noreply@anthropic.com>
pnpm strict mode prevents @inkjs/ui (nested in core's devDeps) from
resolving react in consuming packages. Public hoisting ensures a single
shared copy is accessible across the workspace.

Co-Authored-By: Claude <noreply@anthropic.com>
Introduce `screen()` as the dedicated API for building terminal UI
commands with React/Ink, replacing the previous `command({ render })`
pattern. Screens receive parsed args as component props; runtime
context (config, meta, store) is available via `useConfig()`,
`useMeta()`, and `useStore()` hooks through a `KiddProvider`.

- Add `screen.tsx` with `screen()` factory and exit mode config
- Add `provider.tsx` with `KiddProvider` and context hooks
- Remove `RenderFn`/`RenderProps` from public command types
- Add internal `ScreenRenderFn` type for runtime plumbing
- Clean up runtime types to use proper type imports
- Update kitchen-sink examples to use `screen()` from `@kidd-cli/core/ui`

Co-Authored-By: Claude <noreply@anthropic.com>
Remove direct @inkjs/ui and ink imports in kitchen-sink examples.
All UI components should come through the unified @kidd-cli/core/ui
surface.

Co-Authored-By: Claude <noreply@anthropic.com>
The current API expects `z.object({})` or `Record<string, YargsArgDef>`
for positionals, not arrays. Update preview, show, and category
commands to use zod schemas.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace stale `auth({ http, resolvers })` pattern with separate
`auth({ strategies })` and `http({ headers: createAuthHeaders() })`
middleware. Update README to match.

Co-Authored-By: Claude <noreply@anthropic.com>
Merge base branch imports (Log, Prompts, Spinner types and
RuntimeOptions fields) with render branch additions (ScreenRenderFn,
http middleware in auth example).

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Base automatically changed from feat/middleware-refactor to main March 23, 2026 20:43
Take main's base-context approach for log/prompts/spinner while
keeping ScreenRenderFn import from this branch. Update docs and
icons context to match.

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

@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: 6

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

Inline comments:
In `@contributing/standards/typescript/components.md`:
- Around line 15-23: The fenced directory-listing code blocks in
contributing/standards/typescript/components.md lack language identifiers;
update each triple-backtick fence enclosing the directory examples (the blocks
showing commands/ with entries like deploy.ts, status.tsx, dashboard/index.tsx
and _components/StatusTable.tsx) to use a plaintext/text language tag (e.g.,
change ``` to ```text) so markdown lint rules pass and rendering is consistent;
apply the same change to all similar blocks mentioned in the review (the other
directory-listing examples in the file).
- Around line 167-179: The render function in the command example uses await
inside a synchronous function; make the render method async so you can await the
dynamic import. Specifically, change the render(props: RenderProps) function to
async, then await import('ink') when importing render, call render(<StatusView
{...props} />) to get { waitUntilExit }, and return await waitUntilExit() (or
return waitUntilExit()) so the async flow is correct for the command export
default command(...) block.

In `@examples/kitchen-sink/package.json`:
- Around line 15-21: package.json declares runtime "react": ">=18.0.0" while
devDependency uses "@types/react": "^19.0.0", causing React 19 type features
(e.g., async components) to be allowed by TypeScript but unsupported at runtime;
update the devDependency by changing "@types/react" from "^19.0.0" to "^18.0.0"
(or instead bump "react" to v19 if you intend to use React 19) so the types
match the runtime and prevent false-positive type acceptance.

In `@examples/kitchen-sink/src/commands/greet.ts`:
- Around line 19-23: Replace the if/else in greet.ts with a ts-pattern match on
ctx.args.shout: import match from 'ts-pattern', then replace the conditional
around ctx.args.shout so it calls match(ctx.args.shout).with(true, () =>
ctx.log.raw(greeting.toUpperCase())).with(false, () =>
ctx.log.raw(greeting)).run() (or use .otherwise if preferred); keep references
to greeting and ctx.log.raw exactly as used now.

In `@packages/core/src/ui/screen.tsx`:
- Around line 152-157: The two-branch typeof check in resolveValue should be
replaced with ts-pattern matching: import { match, P } from 'ts-pattern' and use
match(value).with(P.when(v => typeof v === 'function'), v => (v as () =>
T)()).otherwise(v => v as T | undefined) so the function resolves callables and
returns plain values via pattern arms; update resolveValue to use match (keeping
the Resolvable<T> typing) and remove the typeof conditional.

In `@PLAN.md`:
- Around line 606-628: The fenced code blocks containing directory listings (the
blocks beginning with "src/middleware/logger/", "src/middleware/report/",
"src/ui/                        (Phase 3)", and
"contributing/standards/typescript/components.md  (Phase 4)") must include a
language identifier; update each opening fence from ``` to ```text (or
```plaintext) so the listings comply with the documentation standard and render
correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ab0d5d65-3299-4faf-b7eb-517b4d19d20b

📥 Commits

Reviewing files that changed from the base of the PR and between 9cd2217 and 7a4f6af.

⛔ Files ignored due to path filters (2)
  • .changeset/render-support-ui-components.md is excluded by !.changeset/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (40)
  • PLAN.md
  • contributing/standards/typescript/components.md
  • examples/advanced/src/commands/deploy/preview.ts
  • examples/authenticated-service/README.md
  • examples/authenticated-service/cli/src/index.ts
  • examples/icons/commands/category.ts
  • examples/icons/commands/show.ts
  • examples/kitchen-sink/kidd.config.ts
  • examples/kitchen-sink/package.json
  • examples/kitchen-sink/src/commands/dashboard.tsx
  • examples/kitchen-sink/src/commands/deploy.tsx
  • examples/kitchen-sink/src/commands/greet.ts
  • examples/kitchen-sink/src/index.ts
  • examples/kitchen-sink/tsconfig.json
  • packages/bundler/src/autoloader/scan-commands.ts
  • packages/cli/src/commands/add/command.ts
  • packages/cli/src/commands/add/middleware.ts
  • packages/cli/src/commands/init.ts
  • packages/core/package.json
  • packages/core/src/autoload.ts
  • packages/core/src/cli.ts
  • packages/core/src/context/types.ts
  • packages/core/src/middleware/icons/icons.ts
  • packages/core/src/runtime/register.ts
  • packages/core/src/runtime/runtime.ts
  • packages/core/src/runtime/types.ts
  • packages/core/src/types/command.ts
  • packages/core/src/types/index.ts
  • packages/core/src/ui/confirm.tsx
  • packages/core/src/ui/index.ts
  • packages/core/src/ui/multi-select.tsx
  • packages/core/src/ui/password-input.tsx
  • packages/core/src/ui/provider.tsx
  • packages/core/src/ui/screen.tsx
  • packages/core/src/ui/select.tsx
  • packages/core/src/ui/spinner.tsx
  • packages/core/src/ui/text-input.tsx
  • packages/core/tsconfig.json
  • packages/core/tsdown.config.ts
  • tsconfig.base.json

- Remove stale @kidd-cli/core/logger import from kitchen-sink (export
  no longer exists; log/prompts/spinner are on base context)
- Rewrite components.md for screen() API (replaces old render/RenderProps
  docs, adds hooks/exit behavior/import conventions)
- Add text lang identifiers to markdown code blocks
- Use ts-pattern in greet.ts example
- Bump react peer dep to >=19.0.0 to match @types/react@^19

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

@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: 1

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

Inline comments:
In `@contributing/standards/typescript/components.md`:
- Around line 171-187: The example is using z.object(...) but never imports z
from 'zod', causing a ReferenceError; fix it by adding an import for z (e.g.,
import { z } from 'zod') at the top of the file alongside the existing imports
so the options: z.object(...) in the export default screen(...) call resolves
correctly; ensure the import is added before the StatusView/screen usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc30eca6-9321-4138-b7fa-8953811cb970

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4f6af and d6d8d38.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (4)
  • contributing/standards/typescript/components.md
  • examples/kitchen-sink/package.json
  • examples/kitchen-sink/src/commands/greet.ts
  • examples/kitchen-sink/src/index.ts

TypeScript 6.0.2 has peer dependency conflicts with @typescript-eslint,
tsdown, and eslint-plugin-functional. Downgrade to ^5.9.3 and add
explicit type annotations for isolatedDeclarations compatibility.

Co-Authored-By: Claude <noreply@anthropic.com>
Add screen.test.ts covering the screen() factory (tagged output,
render property, option/metadata preservation, Resolvable resolution)
and render function behavior (ink integration, KiddProvider wrapping,
auto/manual exit modes). Add render execution tests to runtime.test.ts
covering render invocation, context prop passing, middleware bypass,
and error handling. Delete stale PLAN.md.

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

@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: 2

🤖 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/screen.test.ts`:
- Around line 196-215: The test enables fake timers with vi.useFakeTimers() but
doesn't guarantee restoration on failure; wrap the fake timer setup and the test
body in a try/finally so vi.useRealTimers() is always called; locate the test
case using symbols vi.useFakeTimers/useRealTimers, mockedInkRender, screen and
the renderPromise variable and move the vi.useRealTimers() call into a finally
block that runs after awaiting renderPromise so timers are restored even if
assertions (e.g., expect(unmount).toHaveBeenCalledOnce()) throw.
- Around line 7-12: Update the Ink mock to use Vitest's dynamic-import form:
replace vi.mock('ink', ...) with vi.mock(import('ink'), ...) while keeping the
same mock implementation for render (returning unmount and waitUntilExit mocks).
Ensure the mock still defines render as vi.fn(() => ({ unmount: vi.fn(),
waitUntilExit: vi.fn().mockResolvedValue(undefined) })) so tests referencing
render, unmount, and waitUntilExit behave unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 53159fe7-265e-496e-af1b-f419c5485c2e

📥 Commits

Reviewing files that changed from the base of the PR and between 50f8731 and 7c36e8e.

📒 Files selected for processing (2)
  • packages/core/src/runtime/runtime.test.ts
  • packages/core/src/ui/screen.test.ts

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