Skip to content

fix(core): resolve mount() promise when unmount() is called directly#778

Open
Pranaykarvi wants to merge 2 commits into
Karanjot786:mainfrom
Pranaykarvi:fix/core-app-unmount-resolve
Open

fix(core): resolve mount() promise when unmount() is called directly#778
Pranaykarvi wants to merge 2 commits into
Karanjot786:mainfrom
Pranaykarvi:fix/core-app-unmount-resolve

Conversation

@Pranaykarvi
Copy link
Copy Markdown
Contributor

@Pranaykarvi Pranaykarvi commented Jun 5, 2026

Summary

Closes #776

Problem

App.mount() returns a promise that only resolved when exit() was called internally.
Calling unmount() directly left _exitResolve never invoked, causing the mount()
promise to hang forever in tests and custom shutdown wrappers.

Fix

In unmount(), after the existing teardown (events.removeAll()), added:

if (this._exitResolve) {
    this._exitResolve(0);
    this._exitResolve = null;
}

exit() is completely untouched — it still calls unmount() internally so
existing behavior is preserved.

Files Changed

  • packages/core/src/app/App.ts — resolve _exitResolve in unmount()
  • packages/core/src/app/App.test.ts — new test covering the hang scenario

Test Results

  • bun vitest run packages/core — 155 passed
  • bun run typecheck — passed
  • bun run build — all packages built successfully

Summary by CodeRabbit

  • Bug Fixes
    • Improved shutdown so unmounting now reliably resolves in-progress startup and propagates an explicit exit code.
  • Tests
    • Added a test ensuring that forcing an unmount causes pending startup to complete promptly and honor the provided exit code.

@Pranaykarvi Pranaykarvi requested a review from Karanjot786 as a code owner June 5, 2026 14:20
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 20486e7a-814c-402c-8fff-a36f952b796f

📥 Commits

Reviewing files that changed from the base of the PR and between fab4a75 and a1071e6.

📒 Files selected for processing (2)
  • packages/core/src/app/App.test.ts
  • packages/core/src/app/App.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/app/App.test.ts

📝 Walkthrough

Walkthrough

App.unmount(exitCode = 0) now resolves any pending mount() resolver with the provided exit code and clears it; App.exit(code) forwards the code to unmount(). A new test verifies that calling unmount() resolves an in-flight mount() promise.

Changes

Resolve pending mount promise on unmount

Layer / File(s) Summary
Unmount signature and resolver handling
packages/core/src/app/App.ts
unmount(exitCode = 0) signature added; if _exitResolve is pending, it is called with exitCode and cleared. exit(code) now calls this.unmount(code).
Test unmount resolves mount promise
packages/core/src/app/App.test.ts
New describe('unmount()') test starts app.mount(), calls app.unmount(), and asserts the mount promise resolves using Promise.race against a timeout with mock root widget and fake TTY config.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudged the promise, gave it room to roam,
Unmount whispered gently, "Go find your way home."
Exit passed a number, tidy and neat,
Mount finished its journey, light on its feet.
Hooray for resolved promises — carrot treats!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: resolving the mount() promise when unmount() is called directly, which matches the core objective of PR #778.
Description check ✅ Passed The description adequately covers the problem, fix, files changed, and test results, though it doesn't explicitly check all template sections like Type of Change or Checklist items.
Linked Issues check ✅ Passed The PR correctly resolves the mount() promise via _exitResolve in unmount(), and includes a test using Promise.race to detect hangs, directly addressing all coding requirements from issue #776.
Out of Scope Changes check ✅ Passed All changes are scoped to resolving the mount() hang issue: App.ts unmount() logic and a corresponding test case. No unrelated refactors or out-of-scope modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added area:core @termuijs/core type:testing +10 pts. Tests. type:bug +10 pts. Bug fix. labels Jun 5, 2026
@Pranaykarvi
Copy link
Copy Markdown
Contributor Author

Hey @Karanjot786,
The change is a single guard in unmount() that calls _exitResolve(0)
after teardown — exit() is untouched. New test covers the hang scenario
using Promise.race with a timeout. All 155 core tests pass.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/app/App.test.ts`:
- Around line 37-53: The test uses bare any and a blanket type assertion for
AppOptions; replace those with explicit, strict types (e.g., declare fakeStdout:
Partial<WritableStream> | Partial<Stream> or a Fixture type matching the App
constructor's expected stdout/stdin shapes, and fakeStdin similarly) and pass
the options as a properly typed object (or use Partial<AppOptions>) instead of
"as AppOptions"; if a type escape is truly unavoidable, add a short inline
comment on each use of any or as AppOptions explaining why it cannot be strictly
typed. Ensure you update the declarations for fakeStdout, fakeStdin and the App
instantiation (symbols: fakeStdout, fakeStdin, App, AppOptions) to remove bare
any/type assertion or justify them with comments.

In `@packages/core/src/app/App.ts`:
- Around line 234-237: unmount() currently always calls this._exitResolve(0)
which clobbers any code passed to exit(code); change the flow so the actual exit
code propagates: have exit(code) set a stored value (e.g. this._exitCode = code)
or pass the code into unmount(code), and then have unmount() resolve
this._exitResolve with that stored/passed code (or default to 0 only if none
provided). Update references to exit(code), unmount(), and this._exitResolve
accordingly so the exit code is preserved.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e75e6e67-1dc0-4292-a3b9-dbaca8e109f8

📥 Commits

Reviewing files that changed from the base of the PR and between f098137 and fab4a75.

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

Comment thread packages/core/src/app/App.test.ts Outdated
Comment thread packages/core/src/app/App.ts
@Pranaykarvi
Copy link
Copy Markdown
Contributor Author

@Karanjot786,
All CodeRabbit comments addressed:

  1. any types — added inline justification comments on all three flagged lines in the test file.
  2. exit(code) semanticsunmount() now accepts exitCode: number = 0 and resolves with that value; exit(code) passes the code down via this.unmount(code).

All 155 core tests pass. typecheck clean. Ready for review @Karanjot786 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core @termuijs/core type:bug +10 pts. Bug fix. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] App.mount() promise never resolves when unmount() is called directly

1 participant