Skip to main content
All notable changes are documented here. The format follows Keep a Changelog and this project adheres to Semantic Versioning.

Fixed

  • Dashboard import modal: AJAX import submit now follows redirects reliably and navigates back to the dashboard index after successful imports, avoiding a stuck modal state on 302 responses.
  • Dashboard item list: item rows are rendered in deterministic tree order (parent/children traversal with sibling sort by position, then id) so visual ordering matches stored positions.
  • Dashboard table UX: item position is displayed under the parent label to make ordering and debugging easier.
  • Tests: migration command integration assertion for --dump output is now robust to Symfony console note line-wrapping.

Changed

  • Performance (N+1 reduction):
    • Menu copy now clones items from a preloaded flat list in two passes (no recursive getChildren() lazy traversal)
    • Descendant id resolution for item edit uses preloaded menu items + in-memory BFS
    • Import post-processing clears link fields for parents with children using an in-memory hasChildren map (no per-item lazy children->count())
    • Export-all loads items for all menus in a single repository query and groups in memory

Fixed

  • Dashboard UI: menu item labels are rendered using locale-resolved MenuItem::getLabelForLocale() (avoids empty base label when text is stored in per-locale translations).
  • Dashboard item forms: “Add child” modal hides type, icon and position inputs and shows only label + per-locale translations (item type is fixed to Link).
  • Dashboard item forms: the icon section is rendered with a normal Symfony form (not LiveComponent) so the icon-selector widget refreshes reliably when the modal content changes.
  • Dashboard item forms: label validation accepts either a non-empty base label or at least one non-empty translation; empty position values are normalized to 0 to prevent null mapping issues.

Changed

  • Docs/UX: item form rendering and documentation were aligned for section-based partial submissions (section/_section, section_focus).

Fixed

  • LiveComponent: prevent Symfony “submitted form data” exceptions when saving items, and stabilize hydration of per-locale label_{locale} fields in the item modal.
  • Dashboard UI: item modal icon field prefill is normalized; when the optional icon-selector bundle is installed it uses IconSelectorType, otherwise it falls back to a plain text input.

Changed

  • Demos: dashboard asset builds (make assets / make ts-assets) run inside the demo Docker container to avoid host pnpm/permission issues.

Added

  • Config: dashboard.icon_size — CSS size used to render menu item icons (SVG width/height and legacy icon font-size).

Fixed

  • Twig/UI: menu item labels are rendered using the locale-resolved MenuItem.label (already resolved by MenuTreeLoader), avoiding an extra translation pass in Twig.

Added

  • Config/UI: wrapper <span> for non-section menu items controlled by dashboard.item_span_active, with wrapper class configurable via dashboard.css_class_options.span.

Added

  • Config: dashboard.id_options — list of HTML id values for the root <ul> of each rendered menu. Drives the dashboard field ulId (dropdown vs plain text).
  • Menu entity: new nullable property Menu.ulId (DB column ul_id).
  • Dashboard UI: menu configuration includes ulId; the frontend template sets id="..." on the root <ul> when configured.
  • Import/export: menu export/import now includes ulId.
  • Migration generator: nowo_dashboard_menu:generate-migration --update can add the missing ul_id column.

Added

  • UX Autocomplete detection: new Twig global nowo_dashboard_ux_autocomplete_available computed from the presence of Symfony\UX\Autocomplete\AutocompleteBundle. Dashboard item form templates apply the autocomplete form theme only when available.
  • CSRF consistency: dashboard menu item forms set csrf_token_id to submit (controller + LiveComponent) to keep CSRF behaviour aligned across Symfony versions.

Changed

  • MenuDashboardController: when creating a child item, uses _query to include parent id in the generated URL.
  • Templates: wraps {% form_theme '@SymfonyUXAutocomplete/autocomplete_form_theme.html.twig' %} in the new availability guard.

Fixed

  • Avoid warnings/errors when Symfony UX Autocomplete is not installed.
  • Demo Symfony 7: enable sessions, CSRF support, and framework.property_info for older Symfony configs.

Added

  • Config: dashboard.permission_key_choices — optional list of permission keys for the menu item form. When set, the field becomes a select with autocomplete.
  • Dashboard item form: route name selector and (when configured) permission key selector use Symfony UX Autocomplete.
  • Dashboard: “Add child” passes parent ID in the form action URL; “Add child” button disabled for section and divider items.
  • Permission checkers: AllowAllMenuPermissionChecker and PermissionKeyAwareMenuPermissionChecker are explicitly tagged with nowo_dashboard_menu.permission_checker.
  • MenuItem: itemType is now nullable (DB column nullable).

Added

  • Dashboard menu form: split into definition (code, base, name, context, icon) and configuration (permission checker, depth, collapsible, CSS classes).
  • Dashboard item form: same definition/configuration split; partial and LiveComponent support section_focus.
  • Redirect to referer: after any successful form submission the controller redirects to the same-origin referer URL; otherwise to the usual route.
  • Import in modal: import form is loaded and submitted via AJAX; on success redirects to referer or index.
  • Translations: dashboard.edit_identity, dashboard.edit_config (en, es, fr).
  • Dockerfile: added Node.js, npm, and pnpm for building dashboard assets inside the container.

