Skip to content

Release v0.1.8 — Phase 8: Dynamic Settings Form#120

Merged
menvil merged 35 commits into
mainfrom
release/v0.1.8-phase08-dynamic-settings-form
May 31, 2026
Merged

Release v0.1.8 — Phase 8: Dynamic Settings Form#120
menvil merged 35 commits into
mainfrom
release/v0.1.8-phase08-dynamic-settings-form

Conversation

@menvil

@menvil menvil commented May 31, 2026

Copy link
Copy Markdown
Owner

Release v0.1.8 — Phase 8: Dynamic Settings Form

Adds the third step of the main flow — a fully schema-driven settings form — between target selection and a convert placeholder.

File uploaded → Choose target format → Configure settings → (Convert placeholder, Phase 9)

Scope (CONV-096 → CONV-111)

  • CONV-096 Settings step guard (requires file + selected target)
  • CONV-097 Dynamic options form partial (schema-driven, unsupported-type fallback)
  • CONV-098–102 Field renderers: segmented, select, toggle, color, number, range
  • CONV-103 Load converter optionsSchema() on target selection
  • CONV-104 Initialize default options from schema
  • CONV-105 Render PNG → JPG settings
  • CONV-106 Render PNG → PDF settings (distinct, schema-driven)
  • CONV-107 Field-level validation errors via OptionsValidator (InvalidConverterOptionsException now carries the option key)
  • CONV-108 Validate before advancing to the convert placeholder step
  • CONV-109 Preserve settings state across settings → format → settings; reset on target change
  • CONV-110 Estimated cost placeholder (no billing logic)
  • CONV-111 Phase 8 smoke tests

Architecture

The UI never hardcodes PNG→JPG / PNG→PDF forms. DashboardConverter loads the selected converter's schema; dynamic-options-form renders fields purely from that schema and delegates to per-type renderers. The real converter schema is a list of fields (each with a key), so the form iterates the list.

Out of scope (unchanged)

No ConversionJob, queue, real conversion, download, credits/billing, cost estimator, API, or preset persistence.

Verification

  • composer test — 233 passed (630 assertions)
  • composer lint — pass
  • npm run build — pass
  • php artisan migrate:fresh --seed — pass

🤖 Generated with Claude Code


Summary by cubic

Adds a schema-driven Settings step that renders converter options dynamically and validates them before continuing to the Convert placeholder. Enables distinct PNG→JPG and PNG→PDF configurations without hardcoded UI. (CONV-096 → CONV-111)

  • New Features

    • Guarded Settings step; requires uploaded file and selected target.
    • Dynamic options form from optionsSchema() with a visible fallback; fields: segmented, select, toggle, color, number, range.
    • Load schema on target select; initialize defaults; remember per-target options; reset on target change.
    • Field-level validation via OptionsValidator; InvalidConverterOptionsException includes the option key; block continue and show inline errors.
    • Schema-driven settings for PNG→JPG and PNG→PDF with distinct fields.
    • Estimated cost placeholder on Settings; Convert step placeholder with Back to Settings.
  • Bug Fixes

    • Fix toggle focus ring when the checkbox is visually hidden by adding .peer:focus-visible ~ .ca-focus-ring-peer.
    • Harden PNG→PDF smoke test with assertHasNoErrors() to surface Livewire issues.

Written for commit b519b13. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a settings step to the file converter dashboard, allowing users to configure conversion options before processing.
    • Introduced multiple field types for flexible option configuration, including color pickers, number inputs, range sliders, segmented buttons, dropdowns, and toggles.
    • Settings are validated before conversion and intelligently preserved or reset when switching between target formats.

menvil and others added 30 commits May 31, 2026 19:29
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…p-guard

CONV-096: Add settings step guard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ptions-form-partial

CONV-097: Create dynamic options form partial
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eld-renderer

CONV-098: Add segmented field renderer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-renderer

CONV-099: Add select field renderer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-renderer

CONV-100: Add toggle field renderer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…renderer

CONV-101: Add color field renderer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ange-field-renderer

CONV-102: Add number and range field renderer
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ptions-schema-on-target-selection

CONV-103: Load converter options schema on target selection
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lt-converter-options

CONV-104: Initialize default converter options
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g-settings

CONV-105: Render PNG to JPG settings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…f-settings

CONV-106: Render PNG to PDF settings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ld-validation-errors

CONV-107: Add settings field validation errors
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s-before-convert-step

CONV-108: Validate settings before convert step
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s-state-during-navigation

CONV-109: Preserve settings state during navigation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
menvil and others added 3 commits May 31, 2026 20:30
…st-placeholder

