Skip to content

[#13] Added bounds, step and increment keys to the number widget.#29

Merged
AlexSkrypnyk merged 3 commits into
mainfrom
feature/13-number-bounds
Jul 10, 2026
Merged

[#13] Added bounds, step and increment keys to the number widget.#29
AlexSkrypnyk merged 3 commits into
mainfrom
feature/13-number-bounds

Conversation

@AlexSkrypnyk

Copy link
Copy Markdown
Member

Closes #13

Summary

Adds optional min, max and step bounds to number fields, enabling Up/Down arrow-key stepping and inline range validation in the interactive widget, with matching enforcement in the headless engine, the schema validator and the generated JSON schema.
A new NumberBounds value object centralizes the range arithmetic, the human-readable range phrase, and a shared violation() check so every enforcement surface - widget, engine and validator - agrees on the same rule, including for float values.
Fields that declare no bounds keep their existing plain-integer behaviour: the arrow keys stay inert and no range validation runs.

Changes

  • Added DrevOps\Tui\Config\NumberBounds, a readonly value object holding optional min/max/step with contains(), describe(), violation(), clamp() and step() methods.
  • Added fluent FieldBuilder::min(), ::max() and ::step() methods, plus a buildBounds() step that assembles a NumberBounds only when at least one of the three is declared.
  • FieldBuilder::buildBounds() throws ConfigException at config time when min exceeds max, or when step is not positive.
  • Added a bounds property to Config\Field, threaded through WidgetFactory into NumberWidget.
  • NumberWidget now handles Up/Down keys via a new adjust() method that steps the value by the bounds' step (default 1), clamped to the range; it renders its own key-hint line (↑/↓ adjust · enter accept · esc cancel) only when bounds are declared, via rendersHint().
  • NumberWidget::accept() rejects an out-of-range value inline with a message built from NumberBounds::describe().
  • Extracted TextWidget::caretLine() so NumberWidget::view() can reuse the caret-rendering logic while adding its own hint and error rows.
  • Engine::validateValue() and the new SchemaValidator::checkBounds() both call NumberBounds::violation(), so headless --prompts/environment input and JSON-schema-driven validation reject out-of-range values with the same message.
  • SchemaGenerator emits min, max and step keys per field, and the new AgentHelp::rangeNote() appends a compact range annotation (e.g. (between 1 and 10, step 2)) to the agent-facing field help.
  • NumberBounds::violation() narrows its input to int|float before checking, so a non-numeric value passes through untouched while a float is enforced identically to an int - closing a bypass where an out-of-range float could previously slip past bounds enforcement.
  • Documented the feature in README.md with a usage example.
  • Fully unit-tested: 527 tests pass, with every touched file at 100% line coverage.

Before / After

┌─ BEFORE ──────────────────────────────────────────────────────────────
│ HTTP port
│ > 99999█
│
│ Up/Down  ... inert, handled as plain text input
│ Enter    ... any integer accepted, including 99999
│ Headless ... --prompts / environment values pass through unchecked
│ Schema   ... no min/max/step keys emitted
└───────────────────────────────────────────────────────────────────────

┌─ AFTER ───────────────────────────────────────────────────────────────
│ HTTP port  (min: 1, max: 65535, step: 1)
│ > 65535█
│ ↑/↓ adjust · enter accept · esc cancel
│
│ Up/Down  ... steps by `step` (default 1), clamped to [min, max]
│ Enter    ... out-of-range value rejected inline: "Enter a number between 1 and 65535."
│ Headless ... Engine::collect() rejects the same out-of-range ints and floats
│ Schema   ... min/max/step emitted; SchemaValidator enforces them with the same message
└───────────────────────────────────────────────────────────────────────

@AlexSkrypnyk AlexSkrypnyk added the A3 Board worker 3 label Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cb75e267-274e-440f-a31c-5c644572328e

📥 Commits

Reviewing files that changed from the base of the PR and between db1d39d and 2202bea.

📒 Files selected for processing (19)
  • README.md
  • src/Builder/FieldBuilder.php
  • src/Config/Field.php
  • src/Config/NumberBounds.php
  • src/Engine/Engine.php
  • src/Schema/AgentHelp.php
  • src/Schema/SchemaGenerator.php
  • src/Schema/SchemaValidator.php
  • src/Widget/NumberWidget.php
  • src/Widget/TextWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Config/NumberBoundsTest.php
  • tests/phpunit/Unit/Engine/EngineDeclaredBehaviourTest.php
  • tests/phpunit/Unit/Schema/AgentHelpTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/NumberWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/13-number-bounds

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

@github-actions

Copy link
Copy Markdown
Code Coverage Report:
  2026-07-10 11:54:22

 Summary:
  Classes: 90.00% (54/60)
  Methods: 97.46% (384/394)
  Lines:   99.24% (1566/1578)

DrevOps\Tui\Answers\Answer
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Answers\Answers
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Answers\SummaryFormatter
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Builder\FieldBuilder
  Methods: 100.00% (22/22)   Lines: 100.00% ( 73/ 73)
DrevOps\Tui\Builder\Form
  Methods:  92.86% (13/14)   Lines:  98.33% ( 59/ 60)
DrevOps\Tui\Builder\PanelBuilder
  Methods: 100.00% (17/17)   Lines: 100.00% ( 29/ 29)
DrevOps\Tui\Condition\CompositeCondition
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Condition\Condition
  Methods: 100.00% (10/10)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Config\Config
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 17/ 17)
DrevOps\Tui\Config\Field
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Config\NumberBounds
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Config\Option
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Config\Panel
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Derive\Derive
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 14/ 14)
DrevOps\Tui\Derive\Deriver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 13/ 13)
DrevOps\Tui\Derive\Transform
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Discovery\AbstractDiscover
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Discovery\Dotenv
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Discovery\JsonValue
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Discovery\PathExists
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Discovery\Scan
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Engine\Engine
  Methods: 100.00% (12/12)   Lines: 100.00% ( 92/ 92)
