Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/choosing-your-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ The Imperative API is the foundation of TermUI. You work directly with classes a
- You need the **absolute lowest overhead** and maximum performance.
- You prefer **Object-Oriented Programming** and want to manage state in class properties.

### Common use case: Custom widget

When building a reusable widget (e.g., a progress bar) for others to import:

```typescript
import { Widget, Style } from '@termuijs/core'

export class ProgressBar extends Widget {
private progress: number = 0

setProgress(value: number) {
this.progress = Math.max(0, Math.min(1, value))
this.markDirty() // Request re-render
}

render(buffer) {
const width = this.rect.width
const filled = Math.round(width * this.progress)
const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
buffer.write(0, 0, bar, Style.fg('green'))
}
Comment on lines +51 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify recommended extension point in widgets
rg -n -C3 "protected _renderSelf\s*\(|render\s*\(screen" packages/widgets/src/base/Widget.ts

Repository: Karanjot786/TermUI

Length of output: 583


🏁 Script executed:

#!/bin/bash
# Check if _renderSelf method exists in Widget class
rg -n "_renderSelf" packages/widgets/src/base/Widget.ts

Repository: Karanjot786/TermUI

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Read the actual documentation file to see the code at lines 51-56
sed -n '45,65p' docs/choosing-your-api.md

Repository: Karanjot786/TermUI

Length of output: 610


🏁 Script executed:

#!/bin/bash
# Get more context from Widget.render to understand the full pipeline
sed -n '250,310p' packages/widgets/src/base/Widget.ts

Repository: Karanjot786/TermUI

Length of output: 2278


🏁 Script executed:

#!/bin/bash
# Check if there are other incorrect render() overrides in the documentation
rg -n "render\s*\(buffer\)|render\s*\(screen\)" docs/choosing-your-api.md

Repository: Karanjot786/TermUI

Length of output: 84


The imperative widget example overrides the wrong render method.

For Widget, examples should implement _renderSelf(screen) rather than overriding render(...); overriding render bypasses core render pipeline behavior including visibility checks, clipping, error handling, performance tracking, border rendering, and child rendering as shown in Widget.render.

Suggested fix
-  render(buffer) {
-    const width = this.rect.width
-    const filled = Math.round(width * this.progress)
-    const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
-    buffer.write(0, 0, bar, Style.fg('green'))
-  }
+  protected _renderSelf(screen: Screen): void {
+    const { x, y, width } = this.rect
+    const filled = Math.round(width * this.progress)
+    const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
+    screen.writeString(x, y, bar, { fg: { type: 'named', name: 'green' } })
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
render(buffer) {
const width = this.rect.width
const filled = Math.round(width * this.progress)
const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
buffer.write(0, 0, bar, Style.fg('green'))
}
protected _renderSelf(screen: Screen): void {
const { x, y, width } = this.rect
const filled = Math.round(width * this.progress)
const bar = '█'.repeat(filled) + '░'.repeat(width - filled)
screen.writeString(x, y, bar, { fg: { type: 'named', name: 'green' } })
}
🤖 Prompt for 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.

In `@docs/choosing-your-api.md` around lines 51 - 56, The imperative widget
example is overriding the `render(buffer)` method when it should override
`_renderSelf(screen)` instead. Change the method signature from `render(buffer)`
to `_renderSelf(screen)` and adjust the implementation as necessary to work with
the correct method. Overriding `_renderSelf` ensures the example uses the proper
render pipeline that includes visibility checks, clipping, error handling,
performance tracking, border rendering, and child rendering, rather than
bypassing these core behaviors.

}

### Example: Imperative Counter

```typescript
Expand Down
32 changes: 32 additions & 0 deletions packages/widgets/src/Spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect, useState } from '@termuijs/jsx'
import { caps } from '@termuijs/core'
import { Text } from './Text'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where Text is actually defined/exported
fd -i 'Text.ts' packages/widgets/src
rg -n -C2 'export\s+\{\s*Text\s*\}\s+from' packages/widgets/src/index.ts

Repository: Karanjot786/TermUI

Length of output: 440


🏁 Script executed:

cat -n packages/widgets/src/Spinner.ts | head -20

Repository: Karanjot786/TermUI

Length of output: 765


🏁 Script executed:

# Double-check if Text.ts exists at the root of src
ls -la packages/widgets/src/Text.ts 2>&1

Repository: Karanjot786/TermUI

Length of output: 138


Fix broken import path for Text component.

The import path ./Text does not resolve; Text is located in the display subdirectory.

Suggested fix
-import { Text } from './Text'
+import { Text } from './display/Text.js'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Text } from './Text'
import { Text } from './display/Text.js'
🤖 Prompt for 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.

