Skip to content

feat(ui): add Rating widget#728

Open
Satvik-art-creator wants to merge 3 commits into
Karanjot786:mainfrom
Satvik-art-creator:feat/add-rating-widget
Open

feat(ui): add Rating widget#728
Satvik-art-creator wants to merge 3 commits into
Karanjot786:mainfrom
Satvik-art-creator:feat/add-rating-widget

Conversation

@Satvik-art-creator
Copy link
Copy Markdown
Contributor

@Satvik-art-creator Satvik-art-creator commented Jun 4, 2026

Related Issue

Closes #258

Summary

Add a star-rating selector widget to @termuijs/ui that renders filled/empty star glyphs controlled by arrow keys.

Which package(s) does this affect?

  • @termuijs/ui

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)
  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🧹 Refactor (no functional changes)
  • ✅ Test update

Changes

[NEW] packages/ui/src/Rating.ts

  • Rating class extending Widget with:
    • constructor(style?, opts?) matching the issue API contract
    • setValue(value) / getValue() methods; setValue calls markDirty()
    • value and max getters
    • onSelect callback fired on enter
    • handleKey() supporting right, left, home, end, enter
    • _renderSelf() with caps.unicode fallback (★☆ vs *-)
    • filledChar, emptyChar, filledColor options
    • focusable = true

[NEW] packages/ui/src/Rating.test.ts

  • 15 tests covering all acceptance criteria:
    • Renders 5 empty stars on init
    • Right key fills one star (with render verification)
    • Left key does not go below 0
    • Enter fires onSelect with current value
    • ASCII fallback renders * and -
    • setValue clamps to max and calls markDirty()
    • Home/end key support
    • Does not handle space key
    • Renders filled/empty unicode stars correctly
    • Right key caps at max
    • Custom max, defaults, focusable flag, getValue()
  • All key-interaction tests use full widget lifecycle (Screen + updateRect() + render())
  • Uses createKeyEvent from @termuijs/core for proper KeyEvent construction
  • Uses vi.spyOn for caps, never mutates shared state

[MODIFY] packages/ui/src/index.ts

  • Added Rating and RatingOptions exports

Checklist

  • I have read AGENTS.md and followed its rules
  • My change is confined to packages/ui only
  • I export new public APIs from the package index.ts
  • I followed the reference pattern (Toggle.ts)
  • bun vitest run packages/ui passes (46 files, 333+ tests)
  • No bun.lock changes
  • No files touched outside packages/ui
  • Tests use real Screen from @termuijs/core
  • Tests use vi.spyOn for caps, never direct mutation
  • All tests assert observable behavior (no placeholder tests)
  • Conventional commit format used

Verification

  • bun vitest run packages/ui — all test files pass
  • bun vitest run packages/ui/src/Rating.test.ts — 15/15 passed
  • git diff upstream/main --stat confirms only 3 files in packages/ui

GSSoC 2026

Add a star-rating selector widget to @termuijs/ui that renders
filled/empty star glyphs controlled by arrow keys.

- right/left arrows adjust the value (capped at 0..max)
- enter fires onChange with the current value
- home/end jump to min/max
- readonly mode disables key input
- caps.unicode glyph fallback (★☆ vs *-)
- 15 tests covering all acceptance criteria

Closes Karanjot786#258
@github-actions github-actions Bot added type:feature +10 pts. New feature. area:ui @termuijs/ui type:testing +10 pts. Tests. labels Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new Rating widget (keyboard-driven star selector) with tests and package exports, and refactors MeterOptions to simplified thresholds plus a new setLabel method.

Changes

Rating Widget

Layer / File(s) Summary
Rating contract and core implementation
packages/ui/src/Rating.ts
RatingOptions and Rating class: _max/_value initialization with clamping, setValue/getValue, rendering using caps.unicode (Unicode stars) with ASCII fallback, truncation to widget width.
Keyboard handling and events
packages/ui/src/Rating.ts
handleKey processes lowercase keys: right/left increment/decrement (bounded), home/end jump to 0/_max, enter invokes onSelect, space ignored.
Comprehensive Vitest suite
packages/ui/src/Rating.test.ts
Tests verify default rendering (5 empty stars), Unicode and ASCII fallbacks, key interactions (right/left/home/end/enter/space), setValue clamping and markDirty, defaults, focusable, and getValue().
Package export
packages/ui/src/index.ts
Re-exports Rating and RatingOptions from ./Rating.js.

