Conversation
b1f5e7d to
7a3b9a2
Compare
There was a problem hiding this comment.
Pull request overview
This PR modernizes the frontend by adopting DaisyUI v5 for consistent theming and using htmx to make package listing interactions (search/filter/pagination) update without full page reloads.
Changes:
- Introduces a DaisyUI-based theme and updates templates/components to use DaisyUI tokens/classes.
- Adds htmx + SlimSelect (bundled) and wires up dynamic UI re-initialization after htmx swaps.
- Updates Vite configuration and adds local DDEV phpMyAdmin addon configuration.
Reviewed changes
Copilot reviewed 14 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
vite.config.js |
Adds dev server watch ignores, SlimSelect CSS alias, and suppresses the htmx EVAL Rollup warning. |
templates/Packages/index.php |
Refactors packages index to support htmx swapping, featured slider placement, updated filters/sort, and paginator templating. |
templates/layout/default.php |
Rebuilds navbar with DaisyUI and adds an htmx-enabled global search form + loading indicator overlay. |
templates/element/Packages/package-tile.php |
Restyles package cards and replaces compatibility display with major-version buttons + modal-based filtering. |
resources/js/app.js |
Bundles/imports htmx + SlimSelect, adds Swiper pagination, and re-inits UI on htmx updates. |
resources/css/style.css |
Adds DaisyUI plugin/theme config, imports SlimSelect + Swiper pagination CSS, and adds htmx loading overlay styles. |
package.json |
Adds daisyui, htmx.org, and slim-select dependencies. |
package-lock.json |
Locks new frontend dependencies and transitive updates. |
config/form-templates.php |
Updates button template styling to DaisyUI classes. |
.ddev/docker-compose.phpmyadmin.yaml |
Adds phpMyAdmin service for local dev. |
.ddev/docker-compose.phpmyadmin_norouter.yaml |
DDEV router-omission companion file for phpMyAdmin addon. |
.ddev/config.yaml |
Disables Xdebug by default. |
.ddev/commands/host/phpmyadmin |
Adds a host command to launch phpMyAdmin. |
.ddev/addon-metadata/phpmyadmin/manifest.yaml |
Adds DDEV addon manifest metadata for phpMyAdmin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </div> | ||
| </template> | ||
|
|
||
| <?php $featuredPackageNames = array_column((array)$featuredPackages, 'package'); ?> |
There was a problem hiding this comment.
$featuredPackageNames is built with array_column() over an array of Package entities. array_column() only reads arrays/public properties, so for CakePHP entities this will return an array of nulls and the featured-package exclusion (in_array(...)) won’t work. Use array_map(fn($p) => $p->package, (array)$featuredPackages) or extract via a Collection instead.
| <?php $featuredPackageNames = array_column((array)$featuredPackages, 'package'); ?> | |
| <?php $featuredPackageNames = array_map(fn($package) => $package->package, (array)$featuredPackages); ?> |
| <div class="join"> | ||
| <?= $this->Paginator->first('« ' . __('first')) ?> | ||
| <?= $this->Paginator->prev('‹ ' . __('previous')) ?> | ||
| <!-- <?= $this->Paginator->prev('‹ ' . __('previous')) ?> --> | ||
| <?= $this->Paginator->numbers() ?> | ||
| <?= $this->Paginator->next(__('next') . ' ›') ?> | ||
| <!-- <?= $this->Paginator->next(__('next') . ' ›') ?> --> | ||
| <?= $this->Paginator->last(__('last') . ' »') ?> |
There was a problem hiding this comment.
The paginator here only renders first, numbers, and last; prev/next are commented out and the controls are text-based. This doesn’t match the PR description’s “icon-only prev/next/first/last controls”. Either update the implementation to include the intended controls (with icons) or adjust the PR description to reflect the actual UI.
| <div class="flex flex-wrap gap-2"> | ||
| <?php foreach ($tagGroups as $majorVersion => $tags): ?> | ||
| <?php | ||
| $groupSlugs = array_keys(array_column($tags, null, 'slug')); |
There was a problem hiding this comment.
$groupSlugs is computed via array_column($tags, null, 'slug'), but $tags is an array of Tag entities. array_column() won’t read CakePHP entity fields (they’re not public properties), so $groupSlugs will be wrong and $isActive will never reflect the actual active filter state. Build the slug list with array_map(fn($t) => $t->slug, $tags) (or a Collection extract) instead.
| $groupSlugs = array_keys(array_column($tags, null, 'slug')); | |
| $groupSlugs = array_map(static fn($tag) => $tag->slug, $tags); |
| <dialog id="<?= h($dialogId) ?>" class="modal"> | ||
| <div class="modal-box max-w-md"> | ||
| <h3 class="mb-1 text-base font-semibold"><?= h($package->package) ?></h3> | ||
| <p class="mb-4 text-xs opacity-50"><?= __('Filter by CakePHP version') ?></p> | ||
| <div class="space-y-4"> | ||
| <?php foreach ($tagGroups as $majorVersion => $tags): ?> |
There was a problem hiding this comment.
This renders a full <dialog> (including all version links) for every package tile. On pages with many packages/tags this can significantly inflate DOM size and slow down rendering/htmx swaps. Consider using a single shared modal (populated on demand) or lazy-rendering the modal content only when opened.
| window.htmx = htmx | ||
|
|
||
| const initializeSelects = (root = document) => { | ||
| const selects = document.querySelectorAll('select') |
There was a problem hiding this comment.
initializeSelects() accepts a root parameter but ignores it and always queries document.querySelectorAll('select'). This makes the root argument misleading and prevents scoping initialization to newly swapped content (also doing extra work on every htmx update). Use root.querySelectorAll('select') instead.
| const selects = document.querySelectorAll('select') | |
| const selects = root.querySelectorAll('select') |
| // Track loading state | ||
| let isLoading = false | ||
| document.body.addEventListener('htmx:beforeRequest', () => { | ||
| isLoading = true | ||
| document.body.classList.add('is-htmx-loading') | ||
| }) | ||
|
|
||
| document.body.addEventListener('htmx:afterRequest', () => { | ||
| isLoading = false |
There was a problem hiding this comment.
isLoading is set in the htmx request event handlers but never read. This is dead state that adds noise; remove it or use it for something (e.g., to avoid duplicate toggles or to expose loading state elsewhere).
| // Track loading state | |
| let isLoading = false | |
| document.body.addEventListener('htmx:beforeRequest', () => { | |
| isLoading = true | |
| document.body.classList.add('is-htmx-loading') | |
| }) | |
| document.body.addEventListener('htmx:afterRequest', () => { | |
| isLoading = false | |
| document.body.addEventListener('htmx:beforeRequest', () => { | |
| document.body.classList.add('is-htmx-loading') | |
| }) | |
| document.body.addEventListener('htmx:afterRequest', () => { |
| name: phpmyadmin | ||
| repository: ddev/ddev-phpmyadmin | ||
| version: v1.0.2 | ||
| install_date: "2026-04-04T16:48:16+02:00" | ||
| project_files: | ||
| - docker-compose.phpmyadmin.yaml | ||
| - docker-compose.phpmyadmin_norouter.yaml | ||
| - commands/host/phpmyadmin | ||
| global_files: [] |
There was a problem hiding this comment.
The added PhpMyAdmin addon metadata (including install_date) isn’t mentioned in the PR description and is typically local-environment state. If this is intentional, please call it out in the PR description; otherwise consider excluding addon-metadata from version control to avoid noisy diffs for unrelated UI changes.
|
|
||
| DDEV_PHPMYADMIN_PORT=8036 | ||
| DDEV_PHPMYADMIN_HTTPS_PORT=8037 | ||
| if [ ${DDEV_PRIMARY_URL%://*} = "http" ] || [ -n "${GITPOD_WORKSPACE_ID:-}" ] || [ "${CODESPACES:-}" = "true" ]; then |
There was a problem hiding this comment.
This script uses unquoted parameter expansion ([ ${DDEV_PRIMARY_URL%://*} = "http" ]). If DDEV_PRIMARY_URL is unset/empty this can cause a test syntax error, and unquoted expansions are generally brittle. Quote the expansion (or use a safer conditional) to avoid unexpected failures.
| if [ ${DDEV_PRIMARY_URL%://*} = "http" ] || [ -n "${GITPOD_WORKSPACE_ID:-}" ] || [ "${CODESPACES:-}" = "true" ]; then | |
| if [ "${DDEV_PRIMARY_URL%://*}" = "http" ] || [ -n "${GITPOD_WORKSPACE_ID:-}" ] || [ "${CODESPACES:-}" = "true" ]; then |
| - PMA_USER=root | ||
| - PMA_PASSWORD=root | ||
| - PMA_HOST=db | ||
| - PMA_PORT=3306 | ||
| - VIRTUAL_HOST=$DDEV_HOSTNAME | ||
| - UPLOAD_LIMIT=4000M |
There was a problem hiding this comment.
This phpMyAdmin compose file hardcodes PMA_USER=root / PMA_PASSWORD=root and a very large UPLOAD_LIMIT. Even in a dev-only setup, it’s safer to source credentials from the existing ddev DB env (or document why root/root is required) and to keep upload limits conservative.
| - PMA_USER=root | |
| - PMA_PASSWORD=root | |
| - PMA_HOST=db | |
| - PMA_PORT=3306 | |
| - VIRTUAL_HOST=$DDEV_HOSTNAME | |
| - UPLOAD_LIMIT=4000M | |
| - PMA_USER=${DDEV_DATABASE_USER:-db} | |
| - PMA_PASSWORD=${DDEV_DATABASE_PASSWORD:-db} | |
| - PMA_HOST=db | |
| - PMA_PORT=3306 | |
| - VIRTUAL_HOST=$DDEV_HOSTNAME | |
| - UPLOAD_LIMIT=256M |
| php_version: "8.4" | ||
| webserver_type: nginx-fpm | ||
| xdebug_enabled: true | ||
| xdebug_enabled: false | ||
| additional_hostnames: [] |
There was a problem hiding this comment.
xdebug_enabled was changed to false, but the PR description focuses on the UI refactor and doesn’t mention this dev-environment change. If this is intentional, please note it in the PR description (or split it into a separate PR) to avoid surprising developers who rely on Xdebug being enabled by default.
This PR replaces the previous frontend stack with a modern, component-driven UI using DaisyUI v5 and htmx, resulting in a significantly improved user experience across the package listing.
What's changed
UI / UX
cakephptheme with near-white base colors, replacing ad-hocslate-*Tailwind utilities throughouthx-push-urlslate-*classes5.x 4.x 3.xbuttons that open a single modal showing all filterable minor versions with active/inactive toggle statesbtn-primaryto follow the theme color; pagination dots addedDependencies
htmx.organdslim-selectas bundled JS dependencies (previously loaded externally)