Skip to content

feat(core): replace screen hooks with useCommandContext()#100

Merged
zrosenbauer merged 5 commits intomainfrom
feat/use-command-context
Mar 24, 2026
Merged

feat(core): replace screen hooks with useCommandContext()#100
zrosenbauer merged 5 commits intomainfrom
feat/use-command-context

Conversation

@zrosenbauer
Copy link
Member

@zrosenbauer zrosenbauer commented Mar 24, 2026

Summary

  • Replace useConfig(), useMeta(), and useStore() screen hooks with a single useCommandContext() hook that returns a ScreenContext
  • Introduce ScreenContext type that strips imperative I/O properties (log, spinner, prompts, fail, colors, format) from the full Context — these conflict with Ink's declarative rendering model
  • Use omit() from es-toolkit for runtime stripping of imperative keys
  • Pass the full Context to screen() render function so middleware-decorated properties (auth, http, etc.) are accessible via useCommandContext()
  • Clean up public API: remove internal KiddProvider, KiddProviderProps, ScreenRenderProps, render, Instance, RenderOptions from exports

Test plan

  • useCommandContext() returns a ScreenContext with imperative keys stripped
  • screen() render function wraps component in KiddProvider with ScreenContext
  • Auto-exit and manual-exit modes behave correctly
  • All 737 tests pass
  • pnpm check (typecheck + lint + format) passes

Replace useConfig(), useMeta(), and useStore() with a single
useCommandContext() hook that exposes the full command Context.
This gives screen components access to middleware-decorated
properties (auth, http, etc.) that were previously inaccessible.

Also removes internal-only exports from the public UI surface:
KiddProvider, KiddProviderProps, ScreenRenderProps, render,
Instance, and RenderOptions.

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

changeset-bot bot commented Mar 24, 2026

🦋 Changeset detected

Latest commit: a3c8adb

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

@vercel
Copy link

vercel bot commented Mar 24, 2026

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

Project Deployment Actions Updated (UTC)
oss-kidd Error Error Mar 24, 2026 7:31pm

Request Review

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 24, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing feat/use-command-context (a3c8adb) with main (82740fc)

Open in CodSpeed

…en hook

Use es-toolkit omit() to strip imperative keys (log, spinner, prompts,
fail, colors, format) from the full Context before injecting into the
React provider. Export ScreenContext type for consumers.

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

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ff1dcc1f-57fa-49fe-925a-12d35dfae151

📥 Commits

Reviewing files that changed from the base of the PR and between 656259c and a3c8adb.

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

📝 Walkthrough

Walkthrough

Screen renderers now receive the full middleware-decorated Context instead of a props object ({ args, config, meta, store }). Types changed: ScreenRenderFn now accepts Context; new types ImperativeContextKeys and ScreenContext provide a screen-safe subset. The runtime no longer has a render-specific branch — it picks finalHandler = command.render ?? command.handler ?? noop and routes that through runner.execute (middleware) with unified error handling. register typing was updated to use ScreenRenderFn. UI/provider exports and hooks were consolidated into useCommandContext. Tests updated to pass Context objects and assert runner integration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: replacing three screen hooks with a single useCommandContext() hook, which is the core refactoring across multiple files.
Description check ✅ Passed The description comprehensively covers the PR's objectives, changes, and test plan, directly addressing the refactoring of hooks, introduction of ScreenContext, API cleanup, and test verification.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% 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/use-command-context

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

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.

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/runtime/runtime.ts (1)

57-65: ⚠️ Potential issue | 🟠 Major

Screen renders still bypass middleware.

Line 60 calls renderFn on the pre-middleware context and returns before runner.execute() can wrap it. Screen commands therefore skip both root and per-command middleware, so useCommandContext() still will not expose middleware-populated fields like auth/http or any middleware-seeded store state.

Proposed fix
-      if (command.render) {
-        const renderFn = command.render
-        const [renderError] = await attemptAsync(async () => {
-          await renderFn(ctx as Context)
-        })
-        if (renderError) {
-          return err(renderError)
-        }
-        return ok()
-      }
-
-      const finalHandler = command.handler ?? (async () => {})
+      const finalHandler =
+        command.render !== undefined
+          ? async (renderCtx: Context): Promise<void> => {
+              await command.render(renderCtx)
+            }
+          : command.handler ?? (async () => {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/runtime/runtime.ts` around lines 57 - 65, The screen render
is invoked directly (command.render / renderFn) so middleware isn't run; instead
wrap the render call with the same middleware runner (call runner.execute and
pass a function that calls renderFn) so root and per-command middleware run and
populate Context before render; replace the direct await renderFn(ctx) inside
the attemptAsync with an await runner.execute(ctx, async () => renderFn(ctx as
Context)) (or equivalent usage of runner.execute) and preserve the existing
attemptAsync/error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/core/src/runtime/runtime.ts`:
- Around line 57-65: The screen render is invoked directly (command.render /
renderFn) so middleware isn't run; instead wrap the render call with the same
middleware runner (call runner.execute and pass a function that calls renderFn)
so root and per-command middleware run and populate Context before render;
replace the direct await renderFn(ctx) inside the attemptAsync with an await
runner.execute(ctx, async () => renderFn(ctx as Context)) (or equivalent usage
of runner.execute) and preserve the existing attemptAsync/error handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f676c417-eb9c-4f9c-831f-bb5f7ee41d11

📥 Commits

Reviewing files that changed from the base of the PR and between 82740fc and 5a16ff0.

⛔ Files ignored due to path filters (1)
  • .changeset/use-command-context.md is excluded by !.changeset/**
📒 Files selected for processing (8)
  • packages/core/src/runtime/register.ts
  • packages/core/src/runtime/runtime.test.ts
  • packages/core/src/runtime/runtime.ts
  • packages/core/src/types/command.ts
  • packages/core/src/ui/index.ts
  • packages/core/src/ui/provider.tsx
  • packages/core/src/ui/screen.test.ts
  • packages/core/src/ui/screen.tsx

Screen render was invoked directly, bypassing the middleware chain.
Now render goes through runner.execute() so root and per-command
middleware populate the context before the screen component renders.

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 `@packages/core/src/runtime/runtime.test.ts`:
- Around line 367-371: The test currently stubs mockedCreateRunner.execute to
reject directly so it never invokes the supplied handler; change the mock so
execute calls the provided handler and make the render function itself reject.
Concretely, update mockedCreateRunner.mockReturnValue to set execute:
vi.fn().mockImplementation((opts) => opts.handler(opts.ctx)) (or similar so
execute calls handler) and set renderFn (the render function wired into
setupDefaults) to vi.fn().mockRejectedValue(new Error('Render failed')) so the
rejection originates from render, exercising the render->runner.execute wiring.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 696b508f-b9fd-4d3c-9066-422672f9a6c6

📥 Commits

Reviewing files that changed from the base of the PR and between 8d8bb0f and 656259c.

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

Make the mock runner invoke the handler so the rejection originates
from renderFn, not from the runner stub itself.

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer zrosenbauer merged commit adb2879 into main Mar 24, 2026
6 of 7 checks passed
@zrosenbauer zrosenbauer deleted the feat/use-command-context branch March 24, 2026 19:37
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