Meter Options Simplification

Layer / File(s) Summary
MeterOptions and setLabel
packages/widgets/src/data/Meter.ts
MeterOptions simplified to low, high, showLabel (removed optimum and colors), and Meter.setLabel(label) added to update label and mark dirty.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

gssoc:approved, level:intermediate, quality:exceptional

Suggested reviewers

  • Karanjot786

Poem

"I hopped through stars so bright and true,
Left and right set each new view,
Enter seals the choice I keep,
Tests watch o'er while I softly sleep,
A tiny rabbit's rating, just for you." 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes unrelated changes to packages/widgets/src/data/Meter.ts (threshold refactoring and setLabel method) which are out of scope per issue #258 requirements to stay confined to packages/ui only. Remove all Meter.ts changes and revert to focus solely on packages/ui Rating widget implementation as specified in the issue scope.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(ui): add Rating widget' clearly and specifically describes the main change - adding a new Rating widget to the ui package.
Linked Issues check ✅ Passed The code changes fully implement the API contract and acceptance criteria from #258: Rating widget with value/max getters/setters, arrow key handling, enter callback, Unicode/ASCII fallback, and comprehensive test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description is comprehensive, well-structured, and covers all required template sections with detailed information about the changes, testing, and verification.

✏️ 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.

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/ui/src/Rating.test.ts`:
- Around line 37-50: The tests call Rating.handleKey directly; update them to
exercise the real widget lifecycle by creating a Screen from `@termuijs/core`,
mounting a Rating instance into that screen, calling updateRect() and render(),
then sending the key event (e.g., using the existing key('right')/key('left')
helper) through the Screen so the Rating receives it via its normal input path;
after render/update assert Rating.value. Apply the same pattern for the other
tests referenced (lines ~52-58, 79-109, 123-129) using Rating, Screen,
updateRect, and render instead of directly invoking handleKey.

In `@packages/ui/src/Rating.ts`:
- Around line 19-27: Update the Rating public API to match the stated contract
by adding a compatibility layer: modify the constructor of the Rating class to
accept constructor(style?, opts?) (i.e., support an optional first style arg and
an options object second) and map that into the existing internal initialization
that currently consumes constructor(options); add an onSelect property that
aliases/forwards to the existing onChange callback (so both names work);
implement setValue(value) and getValue() methods that delegate to the existing
value setter/getter property accessors; keep the existing readonly and onChange
behavior intact so existing consumers still work.
🪄 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: 87f07288-0ab6-4c00-947d-818790d72895

📥 Commits

Reviewing files that changed from the base of the PR and between f90f75c and 65019b9.

📒 Files selected for processing (3)
  • packages/ui/src/Rating.test.ts
  • packages/ui/src/Rating.ts
  • packages/ui/src/index.ts

Comment thread packages/ui/src/Rating.test.ts
Comment thread packages/ui/src/Rating.ts Outdated
…uplicate export

Rating (packages/ui):
- constructor(style?, opts?) to match issue Karanjot786#258 API contract
- rename onChange to onSelect per spec
- add setValue()/getValue() methods with markDirty()
- add filledChar/emptyChar/filledColor options
- remove out-of-scope readonly mode
- add full widget lifecycle (Screen+updateRect+render) to all key tests

Meter (packages/widgets):
- remove duplicate class definition causing Multiple exports build error
- fix threshold color logic to match test expectations

All 2019 tests pass. Closes Karanjot786#258
@github-actions github-actions Bot added the area:widgets @termuijs/widgets label Jun 4, 2026
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/Rating.ts (1)

125-135: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

filledColor option is currently ignored in rendering.

Line [125]-Line [135] always render with styleToCellAttrs(this.style), so filledColor set at Line [66] never affects filled stars. This makes part of the public API non-functional.

Suggested patch
     protected _renderSelf(screen: Screen): void {
         const { x, y, width } = this._rect;
         if (width <= 0) return;

         const attrs = styleToCellAttrs(this.style);

         const filled = this._filledChar ?? (caps.unicode ? '★' : '*');
         const empty = this._emptyChar ?? (caps.unicode ? '☆' : '-');

-        let text = '';
-        for (let i = 0; i < this._max; i++) {
-            text += i < this._value ? filled : empty;
-        }
-
-        screen.writeString(x, y, text.slice(0, width), attrs);
+        const visibleCount = Math.min(this._max, width);
+        const filledCount = Math.min(this._value, visibleCount);
+        const emptyCount = visibleCount - filledCount;
+
+        const filledAttrs = this._filledColor === undefined
+            ? attrs
+            : { ...attrs, fg: this._filledColor };
+
+        if (filledCount > 0) {
+            screen.writeString(x, y, filled.repeat(filledCount), filledAttrs);
+        }
+        if (emptyCount > 0) {
+            screen.writeString(x + filledCount, y, empty.repeat(emptyCount), attrs);
+        }
     }
🤖 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/ui/src/Rating.ts` around lines 125 - 135, The render currently
always uses styleToCellAttrs(this.style) so the filledColor option is ignored;
update the render logic in Rating (the block using styleToCellAttrs,
filled/empty chars, and screen.writeString) to derive separate attrs for filled
and empty characters (e.g., attrsFilled = styleToCellAttrs({ ...this.style,
color: this.filledColor }) and attrsEmpty = styleToCellAttrs(this.style) or by
overriding the foreground color in the attrs), then write the filled and empty
runs with their respective attrs (either by building runs and calling
screen.writeString per run or using whatever per-char attr API exists) instead
of a single attrs for the whole text.
🤖 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.

