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 +40 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:

fd -t f "Widget\.ts" packages/widgets/src/base/ | head -5

Repository: Karanjot786/TermUI

Length of output: 98


🏁 Script executed:

fd -t f "Screen\.ts" packages/core/src/ | head -5

Repository: Karanjot786/TermUI

Length of output: 99


🏁 Script executed:

# Check Widget class structure
rg "class Widget|render\(|_renderSelf" packages/widgets/src/base/ -A 3 | head -50

Repository: Karanjot786/TermUI

Length of output: 2912


🏁 Script executed:

# Get the complete render() method
sed -n '200,300p' packages/widgets/src/base/Widget.ts | cat -n

Repository: Karanjot786/TermUI

Length of output: 4434


🏁 Script executed:

# Search for the full render method more precisely
rg "render\(screen: Screen\)" packages/widgets/src/base/Widget.ts -A 30 | head -50

Repository: Karanjot786/TermUI

Length of output: 1462


🏁 Script executed:

# Search for Screen methods like write, writeString, etc
rg "write|WriteString" packages/core/src/terminal/Screen.ts | head -20

Repository: Karanjot786/TermUI

Length of output: 636


🏁 Script executed:

# Get Screen.writeString() method signature
rg "writeString\(" packages/core/src/terminal/Screen.ts -A 5 | head -30

Repository: Karanjot786/TermUI

Length of output: 215


🏁 Script executed:

# Read the actual documentation file to see the example
cat -n docs/choosing-your-api.md | sed -n '36,60p'

Repository: Karanjot786/TermUI

Length of output: 880


Override _renderSelf(), not render().

This example overrides the public render() method, which bypasses the base class's pipeline entirely. The Widget.render() method handles clipping (based on overflow style), dirty-flag management, border rendering, and child rendering before delegating to the protected _renderSelf(screen) hook. Overriding render() loses all of this and will teach readers the wrong contract.

Additionally, buffer.write() is not a valid Screen API; use screen.writeString() with the widget's rect coordinates instead of writing to (0, 0), which would render at the screen origin rather than at the widget's positioned location.