In `@packages/widgets/src/Spinner.ts` at line 3, The import statement for the Text
component in Spinner.ts uses an incorrect path that does not resolve. Update the
import path from './Text' to './display/Text' to correctly reference the Text
component which is located in the display subdirectory.


export interface SpinnerProps {
speed?: number
label?: string
color?: string
}

const FRAMES_UNICODE = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
const FRAMES_ASCII = ['|', '/', '-', '\\']

export function Spinner({ speed = 80, label = '', color = 'cyan' }: SpinnerProps) {
const [frame, setFrame] = useState(0)
const frames = caps.unicode ? FRAMES_UNICODE : FRAMES_ASCII

useEffect(() => {
if (caps.noMotion) return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify caps API surface and motion helper usage
rg -n -C2 '\bcaps\.(noMotion|motion)\b|prefersReducedMotion\s*\(' packages

Repository: Karanjot786/TermUI

Length of output: 14341


🏁 Script executed:

#!/bin/bash
# Check if caps.noMotion is defined anywhere
rg -n '\bcaps\.noMotion\b' packages

# Check what's exported from core env-caps
rg -n '^export' packages/core/src/terminal/env-caps.ts

# Get exact content around line 19 of Spinner.ts
head -25 packages/widgets/src/Spinner.ts | tail -10

Repository: Karanjot786/TermUI

Length of output: 561


🏁 Script executed:

#!/bin/bash
# Get the full definition of the caps object in env-caps.ts
sed -n '1,42p' packages/core/src/terminal/env-caps.ts

# Check what `@termuijs/core` exports (usually in index.ts or package.json exports)
find packages/core/src -name 'index.ts' -o -name 'index.js' | head -5

# Check if prefersReducedMotion is re-exported from the main entry point
rg -n 'prefersReducedMotion' packages/core/src/index.ts 2>/dev/null || echo "Not found in index.ts, checking package.json"

Repository: Karanjot786/TermUI

Length of output: 1653


Use prefersReducedMotion() instead of the non-existent caps.noMotion.

caps.noMotion is not defined in the caps contract. The caps object exposes only color, unicode, motion, ci, and getters for background and keybindingMode. Using undefined caps.noMotion will silently fail, allowing animations to run in NO_MOTION=1 and CI environments.

Use the documented prefersReducedMotion() function exported from @termuijs/core, which correctly checks caps.motion.

Suggested fix
-import { caps } from '`@termuijs/core`'
+import { prefersReducedMotion } from '`@termuijs/core`'
...
-    if (caps.noMotion) return
+    if (prefersReducedMotion()) return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (caps.noMotion) return
if (prefersReducedMotion()) return
🤖 Prompt for 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.

In `@packages/widgets/src/Spinner.ts` at line 19, The condition at line 19 in
Spinner.ts uses the non-existent caps.noMotion property which will silently
fail. Replace the caps.noMotion check with a call to the prefersReducedMotion()
function imported from `@termuijs/core`, which properly checks the caps.motion
property. This will ensure animations are correctly disabled in NO_MOTION and CI
environments.


const interval = setInterval(() => {
setFrame((f) => (f + 1) % frames.length)
}, speed)

return () => clearInterval(interval)
}, [speed, frames.length])

const spinnerChar = frames[frame]
const display = label ? `${spinnerChar} ${label}` : spinnerChar

return <Text color={color}>{display}</Text>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify .ts files containing JSX tags
rg -nP --type=ts '<[A-Z][A-Za-z0-9]*\b|</[A-Z][A-Za-z0-9]*>' -g '**/*.ts'

Repository: Karanjot786/TermUI

Length of output: 50374


🏁 Script executed:

#!/bin/bash
# Check which Spinner file exists and its content
ls -la packages/widgets/src/ | grep -i spinner
echo "---"
# Check if .ts file exists with JSX
if [ -f "packages/widgets/src/Spinner.ts" ]; then
  echo "=== Spinner.ts (first 35 lines) ==="
  head -35 packages/widgets/src/Spinner.ts
fi
echo "---"
# Check if .tsx file exists
if [ -f "packages/widgets/src/Spinner.tsx" ]; then
  echo "=== Spinner.tsx exists ==="
  head -5 packages/widgets/src/Spinner.tsx
fi

Repository: Karanjot786/TermUI

Length of output: 1054


File should be renamed from .ts to .tsx.

The file contains JSX syntax (<Text color={color}>{display}</Text>) which is incompatible with the .ts extension. TypeScript and supporting tools expect only TypeScript code in .ts files; JSX requires .tsx.

Suggested fix
- packages/widgets/src/Spinner.ts
+ packages/widgets/src/Spinner.tsx
🧰 Tools
🪛 Biome (2.5.0)

[error] 31-31: expected > but instead found color

(parse)


[error] 31-31: Invalid assignment to <Text color

(parse)


[error] 31-31: unterminated regex literal

(parse)

🤖 Prompt for 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.

In `@packages/widgets/src/Spinner.ts` at line 31, The file Spinner.ts contains JSX
syntax (the return statement with the Text component) but has a .ts file
extension, which should only contain TypeScript code. Rename the file from
Spinner.ts to Spinner.tsx to properly indicate that it contains JSX code, which
will allow TypeScript and supporting tools to correctly parse and validate the
JSX syntax in the file.

Source: Linters/SAST tools

}
2 changes: 2 additions & 0 deletions packages/widgets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,5 @@ export { UnorderedList } from './display/UnorderedList.js';
export type { UnorderedListOptions } from './display/UnorderedList.js';
export { Rule } from './display/Rule.js';
export type { RuleOrientation, RuleOptions } from './display/Rule.js';
export { Spinner } from './Spinner'
export type { SpinnerProps } from './Spinner'
Comment on lines +313 to +314

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplicate Spinner exports in this module
rg -n -C1 "export\s+\{\s*Spinner\b" packages/widgets/src/index.ts