Outside diff comments:
In `@packages/ui/src/Rating.ts`:
- Around line 125-135: The render currently always uses
styleToCellAttrs(this.style) so the filledColor option is ignored; update the
render logic in Rating (the block using styleToCellAttrs, filled/empty chars,
and screen.writeString) to derive separate attrs for filled and empty characters
(e.g., attrsFilled = styleToCellAttrs({ ...this.style, color: this.filledColor
}) and attrsEmpty = styleToCellAttrs(this.style) or by overriding the foreground
color in the attrs), then write the filled and empty runs with their respective
attrs (either by building runs and calling screen.writeString per run or using
whatever per-char attr API exists) instead of a single attrs for the whole text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 53fa26bc-8ae7-4064-a7a5-c2aad69e63eb

📥 Commits

Reviewing files that changed from the base of the PR and between 65019b9 and 018cd51.

📒 Files selected for processing (3)
  • packages/ui/src/Rating.test.ts
  • packages/ui/src/Rating.ts
  • packages/widgets/src/data/Meter.ts

@Satvik-art-creator
Copy link
Copy Markdown
Contributor Author

Satvik-art-creator commented Jun 4, 2026

Hi @Karanjot786,

While working on the Rating widget (#258), I discovered a pre-existing bug in packages/widgets/src/data/Meter.ts — the file contains the Meter class defined twice (lines 1–105 and lines 106–205), which causes bun run build to fail with Multiple exports with the same name "Meter" and all 10 Meter tests to fail.

I filed a separate bug report for this (issue #737) but had to close it temporarily because my Rating PR was still open — if someone commented on the Meter issue, the bot would auto-assign it to them before I could work on it.

Could you please reopen issue #737 after this PR is merged? I'd like to submit a fix for the Meter duplicate as a separate PR scoped to packages/widgets under the GSSOC 26, keeping it clean and one-change-per-PR as per AGENTS.md.

Thanks!

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

Labels

area:ui @termuijs/ui area:widgets @termuijs/widgets type:feature +10 pts. New feature. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ui): add Rating widget

1 participant