Repository navigation
Release v2.0.0
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 themenowo_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 percss_framework, identical to theui.*macros) andTheme\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 thecsp_noncerequest attribute),nowo_ui_theme_css(),nowo_ui_breadcrumb_kit()/nowo_ui_breadcrumb_kit_available()(BreadcrumbKit bridge), filter|nowo_ui_trans; globalsnowo_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(canonicalnowo_ui_*blocks, UiKit asset package only) and scaffoldspage/{list,show,form,settings,dashboard,confirm,wizard,auth}.html.twigwith 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(Paginationobject, page / per-page parameter names),_page_header(header),_row_actions(Actionobjects),_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 addphpunit/phpunit, andsymfony/formfor form pages, torequire-dev; listed insuggest). Bundles returnPageFixtures (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 indocs/TEMPLATES.md, canonical blocks rendered and filled, no<html>/ CDN assets / inlineon*=/confirm(/javascript:, no framework utility classes outside theme slots (FrameworkClassesallow-lists percss_frameworkderived fromThemeSlots), stub theme classes / attributes /page_headerpartial 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_headerpartial 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. Bootstrapbadge 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 intheme.bundles.<B>.classes); most specific wins, the semanticnowo-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. _cardreads the theme slotscard,card_header,card_body,card_footer(new slot) andcards, and acceptsLabeltitles / intros,actions(View\Actionlist),id,attr,cards(grid of cards through thecardpartial) andbundle; 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).
- Configuration:
-
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 setnowo_ui_page_typeand pass it to their partials;nowo_ui_class()/nowo_ui_attrs()gain an optionalpageargument (default: the context'snowo_ui_page_type); newnowo_ui_html_attrs(map)andTheme::renderAttributeMap();ThemeSlots::PAGE_TYPES. -
Confirm dialogs:
_confirmcancel / confirm buttons read thebtn_secondary/btn_<variant>slots (andframework_defaults) instead of the bareui.btn()macro (1.x output unchanged without host classes). Type-to-confirmView\Confirm::expectedValue(+expectedLabel,expectedPattern()): input withrequired+patternjoined to the POST form, confirm button disabled until the value matches (nowo-ui-confirm.js:syncExpected(),resetExpected(),syncAllExpected(); CSP-safe), also on the full-pageConfirmPage. Translationconfirm.type_to_confirm(7 locales);_action_post/_action_form_viewacceptform_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 thetabspartial, which now reads thetabs/tab/tab_activeslots andLabellabels); new partial nameslist_filters,detail_list,section_form,wizard_form. Scaffolds pass the 1.x shapes next to the view models (page_header:title/intro/ pre-renderedtoolbar;pagination:Pagination::toArray()+pagination_model;row_actions: 1.x hashes +view, row confirm dialogs rendered once after the table);PaginationandActionimplement read-onlyArrayAccesswith the 1.x keys.data-nowo-ui-regionon the standard regions andattributeson 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) viaListPage::filterFields;filtersSubmit(label / icon / variant of the apply button, icon through the icon partial),filtersClearandfilterActionsasActions; slotfilters_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(cardsdefault,scroll) — the table wrap is a size container,Column::priority2 / 3 hide below 48rem / 60rem, below 40rem rows become labelled cards (td[data-label]). -
Form secondary actions in the page header:
FormPage::secondaryActionsInHeader(defaulttrue;falsekeeps 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), theactionpartial takes adisplaycontext option,ListPage::filtersDisplaysets it for the apply / clear / extra filter buttons, and the new slotbtn_icon(nowo-ui-btn--icon) marks icon-only toolbar buttons. -
List primary actions in the filter row:
ListPage::headerActionsInFilters(null = the new theme optiontheme.pages.list.header_actions_in_filters, defaultfalse) renders the page-header actions at the end of the filter row (nowo-ui-filters__header-actions, regionheader-actions) instead of the page header;Theme::listHeaderActionsInFilters(),PageHeader::withActions(),list_filterspartial inputheader_actions, scaffold macropage_header(…, header_actions)(the page header then receives aheaderwithout 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--stackedmodifier, 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, defaultend) + slottable_actions_cellreplace the hard-codednowo-ui-cell--end(the action cluster follows the alignment); variantneutral(Action::resolvedVariant()default for kinds other than delete / create / save) + slotaction_neutral, so an explicitsecondaryis distinguishable; card-mode CSS keeps inline cell content (badges) at its own width andnowo-ui-table-wrap--flush(via thetable_wrapslot) 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 afternowo-ui.css. -
Confirm and show hooks (final Beacon validation): slot
confirm_expecton the type-to-confirm wrapper (nowo-ui-confirm__expect, dialog and page); slotdetail_list_metaon the show scaffold's meta list (nowo-ui-detail-list--meta, added todetail_list); a 1.xbodypassed to_confirmis wrapped in a<div>with theconfirm_messageslot when the theme sets classes / attributes for it (1.x markup unchanged with an empty theme);Confirm::messageInHeader(null = the newtheme.confirm_message_in_header, defaultfalse;Theme::confirmMessageInHeader()) renders the message under the title inside the header (dialog and page);confirm_message/confirm_expectslot attributes are now rendered. -
Minor slots for kit class maps (final Beacon validation):
btn_link(toolbarActionvariantlink, defaults =ui.btn('link')),muted(defaults =ui.muted(); on the—placeholder of empty detail-list values),modal_panel/modal_header/modal_body/modal_footer(+ themodalslot, host classes and attributes in_modal_shell, which also acceptsbundle; 1.x markup unchanged with an empty theme, legacy fixture added),breadcrumb_link/breadcrumb_separator(plain trail; the separator is now anaria-hiddenelement, the CSS::beforestays 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
confirmslot (.acme-dialog:not([open]) { display: none }) while the full-page confirm usesconfirm_page, styles the show meta list (detail_list_meta) and puts a Tailwind child variant insort_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.cssships inside@layer nowo-uiwith 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.csskeeps the 1.x unlayered stylesheet for hosts that relied on the kit beating unlayered framework CSS.nowo-ui.cssis generated from it (pnpm run build:css, checked byLayeredCssTest). - Template contract — theme class lists accept any printable character (final Beacon validation):
",',<,>(and&) are no longer rejected intheme.classes,theme.pages.<type>.classesandtheme.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 quotedclassattribute (ClassEscapingTestrenders 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.twigprinted the dialog root slotconfirm(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 slotconfirm_page(nowo-ui-confirm-page) next tocard;confirmstays on the<dialog>only. - Template contract — list header actions in the filter row sit right after the filter actions (same
filters_actionsgroup): the group no longer grows (flex: 1 1 autodropped) andnowo-ui-filters__header-actionsno longer pushes itself to the row end (margin-inline-start: autodropped). PAGE_CONTRACT documents the filter row layout andflex-noneonfilters_actionsfor 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 differentConfirm(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 wholeConfirmcontent, 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 explicitConfirm::$idshared by different content throws). - List pagination keeps filters in 1.x overrides (validation round 3): the list scaffold passed only the
paginationarray, so an untouched 1.x_paginationoverride (which reads the top-levelroute,route_params,query) lost the filters and sort. It now also passesroute,route_paramsandquery(current request query without the page parameters, merged withPagination::query) pluspage_param/per_page_param; the default partial acceptspage_param/per_page_paramas 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 ofnowo_ui_show_afterno longer touches it. - Badge contrast on Bootstrap 5 / Tabler: the
ui.badge()macro and thebadge*slot defaults usedbg-success/bg-danger(background with!important) without a text utility, so the semanticnowo-ui-badge--*text colour (dark green / red) landed on the solid Bootstrap fill. They now usetext-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.cssalso clears the tinted border on solid badges and gives 1.xbg-*badges without a text utility Bootstrap's badge text colour.