Welcome! This guide is designed to help contributors set up their local development environment, understand the monorepo architecture, adhere to code quality standards, run workspace commands, and follow the validation workflow before submitting a Pull Request.
Ensure you have the following installed locally:
- Bun (>= 1.3.0) — Mandatory. Development is Bun-only; do not use
npm,yarn, orpnpm. - Git — Latest version.
- Node.js (>= 18.0.0) — Only required to verify package compatibility on Node, or if you consume published npm packages.
- A Modern Terminal — Supporting ANSI escape codes and unicode characters (e.g., Windows Terminal, iTerm2, Alacritty, GNOME Terminal).
If you are developing on Windows, we highly recommend using Windows Terminal with PowerShell. If you run into execution policy errors when running Bun scripts, run:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserFork the repository on GitHub and clone your fork:
git clone https://github.com/<your-username>/TermUI.git
cd TermUIInstall dependencies from the repository root:
bun installWarning
Never edit bun.lock by hand. If your change requires a dependency, add it to the package's specific package.json and run bun install at the root. If bun.lock contains unrelated changes, revert it before committing using git checkout origin/main -- bun.lock.
Build all packages in the correct dependency order:
bun run buildTermUI is managed as a Bun workspace monorepo. All packages are located under the packages/ directory, and publish as @termuijs/<package-name>.
- Independence: Each package is independent. Do not introduce circular dependencies.
-
Dependency Direction: The dependency graph flows from low-level to high-level:
-
core$\leftarrow$ everything else -
widgets$\leftarrow$ ui
-
-
Imports: Do not import across packages unless explicitly specified in the issue. If package
Adepends onB, specify the dependency inpackages/A/package.jsonunderdependenciesas"@termuijs/B": "workspace:*"and runbun installfrom the root.
For testing guidelines and recommended practices, see
docs/TESTING_BEST_PRACTICES.md.
Each package under packages/ follows a standardized layout:
packages/<package-name>/
├── src/
│ ├── index.ts # Public exports (named exports only)
│ └── data/
│ ├── WidgetName.ts # Main logic
│ └── WidgetName.test.ts # Vitest unit tests (placed next to source)
├── package.json
└── tsconfig.json
Run all commands from the repository root:
| Command | Action |
|---|---|
bun install |
Install workspace-wide dependencies and link workspaces. |
bun run build |
Builds all packages using turbo in dependency order. |
bun run lint |
Runs the workspace linter. |
bun run typecheck |
Runs TypeScript typechecks across all workspaces. |
bun vitest run |
Runs the full Vitest suite. |
bun vitest run packages/<name> |
Runs Vitest tests exclusively for the specified package. |
To test your changes visually, you can start any of the example apps. For example, to run the system monitoring dashboard:
cd examples/dashboard
bun run devThis starts the Bun-native hot-reloading dev server.
To maintain code quality and prevent build breaks, adhere strictly to the following rules:
- No
any: Type assertions must be avoided. If you must useanyor a type assertion, include an inline comment explaining why. - No
@ts-ignore: Use@ts-expect-errorwith a descriptive comment if a compiler error is absolutely unresolvable, but prefer proper type safety. - Named Exports Only: Do not use
export default. Use named exports for all APIs.
- Always use the
node:prefix when importing Node built-in modules:import { readFileSync } from 'node:fs'; // Correct import { readFileSync } from 'fs'; // Incorrect
If you are adding or modifying a widget:
- Canonical Reference: Read
packages/widgets/src/data/Gauge.tsandpackages/widgets/src/data/Gauge.test.tsfirst. Match their constructor signature and coding patterns. - Dirty States: Every method modifying a widget's state must call
this.markDirty(). This triggers the layout engine to queue a re-render. - Key Handling: Widgets that process keyboard events must implement
handleKey(event: KeyEvent)using types from@termuijs/core. - Key Name Convention: Key names must be lowercase (e.g.,
enter,escape,left,right,space,up,down). Never use capitalized variants likeEnterorArrowUp. - Console Logging: Do not leave
console.logor debug print statements in the package code. Use proper logging, event emission, or test assertions. - Unicode Capabilities: Support non-ASCII symbols with an ASCII fallback using the
caps.unicodecapability flag:const borderChar = caps.unicode ? '█' : '#';
We use Vitest for testing. All unit tests must be kept in <FileName>.test.ts files adjacent to their source code.
Tests must use the real Screen object from @termuijs/core to render and assert visual outcomes:
import { describe, it, expect } from 'vitest';
import { Screen } from '@termuijs/core';
import { MyWidget } from './MyWidget';
describe('MyWidget', () => {
it('renders the expected text content', () => {
const screen = new Screen(40, 10);
const widget = new MyWidget();
widget.updateRect({ x: 0, y: 0, width: 40, height: 10 });
widget.render(screen);
const row0 = screen.back[0].map(c => c.char).join('');
expect(row0).toContain('expected text');
});
});Do not mutate capability flags directly (e.g. caps.unicode = false), as these are shared global singletons and will leak across tests. Instead, mock them via Vitest's vi.spyOn:
import { vi, afterEach } from 'vitest';
import { caps } from '@termuijs/core';
afterEach(() => {
vi.restoreAllMocks();
});
it('falls back to ASCII characters when unicode is disabled', () => {
vi.spyOn(caps, 'unicode', 'get').mockReturnValue(false);
// perform test steps and assert ASCII fallback
});Before submitting a Pull Request, run the validation commands and verify all checks pass locally:
- Verify Builds, Tests, and Types:
Run the following pipeline from the repository root:
bun run build && bun vitest run && bun run typecheck
- Follow Conventional Commits:
Format your commits using conventional prefixes:
feat(widgets): add Sparkline widgetfix(core): handle empty event buffertest(ui): add Modal unit testsdocs: expand DEVELOPMENT.md
- Keep Changes Focused: Confine your changes to the relevant package. Do not bundle formatting adjustments, refactors, or unrelated changes into your Pull Request.
- Link Issues:
Always link the issue you are resolving in the PR description (e.g.
Closes #1618).