pentiminax/ux-driver
A lightweight Laravel package that brings a driver-based approach to UX features, letting you define and swap user-experience behaviors through clean, configurable drivers. Integrates with Blade and Laravel's service container to keep interface logic organized, reusable, and easy to test.
Architecture fit. pentiminax/ux-driver is a Symfony UX bundle that wraps driver.js (product tours, hints/beacons, popovers) and exposes it through Twig Components plus a PHP builder API. v0.2.0 is explicitly a consolidation release, and the architecture story it tells is the right one for a 0.x package: four duplicated concerns collapsed into single owners — Html\DriverOptions (the one list of innerHTML sinks and the one escaping pass), StimulusContract (the one PHP↔JS declaration of controller ids, values, targets, actions, data-*), Model\Options + Enum\Normalizer + Model\OverlayOptions (one bucketing/validation/normalization pipeline), and thin Twig components that delegate to the Model. The standout is the contract test that parses assets/src/ and fails when PHP and JS drift — that is a real integration seam being enforced by CI rather than by convention.
Integration feasibility. Low for a Symfony shop already on UX: composer install, bundle registration, asset pipeline (AssetMapper or Webpack Encore) for the Stimulus controllers, driver.js 1.8+ on the front end. Requirements are unchanged from v0.1.0 (PHP 8.2+, Symfony 7/8, StimulusBundle 2/3, TwigComponent 2/3, Twig 3.8+), so no floor bump gates adoption. Note the framing risk: despite being a PHP package, this is Symfony-specific — the Twig Components and StimulusBundle wiring do not port to Laravel. A Laravel adoption would mean reusing the Model\* / Html\* / Enum\* classes as a standalone serialization layer and rewriting the rendering layer, which is a meaningfully larger project than "composer require."
Technical risk. Moderate, and concentrated in the "Behavior changes" section rather than the refactors:
Twig\Components\Step (titleHtml(), descriptionHtml(), sideValue(), alignValue()) and the Hint equivalents are a breaking change for any custom template that called them. The shipped templates do not, so the blast radius is limited to teams that forked/overrode component templates — but those teams will hit a hard Twig\RuntimeError at render time.Model\Hint::$element is now nullable and getElement(): ?string. Anything that assumed a non-null string (typed properties, string concatenation, strict comparisons) needs a guard.animate, smoothScroll, and allowClose unless explicitly set. Runtime behavior is identical (they were driver.js defaults), but snapshot tests, fixture JSON, and any JS reading the config blob will see a diff.assets/src/ parsing test adds CI coupling to the JS file layout; a benign refactor of the asset tree can flip it red for non-behavioral reasons.Key questions. (1) Is the DriverOptions escaping policy allow-list based and documented, so a contributor adding an innerHTML option knows the one place to register it? (2) Does the contract test run against source only, or also built/emitted assets — and does it fail closed if assets/src/ is absent? (3) Which of the newly public classes (Model\Options, StimulusContract, Html\DriverOptions, Enum\Normalizer) are intended as stable API versus incidental exports? (4) What is the deprecation policy between 0.2.x and 1.0 — will the removed component helpers get a documented migration note? (5) Was the payload-shortening change called out in the upgrade notes as a test-fixture concern, not just a behavior note?
Stack fit. Native for Symfony 7.x/8.x applications using Twig Components and StimulusBundle, with driver.js 1.8+ as the client runtime. If the app ships assets via AssetMapper, integration is close to zero-config; Webpack Encore works but needs the Stimulus controllers registered manually. Non-Symfony PHP stacks should treat this as a reference implementation of the serialization layer, not a drop-in.
Migration path. Bounded and mechanical: grep application and custom templates for titleHtml(, descriptionHtml(, sideValue(, alignValue( and redirect them to the model equivalents; regenerate any fixtures or snapshot assertions that capture the tour option payload; audit call sites of Hint::getElement() for nullable handling; update any tests or alerts matching the beacon error string. Teams that never overrode component templates and never asserted on raw payload should be able to bump without code changes.
Compatibility. No PHP, Symfony, Twig, StimulusBundle, or driver.js requirement moved, so the version constraint itself is a clean swap. The compatibility risk is entirely template-level and test-level, which means it is discoverable in a single CI run rather than in production — provided the suite covers rendered output. Pin the exact version (0.2.0) through rollout rather than a caret range, since 0.x allows breaking minors.
Sequencing. (1) Branch and pin 0.2.0. (2) Run the full suite plus any template-rendering/integration tests; triage failures into "removed method," "nullable element," and "payload snapshot" buckets. (3) Patch custom templates to call the model. (4) Refresh fixtures. (5) Do a browser pass on one representative tour and one hints overlay to confirm visual parity — the payload should be shorter with identical behavior. (6) Confirm the package's contract test actually executes in CI and is not excluded by path filters, since it is now load-bearing for PHP↔JS consistency. (7) Ship behind normal release cadence; no feature flag needed.
Maintenance. This release materially lowers the cost of the most common maintenance task: adding or changing a driver.js option. It is now one entry in the option table, one entry in DriverOptions if it touches innerHTML, and one entry in StimulusContract if it touches the JS boundary — instead of hunting four call sites. For a package that tracks driver.js releases, that is the difference between routine upgrades and risky ones.
Support. Error-message wording is now consistent across step, hint, and beacon ("Serializable driver.js … options are: …"), which shortens triage for misconfiguration tickets. The tradeoff is a wider public API surface — Model\Options, Enum\Normalizer, Model\OverlayOptions, Html\DriverOptions, StimulusContract, plus options(array) entry points — which shifts support burden from bug triage toward API stability and deprecation management as the package approaches 1.0.
Scaling. No server-side implications; the work happens client-side. Render cost improves slightly: Driver:Step and Driver:Hint now build one model per render instead of three, and Driver:Hints dropped ~40 lines of per-setter calls. Tours with many hints benefit most, though at typical tour sizes (tens of steps) this is not a performance concern so much as a correctness win.
Failure modes. Loud and localized where it matters: custom templates calling removed helpers throw at render time, not silently. The riskier failures are quiet — a new innerHTML-bound option added outside DriverOptions would bypass escaping, which the single-sink design mitigates but does not eliminate for contributors who don't know the rule; and a benign refactor of assets/src/ can turn the contract test red, creating alert fatigue that tempts teams to skip it. Third-party template overrides remain the least observable risk, since they are outside the package's test coverage.
Ramp-up. A Symfony-fluent developer needs roughly half a day to a day: driver.js option semantics, the Twig Component/builder duality, and the Stimulus controller wiring are the learning curve. The new public models make extension work easier to reason about than the pre-0.2.0 structure, since there is one obvious place per concern. Teams attempting a Laravel or non-Symfony port should budget substantially more — that is a reimplementation of the presentation layer, not an integration.
How can I help you explore Laravel packages today?