# Changelog ## 1.9.3 — 2026-05-08 ### Fixed - **Collection default sort direction ignored** — `$params->sortDir` defaults to `'asc'` (a non-empty string), so `$params->sortDir ?: $collection->sortDir` always resolved to `'asc'`, never falling through to the configured direction. Fixed by checking `$input->get('sort')` to distinguish an explicit URL sort from the default, and using collection `sortBy`/`sortDir` as a pair when no URL sort is present. - **View icon shown for pages without a template view file** — the View action was displayed for any page with a non-empty URL, even when no frontend template file existed, resulting in a 404 on click. Now gated behind `$page->viewable()`. - **Add button shown when template disallows new pages** — the "Add" button appeared regardless of the template's `noParents` setting. Added `canAddNew()` to `Collection` which returns `false` when `noParents = 1` (no new pages) or `noParents = -1` and a page with that template already exists (singleton). ### Added - **Sort indicator in selector note** — the `Selector:` details block now shows the active sort field and direction inline (e.g. `· Sort: created DESC`), reflecting either the URL override or the collection default. --- ## 1.9.2 — 2026-05-04 ### Fixed - **`FieldtypeDatetime` / `FieldtypeDate` showing unix timestamp** — date fields were only formatted when the column type was explicitly set to `date` in collection settings. Now auto-detected by `$ftName` so any datetime field renders correctly with the configured date format without manual override. - **`created` / `modified` system fields showing unix timestamp** — PW system fields `created` and `modified` were not handled in `renderCellValue()` and fell through to the generic scalar renderer, outputting a raw integer. Now explicitly caught before field lookup and passed through `formatDate()`. - **Filter dropdowns not applying** — `fetchTable()` in `collections.js` used `new URLSearchParams(params).toString()` which percent-encodes `[` and `]` as `%5B%5D`. PHP's array parsing requires literal brackets in `filter[field]=value`, so `$input->get('filter')` returned `null` instead of an array. Query string is now built manually to keep brackets unencoded. - **API log growing unbounded in production** — every API request and API key authentication wrote a line to the `collections` log regardless of environment. Both `log->save()` calls are now gated behind `$this->wire('config')->debug`, so logs are only written when Tracy / debug mode is active. - **Apply button not appearing when typing in search field** — the Apply button was only rendered inside the `if (!empty($filterOptions))` block, so it was absent from the DOM on collections without filter dropdowns, making `showApplyBtn()` a no-op. Button is now rendered unconditionally (always hidden by default) outside the filters block. - **Search input `input` event never firing** — `getElementById('collections-search-input')` and other direct DOM lookups ran at script parse time, before the DOM was ready (script loaded via `$config->scripts->add()` in `
`). All DOM-dependent initialisation is now wrapped in `DOMContentLoaded` handlers. - **Pressing Enter in search field losing active filters** — the search form submitted natively, discarding filter dropdown values that were applied via AJAX (hidden inputs in the form are server-rendered and not updated on the client). Form submit is now intercepted and routed through `fetchTable`, identical to clicking Apply. - **Clear button absent when filters restored from localStorage** — the Clear button was PHP-rendered only when `$params` had active filters, so on a clean page load it was missing from the DOM entirely. It is now always rendered (hidden by default) and shown/hidden via JS alongside the Apply button. - **Apply button not shown after localStorage restore** — `restoreFilterState()` restored UI controls and called `fetchTable` but never called `showApplyBtn()`, leaving the button hidden despite active filters being present. ### Added - **Persistent filter state per collection** — search query and filter dropdown values are saved to `localStorage` under `collections_filters_{col}` after every successful table fetch. On page load, if the URL carries no active filters, the saved state is restored and the table is re-fetched automatically. Clicking Clear wipes the saved state so nothing is restored on the next visit. --- ## 1.9.1 — 2026-04-25 ### Added - **Thumbnail size setting** — Global Settings now has a Width × Height input (32–128 px) for preview thumbnails. Previously hardcoded to 32×32. Value stored in `collections_global` as `thumb_width` / `thumb_height`. - **`matrixTypeName()` helper** — safe internal method for reading the matrix type name from a `RepeaterMatrixPage`. Falls back to reading `repeater_matrix_type` integer from the field config when the `matrix()` hook method is unavailable in the current context. - **`matrixTypeN()` helper** — companion to `matrixTypeName()`, returns the matrix type integer index. - **`resolveRepeater()` helper** — normalises a Repeater field value that PW returns as an integer page ID into a `RepeaterPageArray` by loading the container page and calling `->children()`. - **Matrix → Repeater → subfield path** — dot-notation now resolves three-segment paths where the middle segment is a Repeater field on a Matrix item rather than a type name (e.g. `media.property_photos.photos`). - **Combo Checkboxes array support in dot-notation** — `renderDotNotation` now resolves array values (multi-select Checkboxes subfields) through `resolveComboOptionLabel()` and joins them with `, `. ### Fixed - **`RepeaterMatrixPageArray` intercepted by `instanceof PageArray`** — `FieldtypeRepeaterMatrix` dispatch was placed after the generic `PageArray` check, so matrix fields were rendered as plain page-reference arrays. Matrix branch is now checked first. - **`matrix()` hook not callable** — `method_exists()` and `hasMethod()` both fail for the `matrix()` hook method on `RepeaterMatrixPage` in the renderer context. Replaced with `matrixTypeName()` helper that catches exceptions and falls back to `getUnformatted('repeater_matrix_type')`. - **`getUnformatted()` on RepeaterMatrix returns raw IDs** — `renderCellValue()` and `renderDotNotation()` now use `$page->get()` (formatted) for `FieldtypeRepeaterMatrix` and `FieldtypeRepeater` fields. `getUnformatted()` on these types returns a comma-separated string of page IDs rather than a `RepeaterMatrixPageArray`. - **`Pageimages` cast to string showing filenames** — when `instanceof Pageimages` check failed due to missing namespace resolution, the object fell through to `(string) $val` which returns filenames comma-joined. `renderScalarOrObject()` now uses fully-qualified `\ProcessWire\Pageimage` / `\ProcessWire\Pageimages` class names and adds an `is_object()` trap as final guard. - **`SelectableOptionArray` rendering `1` instead of label** — `SelectableOptionArray` extends `WireArray`, so it was caught by the generic `WireArray` branch which cast each item with `(string)` returning the numeric ID. Now explicitly dispatched to `renderOptions()` before the `WireArray` check. - **`Array to string` warning from non-searchable ProField columns** — `buildSelector()` excluded dot-notation columns but still passed `FieldtypeTable` and similar non-searchable fields to the PW `%=` selector, causing internal array-to-string conversion. All non-searchable types now excluded from both text and page-ref search parts. ### Changed - **Sidebar group order** — groups now render in fixed order: **Content → Taxonomy → Custom** (then any other groups alphabetically). Previously the order depended on which group appeared first among the configured collections. Applies to both the sidebar nav (`layout.php`) and the dashboard grid (`dashboard.php`). - **`renderScalarOrObject()`** — all `instanceof` checks now use fully-qualified `\ProcessWire\*` class names to avoid namespace resolution failures in the renderer context. --- ## 1.9.0 — 2026-04-22 ### Added - **ProFields: Repeater Matrix support** — `FieldtypeRepeaterMatrix` fields now render in the collection table. Shows item count with per-type breakdown (e.g. `3 items · hero, text ×2`), using human-readable type labels from field configuration. - **ProFields: Combo support** — `FieldtypeCombo` fields now render in the collection table. Shows the first 1–2 non-empty subfields with their labels. Supports text, numeric, `Page` reference, `PageArray`, and `Pageimage` subfield types. - **Dot-notation column syntax** — any column can now reference a subfield using `field.subfield` syntax (e.g. `address.city`, `address.country`). Works with Combo, Repeater Matrix, and Table fields. - `address.city` — Combo subfield value - `address.ref_field.title` — Combo subfield Page reference chained to a property - `blocks.title` — first Repeater Matrix item's subfield - `blocks.hero.title` — first item of type `hero`, subfield `title` - `prices.amount` — first Table row, named column - `prices.*.amount` — all Table rows, named column, joined with `, ` - **ProFields: Table full render** — `FieldtypeTable` columns now render as a compact inline mini-table with all rows and column headers, using `TableRows::getColumns()` for automatic column detection. - **Table cell type handling** — Table cell values are now rendered by column type: `image` → thumbnail, `file` → filename, `Page` → title, `PageArray` → comma-separated titles, `array` (selectMultiple) → joined string, scalar → text. - **`renderTableCell()` method** — new internal method handling type-aware rendering of individual Table cell values. - **`renderDotNotation()` method** — new internal method resolving dot-notation column paths against Combo, Repeater Matrix, Table, and generic PW field chains. - **`renderScalarOrObject()` method** — new internal helper used by dot-notation rendering; dispatches to the correct renderer based on value type (`Pageimage`, `Page`, `PageArray`, scalar). ### Fixed - **`Array to string conversion` warning** — `buildSelector()` in `Collection.php` was passing `FieldtypeTable` (and other non-searchable ProField types) to PW's `%=` text selector, which internally returned an array when building SQL for multi-table fields. Non-searchable types (`FieldtypeTable`, `FieldtypeRepeaterMatrix`, `FieldtypeCombo`, `FieldtypeRepeater`, `FieldtypeFile`, `FieldtypeImage`) are now explicitly excluded from the search selector. - **Dot-notation columns included in search selector** — `address.city`-style columns were passed to `wire('fields')->get()` and `%=` selectors, causing unknown-field warnings. Dot-notation columns are now skipped in `buildSelector()`. - **Null field in `$allSearchFields`** — `buildSelector()` no longer adds a `%=` part for fields that `wire('fields')->get()` cannot resolve. - **Dark mode bulk bar** — `body:not(.pw-dark)` selector was inverted, applying dark background (`#1f2937`) to the light theme instead of dark. Fixed to `body.pw-dark`. - **Protable hover in dark mode** — `.collections-protable tbody tr:hover td` used hardcoded `#f9f9f9` fallback which rendered as a white flash in dark mode. Replaced with `rgba(0,0,0,0.03)`. - **Dot-notation column CSS class** — `