CONV-110: Add estimated cost placeholder
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ings-smoke-tests

CONV-111: Add dynamic settings smoke tests
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a complete settings phase to the DashboardConverter Livewire component. Users select a target format, load converter-specific options from a schema, configure them via dynamically rendered field UI, and validate before conversion. Exception handling maps validation errors to field-level Livewire errors, and options are cached per target format for navigation persistence.

Changes

Settings Phase Workflow

Layer / File(s) Summary
Exception contract for field-scoped error reporting
app/Support/Converters/Exceptions/InvalidConverterOptionsException.php
InvalidConverterOptionsException now accepts and stores an optional optionKey parameter, enabling static factory methods to identify which option failed; a new fieldErrors() method exposes errors as a field-scoped associative array for Livewire error bag integration.
Component state and step validation
app/Livewire/Dashboard/DashboardConverter.php (imports, properties, step gating)
New component properties track selected converter key, options schema, current option values, and a per-target cache (optionsByTarget); step validation is expanded with guards for the settings step to require both an uploaded file and selected target format; new goToSettingsStep() method centralizes entry logic.
Target format selection and settings validation
app/Livewire/Dashboard/DashboardConverter.php (selectTargetFormat, continueFromSettings, validateSettings)
selectTargetFormat() loads the converter and schema, stores defaults or cached options, and advances to settings; validateSettings() invokes OptionsValidator and maps InvalidConverterOptionsException field errors into Livewire's error bag; continueFromSettings() transitions to convert on success; helper methods initialize defaults and cache current options per target.
Settings UI field templates and dynamic form
resources/views/livewire/dashboard/dashboard-converter/fields/*, resources/views/livewire/dashboard/dashboard-converter/partials/dynamic-options-form.blade.php
Five new field-type templates (color with sync'd hex input, number with min/max/step, range with live update, segmented with button group, select with options, toggle with switch visuals) render schema-driven controls with Livewire binding; dynamic-options-form partial iterates the schema, includes per-type field views, shows validation errors, and falls back to unsupported-type message.
Dashboard converter view settings integration
resources/views/livewire/dashboard/dashboard-converter.blade.php
Stepper step-label logic updated to use match ($step) for format/settings/convert; settings step panel now includes dynamic-options-form, "Estimated cost" placeholder, and "Continue" button wired to continueFromSettings; convert step updated with "Ready to convert …" message and phase text.
Settings field rendering and binding tests
tests/Feature/Livewire/DashboardConverterSettingsFieldsTest.php
New test helper settingsComponent() mounts the component in settings step with configurable schema and options; test suite verifies segmented, select, toggle, color, number, and range fields render labels/help/values correctly and that Livewire bindings update the options object; also tests unsupported-field fallback message.
Settings step navigation and option persistence
tests/Feature/Livewire/DashboardConverterSettingsStepTest.php, tests/Feature/Livewire/DashboardConverterSettingsNavigationTest.php
Tests verify settings step is blocked without file or target format, options render correctly per source/target pair, defaults initialize from schema, options reset when format changes, unsupported formats return to format step, and compatible settings persist when re-selecting the same format; file reference remains intact across navigation.
Settings validation and error handling
tests/Feature/Livewire/DashboardConverterSettingsValidationTest.php
Tests verify invalid options trigger per-field validation errors, default options pass, unknown keys are rejected, continueFromSettings transitions to convert on success or stays at settings with errors on failure, and neither validation nor continuation create spurious FileRecord rows.
End-to-end converter flow with settings
tests/Feature/Livewire/DashboardConverterSettingsSmokeTest.php
Smoke tests drive complete PNG→JPG and PNG→PDF flows through upload, format selection, settings, and conversion, asserting step transitions, format-specific field presence (Quality for JPG, Page size for PDF), option value updates, and correct FileRecord creation without Livewire errors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • menvil/FileConverter#103: Extends the existing target-format step in DashboardConverter by enhancing selectTargetFormat() and step-navigation to load converter/options schema and transition into the new settings phase.
  • menvil/FileConverter#59: The PR's settings validation logic builds directly on converter domain core by updating InvalidConverterOptionsException to carry an optionKey and expose field-scoped errors.
  • menvil/FileConverter#90: Both PRs modify the step-driven DashboardConverter flow; the referenced PR adds the initial upload→format component/UI, while this PR expands that same component with new settings-step state, navigation guards, and validation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% 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
Title check ✅ Passed The title accurately reflects the main addition: a dynamic settings form as the third step of the converter flow, placed between target format selection and conversion.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.1.8-phase08-dynamic-settings-form

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the release Triggers AI code review (CodeRabbit, Cubic) label May 31, 2026

@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: 2

♻️ Duplicate comments (2)
tests/Feature/Livewire/DashboardConverterSettingsNavigationTest.php (2)

39-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same method name issue as previous tests.

This test also calls goToFormatStep() on line 47. Verify this matches the actual method name in the component.

🤖 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/Feature/Livewire/DashboardConverterSettingsNavigationTest.php` around
lines 39 - 50, The test calls goToFormatStep() on the DashboardConverter
Livewire component but the component uses a different method name; update the
test to call the actual method on the component (e.g., rename the test call from
goToFormatStep() to the real method name defined in the DashboardConverter
component such as goToFormat, navigateToFormatStep, or whatever the component
exposes), ensuring the Livewire::test chain uses the correct method name and
that assertions still verify 'step' is 'format' and 'currentFileId' remains the
file id.

22-37: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same method name issue as previous test.

This test also calls goToFormatStep() on line 31. Verify this matches the actual method name in the component.

🤖 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/Feature/Livewire/DashboardConverterSettingsNavigationTest.php` around
lines 22 - 37, The test calls the Livewire method goToFormatStep() on the
DashboardConverter component but the component uses a different method name;
open the DashboardConverter Livewire component, confirm the actual method that
advances to the format step (e.g., goToFormatStep vs goToFormat or
goToConversionStep), and update the test in
DashboardConverterSettingsNavigationTest.php to call the correct method name
(replace call('goToFormatStep') with the component's actual method) so the test
matches the component API.
🧹 Nitpick comments (1)
tests/Feature/Livewire/DashboardConverterSettingsFieldsTest.php (1)

132-162: ⚡ Quick win

Consider splitting into separate tests for number and range fields.

This test combines two distinct field types (number and range) in a single test case. Splitting them would improve test isolation, making it easier to identify which specific field type fails and improving debugging clarity.

♻️ Proposed refactor to split into two tests
-it('renders number and range option fields with bounds', function () {
+it('renders a number option field with bounds', function () {
     settingsComponent(
         schema: [
             [
                 'key' => 'width',
                 'type' => 'number',
                 'label' => 'Width',
                 'default' => 1200,
                 'min' => 1,
                 'max' => 10000,
             ],
-            [
-                'key' => 'compression',
-                'type' => 'range',
-                'label' => 'Compression',
-                'default' => 80,
-                'min' => 0,
-                'max' => 100,
-                'step' => 1,
-            ],
         ],
-        options: ['width' => 1200, 'compression' => 80],
+        options: ['width' => 1200],
     )
         ->assertSee('Width')
-        ->assertSee('Compression')
         ->assertSeeHtml('type="number"')
+        ->assertSeeHtml('wire:model.live.debounce.300ms="options.width"');
+});
+
+it('renders a range option field with bounds and step', function () {
+    settingsComponent(
+        schema: [
+            [
+                'key' => 'compression',
+                'type' => 'range',
+                'label' => 'Compression',
+                'default' => 80,
+                'min' => 0,
+                'max' => 100,
+                'step' => 1,
+            ],
+        ],
+        options: ['compression' => 80],
+    )
+        ->assertSee('Compression')
         ->assertSeeHtml('type="range"')
-        ->assertSeeHtml('wire:model.live.debounce.300ms="options.width"')
         ->assertSeeHtml('wire:model.live="options.compression"')
         ->assertSeeHtml('data-testid="range-value-compression"');
 });
🤖 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/Feature/Livewire/DashboardConverterSettingsFieldsTest.php` around lines
132 - 162, The test it('renders number and range option fields with bounds',
which uses settingsComponent with schema keys 'width' (type 'number') and
'compression' (type 'range'), should be split into two focused tests: one that
only asserts the number field behavior for 'width' (presence of label,
type="number", wire:model.live.debounce...="options.width", min/max/default) and
a second that only asserts the range field behavior for 'compression' (label,
type="range", wire:model.live="options.compression", step/min/max/default and
presence of data-testid="range-value-compression"); create two test cases named
clearly (e.g., it('renders number option field with bounds') and it('renders
range option field with bounds')), each calling settingsComponent with a
single-field schema and asserting only the relevant expectations to improve
isolation and debuggability.
🤖 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
`@resources/views/livewire/dashboard/dashboard-converter/fields/toggle.blade.php`:
- Around line 16-18: The focus ring is applied to the non-focusable track span
(class "ca-focus-ring") instead of the checkbox input ("peer sr-only"); either
move the "ca-focus-ring" class from the track span to the input element (the
checkbox with classes "peer sr-only") so the actual focus-visible styles fire,
or keep the class on the span but change it to use the peer focus variant (e.g.,
replace "ca-focus-ring" on the span with a peer-based variant like
"peer-focus-visible:ca-focus-ring") so the visual track receives the focus ring
when the hidden input gains keyboard focus.

In `@tests/Feature/Livewire/DashboardConverterSettingsSmokeTest.php`:
- Around line 33-49: The test is missing a Livewire no-errors guard so component
errors can be masked; update the Livewire test chain for DashboardConverter (the
sequence that calls
selectTargetFormat('jpg')->call('goToFormatStep')->call('selectTargetFormat','pdf'))
to include ->assertHasNoErrors() after the interactions that may trigger
validation/state errors (e.g., after the first selectTargetFormat/goToFormatStep
sequence or immediately before asserting visibility) so Livewire failures
surface instead of leaving stale UI that makes assertSee/assertDontSee pass.

---

Duplicate comments:
In `@tests/Feature/Livewire/DashboardConverterSettingsNavigationTest.php`:
- Around line 39-50: The test calls goToFormatStep() on the DashboardConverter
Livewire component but the component uses a different method name; update the
test to call the actual method on the component (e.g., rename the test call from
goToFormatStep() to the real method name defined in the DashboardConverter
component such as goToFormat, navigateToFormatStep, or whatever the component
exposes), ensuring the Livewire::test chain uses the correct method name and
that assertions still verify 'step' is 'format' and 'currentFileId' remains the
file id.
- Around line 22-37: The test calls the Livewire method goToFormatStep() on the
DashboardConverter component but the component uses a different method name;
open the DashboardConverter Livewire component, confirm the actual method that
advances to the format step (e.g., goToFormatStep vs goToFormat or
goToConversionStep), and update the test in
DashboardConverterSettingsNavigationTest.php to call the correct method name
(replace call('goToFormatStep') with the component's actual method) so the test
matches the component API.

---

Nitpick comments:
In `@tests/Feature/Livewire/DashboardConverterSettingsFieldsTest.php`:
- Around line 132-162: The test it('renders number and range option fields with
bounds', which uses settingsComponent with schema keys 'width' (type 'number')
and 'compression' (type 'range'), should be split into two focused tests: one
that only asserts the number field behavior for 'width' (presence of label,
type="number", wire:model.live.debounce...="options.width", min/max/default) and
a second that only asserts the range field behavior for 'compression' (label,
type="range", wire:model.live="options.compression", step/min/max/default and
presence of data-testid="range-value-compression"); create two test cases named
clearly (e.g., it('renders number option field with bounds') and it('renders
range option field with bounds')), each calling settingsComponent with a
single-field schema and asserting only the relevant expectations to improve
isolation and debuggability.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: debd9a4a-96f0-4581-ac4b-f3bf4216b604

📥 Commits

Reviewing files that changed from the base of the PR and between 1df82de and 298fa3c.

📒 Files selected for processing (15)
  • app/Livewire/Dashboard/DashboardConverter.php
  • app/Support/Converters/Exceptions/InvalidConverterOptionsException.php
  • resources/views/livewire/dashboard/dashboard-converter.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/color.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/number.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/range.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/segmented.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/select.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/fields/toggle.blade.php
  • resources/views/livewire/dashboard/dashboard-converter/partials/dynamic-options-form.blade.php
  • tests/Feature/Livewire/DashboardConverterSettingsFieldsTest.php
  • tests/Feature/Livewire/DashboardConverterSettingsNavigationTest.php
  • tests/Feature/Livewire/DashboardConverterSettingsSmokeTest.php
  • tests/Feature/Livewire/DashboardConverterSettingsStepTest.php
  • tests/Feature/Livewire/DashboardConverterSettingsValidationTest.php

Comment thread tests/Feature/Livewire/DashboardConverterSettingsSmokeTest.php
menvil and others added 2 commits May 31, 2026 20:51
Address Phase 8 review:
- Toggle focus ring was on the non-focusable track span; ca-focus-ring keys on
  the element's own :focus-visible, so it never fired for the hidden peer input.
  Add a .peer:focus-visible ~ .ca-focus-ring-peer rule and apply it to the track.
- Add assertHasNoErrors() to the PNG->PDF smoke test so Livewire failures surface
  instead of leaving stale UI that lets assertSee/assertDontSee pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix toggle focus ring and harden settings smoke test
@menvil menvil merged commit bc17979 into main May 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Triggers AI code review (CodeRabbit, Cubic)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant