Skip to content

[#9] Added a date picker calendar widget.#32

Merged
AlexSkrypnyk merged 8 commits into
mainfrom
feature/9-date-picker
Jul 13, 2026
Merged

[#9] Added a date picker calendar widget.#32
AlexSkrypnyk merged 8 commits into
mainfrom
feature/9-date-picker

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 10, 2026

Copy link
Copy Markdown
Member

Closes #9

Summary

Adds a date field type: a month-calendar widget navigable by day, week and month, returning a normalized ISO YYYY-MM-DD string. The widget is wired through the full stack - builder (minDate/maxDate/weekStart), config (DateBounds, Weekday), factory, engine validation, and JSON schema/agent-help - so the range and week-start declared on a field behave identically whether the value comes from the interactive TUI or headless collection. Navigation is routed through the configurable key map, like every other widget.

Changes

  • Widget: src/Widget/DateWidget.php renders a "Month YYYY" heading, a weekday header ordered from the configured week-start day, and a day grid. Day and week movement and Accept resolve through the injected key map - so the arrow keys, the vim preset's h/j/k/l and any consumer remap all reach them - while PageUp/PageDown change month and Home/End jump to the first/last day of the month. The cursor is always clamped into the declared range; out-of-range days stay visible but dimmed. The widget draws its own key-hint line (rendersHint() returns TRUE), and playground/3-widgets/widgets.php skips the generic hint line for such widgets so the two can't contradict.
  • Bounds and week model: src/Config/DateBounds.php holds an optional inclusive min/max (DateTimeImmutable) and a Weekday, and rejects a minimum declared after the maximum in its own constructor. It provides strict Y-m-d parsing (round-tripped so a malformed or rolled-over value like 2026-02-30 is rejected rather than silently normalized), contains(), clamp(), a human describe() range phrase, and a violation() check shared by the widget, engine and schema validator. src/Config/Weekday.php is a new ISO-8601-backed enum (Monday = 1 ... Sunday = 7) with abbreviation(), fromDate(), sequence() (the seven weekdays in display order from a given start) and columnOf() (grid column placement).
  • Key bindings: VimKeyMap binds h/j/k/l to the date scope's move actions - safe because the calendar takes no typed input, like the single-choice list - so the vim preset drives the calendar the same way it drives the panel navigator.
  • Builder wiring: PanelBuilder::date() adds a date field; FieldBuilder::minDate(), maxDate() and weekStart() declare the range and first weekday, assembled into a DateBounds on build() via FieldBuilder::buildDateBounds(). An invalid bound string or a min declared after max throws ConfigException; the date setters are inert when called on a non-date field.
  • Config and factory: Field gains a dateBounds property alongside the existing bounds; FieldType::Date is a new enum case; WidgetFactory seeds a DateWidget from the field's current string value and declared bounds.
  • Headless engine: Engine::validateValue() checks dateBounds?->violation() alongside the existing number-bounds check, so a --prompts or environment value outside the declared range is rejected with the same range phrase the interactive widget shows.
  • Schema and agent help: SchemaValidator type-checks date values (an empty string, or a strict Y-m-d date) and enforces the declared range; SchemaGenerator emits min_date, max_date and week_start on the field's schema entry; AgentHelp appends a range annotation (or YYYY-MM-DD when unbounded) to a date field's help line.
  • Demo and docs: playground/3-widgets/widget-date.php is a new standalone demo; widgets.php and show.php include the date widget in the combined showcase; docs/util/update-assets.php adds an expect script and row count for the date widget and regenerates the four combined widgets*.svg assets plus four new per-widget widget-date*.svg assets; README.md documents the date field (usage, bounds, key hints, screenshots) and updates the widget count to include it.
  • Tests: new DateBoundsTest, WeekdayTest and DateWidgetTest cover the added classes directly - navigation (including vim-preset remapping through an injected key map), bounds clamping and dimming on both edges, week-start layout, the constructor invariant, and boundary/impossible-date cases; FormTest, WidgetFactoryTest, EngineDeclaredBehaviourTest, InputResolverTest, SchemaGeneratorTest, SchemaValidatorTest and AgentHelpTest are extended to cover the date field type end to end. The full suite passes with 100% coverage on all new and touched classes.

Before / After

+-- BEFORE: no `date` type ------------------------------+
| FieldType surface (no calendar type):
|   text  number  textarea  password  select
|   multiselect  suggest  search  multisearch
|   confirm  toggle  filepicker  multifilepicker  pause
|
| A release date is entered as plain text - no range
| check, no calendar, no day/week/month navigation:
|
|   $p->text('release', 'Release date')
|     ->default('2026-07-15');
+----------------------------------------------------------+

                          |
                          v

+-- AFTER: a `date` calendar widget -----------------------+
| FieldType surface (adds `date`):
|   text  number  date  textarea  password  select
|   multiselect  suggest  search  multisearch
|   confirm  toggle  filepicker  multifilepicker  pause
|
|   $p->date('release', 'Release date')
|     ->minDate('2026-01-01')->maxDate('2026-12-31')
|     ->weekStart(Weekday::Sunday);
|
|            July 2026
|   Su Mo Tu We Th Fr Sa
|             1  2  3  4
|    5  6  7  8  9 10 11
|   12 13 14 [15] 16 17 18
|   19 20 21 22 23 24 25
|   26 27 28 29 30 31
|
|   arrows/hjkl day + week   PgUp/PgDn month
|   Enter accept   Esc cancel
|
|   Range and week-start are enforced in the widget AND
|   in headless collection (env vars / --prompts).
+------------------------------------------------------------+

Summary by CodeRabbit

  • New Features
    • Added a calendar-based date widget with month navigation, keyboard controls, configurable week starts, and ISO YYYY-MM-DD output.
    • Added date fields with optional minimum and maximum date constraints.
    • Added date validation, schema metadata, and helpful range guidance.
  • Documentation
    • Expanded widget documentation with date behavior, controls, output format, and configuration options.
  • Examples
    • Added interactive and showcase examples for the date widget.

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

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

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: 44 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: f6534dd8-5b8b-47f3-9a99-af7d4b9da457

📥 Commits

Reviewing files that changed from the base of the PR and between 0a1ad0b and 13fb51c.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-date-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-date-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii.svg is excluded by !**/*.svg
  • docs/assets/widgets-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets.svg is excluded by !**/*.svg
📒 Files selected for processing (28)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/show.php
  • playground/3-widgets/widget-date.php
  • playground/3-widgets/widgets.php
  • src/Builder/FieldBuilder.php
  • src/Builder/PanelBuilder.php
  • src/Config/DateBounds.php
  • src/Config/Field.php
  • src/Config/FieldType.php
  • src/Config/Weekday.php
  • src/Engine/Engine.php
  • src/Input/VimKeyMap.php
  • src/Schema/AgentHelp.php
  • src/Schema/SchemaGenerator.php
  • src/Schema/SchemaValidator.php
  • src/Widget/DateWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Config/DateBoundsTest.php
  • tests/phpunit/Unit/Config/WeekdayTest.php
  • tests/phpunit/Unit/Engine/EngineDeclaredBehaviourTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/AgentHelpTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/DateWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php
📝 Walkthrough

Walkthrough

Adds a date field type with bounded calendar navigation, configurable week starts, ISO output, headless validation and schema metadata, interactive examples, tests, static assets, and README documentation.

Changes

Date picker feature

Layer / File(s) Summary
Date contracts and builder configuration
src/Config/*, src/Builder/*, tests/phpunit/Unit/Config/*, tests/phpunit/Unit/Builder/FormTest.php
Adds FieldType::Date, Weekday, DateBounds, date field storage, fluent date configuration, strict parsing, bounds validation, and unit coverage.
Calendar widget and interactive wiring
src/Widget/DateWidget.php, src/Widget/WidgetFactory.php, src/Input/VimKeyMap.php, playground/3-widgets/*, tests/phpunit/Unit/Widget/*
Adds month-calendar navigation, ISO date acceptance, bounds clamping, themed rendering, vim bindings, factory integration, playground examples, and widget tests.
Validation and schema integration
src/Engine/Engine.php, src/Schema/*, tests/phpunit/Unit/{Engine,Resolver,Schema}/*
Adds date type and range validation, headless string handling, generated date metadata, and date annotations in agent help.
Documentation and static widget assets
README.md, docs/util/update-assets.php
Documents date navigation, output, defaults, bounds, week starts, headless behavior, and updates static asset generation for the date widget.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant WidgetFactory
  participant DateWidget
  participant DateBounds
  User->>WidgetFactory: create date field widget
  WidgetFactory->>DateWidget: seed value and bounds
  User->>DateWidget: navigate calendar
  DateWidget->>DateBounds: clamp cursor
  User->>DateWidget: confirm selection
  DateWidget-->>User: return ISO date
Loading

Possibly related issues

  • Add a date picker (calendar) widget #9 — Directly covers the date-picker implementation, including navigation, bounds, builder integration, validation, schema support, rendering, tests, and documentation.

Possibly related PRs

  • drevops/tui#3 — Both changes modify the shared widget interaction rendering and hint composition in playground/3-widgets/widgets.php.

Suggested labels: A1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new date picker calendar widget.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/9-date-picker

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

@github-actions

This comment has been minimized.

@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.58%. Comparing base (4c931f2) to head (13fb51c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #32      +/-   ##
==========================================
+ Coverage   99.55%   99.58%   +0.02%     
==========================================
  Files          73       76       +3     
  Lines        2272     2411     +139     
==========================================
+ Hits         2262     2401     +139     
  Misses         10       10              

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/Engine/Engine.php (1)

190-204: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject malformed FieldType::Date input in Engine::validateValue()
Tui::collect() feeds raw --prompts/env values into Engine::collect(), and this path only checks dateBounds; DateBounds::violation() returns NULL on parse failure, so bad date strings can still be accepted unchanged. Add a strict date parse/type check here.

🤖 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 `@src/Engine/Engine.php` around lines 190 - 204, The validateValue() method
must reject malformed FieldType::Date inputs before applying date bounds. Add a
strict type check and date parse validation for fields whose type is
FieldType::Date, returning an appropriate validation error when the value is not
a valid date; preserve the existing bounds and custom validator behavior for
other field types.
🤖 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 `@src/Schema/SchemaGenerator.php`:
- Around line 53-55: In the schema-generation method, serialize dateBounds
weekStart consistently with other enums by replacing the weekStart ->name access
with ->value, unless the schema explicitly requires the enum case name rather
than its backing value.

---

Outside diff comments:
In `@src/Engine/Engine.php`:
- Around line 190-204: The validateValue() method must reject malformed
FieldType::Date inputs before applying date bounds. Add a strict type check and
date parse validation for fields whose type is FieldType::Date, returning an
appropriate validation error when the value is not a valid date; preserve the
existing bounds and custom validator behavior for other field types.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e73611b6-6658-414e-8cc7-42beb57aeb45

📥 Commits

Reviewing files that changed from the base of the PR and between 1b768e2 and 8209113.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-date-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-date-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii.svg is excluded by !**/*.svg
  • docs/assets/widgets-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/show.php
  • playground/3-widgets/widget-date.php
  • playground/3-widgets/widgets.php
  • src/Builder/FieldBuilder.php
  • src/Builder/PanelBuilder.php
  • src/Config/DateBounds.php
  • src/Config/Field.php
  • src/Config/FieldType.php
  • src/Config/Weekday.php
  • src/Engine/Engine.php
  • src/Schema/AgentHelp.php
  • src/Schema/SchemaGenerator.php
  • src/Schema/SchemaValidator.php
  • src/Widget/DateWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Config/DateBoundsTest.php
  • tests/phpunit/Unit/Config/WeekdayTest.php
  • tests/phpunit/Unit/Engine/EngineDeclaredBehaviourTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/AgentHelpTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/DateWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php

Comment thread src/Schema/SchemaGenerator.php Outdated
@github-actions

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/9-date-picker branch from 778d723 to e47aff7 Compare July 10, 2026 22:59
@github-actions

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (2)
src/Schema/SchemaValidator.php (1)

116-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same bounds-precedence duplication as Engine::validateValue().

checkBounds() and checkDateBounds() are structurally identical aside from which bound property they read, and together they replicate the same "prefer number bounds, fall back to date bounds" precedence duplicated in src/Engine/Engine.php (Line 196). Consolidating via a shared Field::boundsViolation() helper (mirroring the existing optionError() precedent) would collapse both methods into one and keep the two validators from silently diverging.

♻️ Proposed consolidation
-    $bounds_error = $this->checkBounds($field, $value);
-    if ($bounds_error !== NULL) {
-      return $bounds_error;
-    }
-
-    $date_error = $this->checkDateBounds($field, $value);
-    if ($date_error !== NULL) {
-      return $date_error;
-    }
+    $violation = $field->boundsViolation($value);
+    if ($violation !== NULL) {
+      return sprintf('Question "%s" must be %s.', $field->id, $violation);
+    }
🤖 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 `@src/Schema/SchemaValidator.php` around lines 116 - 138, Consolidate bound
selection into a shared Field::boundsViolation() helper, applying the existing
precedence of numeric bounds before date bounds. Update
SchemaValidator::checkBounds() and checkDateBounds() to use this helper, remove
the duplicate date-specific method, and update Engine::validateValue() to use
the same centralized logic so all validators remain consistent.
src/Engine/Engine.php (1)

195-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating the bounds-precedence logic into Field.

This line duplicates the exact same "prefer number bounds, fall back to date bounds" precedence that SchemaValidator::checkBounds()/checkDateBounds() implement separately (see src/Schema/SchemaValidator.php Lines 116-138). Field already exposes optionError() as a shared helper consumed by both Engine and SchemaValidator for the analogous options-validation concern — the same pattern here would prevent the two call sites from silently diverging if the precedence or bounds types ever change.

♻️ Proposed consolidation (requires adding a method to Field.php, not shown in this batch)
// In src/Config/Field.php
public function boundsViolation(mixed $value): ?string {
  return $this->bounds?->violation($value) ?? $this->dateBounds?->violation($value);
}
 protected function validateValue(Field $field, mixed $value): ?string {
-    $violation = $field->bounds?->violation($value) ?? $field->dateBounds?->violation($value);
+    $violation = $field->boundsViolation($value);
     if ($violation !== NULL) {
       return sprintf('must be %s.', $violation);
     }
🤖 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 `@src/Engine/Engine.php` around lines 195 - 209, Consolidate bounds precedence
in Field by adding a boundsViolation(mixed $value): ?string helper that prefers
bounds over dateBounds. Update Engine::validateValue and
SchemaValidator::checkBounds/checkDateBounds to call this shared method instead
of duplicating null-coalescing logic, preserving the existing violation handling
and messages.
🤖 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 `@README.md`:
- Around line 150-152: Update the README configuration example to import
DrevOps\Tui\Config\Weekday alongside the other imports, so the Weekday::Sunday
reference resolves when copied.

In `@src/Builder/FieldBuilder.php`:
- Around line 692-693: The parseBoundDate method currently accepts an
unrestricted string for the min/max selector; introduce a pure or backed enum
representing these two values, update parseBoundDate and related logic to accept
the enum, and pass the enum cases from the min/max calls around the affected
validation block. Use the enum’s value only when constructing exception
messages, including all branches covering the selector.

In `@src/Config/DateBounds.php`:
- Around line 30-35: Validate the bound ordering in the DateBounds constructor:
when both min and max are provided, reject cases where min is later than max,
using the class’s existing exception or validation convention. Keep null bounds
valid and ensure every direct DateBounds instantiation preserves the invariant
consumed by contains(), describe(), and clamp().

In `@src/Widget/WidgetFactory.php`:
- Line 60: The DateWidget currently hard-codes navigation keys instead of using
WidgetFactory’s injected keymap, so custom date bindings are ignored. Update
DateWidget::handle() and move() to resolve navigation through the configured
Action bindings, and ensure WidgetFactory passes that keymap when creating the
DateWidget; add a test covering remapped date navigation.

In `@tests/phpunit/Unit/Widget/DateWidgetTest.php`:
- Around line 201-212: Add symmetric max-bound coverage to
testDimsOutOfRangeDays by creating DateBounds with a max date and asserting a
day after that maximum is rendered with the theme description/dimmed styling,
while preserving the existing cursor-day assertion.

---

Outside diff comments:
In `@src/Engine/Engine.php`:
- Around line 195-209: Consolidate bounds precedence in Field by adding a
boundsViolation(mixed $value): ?string helper that prefers bounds over
dateBounds. Update Engine::validateValue and
SchemaValidator::checkBounds/checkDateBounds to call this shared method instead
of duplicating null-coalescing logic, preserving the existing violation handling
and messages.

In `@src/Schema/SchemaValidator.php`:
- Around line 116-138: Consolidate bound selection into a shared
Field::boundsViolation() helper, applying the existing precedence of numeric
bounds before date bounds. Update SchemaValidator::checkBounds() and
checkDateBounds() to use this helper, remove the duplicate date-specific method,
and update Engine::validateValue() to use the same centralized logic so all
validators remain consistent.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f35571a-5b1e-49b2-85df-a56813e95d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 8209113 and e47aff7.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-date-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-date-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii.svg is excluded by !**/*.svg
  • docs/assets/widgets-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/show.php
  • playground/3-widgets/widget-date.php
  • playground/3-widgets/widgets.php
  • src/Builder/FieldBuilder.php
  • src/Builder/PanelBuilder.php
  • src/Config/DateBounds.php
  • src/Config/Field.php
  • src/Config/FieldType.php
  • src/Config/Weekday.php
  • src/Engine/Engine.php
  • src/Schema/AgentHelp.php
  • src/Schema/SchemaGenerator.php
  • src/Schema/SchemaValidator.php
  • src/Widget/DateWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Config/DateBoundsTest.php
  • tests/phpunit/Unit/Config/WeekdayTest.php
  • tests/phpunit/Unit/Engine/EngineDeclaredBehaviourTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/AgentHelpTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/DateWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php

Comment thread README.md
Comment thread src/Builder/FieldBuilder.php Outdated
Comment thread src/Config/DateBounds.php
Comment thread src/Widget/WidgetFactory.php
Comment thread tests/phpunit/Unit/Widget/DateWidgetTest.php
@github-actions

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/9-date-picker branch from 075ee98 to 0a1ad0b Compare July 13, 2026 00:01
@github-actions

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/Schema/SchemaValidator.php (1)

116-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge checkBounds()/checkDateBounds() into a single range check.

The two methods are identical except for the property accessed (bounds vs dateBounds). Engine::validateValue() already coalesces the two bound sources with ?? — mirror that here to avoid duplicated logic and keep the two validators aligned as new bound types are added.

♻️ Proposed refactor
-    $bounds_error = $this->checkBounds($field, $value);
-    if ($bounds_error !== NULL) {
-      return $bounds_error;
-    }
-
-    $date_error = $this->checkDateBounds($field, $value);
-    if ($date_error !== NULL) {
-      return $date_error;
-    }
+    $range_error = $this->checkRangeBounds($field, $value);
+    if ($range_error !== NULL) {
+      return $range_error;
+    }

     return $this->checkOptions($field, $value);
   }

   /**
-   * Check a number value against its declared bounds.
+   * Check a value against its declared number or date bounds.
    *
    * `@param` \DrevOps\Tui\Config\Field $field
    *   The field.
    * `@param` mixed $value
    *   The value.
    *
    * `@return` string|null
    *   An error, or NULL when in range (or when the field declares no bounds).
    */
-  protected function checkBounds(Field $field, mixed $value): ?string {
-    $violation = $field->bounds?->violation($value);
-
-    return $violation === NULL ? NULL : sprintf('Question "%s" must be %s.', $field->id, $violation);
-  }
-
-  /**
-   * Check a date value against its declared range.
-   *
-   * `@param` \DrevOps\Tui\Config\Field $field
-   *   The field.
-   * `@param` mixed $value
-   *   The value.
-   *
-   * `@return` string|null
-   *   An error, or NULL when in range (or when the field declares no range).
-   */
-  protected function checkDateBounds(Field $field, mixed $value): ?string {
-    $violation = $field->dateBounds?->violation($value);
+  protected function checkRangeBounds(Field $field, mixed $value): ?string {
+    $violation = $field->bounds?->violation($value) ?? $field->dateBounds?->violation($value);

     return $violation === NULL ? NULL : sprintf('Question "%s" must be %s.', $field->id, $violation);
   }

Also applies to: 122-138

🤖 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 `@src/Schema/SchemaValidator.php` around lines 116 - 120, Merge checkBounds()
and checkDateBounds() into one shared range-check method, selecting the bounds
source with the same null-coalescing behavior as Engine::validateValue() (bounds
?? dateBounds). Update callers to use the unified method and remove the
duplicated date-specific implementation, preserving the existing violation
message and return behavior.
🤖 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 `@tests/phpunit/Unit/Schema/AgentHelpTest.php`:
- Around line 57-71: The testDateRangeAnnotation test should also cover
one-sided date bounds handled by dateNote(): add date fields configured with
minDate only and maxDate only, then assert the generated help contains the
corresponding “on or after” and “on or before” annotations. Preserve the
existing fully bounded and unbounded date assertions.

---

Outside diff comments:
In `@src/Schema/SchemaValidator.php`:
- Around line 116-120: Merge checkBounds() and checkDateBounds() into one shared
range-check method, selecting the bounds source with the same null-coalescing
behavior as Engine::validateValue() (bounds ?? dateBounds). Update callers to
use the unified method and remove the duplicated date-specific implementation,
preserving the existing violation message and return behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1e61890-07b6-4154-b340-58b995fef811

📥 Commits

Reviewing files that changed from the base of the PR and between e47aff7 and 0a1ad0b.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-date-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-date-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-date.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii.svg is excluded by !**/*.svg
  • docs/assets/widgets-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets.svg is excluded by !**/*.svg
📒 Files selected for processing (28)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/show.php
  • playground/3-widgets/widget-date.php
  • playground/3-widgets/widgets.php
  • src/Builder/FieldBuilder.php
  • src/Builder/PanelBuilder.php
  • src/Config/DateBounds.php
  • src/Config/Field.php
  • src/Config/FieldType.php
  • src/Config/Weekday.php
  • src/Engine/Engine.php
  • src/Input/VimKeyMap.php
  • src/Schema/AgentHelp.php
  • src/Schema/SchemaGenerator.php
  • src/Schema/SchemaValidator.php
  • src/Widget/DateWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Config/DateBoundsTest.php
  • tests/phpunit/Unit/Config/WeekdayTest.php
  • tests/phpunit/Unit/Engine/EngineDeclaredBehaviourTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/AgentHelpTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/DateWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php

Comment thread tests/phpunit/Unit/Schema/AgentHelpTest.php
@github-actions

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/9-date-picker branch from aa8a4c4 to 13fb51c Compare July 13, 2026 00:15
@github-actions

Copy link
Copy Markdown
Code Coverage Report:
  2026-07-13 00:17:08

 Summary:
  Classes: 93.42% (71/76)
  Methods: 98.38% (546/555)
  Lines:   99.59% (2401/2411)

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% (36/36)   Lines: 100.00% (124/124)
DrevOps\Tui\Builder\Form
  Methods:  93.33% (14/15)   Lines:  98.44% ( 63/ 64)
DrevOps\Tui\Builder\PanelBuilder
  Methods: 100.00% (20/20)   Lines: 100.00% ( 32/ 32)
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\DateBounds
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 25/ 25)
DrevOps\Tui\Config\Field
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Config\FieldType
  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% ( 3/ 3)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Config\Panel
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Config\Weekday
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
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% ( 95/ 95)
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\Binding
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Input\DefaultKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 34/ 34)
DrevOps\Tui\Input\Key
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Input\KeyMap
  Methods: 100.00% (12/12)   Lines: 100.00% ( 65/ 65)
DrevOps\Tui\Input\KeyMapManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Input\KeyParser
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 68/ 68)
DrevOps\Tui\Input\Scope
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 11/ 11)
DrevOps\Tui\Input\ScopedKeyMap
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  4/  4)
DrevOps\Tui\Input\VimKeyMap
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 12/ 12)
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% ( 95/ 95)
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% ( 4/ 4)   Lines: 100.00% ( 29/ 29)
DrevOps\Tui\Schema\SchemaGenerator
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 33/ 33)
DrevOps\Tui\Schema\SchemaValidator
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 44/ 44)
DrevOps\Tui\Theme\DefaultTheme
  Methods:  92.65% (63/68)   Lines:  97.29% (215/221)
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% (18/18)   Lines: 100.00% ( 48/ 48)
DrevOps\Tui\Widget\ChoiceListTrait
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 21/ 21)
DrevOps\Tui\Widget\ConfirmWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 22/ 22)
DrevOps\Tui\Widget\DateWidget
  Methods: 100.00% (13/13)   Lines: 100.00% ( 59/ 59)