Suggested fix
 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) {
+  protected _renderSelf(screen: Screen) {
     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'))
+    screen.writeString(this.rect.x, this.rect.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 40 - 56, The ProgressBar class should
override the protected _renderSelf() method instead of the public render()
method to preserve the base class's rendering pipeline that handles clipping,
dirty-flag management, borders, and child widgets. Replace the render() method
signature with _renderSelf(screen) and update the implementation to use
screen.writeString() with the widget's rect coordinates (this.rect.x and
this.rect.y) instead of the invalid buffer.write() API call that writes to (0,
0). This ensures the progress bar renders at the correct position within the
widget's bounds rather than at the screen origin.

}

### Example: Imperative Counter

```typescript
Expand Down
100 changes: 100 additions & 0 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,103 @@ export class TermUIValidationError extends TermUIError {
}
}

/**
* @fileoverview Runtime capability detection for terminal environments.
*
* TermUI checks the terminal environment at module load to determine
* which features are safe to use. These flags are read-only and
* evaluated once — they do not react to runtime environment changes.
*
* Environment variables:
* - NO_UNICODE=1 → Disable unicode, use ASCII fallbacks
* - NO_MOTION=1 → Skip animations, render static output
* - NO_COLOR=1 → Strip ANSI color codes
* - TERMUI_KEYBINDINGS=vim|emacs → Set navigation mode
*
* @module @termuijs/core/caps
*/

/**
* Terminal capability flags.
*
* These are evaluated once when the module loads. All built-in widgets
* check these flags automatically. Use them in custom widgets to provide
* graceful fallbacks for restricted terminal environments (e.g., CI,
* screen readers, or user preference).
*
* @example
* ```ts
* import { caps } from '@termuijs/core'
*
* const bullet = caps.unicode ? '●' : '*' // Safe for any terminal
* const bar = caps.unicode ? '█' : '#' // Progress bar fallback
* ```
*/
export const caps = {
/**
* Whether unicode characters are supported.
*
* Set `NO_UNICODE=1` to disable unicode and force ASCII fallbacks.
* Useful in CI environments or terminals with limited font support.
*
* @default true (when NO_UNICODE is not set)
*/
unicode: !process.env.NO_UNICODE,

/**
* Whether animations and motion effects are supported.
*
* Set `NO_MOTION=1` to skip all animations and render static output.
* Respects `prefers-reduced-motion` accessibility preference.
*
* @default true (when NO_MOTION is not set)
*/
motion: !process.env.NO_MOTION,

/**
* Whether ANSI color codes are supported.
*
* Set `NO_COLOR=1` to disable all color output. Useful for:
* - Piping output to files
* - Terminals without color support
* - User preference for plain text
*
* @default true (when NO_COLOR is not set)
*/
color: !process.env.NO_COLOR,
Comment on lines +73 to +95

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 | ⚡ Quick win

Align capability mapping with the existing core env-cap contract.

Line 73, Line 83, and Line 95 currently ignore TERM === 'dumb' and CI behavior, which diverges from packages/core/src/terminal/env-caps.ts and can produce inconsistent widget behavior depending on which caps export callers use.

Suggested fix
 export const caps = {
-  unicode: !process.env.NO_UNICODE,
+  unicode: !process.env.NO_UNICODE && process.env.TERM !== 'dumb',
@@
-  motion: !process.env.NO_MOTION,
+  motion: !process.env.NO_MOTION && !process.env.CI,
@@
-  color: !process.env.NO_COLOR,
+  color: !process.env.NO_COLOR && process.env.TERM !== 'dumb',
📝 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
unicode: !process.env.NO_UNICODE,
/**
* Whether animations and motion effects are supported.
*
* Set `NO_MOTION=1` to skip all animations and render static output.
* Respects `prefers-reduced-motion` accessibility preference.
*
* @default true (when NO_MOTION is not set)
*/
motion: !process.env.NO_MOTION,
/**
* Whether ANSI color codes are supported.
*
* Set `NO_COLOR=1` to disable all color output. Useful for:
* - Piping output to files
* - Terminals without color support
* - User preference for plain text
*
* @default true (when NO_COLOR is not set)
*/
color: !process.env.NO_COLOR,
unicode: !process.env.NO_UNICODE && process.env.TERM !== 'dumb',
/**
* Whether animations and motion effects are supported.
*
* Set `NO_MOTION=1` to skip all animations and render static output.
* Respects `prefers-reduced-motion` accessibility preference.
*
* `@default` true (when NO_MOTION is not set)
*/
motion: !process.env.NO_MOTION && !process.env.CI,
/**
* Whether ANSI color codes are supported.
*
* Set `NO_COLOR=1` to disable all color output. Useful for:
* - Piping output to files
* - Terminals without color support
* - User preference for plain text
*
* `@default` true (when NO_COLOR is not set)
*/
color: !process.env.NO_COLOR && process.env.TERM !== 'dumb',
🤖 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/core/src/errors.ts` around lines 73 - 95, The capability properties
unicode, motion, and color in this file are only checking for NO_UNICODE,
NO_MOTION, and NO_COLOR environment variables respectively, but they ignore the
TERM === 'dumb' check and CI environment behavior that exist in the env-caps.ts
contract. Update each of these three properties to also account for TERM being
'dumb' and CI conditions to align with the existing implementation in
packages/core/src/terminal/env-caps.ts, ensuring consistent capability detection
regardless of which caps export callers use.


/**
* Global keybinding navigation mode.
*
* Configures the default keyboard navigation scheme across all widgets.
*
* - `"default"` → Arrow keys (↑↓←→) for navigation
* - `"vim"` → j/k/h/l for navigation (hjkl)
* - `"emacs"` → Ctrl+n/p/f/b for navigation
*
* Set via environment variable: `TERMUI_KEYBINDINGS=vim`
*
* @default "default"
*/
keybindingMode: (process.env.TERMUI_KEYBINDINGS as 'default' | 'vim' | 'emacs') || 'default',

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
set -euo pipefail

# Verify the current keybinding assignment and type assertion usage.
rg -n "keybindingMode|TERMUI_KEYBINDINGS|as 'default' \| 'vim' \| 'emacs'" packages/core/src/errors.ts

# Verify canonical env-caps behavior for comparison.
rg -n "export const caps|TERM !== 'dumb'|NO_MOTION|CI|keybindingMode" packages/core/src/terminal/env-caps.ts

Repository: Karanjot786/TermUI

Length of output: 715


🏁 Script executed:

# Read the full context around line 110 in errors.ts
sed -n '100,120p' packages/core/src/errors.ts

# Get the full keybindingMode implementation in env-caps.ts
sed -n '15,35p' packages/core/src/terminal/env-caps.ts

Repository: Karanjot786/TermUI

Length of output: 1526


Add inline comment or implement runtime validation for type assertion.

Line 110 uses a type assertion without an inline comment explaining why, which violates the coding guidelines. Additionally, the assertion allows arbitrary strings to be treated as valid keybinding modes; the proper approach is runtime validation as implemented in env-caps.ts:

Suggested fix
-  keybindingMode: (process.env.TERMUI_KEYBINDINGS as 'default' | 'vim' | 'emacs') || 'default',
+  keybindingMode:
+    process.env.TERMUI_KEYBINDINGS === 'vim' || process.env.TERMUI_KEYBINDINGS === 'emacs'
+      ? process.env.TERMUI_KEYBINDINGS
+      : 'default',
📝 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
keybindingMode: (process.env.TERMUI_KEYBINDINGS as 'default' | 'vim' | 'emacs') || 'default',
keybindingMode:
process.env.TERMUI_KEYBINDINGS === 'vim' || process.env.TERMUI_KEYBINDINGS === 'emacs'
? process.env.TERMUI_KEYBINDINGS
: 'default',
🤖 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/core/src/errors.ts` at line 110, The keybindingMode property on line
110 uses a type assertion without runtime validation, which allows arbitrary
environment variable values to be treated as valid keybinding modes. Replace the
type assertion with runtime validation that checks if the
process.env.TERMUI_KEYBINDINGS value is one of the allowed modes ('default',
'vim', 'emacs') and falls back to 'default' if it is not. This approach should
follow the same validation pattern implemented in env-caps.ts to ensure type
safety at runtime rather than relying solely on the TypeScript assertion.

Source: Coding guidelines

}

/**
* Checks if the current terminal meets WCAG AA contrast requirements.
*
* This is a convenience helper for theme authors who want to ensure
* accessible color combinations when color support is enabled.
*
* @param foreground - Foreground color in hex, RGB, or named format
* @param background - Background color in hex, RGB, or named format
* @returns true if the combination meets WCAG AA standards
*
* @example
* ```ts
* import { meetsAA } from '@termuijs/core'
* meetsAA('#ffffff', '#000000') // true
* ```
*/
// If this helper exists in the file, add JSDoc above it:
// export function meetsAA(foreground: string, background: string): boolean { ... }

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'

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
set -euo pipefail

# Confirm compiler settings relevant to JSX parsing and module resolution.
fd -i 'tsconfig*.json' -x sh -c 'echo "== $1 =="; rg -n "\"jsx\"|\"moduleResolution\"" "$1" || true' sh {}

# Find JSX-like syntax inside .ts files in touched areas.
rg -n --iglob '*.ts' 'return\s*<|render\(<|<[A-Z][A-Za-z0-9]*\b' packages/widgets/src tests/helpers

Repository: Karanjot786/TermUI

Length of output: 21395


🏁 Script executed:

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

Repository: Karanjot786/TermUI

Length of output: 1173


🏁 Script executed:

cat packages/widgets/tsconfig.json

Repository: Karanjot786/TermUI

Length of output: 308


🏁 Script executed:

cat ../../tsconfig.base.json 2>/dev/null || cat tsconfig.base.json 2>/dev/null || find . -name "tsconfig.base.json" -exec cat {} \;

Repository: Karanjot786/TermUI

Length of output: 564


🏁 Script executed:

rg -A 20 "interface.*[Cc]aps|type.*[Cc]aps|export.*caps" packages/core/src --type ts | head -100

Repository: Karanjot786/TermUI

Length of output: 4761


File won't compile as .ts because it contains JSX. Also, the motion check references a nonexistent property.

Line 31 uses JSX syntax in a .ts file without JSX compiler configuration. Rename to .tsx (and update imports accordingly).

Additionally, line 19 checks caps.noMotion, but the actual property is caps.motion (available as !caps.motion for the no-motion check).

🧰 Tools
🪛 GitHub Actions: CI / 0_build-and-test.txt

[error] 1-1: Command failed: bun run build exited with code 1.

🪛 GitHub Actions: CI / build-and-test

[error] 1-1: Build failed: script "build" exited with code 1 (bun run build).

🤖 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 1, Rename the Spinner.ts file to
Spinner.tsx since it contains JSX syntax that requires JSX compilation support.
Update all import statements throughout the codebase that reference Spinner.ts
to use Spinner.tsx instead. Additionally, locate the motion check that currently
references caps.noMotion and replace it with !caps.motion, since the actual
property available in the caps object is motion (and its negation is used to
check for no-motion support).

Sources: Linters/SAST tools, Pipeline failures

import { caps } from '@termuijs/core'
import { Text } from './Text'

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

Comment on lines +16 to +20

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 | ⚡ Quick win

Use caps.motion (or !caps.motion) instead of caps.noMotion.

Line 19 references a capability key that doesn’t exist in the provided core caps contract (motion), so reduced-motion behavior won’t trigger correctly.

Suggested fix
-    if (caps.noMotion) return
+    if (!caps.motion) 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
const frames = caps.unicode ? FRAMES_UNICODE : FRAMES_ASCII
useEffect(() => {
if (caps.noMotion) return
const frames = caps.unicode ? FRAMES_UNICODE : FRAMES_ASCII
useEffect(() => {
if (!caps.motion) 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` around lines 16 - 20, In the useEffect hook
within the Spinner component, replace the check for caps.noMotion with
!caps.motion since noMotion is not a valid capability key in the core caps
contract. Change the condition from if (caps.noMotion) return to if
(!caps.motion) return to properly detect and respect the user's motion
preferences.

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>
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify duplicate Spinner exports in the index.
rg -n "export \\{[^}]*\\bSpinner\\b" packages/widgets/src/index.ts

# Verify extensionless Spinner re-exports.
rg -n "from './Spinner'" packages/widgets/src/index.ts

Repository: Karanjot786/TermUI

Length of output: 261


🏁 Script executed:

# Check if Spinner files exist in packages/widgets/src
fd -t f "Spinner" packages/widgets/src/ | head -20

# Check the directory structure
ls -la packages/widgets/src/ | grep -i spinner || echo "No direct spinner file found"

Repository: Karanjot786/TermUI

Length of output: 242


🏁 Script executed:

# Check tsconfig to confirm moduleResolution
cat packages/widgets/tsconfig.json | grep -A 2 "moduleResolution" || cat tsconfig.json | grep -A 2 "moduleResolution" || echo "Check compilerOptions"

Repository: Karanjot786/TermUI

Length of output: 148


🏁 Script executed:

# Get the full context around lines 313-314 to understand intent
sed -n '310,320p' packages/widgets/src/index.ts

Repository: Karanjot786/TermUI

Length of output: 328


🏁 Script executed:

# Check packages/widgets tsconfig
cat packages/widgets/tsconfig.json | grep -A 5 "moduleResolution\|compilerOptions"

Repository: Karanjot786/TermUI

Length of output: 188


🏁 Script executed:

# Read the new Spinner.ts file to understand its purpose
cat packages/widgets/src/Spinner.ts

Repository: Karanjot786/TermUI

Length of output: 949


🏁 Script executed:

# Check the existing feedback/Spinner.ts
cat packages/widgets/src/feedback/Spinner.ts

Repository: Karanjot786/TermUI

Length of output: 8580


🏁 Script executed:

# Check the full tsconfig.json to see moduleResolution
cat packages/widgets/tsconfig.json

Repository: Karanjot786/TermUI

Length of output: 308


🏁 Script executed:

# Check the base tsconfig to see moduleResolution setting
cat packages/tsconfig.base.json | grep -A 5 "moduleResolution"

Repository: Karanjot786/TermUI

Length of output: 122


🏁 Script executed:

# Verify both Spinner types to confirm they're truly different
echo "=== feedback/Spinner.ts class definition ===" && head -20 packages/widgets/src/feedback/Spinner.ts
echo "=== Spinner.ts function definition ===" && head -20 packages/widgets/src/Spinner.ts

Repository: Karanjot786/TermUI

Length of output: 1573


Duplicate Spinner export breaks the build.

Lines 313–314 export Spinner again, conflicting with the existing export at Line 137. TypeScript will reject duplicate identifiers in the same module. Additionally, both new re-exports are missing the .js extension used consistently throughout the rest of this file.

Choose one approach:

  • Remove lines 313–314 if you don't need the JSX Spinner exported from the main index.
  • Or alias one of them to avoid the conflict (e.g., export { Spinner as JsxSpinner } from './Spinner.js').
  • Add .js extension either way to match the codebase pattern.
🧰 Tools
🪛 GitHub Actions: CI / 0_build-and-test.txt

[error] 313-313: Build failed (esbuild): Multiple exports with the same name "Spinner". Previously exported at src/index.ts:137:9.


[error] 313-313: Build failed (TypeScript): TS2300 Duplicate identifier 'Spinner'.


[error] 313-313: Build failed (TypeScript): TS2835 Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './Spinner.js'?


[error] 314-314: Build failed (TypeScript): TS2835 Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './Spinner.js'?

🪛 GitHub Actions: CI / build-and-test

[error] 313-313: esbuild error: Multiple exports with the same name "Spinner". Initially exported at src/index.ts:137: export { Spinner, SPINNER_FRAMES } from './feedback/Spinner.js';


[error] 313-313: TypeScript error TS2300: Duplicate identifier 'Spinner' (duplicate export at src/index.ts:313).


[error] 313-314: TypeScript error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when moduleResolution is node16/nodenext. Did you mean './Spinner.js'?


[error] 313-314: TypeScript error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when moduleResolution is node16/nodenext. Did you mean './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, The Spinner and
SpinnerProps exports at lines 313–314 duplicate the existing exports from line
137, causing a build error. Either remove these duplicate export statements if
they're not needed, or rename one of them using an alias (e.g., export as
JsxSpinner) to avoid the conflict. Additionally, add the .js extension to the
file path in the export statement to maintain consistency with the rest of the
file's export pattern.

