Conversation
Aliases the bare @pentiminax/ux-datatables specifier at the compiled controller inside the Composer package, since the package is not published to npm, and ignores the six unused style frameworks so webpack does not try to resolve their static import() calls at build time. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
assets/package.json added five datatables.net packages but bun.lock was never regenerated, so it had zero references to them. bun install resolves to the workspace root, not assets/bun.lock, which was a stale duplicate bun no longer maintains and is left untouched here. Also drops the "test-app" workspace entry from the root package.json, which pointed at a directory that has never existed and blocked bun install from resolving workspaces at all. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Defines the datagrid: block for platform.yaml along with its fluent PHP builder, covering page size, responsive and column control toggles, the table CSS class, the Ajax route access attribute and export formats. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
The bundle is yielded from the Kernel only when the config section does not disable it, so an application that does not want grids loads neither this bundle nor pentiminax/ux-datatables. The extension prepends the platform's page size, table class and Bootstrap 5 edit modal templates onto the datatables extension. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
`Pentiminax\UX\DataTables\DataTablesBundle` derives its container extension alias as `data_tables` (AbstractBundle's default underscore of the class name), not `datatables`, so the prepend() block that forwards page size, table class and the Bootstrap 5 edit modal templates was silently dead in every real application; only the test's own stub extension, which hardcoded the wrong alias, made it look wired up. A new test now pins the stub's alias to the real bundle's so this cannot rot unnoticed again. Also creates `src/Bundle/DataGrid/templates/` (populated in Task 7) since Twig's FilesystemLoader throws immediately if a registered namespace path does not exist, and the grid bundle defaults to enabled. Kernel::isDataGridEnabled() now coerces the `enabled` flag with filter_var() instead of a strict `=== true` comparison: unlike two-factor, which defaults off and safely degrades on a type mismatch, the grid defaults on, so a strict comparison silently disabled it for "true", 1, or an unresolved %env(...)% placeholder. Added Kernel tests covering the section being absent, non-array, and enabled/disabled via bool, string and int scalars. Also fills in the previously-unasserted setParameter() calls in the extension test (responsive, column_control, table_class, export.formats, edit_modal.enabled). Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
phpstan.dist.neon scans src/ and tests/, but both prior verification passes scoped phpstan to src/Bundle/DataGrid only, so nine offset-on-mixed and return-type errors in the DataGrid tests went unnoticed. Processor::process() returns array<string, mixed>, so accessing typed keys on the result degrades to mixed; DataGridConfigurationTest now imports the DataGridConfig phpstan type from DataGridConfiguration and annotates its process() helper with it, the same pattern UiConfigurationTest already uses for UiConfig. The extension test's two remaining errors come from ContainerBuilder::getExtensionConfig() returning array<array<string, mixed>>; each nested value is now asserted with assertIsArray() and bound to a locally-typed variable before its keys are read, rather than reaching through three offsets in one expression. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Maps each Doctrine field type to its column type and humanises the title, so a grid renders without declaring columns. Skips fields with no public reader, collection associations, and to-one associations whose target does not stringify, since none of them can be read or ordered meaningfully. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Client's only to-many fixture, tags, targets Tag, which is not Stringable, so testToManyAssociationIsSkipped passed even with the to-many guard removed entirely -- it was really exercising the Stringable check, not the to-many rule. Adds a countries ManyToMany targeting Country, which IS Stringable, so the assertion can only pass because of the to-many guard. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Grids are server-side and Bootstrap 5 styled, carry the Tabler table classes, the configured page size and length menu, and the responsive, column control and server-side export extensions. Columns fall back to Doctrine metadata when the grid declares none. Also registers AbstractDataGrid for autoconfiguration so the container calls setDataGridDefaults() via setter injection on every grid service, mirroring how upstream injects its own DataTableInfrastructure. This closes the gap Task 3 deliberately left open, since it depended on classes this task introduces. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Derives a grid's name from its class, overridable with a NAME constant, and builds a service locator keyed by that name at compile time so a template can render a grid without knowing its class. Duplicate names fail the build rather than silently shadowing one another. Registers DataGridRegistry with autowiring off, since its only argument (the locator) is supplied by DataGridRegistryPass at compile time and there is nothing for the autowirer to resolve. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
testDuplicateNamesAreRejected registered the same class under two service ids, so the exception's two class-name placeholders were always identical. The assertion passed whether DataGridRegistryPass correctly named both colliding classes or wrongly printed one of them twice. Registers two distinct grids that collide on name instead, and asserts both fully- qualified class names appear in the message. No production change: DataGridRegistryPass was already correct. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
<twig:Platform:DataGrid name="client" /> resolves the grid from the registry and renders it, so a page needs no controller wiring. Repeat renders of one grid get a unique element id: the id is read back out of the markup upstream already produced, rather than off the grid itself, because reading it via AbstractDataGrid::getDataTable() would force upstream's initialize() to run and throw for a grid with no DataGridDefaults injected. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Upstream derives the DOM id from a grid's short class name, not its FQCN, so two grids sharing a short name in different namespaces (a case this plan's own NAME override exists to support) would collide on id="ClassName" while landing under two different counter keys, and neither render got rewritten. Key the counter the same way upstream derives the id, and reshape the first-occurrence test so its decoy is actually reachable by the regex. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Add spaces around union (|) and intersection (&) operators in type declarations, and format setAttributes array across multiple lines per TypesSpacesFixer configuration. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Both are template columns rendered server-side through ux_icon(), so they use the configured icon pack rather than the Lucide set upstream resolves in the browser. The action buttons emit the same data-action-type and data-id attributes the upstream controller delegates on, so edit, delete and detail keep working unchanged. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
… the delete() docblock actions.html.twig called path() with a null id whenever a row lacked the idField key, throwing InvalidParameterException for the whole row. Upstream's ActionRowDataResolver treats a failed URL generation as "omit the action"; this now does the same, skipping the CUSTOM link while leaving the row's other actions intact. The delete() docblock claimed the button is disabled without a session, but that behaviour lives in upstream's Stimulus-rendered actionColumnRenderer.js and this column renders its own markup, bypassing it entirely - delete always renders enabled and relies on server-side CSRF validation instead. Also closes a gap where a mutator's own re-send of the template parameters went unguarded whenever a later mutator in the same chain happened to re-send the same state. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
… guard their own re-send The previous fix round's chained test (an action mutator then identifiedBy()) only proved the interaction between the two; identifiedBy() runs last and its own applyTemplate() call re-sends whatever addAction() already mutated, so a broken edit(), delete() or link() that dropped its own applyTemplate() call stayed invisible. Each of the three now has its own isolated test that calls exactly one mutator and asserts immediately, with nothing after it to mask a skipped re-send - the same shape IconColumnTest already used for icons() and fallbackIcon(). No production code changed. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Requires the configured attribute on all eight routes the DataTables bundle registers, and additionally checks a grid's own attribute on the two read routes. The upstream table token identifies which grid is requested, not who is asking, so without the second check any authenticated user holding a token could read that grid. Implemented as a subscriber rather than a prepended access_control rule, which is first-match-wins and would change the meaning of an application's existing rules. AjaxDataTableRegistry and AjaxDataTableTokenManager are both final upstream, so the test builds a real registry over a real token manager and a real ServiceLocator instead of stubbing them, deriving tokens through the registry's own getToken(). Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
… is resolvable, including ux_datatables_ajax_templates, which carries the same unprivileged read token in the request body and validates no CSRF. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Imports the DataTables Bootstrap 5 theme and the enabled extensions, then removes the duplicated table chrome, gives the length menu and search box Tabler's control styling, and aligns the info and paging rows with the card spacing. Also ignores assets/node_modules/ and assets/public/, the build output directories this task's frontend build creates; only /node_modules/ was previously ignored at the repo root. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Round-1 review found the partial was written against DataTables 1.x/early-2.x class naming rather than the installed 3.0.3: - .dataTables_wrapper never existed in the installed packages; the bs5 integration's own wrapper class is dt-container. The table/row chrome fix this rule was meant to apply has been dead since round 1. - span.dt-column-order can never match: DataTables creates this element as a bare <div>, and the vendor stylesheet never tag-qualifies the class either. Dropped the tag qualifier to match the vendor's own convention. Also replaced @extend .form-select / @extend .form-control on the length menu and search box with copied declarations (same Bootstrap/Tabler variables and mixins, just not @extend), since extending a vendor class appends our selector as an extra arm to every rule that mentions it - validation states, input-group compounds, floating labels, tom-select overrides included. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
The bs5 integration already applies the real Bootstrap classes to both elements (node_modules/datatables.net-bs5/js/dataTables.bootstrap5.js: search.input is "form-control form-control-sm", length.select is "form-select form-select-sm"), and the vendor CSS already sizes them inline. The round-1 hand-copy was redundant from the start and, on top of that, regressed the dark-mode select chevron and Tabler's layered focus box-shadow, both of which the real .form-select/.form-control rules already handle for free. Deleting the block fixes both regressions and removes the ongoing burden of keeping a hand-copy in sync with Bootstrap. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Adds the user guide covering grid declaration, column auto-detection, the icon and action columns, the security layers and the configuration reference, and regenerates the platform.yaml JSON Schema so the datagrid section autocompletes. Corrects several claims in the original plan against the shipped code: the per-grid security check covers all eight Ajax routes (not two), only three of those eight validate CSRF, ActionsColumn::delete() does not reproduce upstream's client-side disabling, and per-row Ajax actions are unsupported by ActionsColumn. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
Nothing ever read it. The key was wired through to a container parameter under the mistaken assumption that AbstractDataGrid adds a default EDIT action; actions are entirely user-declared on ActionsColumn, so the flag could not gate anything as designed. A schema-visible knob that silently ignores the user is worse than no knob, so it is removed rather than kept around for a follow-up task that has no home in this plan. Also fixes docs/datagrid/customization.md, where the PHP fluent builder section named a nonexistent enabled() method — the builder only has disabled() — which would have sent a reader straight into an undefined method call. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
…id Ajax routes, and drop a stale config key The actions.html.twig template read row[idField], but TemplateColumnRenderer binds row to the source entity and payload to the mapped array, so the id always resolved to null: EDIT/DELETE buttons rendered with an empty data-id and CUSTOM links were silently swallowed by the not-null guard. Read payload[idField] instead, with an attribute(row, idField) fallback for grids that drop the id column in configureColumns(). ActionsColumnTest bound an array as row, matching the same misreading, so it passed either way; it now binds an entity as row and the mapped array as payload, and gained tests for the payload path, the entity fallback, and the missing-identifier case. Upstream's datatables.route_loader is tagged routing.route_loader, which only registers it for lookup — nothing imports it unless a routing config names it with type: service. Without the Flex recipe, a consuming app's first grid render throws RouteNotFoundException. Kernel::configureRoutes() now imports it, guarded by the same isDataGridEnabled() check used for bundle registration. Also removed the commented edit_modal key from platform.yaml's datagrid reference block; DataGridConfiguration has no such option, so uncommenting the block as documented would fail to boot. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
There was a problem hiding this comment.
🟡 Changes recommended
src/Bundle/DataGrid/Resources/config/services.php excludes Config/, preventing DataGridConfiguration (a PlatformConfigurationInterface) from being registered/tagged, which breaks schema generation for the datagrid: section.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR introduces a new DataGridBundle under src/Bundle/DataGrid/ to provide a Doctrine-backed, Tabler-styled, server-side DataTables grid integration (wrapping pentiminax/ux-datatables), including security hardening for all upstream Ajax routes and extensive test + documentation coverage.
Changes:
- Add
SolidWorxPlatformDataGridBundlewith autoconfiguration, defaults (AbstractDataGrid), grid registry + renderer, Doctrine column auto-detection, and Tabler-compatible columns. - Add
DataGridAccessSubscriberto enforce a bundle-wide access gate plus an optional per-grid authorization attribute across all upstream Ajax routes. - Add docs + upgrade notes + schema/config examples; wire frontend assets (Encore aliasing, SCSS imports, DataTables packages).
File summaries
| File | Description |
|---|---|
| UPGRADE.md | Upgrade notes for datagrid feature |
| tests/Bundle/PlatformBundle/KernelTest.php | Kernel opt-in/out + routes tests |
| tests/Bundle/DataGrid/Grid/GridNameResolverTest.php | Grid name derivation tests |
| tests/Bundle/DataGrid/Grid/DataGridRendererTest.php | Renderer id-rewrite/counter tests |
| tests/Bundle/DataGrid/Grid/DataGridRegistryTest.php | Registry lookup + error tests |
| tests/Bundle/DataGrid/Grid/AbstractDataGridTest.php | Defaults + Doctrine fallback tests |
| tests/Bundle/DataGrid/Fixtures/Grid/SecuredDataGrid.php | Fixture: role-secured grid |
| tests/Bundle/DataGrid/Fixtures/Grid/NamedDataGrid.php | Fixture: NAME override grid |
| tests/Bundle/DataGrid/Fixtures/Grid/Legacy/ClientDataGrid.php | Fixture: short-name collision |
| tests/Bundle/DataGrid/Fixtures/Grid/ExpressionSecuredDataGrid.php | Fixture: Expression-secured grid |
| tests/Bundle/DataGrid/Fixtures/Grid/DuplicateNamedDataGrid.php | Fixture: duplicate NAME collision |
| tests/Bundle/DataGrid/Fixtures/Grid/ClientDataGrid.php | Fixture: basic grid |
| tests/Bundle/DataGrid/Fixtures/Entity/Tag.php | Fixture entity for mapping rules |
| tests/Bundle/DataGrid/Fixtures/Entity/Country.php | Fixture entity with __toString |
| tests/Bundle/DataGrid/Fixtures/Entity/Client.php | Fixture entity covering edge cases |
| tests/Bundle/DataGrid/EventSubscriber/DataGridAccessSubscriberTest.php | Subscriber security behavior tests |
| tests/Bundle/DataGrid/DependencyInjection/SolidWorxPlatformDataGridExtensionTest.php | DI extension tests |
| tests/Bundle/DataGrid/DependencyInjection/CompilerPass/DataGridRegistryPassTest.php | Compiler pass mapping tests |
| tests/Bundle/DataGrid/Config/DataGridConfigurationTest.php | Config tree validation tests |
| tests/Bundle/DataGrid/Config/Builder/DataGridConfigBuilderTest.php | Fluent builder tests |
| tests/Bundle/DataGrid/Column/IconColumnTest.php | IconColumn template/params tests |
| tests/Bundle/DataGrid/Column/DoctrineColumnFactoryTest.php | Doctrine type/visibility mapping tests |
| tests/Bundle/DataGrid/Column/ActionsColumnTest.php | ActionsColumn rendering/behavior tests |
| src/Bundle/Platform/Kernel.php | Conditional bundle + route import |
| src/Bundle/DataGrid/Twig/Components/DataGrid.php | Twig component for rendering grids |
| src/Bundle/DataGrid/templates/components/data_grid.html.twig | Twig component template |
| src/Bundle/DataGrid/templates/columns/icon.html.twig | IconColumn cell template |
| src/Bundle/DataGrid/templates/columns/actions.html.twig | ActionsColumn cell template |
| src/Bundle/DataGrid/templates/.gitkeep | Keep templates directory in VCS |
| src/Bundle/DataGrid/SolidWorxPlatformDataGridBundle.php | Bundle class + compiler pass registration |
| src/Bundle/DataGrid/Resources/config/services.php | Service auto-registration config |
| src/Bundle/DataGrid/Grid/GridNameResolver.php | Name resolution implementation |
| src/Bundle/DataGrid/Grid/DataGridRenderer.php | Renderer with unique id suffixing |
| src/Bundle/DataGrid/Grid/DataGridRegistry.php | Name → grid service resolution |
| src/Bundle/DataGrid/Grid/DataGridDefaults.php | Defaults carrier + parameter wiring |
| src/Bundle/DataGrid/Grid/AbstractDataGrid.php | Platform defaults + Doctrine column fallback |
| src/Bundle/DataGrid/Exception/UnknownDataGridException.php | Missing-grid exception helper |
| src/Bundle/DataGrid/EventSubscriber/DataGridAccessSubscriber.php | Ajax route authorization guard |
| src/Bundle/DataGrid/DependencyInjection/SolidWorxPlatformDataGridExtension.php | Parameters, Twig paths, upstream config prepends |
| src/Bundle/DataGrid/DependencyInjection/CompilerPass/DataGridRegistryPass.php | Builds ServiceLocator map for registry |
| src/Bundle/DataGrid/Config/DataGridConfiguration.php | datagrid: config tree |
| src/Bundle/DataGrid/Config/Builder/DataGridConfigBuilder.php | PHP fluent config builder |
| src/Bundle/DataGrid/Column/IconColumn.php | Tabler icon template column |
| src/Bundle/DataGrid/Column/DoctrineColumnFactory.php | Doctrine mapping → columns factory |
| src/Bundle/DataGrid/Column/ActionsColumn.php | Server-rendered row actions column |
| platform.yaml | Example datagrid config block |
| platform-schema.json | JSON schema updates for datagrid |
| package.json | Workspace adjustment |
| docs/index.md | Add Data Grids docs link |
| docs/datagrid/security.md | Security model documentation |
| docs/datagrid/index.md | Data grids entrypoint docs |
| docs/datagrid/grids.md | Grid authoring + defaults docs |
| docs/datagrid/customization.md | Config + styling docs |
| docs/datagrid/columns.md | Column mapping + custom columns docs |
| composer.json | Add datagrid-related PHP deps + autoload |
| assets/webpack.config.js | Alias upstream controller + IgnorePlugin |
| assets/scss/platform.scss | Import datagrid stylesheet |
| assets/scss/_datagrid.scss | DataTables bs5 + Tabler overrides |
| assets/package.json | Add DataTables bs5 packages |
| assets/datatables-missing.js | Stub controller when vendor missing |
| assets/core.ts | Register upstream Stimulus controller |
| .gitignore | Ignore assets build artifacts |
Review details
- Files reviewed: 60/63 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| $services | ||
| ->load(SolidWorxPlatformDataGridBundle::NAMESPACE . '\\', dirname(__DIR__, 2)) | ||
| ->exclude(dirname(__DIR__, 2) . '/{Config,DependencyInjection,Resources,templates}'); |
…d rename the datatable Stimulus controller Upstream registers DataTablesExtension only under the service id datatables.twig_extension, with no class alias, so autowiring it by type (as DataGridRenderer's constructor did) could never resolve and a real application's container would fail to compile -- every existing test stubbed the extension directly, so nothing caught it. Fixed by decorating datatables.twig_extension with a new DataGridTwigExtension, wiring both services explicitly in services.php, and having DataGridRenderer take the decorated inner extension by id. The decorator also replaces render_datatable() with one that delegates to DataGridRenderer, which now also rewrites upstream's hardcoded, unoverridable Stimulus controller identifier (pentiminax--ux-datatables--datatable) down to the short "datatable" name assets/core.ts registers the controller under, in both attribute shapes it appears in, without ever touching the view-value JSON payload's contents. Added a container test that compiles upstream's and our own services.php together for real, catching the exact autowiring failure the rest of the suite's stubbing hides. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
The decorator now owns the only render_datatable function in the container, so narrowing its signature removed upstream's documented second parameter from every consumer. PHP does not error when a userland method is called with more arguments than it declares, so an attributes array passed from a template was silently discarded rather than failing loudly. Restores the parameter on the Twig extension and threads it through DataGridRenderer to the inner extension. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
…tribute-name position instead of a blanket string replacement, so it can no longer corrupt identifier-shaped text inside the escaped JSON view-value payload. Widened the character class to cover addController()'s -class and -outlet suffixes, whose outlet names are not lowercased, alongside -value. Replaced the test that gave false confidence -- it embedded the bare identifier with neither the data- prefix nor a trailing hyphen, so it never exercised the at-risk shape -- with one using the actual corrupting shape, and added coverage for the -class/-outlet suffixes. Claude-Session: https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY
|
Three follow-up commits since the description above, prompted by a request to shorten the Stimulus controller identifier. The rename turned out to be the smaller half.
Every test stubbed the extension, which is why this survived eleven per-task reviews and a whole-branch review. Both services are now wired explicitly, and The rename itself.
Gates unchanged: 99 tests / 167 assertions in the DataGrid suite, PHPStan clean at level max, ECS at the single pre-existing error, and the full suite still at its pre-existing baseline of 13 errors + 2 deprecations in |
Adds a
DataGridBundlethat wrapspentiminax/ux-datatables, so an application gets a Doctrine-backed, Tabler-styled data grid from one class declaration and one Twig tag:Columns come from the entity's Doctrine mapping, the table is server-side, and the Ajax endpoint, table token, CSRF protection and Bootstrap 5 theme are wired for you. Opt out entirely with
datagrid.enabled: false— neither this bundle nor its dependency is registered.What's here
AbstractDataGrid— platform defaults (server-side, Bootstrap 5, page length, responsive, column control, server-side CSV/XLSX export). Every default is overridable through the matchingconfigure*()hook.<twig:Platform:DataGrid name="…" />— grids are addressed by a name derived from the class (ClientDataGrid→client), overridable with aNAMEconstant. Rendering the same grid twice on a page gives the second table a-2id suffix.IconColumn/ActionsColumn— rendered server-side throughux_icon(), so grid icons use the configured Tabler pack rather than upstream's Lucide set. The action buttons emit the attributes upstream's Stimulus controller delegates on, so edit, delete and detail work unchanged.DataGridAccessSubscriber— see below.core.ts, the bs5 chain only, with anIgnorePluginsuppressing the six unused style frameworks. Tabler overrides live inassets/scss/_datagrid.scss.docs/datagrid/, linked from the docs index.Security
Upstream's README is explicit that the table token identifies which grid is requested, not who is asking. Without a guard, any authenticated user holding another grid's token can read it — and that token travels in a query string, which upstream notes leaks through logs and the Referer header.
DataGridAccessSubscriberadds two layers over all eight Ajax routes:datagrid.security.ajax_access, defaultIS_AUTHENTICATED_FULLY.getSecurityAttribute(), checked on every route from which a grid is resolvable, taking the read token from the query string (data,export) or the body (templates), and the action token from the body (the rest).It is deliberately not a prepended
security.access_controlrule: that list is first-match-wins, so injecting an entry would silently change the meaning of an application's existing rules.Worth knowing: only three of upstream's eight controllers validate CSRF at all, and
AjaxTemplateRenderController— which re-hydrates entities from Doctrine by id — validates none. That is why the per-grid check covers everything rather than a subset.Notable behaviours
ActionsColumnrenders its own markup, so it does not reproduce upstream'smutationsEnableddisabling of the delete button. The server still validates CSRF, so a delete attempted without a session fails server-side rather than being disabled client-side.ActionsColumn: their CSRF token is added to the row only after template columns render. Use upstream'sActionColumnfor those.form-select/form-controlto them, so adding rules there would duplicate Bootstrap and break dark mode.Testing
87 tests / 142 assertions covering the config tree and its builder, the DI extension and autoconfiguration, Doctrine column mapping, name resolution and the registry compiler pass, the renderer, both column types, the access subscriber, and the Kernel's opt-out and route registration.
pentiminax/ux-datatablesis pinned~0.87.0— it is pre-1.0 with an active upgrade guide.Pre-existing issues this branch does not fix
These predate the branch and are deliberately out of scope, but they are worth their own work:
assets/package.jsonhas noscriptskey,assets/webpack.config.jsendsexport default Encorerather thanEncore.getWebpackConfig(),@symfony/stimulus-bridgewants acontrollers.jsonalias that does not exist, andESLintPluginhas no config. With those present webpack emits zero output files.vendor/bin/phpstanreports 9 errors andvendor/bin/phpunitreports 13 errors + 2 deprecations, all insrc/Bundle/Saas,tests/Bundle/Saasandsrc/Bundle/Platform/DependencyInjection. This branch touches none of those files and adds no new failures — the DataGrid suite is green and PHPStan is clean at level max oversrc/Bundle/DataGridandtests/Bundle/DataGrid.rectorconflicts with PHPStan on two DataGrid files: it wants to strip@varannotations that PHPStan needs for narrowing before a barereturn. Applying it introduces 3 new PHPStan errors, so both files are left as they are.@tiptap/core2.27.2 → 3.31.3.assets/package.jsonalready declared^3.31.3; the stale lockfile was violating its own range. node(deps): bump @tiptap/starter-kit from 2.27.3 to 3.31.3 #200 is bumping@tiptap/starter-kitto match.https://claude.ai/code/session_013neWyR1xfhg449TrVb4BMY