DrevOps\Tui\Widget\FilePickerWidget
  Methods: 100.00% (30/30)   Lines: 100.00% (187/187)
DrevOps\Tui\Widget\MatchResult
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Widget\MatchTier
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Widget\Matcher
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 79/ 79)
DrevOps\Tui\Widget\MultiSearchWidget
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Widget\MultiSelectWidget
  Methods: 100.00% (14/14)   Lines: 100.00% ( 98/ 98)
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% ( 8/ 8)   Lines: 100.00% ( 44/ 44)
DrevOps\Tui\Widget\PauseWidget
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 10/ 10)
DrevOps\Tui\Widget\SearchWidget
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 57/ 57)
DrevOps\Tui\Widget\SelectWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 36/ 36)
DrevOps\Tui\Widget\SuggestWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 47/ 47)
DrevOps\Tui\Widget\TextWidget
  Methods: 100.00% (11/11)   Lines: 100.00% ( 53/ 53)
DrevOps\Tui\Widget\TextareaWidget
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 52/ 52)
DrevOps\Tui\Widget\ToggleWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 31/ 31)
DrevOps\Tui\Widget\WidgetFactory
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 37/ 37)
DrevOps\Tui\Widget\WidgetRunner
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)

@AlexSkrypnyk AlexSkrypnyk merged commit 8e6d570 into main Jul 13, 2026
9 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/9-date-picker branch July 13, 2026 00:19
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 a date picker (calendar) widget

1 participant