Source: Pipeline failures

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'

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
set -euo pipefail

# Locate JSX usage in .ts tests.
rg -n --iglob '*.ts' '<Spinner|render\(<|<[A-Z][A-Za-z0-9]*\b' tests/helpers

Repository: Karanjot786/TermUI

Length of output: 144


🏁 Script executed:

# Verify the file exists and check its actual extension
ls -la tests/helpers/Spinner.test.ts 2>&1 || echo "File check result: $?"

Repository: Karanjot786/TermUI

Length of output: 140


🏁 Script executed:

# Read the test file to confirm JSX usage and context
head -20 tests/helpers/Spinner.test.ts

Repository: Karanjot786/TermUI

Length of output: 403


🏁 Script executed:

# Check if there are any other .ts files in the same directory with JSX
find tests/helpers -name "*.ts" -type f -exec grep -l "^import.*JSX\|<[A-Z][A-Za-z]*" {} \;

Repository: Karanjot786/TermUI

Length of output: 92


🏁 Script executed:

# Check for TypeScript and Bun configuration
find . -maxdepth 2 -type f \( -name "tsconfig.json" -o -name "tsconfig*.json" -o -name "bunfig.toml" -o -name "tsconfig*.js" \)

Repository: Karanjot786/TermUI

Length of output: 97


🏁 Script executed:

# Check if there's a jsx compiler option in tsconfig
cat tsconfig.json 2>/dev/null | head -50

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Check for Bun configuration
cat bunfig.toml 2>/dev/null

Repository: Karanjot786/TermUI

Length of output: 250


🏁 Script executed:

# Check the base tsconfig for JSX settings
cat tsconfig.base.json | head -60

Repository: Karanjot786/TermUI

Length of output: 564


🏁 Script executed:

# Also check if there's a tsconfig.json in the tests directory
find . -name "tsconfig.json" -type f

Repository: Karanjot786/TermUI

Length of output: 1417


🏁 Script executed:

# Look at the actual error by checking if a build/type check would fail
# Check for any build scripts or test scripts in package.json
head -50 package.json 2>/dev/null || echo "No package.json at root"

Repository: Karanjot786/TermUI

Length of output: 1295


🏁 Script executed:

# Check if tsconfig.base.json includes jsx option (which would allow JSX in .ts files)
grep -A 5 -B 5 '"jsx"' tsconfig.base.json

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Check if tests directory has its own tsconfig or if files extend the base
find tests -name "tsconfig.json" -o -name "tsconfig*.json"

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Look for .tsx files in the project to understand the pattern
find . -name "*.tsx" -type f | head -20