Repository: Karanjot786/TermUI

Length of output: 455


🏁 Script executed:

fd -t f "Spinner" packages/widgets/src/ | head -20

Repository: Karanjot786/TermUI

Length of output: 181


🏁 Script executed:

cat packages/widgets/src/index.ts | sed -n '310,320p'

Repository: Karanjot786/TermUI

Length of output: 328


🏁 Script executed:

find packages/widgets/src -name "*Spinner*" -type f

Repository: Karanjot786/TermUI

Length of output: 181


🏁 Script executed:

head -30 packages/widgets/src/Spinner.ts

Repository: Karanjot786/TermUI

Length of output: 902


🏁 Script executed:

head -30 packages/widgets/src/feedback/Spinner.ts

Repository: Karanjot786/TermUI

Length of output: 1257


Fix module specifier to match pattern used throughout the file.

./Spinner should be ./Spinner.js to match the .js extension pattern consistently used in this index file (e.g., ./feedback/Spinner.js, ./display/Rule.js). While this is a separate JSX hook-based Spinner component (distinct from the class-based Widget exported on line 137), the module specifier should follow the established convention.

Suggested fix
-export { Spinner } from './Spinner'
-export type { SpinnerProps } from './Spinner'
+export { Spinner } from './Spinner.js'
+export type { SpinnerProps } from './Spinner.js'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export { Spinner } from './Spinner'
export type { SpinnerProps } from './Spinner'
export { Spinner } from './Spinner.js'
export type { SpinnerProps } from './Spinner.js'
🤖 Prompt for 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.

In `@packages/widgets/src/index.ts` around lines 313 - 314, Update the module
specifiers in the Spinner export statements to include the `.js` file extension.
Change both `export { Spinner } from './Spinner'` and `export type {
SpinnerProps } from './Spinner'` to use `'./Spinner.js'` instead of
`'./Spinner'` to match the consistent naming convention used throughout this
index file where all other module specifiers include the `.js` extension.

11 changes: 11 additions & 0 deletions tests/helpers/Spinner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, it, expect } from 'bun:test'
import { render } from '@termuijs/testing'
import { Spinner } from '../../packages/widgets/src/Spinner'

describe('Spinner', () => {
it('renders with label', () => {
const t = render(<Spinner label="Loading" />)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find test .ts files containing JSX
rg -nP --type=ts '<[A-Z][A-Za-z0-9]*\b|</[A-Z][A-Za-z0-9]*>' tests

Repository: Karanjot786/TermUI

Length of output: 144


Rename this test file to .test.tsx.

The file tests/helpers/Spinner.test.ts contains JSX syntax (<Spinner label="Loading" />), which cannot be parsed by TypeScript in .ts files. Only .tsx files support JSX parsing.

Suggested fix
- tests/helpers/Spinner.test.ts
+ tests/helpers/Spinner.test.tsx
🧰 Tools
🪛 Biome (2.5.0)

[error] 7-7: expected > but instead found label

(parse)


[error] 7-7: Invalid assignment to <Spinner label

(parse)


[error] 7-7: Expected an expression but instead found '>'.

(parse)


[error] 7-7: Expected an expression but instead found ')'.

(parse)

🤖 Prompt for 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.

In `@tests/helpers/Spinner.test.ts` at line 7, The file Spinner.test.ts contains
JSX syntax in the render call but uses the .ts extension instead of .tsx.
TypeScript only parses JSX in .tsx files. Rename the file from Spinner.test.ts
to Spinner.test.tsx to enable proper JSX parsing by the TypeScript compiler.

Source: Linters/SAST tools

expect(t.renderToString()).toContain('Loading')
t.unmount()
})
})
Loading