[#106] Added choice field options that resolve from the collected answers. - #137
Conversation
This comment has been minimized.
This comment has been minimized.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (4)
📒 Files selected for processing (22)
📝 WalkthroughWalkthroughAdds answer-driven dynamic options for choice fields. Resolvers receive collected answers and context, run during stabilization and validation, reconcile changed values, and expose resolved options through interactive forms, schemas, agent help, documentation, and examples. ChangesAnswer-driven dynamic options
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant PanelController
participant Engine
participant OptionsResolver
participant Field
PanelController->>Engine: settle values with context
Engine->>OptionsResolver: resolve options from answers
OptionsResolver->>Field: update effective option set
Engine->>Field: reconcile changed value
Engine-->>PanelController: settled values and active fields
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #137 +/- ##
==========================================
- Coverage 99.12% 99.09% -0.04%
==========================================
Files 118 119 +1
Lines 4580 4645 +65
==========================================
+ Hits 4540 4603 +63
- Misses 40 42 +2 ☔ View full report in Codecov by Harness. |
|
🚀 Deployed on https://6a688ca03328bb4276ce7ee3--tui-docs.netlify.app |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Render/PanelController.php (1)
862-880: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftA throwing
optionsResolvercrashes the whole interactive session instead of degrading gracefully.
resettle()now runsresolveDynamicOptions()viaEngine::settle(), which throwsEngineExceptionif a field's answer-drivenoptionsResolverclosure throws.resettle()is called from the constructor, every accepted edit, panel entry, and modal close - none of these paths catch this exception, so it propagates out ofrun()(andTui::interact()), ending the session and discarding the answers collected so far.Query-driven options (
optionsSource, used by search/suggest) already handle this case gracefully:resolveQuery()catches\Throwableand shows an inline "Could not load options." message viafailQuery()instead of crashing. Answer-driven resolvers deserve the same treatment, especially sinceresettle()fires on every edit.🤖 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/Render/PanelController.php` around lines 862 - 880, Update resettle() to catch Throwable from engine->settle(), preserving the current answers and interactive session instead of allowing an optionsResolver failure to escape. Handle the failure through the existing inline options-error mechanism used by resolveQuery()/failQuery(), while retaining normal active/value updates and cursor clamping when settling succeeds.
🤖 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 `@docs/content/field-behaviour.mdx`:
- Line 105: Update the callback timing description in the field-behaviour
documentation to distinguish interactive and headless usage: zero-argument
loaders resolve once when the interactive panel opens, while headless collection
resolves the loader up front. Preserve the existing return-value and context
details.
In `@docs/content/widgets/select.mdx`:
- Around line 33-39: Update the options row in the select widget API table to
avoid marking options() itself as required, since repeated option() calls
populate the same choices set. Mark the choices requirement instead, or change
options() to optional while preserving the existing description of both
supported configuration methods.
In `@README.md`:
- Line 70: Qualify the dynamic-options documentation to state that narrowed
option sets remove stale collected values, but explicitly headlessly supplied
invalid values remain available for collection to reject with an
EngineException. Update README.md line 70, the docblock in
playground/19-dynamic-options.php lines 7-12, and the matching summary in
playground/README.md line 36 consistently.
---
Outside diff comments:
In `@src/Render/PanelController.php`:
- Around line 862-880: Update resettle() to catch Throwable from
engine->settle(), preserving the current answers and interactive session instead
of allowing an optionsResolver failure to escape. Handle the failure through the
existing inline options-error mechanism used by resolveQuery()/failQuery(),
while retaining normal active/value updates and cursor clamping when settling
succeeds.
🪄 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: e828416a-542b-46f7-9da3-cb84d055e2be
⛔ Files ignored due to path filters (4)
docs/architecture/dataflow-collect-dark.svgis excluded by!**/*.svgdocs/architecture/dataflow-collect.svgis excluded by!**/*.svgdocs/architecture/dataflow-tui-dark.svgis excluded by!**/*.svgdocs/architecture/dataflow-tui.svgis excluded by!**/*.svg
📒 Files selected for processing (22)
README.mddocs/architecture/dataflow-collect.pumldocs/architecture/dataflow-tui.pumldocs/content/field-behaviour.mdxdocs/content/progress.mdxdocs/content/widgets/select.mdxplayground/19-dynamic-options.phpplayground/README.mdsrc/Builder/FieldBuilder.phpsrc/Builder/Form.phpsrc/Engine/Engine.phpsrc/Model/Field.phpsrc/Model/FieldType.phpsrc/Render/PanelController.phpsrc/Schema/AgentHelp.phpsrc/Schema/OptionsResolver.phpsrc/Schema/SchemaGenerator.phpsrc/Schema/SchemaValidator.phpsrc/Tui.phptests/phpunit/Unit/DynamicOptionsTest.phptests/phpunit/Unit/Engine/EngineConditionalTest.phptests/phpunit/Unit/Schema/SchemaGeneratorTest.php
Choice fields take an 'optionsFor()' resolver called with the run context and re-run whenever the answers change. The set resolves inside the engine's settling, so conditions, headless collection, the schema and the validator all see the same narrowed list, and a value the set no longer holds is reconciled away - a ranking completed, a toggle returned to its first state - unless it was supplied, in which case it is reported.
…oth lifecycles. A callback handed to 'options()' now says by its own signature when it runs: one asking for the run context follows the collected answers, one asking for nothing loads once on panel entry as before. Options declared on a type with no list are rejected when the form is built, whichever form they take. Adds the playground demo, the documentation and the updated data-flow diagrams.
The engine memo now keys on the whole resolver input, so a second run against another directory or in update mode is not answered from the first one's cached options. 'Tui::validate()' takes the run context its sibling schema calls already take and passes it down, and a schema-side resolver that cannot answer empties the list rather than advertising the set some earlier context produced. An exception code that is not an integer coerces instead of failing the report. Reads an answer defensively in the demo and the documented example, since a supplied value is whatever arrived until it is validated.
…assertions. The static analyser reads a generated schema as nested 'mixed', and 'assertIsArray()' does not narrow it without the PHPUnit extension, so the helpers guard with 'is_array()' and 'is_string()' and fail with a message naming the prompt.
…ames. Renames the two test methods whose single-letter word ran into the next capital, collapses the option guard into one condition, and rewraps a comment inside the eighty-column limit.
Emphasis uses underscores and the option table columns are padded to the widest cell, as the formatter writes them.
…lied-value exception. The loader paragraph now covers headless collection resolving it up front, and the summaries in the README, the playground index and the demo docblock say that a value supplied headlessly is reported rather than dropped.
4f5f5ba to
460dc87
Compare
|
Closes #106
Summary
A callback handed to a choice field's
->options()now routes by its own signature: one that takes aContextparameter follows the collected answers and is re-resolved whenever they change, while one that takes no parameters keeps the existing load-once lazy behavior.Resolution runs inside the engine's settling fixpoint (
Engine::stabilize()), before conditions, derives and fix-ups, so the interactive panel, headless collection, the generated schema and the validator all see the same narrowed list.A value the narrowed set no longer holds is reconciled away - dropped from a select or multi-select, a
reorderranking completed to a full permutation, atogglereturned to its first state - except a value supplied headlessly, which the input guard reports instead of silently discarding.The schema now advertises an
options_dynamicflag per field, andTui::validate()takes an optional run context so membership checks resolve against the same answers being validated.Changes
src/Engine/Engine.php): addedresolveDynamicOptions()to resolve answer-driven options inside the settling fixpoint,suppliedInputs()to track which fields were answered headlessly, andoptionsError()to wrap consumer exceptions into anEngineException;stabilize(),settle(),resolveState()andcollect()now thread aContextand a per-field "supplied" map through, and resolved rows are memoised per field against the answers plus the run context so a settle that changes nothing does not re-call the resolver.src/Builder/FieldBuilder.php,src/Model/Field.php):options()inspects the closure's arity withReflectionFunctionand routes a zero-argument callback to the existing$optionsLoaderand a callback taking a parameter to the new$optionsResolver;FieldgainedhasSettledOptions(),hasDynamicOptions()andreconcileValue(), plus build-time guards rejecting options declared on a type with no list (FieldType::supportsOptions()) or a resolver declared alongside a fixed list, a loader or a query source.src/Builder/Form.php): the toggle and reorder option checks now skip fields whose options have not yet settled (loader, resolver or query source), since those rows arrive later rather than at build time.src/Schema/OptionsResolver.php,SchemaGenerator.php,SchemaValidator.php,AgentHelp.php): a newSchema\OptionsResolverresolves a field's answer-driven options against a caller-supplied context, used bySchemaGenerator(which also flags each fieldoptions_dynamic),AgentHelpand the now context-awareSchemaValidator, which checks option membership against the set the answers under validation resolve to.src/Tui.php,src/Render/PanelController.php):Tui::validate()takes an optionalContext;PanelControlleraccepts the facade's already-constructedEngineand the runContextso the same engine resolves both the initial state and every interactive re-settle.docs/content/field-behaviour.mdx, cross-links fromprogress.mdxandwidgets/select.mdx, a runnableplayground/19-dynamic-options.php, README and playground README rows, and both dataflow diagrams (.pumlsources and regenerated.svg) updated to show the new resolution step.tests/phpunit/Unit/DynamicOptionsTest.phpcovers resolution, per-type reconciliation, headless rejection, interactive narrowing, memoisation, and schema/validator/AgentHelp integration, plus build-time guard rejections;EngineConditionalTestandSchemaGeneratorTestare updated for the newsettle()signature and theoptions_dynamicfield.Before / After
Summary by CodeRabbit
New Features
Documentation
Tests