Repository: Karanjot786/TermUI

Length of output: 841


🏁 Script executed:

# Check the Bun documentation/config to see if it handles JSX in .ts files natively
# Also check if there are Bun-specific JSX settings
grep -r "jsx" bunfig.toml package.json tsconfig.base.json 2>/dev/null || echo "No jsx config found"

Repository: Karanjot786/TermUI

Length of output: 82


🏁 Script executed:

# Check what the jsx compiler option is set to in the full tsconfig.base.json
cat tsconfig.base.json

Repository: Karanjot786/TermUI

Length of output: 564


🏁 Script executed:

# Check if there's a vitest/bun config that handles JSX transpilation
find . -maxdepth 2 -name "vite.config.*" -o -name "vitest.config.*" -o -name ".bun*"

Repository: Karanjot786/TermUI

Length of output: 106


🏁 Script executed:

# Check if the packages/testing package has JSX configuration
cat packages/testing/tsconfig.json 2>/dev/null | head -30

Repository: Karanjot786/TermUI

Length of output: 363


🏁 Script executed:

# Verify if Bun's test runner properly handles JSX in .ts files by checking the testing package
cat packages/testing/package.json 2>/dev/null | grep -A 10 -B 10 "main\|export"

Repository: Karanjot786/TermUI

Length of output: 676


🏁 Script executed:

# Check the vitest.config.ts to see how it handles JSX
cat vitest.config.ts

Repository: Karanjot786/TermUI

Length of output: 777


🏁 Script executed:

# Check if tests directory has configuration that allows JSX
find tests -name "*.json" -o -name "*.ts" -o -name "*.config.*"

Repository: Karanjot786/TermUI

Length of output: 149


🏁 Script executed:

# Search for how the test file is actually being transpiled - check if vitest or bun has built-in JSX support
# Also check what happens when we look at actual .tsx test files in the project
find . -path "*/tests/*" -name "*.test.tsx" | head -5

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Let's verify if static analysis really reports parser errors by checking if there are TypeScript type-check issues
# Look for any test configuration in the packages/testing
cat packages/testing/tsconfig.json

Repository: Karanjot786/TermUI

Length of output: 363


🏁 Script executed:

# Verify if the tests/helpers/Spinner.test.ts is actually being run by checking if it's in any test glob patterns
grep -r "tests/helpers" . --include="*.json" --include="*.ts" --include="*.js" | grep -i "test\|glob\|pattern"

Repository: Karanjot786/TermUI

Length of output: 211


🏁 Script executed:

# Double-check the vitest include patterns more carefully
grep -A 5 "include:" vitest.config.ts