DrevOps\Tui\Handler\HandlerRegistry
  Methods:  85.71% ( 6/ 7)   Lines:  95.45% ( 21/ 22)
DrevOps\Tui\Input\ArrayKeyStream
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Input\Key
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Input\KeyParser
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 68/ 68)
DrevOps\Tui\Render\Ansi
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Render\Box
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\ExternalEditor
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Render\Navigator
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\PanelController
  Methods: 100.00% (16/16)   Lines: 100.00% ( 92/ 92)
DrevOps\Tui\Render\Scroller
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Render\Terminal
  Methods: 100.00% (12/12)   Lines: 100.00% ( 35/ 35)
DrevOps\Tui\Render\TerminalControl
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Render\Viewport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Resolver\InputResolver
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Schema\AgentHelp
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 25/ 25)
DrevOps\Tui\Schema\SchemaGenerator
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 28/ 28)
DrevOps\Tui\Schema\SchemaValidator
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 45/ 45)
DrevOps\Tui\Theme\DefaultTheme
  Methods:  91.80% (56/61)   Lines:  96.77% (180/186)
DrevOps\Tui\Theme\ThemeManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Tui
  Methods: 100.00% (12/12)   Lines: 100.00% ( 26/ 26)
DrevOps\Tui\Widget\AbstractWidget
  Methods: 100.00% (10/10)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Widget\ConfirmWidget
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 23/ 23)
DrevOps\Tui\Widget\MultiSearchWidget
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Widget\MultiSelectWidget
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 64/ 64)
DrevOps\Tui\Widget\NumberWidget
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 37/ 37)
DrevOps\Tui\Widget\PasswordDisplay
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Widget\PasswordWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Widget\PauseWidget
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Widget\SearchWidget
  Methods:  80.00% ( 4/ 5)   Lines:  94.59% ( 35/ 37)
DrevOps\Tui\Widget\SelectWidget
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Widget\SuggestWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 37/ 37)
DrevOps\Tui\Widget\TextWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Widget\TextareaWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 48/ 48)
DrevOps\Tui\Widget\ToggleWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Widget\WidgetFactory
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Widget\WidgetRunner
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.23%. Comparing base (db1d39d) to head (2202bea).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #29      +/-   ##
==========================================
+ Coverage   98.92%   99.23%   +0.31%     
==========================================
  Files          59       60       +1     
  Lines        1493     1578      +85     
==========================================
+ Hits         1477     1566      +89     
+ Misses         16       12       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@AlexSkrypnyk AlexSkrypnyk merged commit 35337ae into main Jul 10, 2026
9 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/13-number-bounds branch July 10, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A3 Board worker 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add bounds, step and increment keys to the number widget

1 participant