Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/thelia/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "thelia",
"description": "Develop Thelia 3 with Claude Code: skills and agents for modules, Propel ORM, the API Platform bridge, the Flexy front-office, the default-twig back-office, and Thelia 2 to 3 migration.",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "Thelia"
},
Expand Down
59 changes: 50 additions & 9 deletions plugins/thelia/skills/fresh-install-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,26 @@ Reusable validation protocol for a clean Thelia 3 installation. Run it after any

- DDEV installed and running
- SSH access to the GitHub repos under `thelia/*` for the dev-repo scenario
- **A GitHub token available to Composer** (see the trap below). Without it the install fails, and the error message points nowhere near the cause.
- The target workspace directory must be empty (the protocol deletes and recreates it)

Thelia 3 ships as tagged releases; there is no development branch to install from. Test 1 clones the development repository, whose default branch is `main`. Test 2 installs the published packages. While `3.0.0-beta1` is the newest tag, the skeleton needs `--stability=beta` (or an explicit `thelia/thelia-project:^3.0.0-beta1`), and a project's own `composer.json` needs `"minimum-stability": "beta"` with `"prefer-stable": true`.
Thelia 3 ships as tagged releases; there is no development branch to install from. Test 1 clones the development repository, whose live branch is `main`. Test 2 installs the published packages. The skeleton needs `--stability=beta` (or an explicit version), and a project's own `composer.json` needs `"minimum-stability": "beta"` with `"prefer-stable": true`.

Current tags: `3.0.0-beta3` for `thelia/thelia`, `thelia/core` and `thelia/setup`; `3.0.0-beta5` for `thelia/thelia-project`. Each template moves on its own track: Flexy `1.0.0-beta7`, default-twig back-office `1.0.0-beta7`, PDF `1.0.0-beta6`, legacy back and email `1.0.0-beta4`. The skeleton and the templates are versioned independently of the core, so do not expect the numbers to line up.

### Trap: a missing GitHub token fails the install far from its cause

Composer needs a GitHub token to reach `thelia/thelia-recipes`. Without one, Symfony Flex does not fail loudly: it falls back to auto-generated recipes, so none of Thelia's `config/packages/*.yaml` files are ever written. The install then dies much later on an unrelated-looking message:

```
You must either configure a "public_key" or a "secret_key"
```

Symptom and cause have nothing in common. Before debugging that message, check that `config/packages/` holds Thelia's own configuration files and that Composer is authenticated:

```bash
composer config --global --list | grep github-oauth # or check auth.json / COMPOSER_AUTH
```

---

Expand All @@ -37,7 +54,8 @@ Thelia 3 ships as tagged releases; there is no development branch to install fro
WORKSPACE=<path-to-your-workspace>

PROJECT=thelia-3
BRANCH=main # replace with the branch or tag you want to test
BRANCH=main # the live branch; replace with a tag to test a release
# `twig` is a frozen legacy branch, do not test against it

# 1. Full cleanup
ddev stop --unlist $PROJECT 2>/dev/null
Expand Down Expand Up @@ -72,10 +90,10 @@ ddev exec php bin/install \
# "N module(s) post-activated." (count varies with installed modules)
# "User thelia successfully created."
# No "ERROR:" lines anywhere
# The front-office assets built by bin/install itself (importmap:install, then tailwind:build)

# 6. Build the Flexy front-end theme
ddev exec bash -c "cd templates/frontOffice/flexy && npm install && npm run build"
# Expected: "webpack compiled successfully"
# 6. Build the back-office theme (bin/install does NOT do this one)
ddev exec bash -c "cd templates/backOffice/default-twig && npm install && npm run build"

# 7. Verify the home page
curl -sk https://$PROJECT.ddev.site/ | wc -c
Expand Down Expand Up @@ -106,6 +124,7 @@ ddev exec php bin/console debug:container --deprecations | head -3

- [ ] Install completes with zero errors
- [ ] 4x "Theme ready !" in install output
- [ ] Front-office assets built by `bin/install`; back-office assets built manually
- [ ] Home page returns more than 50 KB with demo products and images
- [ ] Admin login page is accessible
- [ ] Symfony 7.4.x reported
Expand All @@ -131,7 +150,7 @@ rm -rf "$WORKSPACE/$PROJECT"
# 2. Create the project from the tagged release
cd "$WORKSPACE"
composer create-project --stability=beta thelia/thelia-project $PROJECT
# Equivalent, pinned: composer create-project thelia/thelia-project:^3.0.0-beta1 $PROJECT
# Equivalent, pinned: composer create-project thelia/thelia-project:3.0.0-beta5 $PROJECT
cd $PROJECT

# 3. Configure DDEV (MariaDB version can vary by host)
Expand All @@ -151,8 +170,8 @@ ddev exec php bin/install \
--admin_first_name=thelia --admin_last_name=thelia \
--admin_email=thelia@example.com

# 6. Build the Flexy front-end theme
ddev exec bash -c "cd templates/frontOffice/flexy && npm install && npm run build"
# 6. Build the back-office theme (the front-office assets are already built by bin/install)
ddev exec bash -c "cd templates/backOffice/default-twig && npm install && npm run build"

# 7. Verify the home page
curl -sk https://$PROJECT.ddev.site/ | wc -c
Expand All @@ -170,7 +189,7 @@ ddev exec php -r 'require "vendor/autoload.php"; echo Symfony\Component\HttpKern
- `bootstrap.php` must NOT load `vendor/autoload.php` (doing so disables the Symfony Runtime via its `require_once` guard).
- `public/index.php` must load `bootstrap.php` first, then `vendor/autoload_runtime.php`.
- `bin/console` passes through `vendor/thelia/core/Thelia`, not the standard Symfony pattern.
- Constraints in the generated `composer.json`: `^3.0.0-beta1` for `thelia/core` and the skeleton, `^1.0.0-beta1` for the templates, the module's current major for `thelia/*-module`, plus `"minimum-stability": "beta"` and `"prefer-stable": true`.
- Constraints in the generated `composer.json`: `^3.0.0-beta` for `thelia/core`, `^1.0.0-beta` for the templates, the module's current major for `thelia/*-module`, plus `"minimum-stability": "beta"` and `"prefer-stable": true`.

---

Expand All @@ -189,8 +208,30 @@ The most common problems encountered in modules:

---

## Front-office assets: automatic; back-office assets: manual

`bin/install` builds the front-office assets itself, running `importmap:install` then `tailwind:build` when those commands are available. There is nothing to run by hand for Flexy, and no `npm install` either: the theme uses AssetMapper and a Tailwind CLI binary, not Node.

The back-office theme is the exception. Its compiled `dist/` is gitignored, so it ships absent from the package and has to be built once after install:

```bash
ddev exec bash -c "cd templates/backOffice/default-twig && npm install && npm run build"
```

An admin that renders unstyled is this build missing, not a configuration problem.

## Known issues

**LiveComponents return 404:** a project whose `config/routes/ux_live_component.yaml` predates the current recipe is missing `ignore_thelia_view: true` on the `/_components` route. Without it, `Thelia\Core\EventListener\ViewListener` answers `kernel.view` with a themed view, which matches no front-office view, so every LiveComponent round-trip 404s. Flex never rewrites a file that already exists, so an upgraded project keeps the old one. Add the default by hand:

```yaml
live_component:
resource: '@LiveComponentBundle/config/routes.php'
prefix: '/_components'
defaults:
ignore_thelia_view: true
```

**Blank page on thelia-project:** if `bootstrap.php` loads `vendor/autoload.php`, the Symfony Runtime silently deactivates itself because of the `require_once` guard returning `true`. Fix: load only constants in `bootstrap.php`.

**Test suite targeting the wrong database:** `.env.test` must set `DATABASE_NAME=test`, not `db`. If tests are still hitting the dev database, delete `var/propel/test/` to flush the Propel DSN cache.
Expand Down
2 changes: 1 addition & 1 deletion plugins/thelia/skills/thelia3-backoffice-twig/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Hook dispatch isolates each listener, so one listener that throws does not take

- When building a creation form, pass `'locale' => $request->getLocale()` (not the site default) and set `'visible' => true` explicitly. Otherwise new entities save with the default locale and `visible = 0` regardless of what the UI showed.
- When you render a field manually as raw HTML, Symfony does not know it was rendered, so `form_end()` outputs it a second time. Call `{% do form.X.setRendered %}` after the manual render.
- Theme each form explicitly; do not rely on a global theme. The one global form theme is the front-office Flexy theme, and it wins the global lookup, so a back-office form with no local theme renders Flexy `FieldInput` widgets. Opt in per form root with `{% form_theme form with bo_form_themes only %}`, where `bo_form_themes` is a Twig global listing `bootstrap_5_layout.html.twig` (the Bootstrap base) then `@BackOfficeDefaultTwigForm/bo_form_theme.html.twig` (the back-office overrides). The `only` keyword excludes the global Flexy theme, so a back-office form can never pick up a Flexy widget. The back-office theme file holds only the back-office refinements (label, row spacing, checkbox); everything else comes from `bootstrap_5_layout`.
- Theme each form explicitly; do not rely on a global theme. Neither theme registers itself globally any more: Flexy scopes its own widgets with `{% form_theme form with flexy_form_themes only %}` and the back-office does the same with its own list. A back-office form with no `form_theme` tag therefore falls back to Symfony's bare default, not to Bootstrap. Opt in per form root with `{% form_theme form with bo_form_themes only %}`, where `bo_form_themes` is a Twig global listing `bootstrap_5_layout.html.twig` (the Bootstrap base) then `@BackOfficeDefaultTwigForm/bo_form_theme.html.twig` (the back-office overrides). The back-office theme file holds only the back-office refinements (label, row spacing, checkbox); everything else comes from `bootstrap_5_layout`.
- Place the `{% form_theme %}` tag inside the rendered block (typically `{% block content %}`), right before `form_start`. A `{% form_theme %}` outside any block in a template that uses `{% extends %}` is silently ignored by Twig. One tag per form view; it propagates to the field partials you include with `{% include ... with {form: form} %}`.
- Do not write `{% form_theme form '@BackOfficeDefaultTwigForm/bo_form_theme.html.twig' only %}` (a single theme): it throws a 500 (`getTemplateClass(): ... null given`) on `form_start`, because `{% use %}` is not transitive enough under `only` and there is no global fallback. Always list `bootstrap_5_layout.html.twig` first, which is exactly what the `bo_form_themes` global does.
- Module form extensions that add fields through `FORM_BEFORE_BUILD` and `FORM_AFTER_BUILD` events are dead in a Symfony-native form unless a bridge dispatches those legacy events after `createNamed()`. Module-added fields are then rendered by the module's own hook, not by `form_rest`, and `form_end` uses `{render_rest: false}`.
Expand Down
9 changes: 5 additions & 4 deletions plugins/thelia/skills/thelia3-module-migration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat

| Aspect | Thelia 2 | Thelia 3 | Impact |
|---|---|---|---|
| PHP | 8.0 - 8.2 | **8.3+** | strict_types, modern types |
| PHP | 8.0 - 8.2 | **8.3 or 8.4** | strict_types, modern types |
| Symfony | 6.0 - 6.3 | **7.4 LTS** | PHP 8 attributes, MapRequestPayload, Voters |
| API | API Platform 3.x | **4.3 standalone** | Propel-based resources, addons |
| Front templates | Smarty `.html` | **Twig `.html.twig`** (Flexy) | Full template rewrite |
Expand All @@ -26,6 +26,7 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat
| Database namespace | `Thelia\Install\Database` | `Thelia\Core\Install\Database` | Update use statement |
| `configureServices` exclude | `THELIA_MODULE_DIR` constant | Relative path (`__DIR__.'/I18n/*'`) | Simpler |
| API auth | None native | **JWT Lexik 3.2** + `/api/{front\|admin}/login` | Token-based |
| Front assets | Webpack Encore + npm | **AssetMapper + Tailwind CLI** (`importmap.php`, no `package.json`) | Drop the module's Encore entrypoints |

---

Expand Down Expand Up @@ -73,7 +74,7 @@ Keep in config.xml if still needed:
| `{intl l='Hello'}` | `{{ 'Hello'\|trans({}, 'mymodule') }}` |
| `{form name="thelia.customer.login"}` | `{% set form = getForm('thelia.customer.login') %}{{ form_start(form) }}` |
| `{hook name="product.top" product=$product}` | Front: implement `Thelia\Core\Hook\Theme\ThemeHookInterface` answering a point the theme declares (Flexy: `theme_hook('product.top', {product: product})`). BO: `BaseHook` unchanged. |
| `{include file="..."}` | `{% include '@components/...' %}` |
| `{include file="..."}` | `{% include '@Flexy/...' %}` |
| `{assign var=...}` | `{% set ... %}` |
| Complex conditional loop | `{% if %}{% else %}{% endif %}`, Twig filters |

Expand Down Expand Up @@ -178,7 +179,7 @@ T2:

T3:
```php
#[AsLiveComponent(name: 'Flexy:CartItem')]
#[AsLiveComponent]
class CartItem
{
use DefaultActionTrait, ComponentToolsTrait;
Expand Down Expand Up @@ -482,7 +483,7 @@ Payment modules that type-hint the old OpenApi event class (PayPal, Payzen, Cawl
| Stale Twig cache after template override | `cache:clear` |
| `module_template_dirs.php` stale | `cache:clear` after activation |
| `var/propel/test/` cache pointing at wrong database | `bin/test-prepare` auto-purges it |
| `THELIA_VERSION` still read as `'2.6.0'` | The T3 constant is `'3.0.0-beta1'`; a stale value means the old core is still autoloaded |
| `THELIA_VERSION` still read as `'2.6.0'` | The T3 constant carries the current 3.0 beta; a 2.x value means the old core is still autoloaded |
| `getPropelRelatedTableMap()` returns null on concrete resource | Always return `new XxxTableMap()` |
| LiveProp with Propel object | Use DTOs or scalar values only |
| `resources()` called from CLI | Unusable, throws `RuntimeException` (no main request). Add a guard or avoid. |
Expand Down
37 changes: 32 additions & 5 deletions plugins/thelia/skills/thelia3-tooling/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: thelia3-tooling
description: "Operational gotchas when developing and testing Thelia 3: the Thelia console versus bin/console, a stale PHPStan result cache on Propel classes, PHPUnit 11 failing on deprecated XML, JWT keys for the API test suite, and rebuilding a theme's compiled assets after composer update. Use when a Thelia command, the test suite, PHPStan, JWT auth, or a theme's assets behave in a way the code does not explain."
description: "Operational gotchas when developing and testing Thelia 3: the Thelia console versus bin/console, a stale PHPStan result cache on Propel classes, PHPUnit 11 failing on deprecated XML, JWT keys for the API test suite, front-office assets built by bin/install versus the back-office theme built by hand, a missing GitHub token that fails an install with an unrelated error, and LiveComponents answering 404. Use when a Thelia command, an install, the test suite, PHPStan, JWT auth, or a theme's assets behave in a way the code does not explain."
---

# Thelia 3 tooling
Expand Down Expand Up @@ -32,12 +32,39 @@ A deprecated attribute in `phpunit.xml` (for example `cacheResultFile`, or the o

If the dev and test environments share the same `config/jwt/` keypair but use different passphrases, the API test suite cannot decrypt the key and reports `bad decrypt`. The fix is to regenerate the keypair without a passphrase, so the unencrypted key loads in both environments. Suspect this before any application bug when the API suite turns red right after a restart or a test database rebuild.

## composer update deletes a theme's compiled assets
## Which theme assets are built for you, and which are not

Running `composer update` on a Thelia theme removes its compiled `dist/` directory. The next page render then throws a Twig error such as "Could not find the entrypoints file from Webpack". Rebuild the assets locally after an update:
`bin/install` builds the front-office assets itself: it runs `importmap:install` then `tailwind:build` when those commands exist. Flexy is an AssetMapper plus Tailwind CLI theme, so there is no `npm install` and no bundler step to run by hand.

The back-office theme is the exception. Its compiled `dist/` is gitignored and therefore absent from the published package, so it has to be built once:

```bash
cd templates/frontOffice/<theme> && npm install && npm run build
cd templates/backOffice/default-twig && npm install && npm run build
```

An admin that renders with no styling is this missing build, not a broken configuration. Rebuild it after a `composer update` on the theme too, since the update replaces the package directory and takes `dist/` with it.

## A missing GitHub token fails an install on an unrelated message

Composer needs a GitHub token to fetch `thelia/thelia-recipes`. Without one, Symfony Flex does not stop: it falls back to auto-generated recipes, so Thelia's `config/packages/*.yaml` files are never written. The install proceeds and dies much later on a message that names none of this:

```
You must either configure a "public_key" or a "secret_key"
```

Check Composer's authentication and the contents of `config/packages/` before reading that message literally.

## LiveComponents return 404

The `/_components` route must carry `ignore_thelia_view: true` in its defaults. Without it, `Thelia\Core\EventListener\ViewListener` answers `kernel.view` with a themed view, `/_components/...` matches no front-office view, and every LiveComponent round-trip answers 404. The current `thelia/thelia-recipes` recipe sets it, but Flex never rewrites a routing file that already exists, so a project created before it keeps the old one:

```yaml
# config/routes/ux_live_component.yaml
live_component:
resource: '@LiveComponentBundle/config/routes.php'
prefix: '/_components'
defaults:
ignore_thelia_view: true
```

CI usually recreates `dist/` at deploy time, so the committed change is the lockfile, not the build output.
The same flag is the general opt-out from Thelia's view rendering: any route whose controller returns something the theme should not wrap needs it in its defaults. Nothing in the core ever sets it; it comes from route configuration only.
Loading
Loading