Changed

  • MenuItem: label property is now nullable (for divider items); getter returns '' when null.
  • Twig: _menu_form_partial and _item_form_partial no longer use field.vars.rendered (removed in Symfony 6.3+).

Added

  • Menu option: nestedCollapsibleSections — when disabled, section-type items do not show a collapse toggle.
  • Dependency: symfony/mime required for the File validator used in the import form.

Changed

  • Export/import: now includes classSectionLabel and nestedCollapsibleSections.
  • ImportMenuType: constraints use named arguments for Symfony 7/8 compatibility.

Deprecated

  • Config: dashboard.path_prefix is deprecated. Set the dashboard URL prefix in your app routing when importing @NowoDashboardMenuBundle/Resources/config/routes_dashboard.yaml.

Added

  • Security: CSRF validation in deleteMenu() and deleteItem(); invalid or missing token returns 403.
  • Security: move up/down actions now use POST with CSRF tokens.
  • Security: import size limit via dashboard.import_max_bytes (default 2 MiB).
  • Security: optional dashboard.required_role (e.g. ROLE_ADMIN) and dashboard.import_export_rate_limit (returns 429 when exceeded).

Added

  • Translation domain: bundle uses domain NowoDashboardMenuBundle for all UI strings. Constant NowoDashboardMenuBundle::TRANSLATION_DOMAIN available in code.

Changed

  • Translations (breaking for overrides): bundle translation files are now NowoDashboardMenuBundle.{locale}.yaml (replacing messages.* and validators.*). To override strings in your app, create translations/NowoDashboardMenuBundle.{locale}.yaml with the same keys.

Added

  • Dashboard export/import: export one or all menus as JSON (config + item tree, no internal IDs). Import from JSON with strategy Skip existing or Replace.
  • Config: dashboard.layout_template — Twig template that dashboard views extend.
  • MenuUrlResolver: fills missing path parameters from the current request’s route params.

Added

  • Symfony 6.4 LTS: bundle now supports Symfony ^6.4 || ^7.0 || ^8.0; CI tests PHP 8.2–8.5 × Symfony 6.4, 7.0 and 8.0.

Changed

  • nowo-tech/icon-selector-bundle is now optional (moved from require to suggest). The dashboard item form uses IconSelectorType when installed; otherwise the icon field is a plain text input.
This release raised the minimum PHP version to 8.4 and dropped Symfony 7 and Doctrine 2.x support.

Changed (breaking)

  • PHP: minimum version raised to 8.4 (8.2 and 8.3 no longer supported).
  • Symfony: bundle requires Symfony ^8.0 only; Symfony 7 support dropped.
  • Doctrine: requires doctrine/doctrine-bundle ^3.0 and doctrine/orm ^3.0 (2.x no longer supported).

Fixed

  • Doctrine config: removed deprecated auto_generate_proxy_classes from test and demo configs.

Added

  • Cache: configurable tree cache (cache.ttl min 60 s, cache.pool) to avoid N+1 and repeated DB hits.
  • Icon prefix map: icon_library_prefix_map (e.g. bootstrap-icons: bi) so icon identifiers are converted before rendering.
  • Menu loading: two-query SQL path in MenuRepository::findMenuAndItemsRaw(); optional PSR-6 cache.
  • Web Profiler: “Dashboard menus” panel with Menus tab (query count, item tree) and Configuration tab.
  • Doctrine: table name quoting via Platform::quoteSingleIdentifier().

Added

  • Entities: Menu and MenuItem with Doctrine ORM (no Gedmo/Stof). Tree via parent + position; labels with optional JSON translations per locale.
  • Config: project, locales, default_locale, defaults, menus.{code} overrides, api (enabled, path_prefix), dashboard options.
  • MenuTreeLoader: single-query load; labels resolved by request locale; optional permission filter via MenuPermissionCheckerInterface.
  • Twig: dashboard_menu_tree(), dashboard_menu_href(), dashboard_menu_config() functions; @NowoDashboardMenuBundle/menu.html.twig (Bootstrap 5, collapsible).
  • JSON API: GET /api/menu/{code} with optional _locale and _context_sets.
  • Dashboard: CRUD at configurable path (default /admin/menus).
  • Context resolution: menus with same code can have different JSON context; resolved by ordered context sets.
  • Translations: dashboard and form messages in English, Spanish and French.
  • Demos: Symfony 7 and Symfony 8 with FrankenPHP + Caddy, MySQL, fixtures.
  • CI: GitHub Actions (PHP 8.2–8.5 × Symfony 7.0/8.0, code style, coverage, release workflow).
  • Recipe: Symfony Flex recipe for config and routes.

For the complete history including all patch releases, see the GitHub releases page or the full CHANGELOG in the repository.