Skip to content

Release v2.0.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 08:18
· 145 commits to main since this release

v2.0.0

Changelog

Standard page contract (REQ-UI-003). Canonical layouts, page scaffolds (list, show, form, settings, dashboard, confirm, wizard, auth), view models, one global theme (classes, attributes, partials, tokens, per-page-type and per-bundle exceptions) and PageContractTestCase for UI bundles — validated in a real host (symfony-beacon). 1.x partials and macros keep their inputs and output; nowo-ui.css now ships inside @layer nowo-ui (use nowo-ui.unlayered.css for the 1.x cascade). See UPGRADING and PAGE_CONTRACT.md.

Added

  • Standard page contract (REQ-UI-003, phase 0 core) — opt-in; nothing changes for hosts that do not use it. Summary of the API in PAGE_CONTRACT.md.

    • Configuration: nowo_ui_kit.layouts.{admin,guest} (defaults @NowoUiKit/layout/admin.html.twig / guest), nowo_ui_kit.breadcrumbs.{breadcrumb_kit,collection} and the global theme nowo_ui_kit.theme.{classes,attributes,partials,tokens,tokens_dark,bundles} (unknown slot / partial, on* attributes, unsafe token values and bad bundle keys are rejected with explicit messages).
    • Theme: Theme\ThemeSlots (slot vocabulary + framework defaults per css_framework, identical to the ui.* macros) and Theme\Theme (semantic + framework + host classes, per-bundle exceptions, attributes, partial overrides, token CSS).
    • Twig: nowo_ui_class(), nowo_ui_attrs(), nowo_ui_partial(), nowo_ui_theme_tokens() (CSP nonce from the csp_nonce request attribute), nowo_ui_theme_css(), nowo_ui_breadcrumb_kit() / nowo_ui_breadcrumb_kit_available() (BreadcrumbKit bridge), filter |nowo_ui_trans; globals nowo_ui_layout, nowo_ui_theme, nowo_ui_breadcrumbs.
    • View models (Nowo\UiKitBundle\View\): Label, PageHeader, Action, Confirm, Crumb, Link, Pagination (fromArray / fromPagerfanta / fromKnp, duck typed), Column, EmptyState, DetailItem, DetailSection, KpiTile, SettingsSection, WizardStep, ListPage, ShowPage, FormPage, SettingsPage, DashboardPage, ConfirmPage, WizardPage, AuthPage.
    • Layouts layout/{document,admin,guest}.html.twig (canonical nowo_ui_* blocks, UiKit asset package only) and scaffolds page/{list,show,form,settings,dashboard,confirm,wizard,auth}.html.twig with the REQ-UI-003 data blocks.
    • New partials _action (one view-model action: link, POST form, confirm <dialog>, modal), _action_post, _breadcrumbs.
    • Upgraded partials accept view models and theme slot hooks: _pagination (Pagination object, page / per-page parameter names), _page_header (header), _row_actions (Action objects), _confirm (confirm + action), _empty (empty), _toasts / _flashes (trans_domain, slot attributes), _shell / _burger (root_class, label_attr_names, attr, theme partial for the burger). 1.x inputs render as in 1.17.4 (fixture-based BC tests).
    • CSS: the base palette drops to zero specificity when <html data-nowo-ui-host-tokens> is set (UiKit layout + theme.tokens), so host tokens win without !important; semantic styles for breadcrumbs, detail lists, KPI tiles, cards grid, wizard steps, auth panel, danger zone.
    • Translations action.{confirm,back,next,submit,import}, confirm.delete_{title,message}, breadcrumbs.nav, table.actions, wizard.{steps,step_of}, form.danger_zone (7 locales).
    • Contract test for UI bundles: Nowo\UiKitBundle\Test\PageContractTestCase (dev dependency — bundles add phpunit/phpunit, and symfony/form for form pages, to require-dev; listed in suggest). Bundles return PageFixtures (template + context); each page is rendered with a real Twig environment against a stub host layout and a stub theme (StubHost) and checked for: scaffold or unique page declared in docs/TEMPLATES.md, canonical blocks rendered and filled, no <html> / CDN assets / inline on*= / confirm( / javascript:, no framework utility classes outside theme slots (FrameworkClasses allow-lists per css_framework derived from ThemeSlots), stub theme classes / attributes / page_header partial reflected, no raw or missing translation keys. Hooks for template paths, catalogues, form themes, Twig extensions and allowed classes; assertion helpers reusable. Self-tested on UiKit's scaffolds for every framework, with one negative fixture per assertion.
    • Host-theme demo (demo/symfony8, /page-contract): an "Acme Shop" back office using every scaffold with a non-default host theme (host layout bridges, tokens, class map, table attribute, page_header partial override) and a default-theme view (/page-contract/default/…); the demo templates run the contract test; Playwright e2e (make test-e2e) and WebP screenshots of every scaffold (make demo-screenshots, docs/images/demo/page-contract-*.webp).
    • Theme framework_defaults: hosts can drop UiKit's framework defaults (e.g. Bootstrap badge text-bg-success, btn btn-primary, card) globally (theme.framework_defaults: false), per bundle (theme.bundles.<B>.framework_defaults) or per slot (theme.classes.<slot>: {value: '…', framework_defaults: false}, same in theme.bundles.<B>.classes); most specific wins, the semantic nowo-ui-* class always stays. nowo_ui_class() and the 1.x partials honour it (Theme::macroClasses(), Theme::frameworkDefaults()); the plain string / list form is unchanged.
    • _card reads the theme slots card, card_header, card_body, card_footer (new slot) and cards, and accepts Label titles / intros, actions (View\Action list), id, attr, cards (grid of cards through the card partial) and bundle; 1.x inputs render as in 1.17.4 (added to the legacy fixtures).
    • Docs: PAGE_CONTRACT.md (full guide), THEME_SLOTS.md (slot vocabulary with defaults per framework, partials, tokens, per-bundle exception semantics), MIGRATING_BUNDLES.md (bundle recipe).
  • Per-page-type theme variation theme.pages.<list|show|form|settings|dashboard|confirm|wizard|auth>.{classes,attributes,framework_defaults} (validated; precedence bundle slot > bundle > page-type slot > page type > host slot > host). Scaffolds set nowo_ui_page_type and pass it to their partials; nowo_ui_class() / nowo_ui_attrs() gain an optional page argument (default: the context's nowo_ui_page_type); new nowo_ui_html_attrs(map) and Theme::renderAttributeMap(); ThemeSlots::PAGE_TYPES.

  • Confirm dialogs: _confirm cancel / confirm buttons read the btn_secondary / btn_<variant> slots (and framework_defaults) instead of the bare ui.btn() macro (1.x output unchanged without host classes). Type-to-confirm View\Confirm::expectedValue (+ expectedLabel, expectedPattern()): input with required + pattern joined to the POST form, confirm button disabled until the value matches (nowo-ui-confirm.js: syncExpected(), resetExpected(), syncAllExpected(); CSP-safe), also on the full-page ConfirmPage. Translation confirm.type_to_confirm (7 locales); _action_post / _action_form_view accept form_id.

  • Row-action variant slots action_primary, action_secondary, action_danger: host classes per resolved variant in row context (objects and 1.x hashes), e.g. a red delete on Tailwind.

  • Template contract — partial resolution, 1.x shapes, regions: every scaffold / partial include resolves through the theme (_page_header → breadcrumbs / action; _empty, _card, _row_actions → action; settings tabs through the tabs partial, which now reads the tabs / tab / tab_active slots and Label labels); new partial names list_filters, detail_list, section_form, wizard_form. Scaffolds pass the 1.x shapes next to the view models (page_header: title / intro / pre-rendered toolbar; pagination: Pagination::toArray() + pagination_model; row_actions: 1.x hashes + view, row confirm dialogs rendered once after the table); Pagination and Action implement read-only ArrayAccess with the 1.x keys. data-nowo-ui-region on the standard regions and attributes on every page model (scaffold root). Legacy BC fixture for _tabs.

  • List filters: data block nowo_ui_list_filters; View\FilterField (placeholder, width, widget / row classes and attributes) via ListPage::filterFields; filtersSubmit (label / icon / variant of the apply button, icon through the icon partial), filtersClear and filterActions as Actions; slot filters_field.

  • New slots table_head_row, table_head_cell, table_actions_head (+ ListPage::actionsLabel / actionsLabelVisible), kpi_label, kpi_value, kpi_hint (+ KpiTile::linkTile), form_panel (form scaffold wrapper), danger_zone_title, danger_zone_intro (+ FormPage::dangerZoneTitle / dangerZoneIntro).

  • Responsive list tables (CSS only): ListPage::responsive (cards default, scroll) — the table wrap is a size container, Column::priority 2 / 3 hide below 48rem / 60rem, below 40rem rows become labelled cards (td[data-label]).

  • Form secondary actions in the page header: FormPage::secondaryActionsInHeader (default true; false keeps them below the form).

  • Action display modes everywhere (validation round 3): Action::display (icon | text | icon_text, Action::DISPLAYS) is honoured in toolbar / header / card / filter context too (icon-only: visually-hidden label + aria-label / title, kind icon fallback), the action partial takes a display context option, ListPage::filtersDisplay sets it for the apply / clear / extra filter buttons, and the new slot btn_icon (nowo-ui-btn--icon) marks icon-only toolbar buttons.

  • List primary actions in the filter row: ListPage::headerActionsInFilters (null = the new theme option theme.pages.list.header_actions_in_filters, default false) renders the page-header actions at the end of the filter row (nowo-ui-filters__header-actions, region header-actions) instead of the page header; Theme::listHeaderActionsInFilters(), PageHeader::withActions(), list_filters partial input header_actions, scaffold macro page_header(…, header_actions) (the page header then receives a header without actions).

  • Internal slots (validation round 3): card_title, card_intro (_card, show / settings section cards); detail_item, detail_term, detail_value (+ nowo-ui-detail-list--stacked modifier, label over value); confirm_panel, confirm_header, confirm_title, confirm_body, confirm_message, confirm_expect_label, confirm_footer (dialog and full-page confirm); auth_footer, auth_links; kpi_link (whole-tile KPI link only).

  • Actions column, neutral variant, card mode (validation round 3): ListPage::actionsAlign (start | center | end, default end) + slot table_actions_cell replace the hard-coded nowo-ui-cell--end (the action cluster follows the alignment); variant neutral (Action::resolvedVariant() default for kinds other than delete / create / save) + slot action_neutral, so an explicit secondary is distinguishable; card-mode CSS keeps inline cell content (badges) at its own width and nowo-ui-table-wrap--flush (via the table_wrap slot) drops the per-card border inside a bordered panel.

  • Demo (round 3): the host-theme list puts its primary actions in the filter row with an icon-only export and clear button, show pages use stacked detail lists, the KPI link tile and the confirm title use the new slots; e2e covers per-row confirm dialogs, filters kept by pagination links, the actions column alignment and card-mode badges; list / show / dashboard / confirm / default-list screenshots regenerated.

  • Token precedence nowo_ui_tokens(nonce): theme tokens (host defaults) then the appearance SPI style (user choice); the document layout prints it after nowo-ui.css.

  • Confirm and show hooks (final Beacon validation): slot confirm_expect on the type-to-confirm wrapper (nowo-ui-confirm__expect, dialog and page); slot detail_list_meta on the show scaffold's meta list (nowo-ui-detail-list--meta, added to detail_list); a 1.x body passed to _confirm is wrapped in a <div> with the confirm_message slot when the theme sets classes / attributes for it (1.x markup unchanged with an empty theme); Confirm::messageInHeader (null = the new theme.confirm_message_in_header, default false; Theme::confirmMessageInHeader()) renders the message under the title inside the header (dialog and page); confirm_message / confirm_expect slot attributes are now rendered.

  • Minor slots for kit class maps (final Beacon validation): btn_link (toolbar Action variant link, defaults = ui.btn('link')), muted (defaults = ui.muted(); on the — placeholder of empty detail-list values), modal_panel / modal_header / modal_body / modal_footer (+ the modal slot, host classes and attributes in _modal_shell, which also accepts bundle; 1.x markup unchanged with an empty theme, legacy fixture added), breadcrumb_link / breadcrumb_separator (plain trail; the separator is now an aria-hidden element, the CSS ::before stays for overrides without it), table_cell_muted (default list cells without a value print a muted —), row_action_disabled (disabled row actions; objects and 1.x hashes), sortable_header / sort_link (list sortable headers).

  • Demo (final validation): the host theme hides closed dialogs through the confirm slot (.acme-dialog:not([open]) { display: none }) while the full-page confirm uses confirm_page, styles the show meta list (detail_list_meta) and puts a Tailwind child variant in sort_link; e2e covers the confirm page staying visible, the dialogs opening, the escaped arbitrary variant and the header actions right after the filter actions. Screenshots refreshed (list, show, settings, confirm).

Changed

  • Template contract — theme dark tokens are emitted on [data-theme="dark"] (was :root[data-theme="dark"]): same specificity as the appearance SPI, so the appearance printed after them wins.
  • Template contract — CSS cascade layer: nowo-ui.css ships inside @layer nowo-ui with the order statement @layer theme, base, nowo-ui, components, utilities; so Tailwind v4 utilities placed in theme slots beat the kit while the kit stays above Tailwind's preflight (dialogs). nowo-ui.unlayered.css keeps the 1.x unlayered stylesheet for hosts that relied on the kit beating unlayered framework CSS. nowo-ui.css is generated from it (pnpm run build:css, checked by LayeredCssTest).
  • Template contract — theme class lists accept any printable character (final Beacon validation): ", ', <, > (and &) are no longer rejected in theme.classes, theme.pages.<type>.classes and theme.bundles.<B>.classes, so Tailwind arbitrary and child variants ([&>li]:mt-1, content-['·'], data-[state=open]:…) can live in the theme. Class lists are always printed HTML-escaped inside their quoted class attribute (ClassEscapingTest renders every scaffold and partial with a break-out payload in every slot, on the host, per page type and per bundle: no attribute / element injection, no double escaping). Control characters (other than whitespace) and invalid UTF-8 are still refused (class lists must be valid UTF-8 without control characters.).
  • Template contract — full-page confirm root slot (final Beacon validation): page/confirm.html.twig printed the dialog root slot confirm (classes + attributes, and UiKit's own .nowo-ui-confirm <dialog> box rules) on its <section>, so host dialog CSS such as .confirm-dialog:not([open]) { display: none } hid the page. The page now prints the new slot confirm_page (nowo-ui-confirm-page) next to card; confirm stays on the <dialog> only.
  • Template contract — list header actions in the filter row sit right after the filter actions (same filters_actions group): the group no longer grows (flex: 1 1 auto dropped) and nowo-ui-filters__header-actions no longer pushes itself to the row end (margin-inline-start: auto dropped). PAGE_CONTRACT documents the filter row layout and flex-none on filters_actions for themes whose fields grow.

Fixed

  • Confirm dialog ids of row actions (REQ-UI-003 validation round 3): Action::confirmId() hashed only kind / href / route / params, so two row actions with the same target but a different Confirm (or the same static action on every row) printed duplicate dialog ids and every toggle opened the first dialog. The derived id now covers the whole Confirm content, the CSRF token and a scope (Action::$confirmScope, withConfirmScope(), confirmKey()); ListPage::rowActions() scopes actions by row key and the list scaffold renders one dialog per id (ListPage::confirmDialogs(); an explicit Confirm::$id shared by different content throws).
  • List pagination keeps filters in 1.x overrides (validation round 3): the list scaffold passed only the pagination array, so an untouched 1.x _pagination override (which reads the top-level route, route_params, query) lost the filters and sort. It now also passes route, route_params and query (current request query without the page parameters, merged with Pagination::query) plus page_param / per_page_param; the default partial accepts page_param / per_page_param as variables and, through the scaffold, now keeps the request's filters across pages too.
  • Show scaffold: the meta detail list (nowo-ui-detail-list--meta) gets a bottom margin too, so content of nowo_ui_show_after no longer touches it.
  • Badge contrast on Bootstrap 5 / Tabler: the ui.badge() macro and the badge* slot defaults used bg-success / bg-danger (background with !important) without a text utility, so the semantic nowo-ui-badge--* text colour (dark green / red) landed on the solid Bootstrap fill. They now use text-bg-{success,warning,danger,info,light} (Bootstrap 5.2+; text and background set together); on 5.0 / 5.1 the semantic palette paints the badge. nowo-ui.css also clears the tinted border on solid badges and gives 1.x bg-* badges without a text utility Bootstrap's badge text colour.