Repository: Karanjot786/TermUI

Length of output: 493


🏁 Script executed:

# Check if there's a separate test configuration that would pick up tests/helpers files
find . -maxdepth 2 -name "vitest.config.*" -o -name "vite.config.*" -o -name "jest.config.*"

Repository: Karanjot786/TermUI

Length of output: 106


🏁 Script executed:

# Check if there's a tsconfig.json specifically for tests/helpers that might configure JSX
find tests -type f -name "tsconfig.json"

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Verify the actual structure - is tests/helpers meant to be test files or utility helpers?
ls -la tests/helpers/

Repository: Karanjot786/TermUI

Length of output: 361


🏁 Script executed:

# Check if any other test file in tests/helpers exists
find tests -name "*.test.ts" -o -name "*.test.tsx"

Repository: Karanjot786/TermUI

Length of output: 92


🏁 Script executed:

# Check if tests/helpers is imported or referenced anywhere in the actual test files
grep -r "tests/helpers/Spinner" . --include="*.ts" --include="*.tsx" --include="*.js"

Repository: Karanjot786/TermUI

Length of output: 44


🏁 Script executed:

# Verify if the base tsconfig is being used for this file or if there's a different resolution
# Check the compiler options that would apply to tests/helpers/Spinner.test.ts
cat tsconfig.json 2>/dev/null || echo "No root tsconfig.json"

Repository: Karanjot786/TermUI

Length of output: 84


🏁 Script executed:

# Check what the purpose of this file is - is it actually a test or just misnamed?
# Look at the content and see if it's meant to be a test
cat tests/helpers/Spinner.test.ts

Repository: Karanjot786/TermUI

Length of output: 403


🏁 Script executed:

# Verify the vitest include pattern one more time to confirm tests/helpers is not covered
grep -E "include|pattern" vitest.config.ts

Repository: Karanjot786/TermUI

Length of output: 193


🏁 Script executed:

# Check if any configuration extends or applies to tests/ directory
find . -maxdepth 3 -name "tsconfig.json" -exec grep -l "tests" {} \;

Repository: Karanjot786/TermUI

Length of output: 44


Rename test file to .tsx to follow project conventions.

File uses JSX (line 7: <Spinner label="Loading" />) but is currently named .ts. While Bun's test runner handles JSX in .ts files natively, the project consistently uses .tsx for all JSX-containing files (see examples/ and packages/). Rename to .tsx for consistency with TypeScript conventions and the project's established pattern.

🤖 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 1, Rename the file from
Spinner.test.ts to Spinner.test.tsx since the file contains JSX syntax (such as
the Spinner component usage at line 7). This aligns with the project's
established convention of using the .tsx extension for all files that contain
JSX, as seen in the examples/ and packages/ directories.

Source: Linters/SAST tools

import { render } from '@termuijs/testing'
import { Spinner } from '../../packages/widgets/src/Spinner'

describe('Spinner', () => {
it('renders with label', () => {
const t = render(<Spinner label="Loading" />)
expect(t.renderToString()).toContain('Loading')
t.unmount()
})
})
Loading