Introduction
Step-by-step guide for migrating legacy Nette Framework projects to the Demoweb template services architecture (Nette 3.1, PHP 8.1). Follow steps in order. Do not skip steps.
Prerequisites
| Requirement | Details |
|---|---|
| Demoweb template base | Available at ~/projects/81/Demoweb/ |
| PHP 8.1 | Target runtime for all migrated projects |
| Database access | Local MySQL with dev_rafa credentials |
| Reference project | SynekAuto or penzionusynka — already migrated |
| GitLab access | Read access to old repo, write to feature branch |
| Node.js 18 | Required for npm install && npx mix |
Migration Overview
| Step | Name | Output |
|---|---|---|
0 |
Project Setup | Project cloned to correct folder |
1 |
Backup | {ProjectName}_old copy exists |
2 |
Copy Demoweb | New base installed, .git restored |
3 |
Database | DB structure aligned with demoweb_services |
4 |
Supplement IDs | IDs verified, ?? → ?: applied |
5 |
Front Presenters | BasePresenter + HomepagePresenter migrated |
6 |
Latte Templates | Templates copied, Latte 2.11 fixes applied |
7 |
LESS → SASS | All styles converted to SCSS |
8 |
webpack.mix.js | Assets compile successfully |
9 |
config.neon | Services and DB configured |
10 |
GitLab CI | Deploy config verified |
11 |
Testing | All pages return HTTP 200, no Tracy errors |
12 |
Commit & Push | Feature branch pushed, deploy verified |
Step 0 — Project Setup
Clone the project and determine the correct target folder based on PHP version.
Ask the user
- What is the Git clone URL of the project?
- What is the project name? (e.g.,
autosynek,penzionusynka)
Folder selection
Check composer.json → require.php. Target is always ~/projects/81/ regardless of current version.
| PHP version in composer.json | Current folder | Target folder |
|---|---|---|
>= 8.1 |
~/projects/81/ |
~/projects/81/{ProjectName}/ |
>= 7.4 |
~/projects/74/ |
~/projects/81/{ProjectName}/ |
>= 7.1 |
~/projects/71/ |
~/projects/81/{ProjectName}/ |
If the project already exists in a non-81 folder, that becomes the "old" reference used in Step 1.
Step 1 — Backup
Create a clean copy of the old project before overwriting anything.
cp -r ~/projects/{version}/{ProjectName} ~/projects/{version}/{ProjectName}_old
rm -rf ~/projects/{version}/{ProjectName}_old/.git
rm -rf ~/projects/{version}/{ProjectName}_old/.idea
rm -rf ~/projects/{version}/{ProjectName}_old/node_modules
Verification
{ProjectName}_old/exists and contains source files.git,.idea,node_modulesare NOT present in the backup
Step 2 — Erase and Copy Demoweb
Replace project contents with the Demoweb template base, then restore Git history and local config.
Before erasing — save these files
.git/directoryapp/config/config.local.neon.gitlab-ci.yml
cp -r ~/projects/81/Demoweb/* ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.htaccess ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.gitignore ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.deploy_ignore ~/projects/81/{ProjectName}/
mkdir -p temp/cache && chmod 777 temp temp/cache
Do NOT copy .git, .idea, or node_modules from Demoweb. Restore the project's own .git and .gitlab-ci.yml after copying.
Step 3 — Connect Database and Compare Structure
Align the project database with the demoweb_services schema.
Local database connection
database:
dsn: 'mysql:host=mysql-rafa;dbname={ProjectName}'
user: dev_rafa
password: 'WEKdHaYVsUyO/09aTSZKQQ=='
Passwords containing =, :, or special characters must be wrapped in quotes.
Compare tables
diff \
<(mysql -u dev_rafa -p {ProjectName} -e "SHOW TABLES" | sort) \
<(mysql -u dev_rafa -p demoweb_services -e "SHOW TABLES" | sort)
What to import
- Missing tables from
demoweb_services(use--single-transaction,SET FOREIGN_KEY_CHECKS=0) - Missing columns via
ALTER TABLE - Missing
settings_configrows:ADMIN_TAB_*,ADMIN_LOGO_SRC,IS_ESHOP - Missing
settings_translationrows - Missing
modulerows
Step 4 — Check MenuRepository Supplement IDs
Verify supplement ID mapping and fix the null coalescing operator issue.
The menuRepository contains a $supp_x_config array mapping supplement names to IDs. These must match the actual supplement table in the database.
CRITICAL: Old databases store empty strings "" instead of null. Replace ?? with ?: (elvis operator) in:
app/presenters/Admin/ApiPresenter.php— all$names[...] ?? ...patterns- All front filters that access
$item['supplements'][...]
| Wrong | Correct |
|---|---|
$item['supplements']['Name'] ?? 'fallback' |
$item['supplements']['Name'] ?: 'fallback' |
Step 5 — Move Front Presenters
Do NOT replace Demoweb's BasePresenter with the old one. Start from Demoweb's version and add project-specific logic on top.
BasePresenter — what to modify
- Modify
$menusand$pagesstatic arrays to match old project values - Add project-specific injected services (filters, repositories)
- Replace
beforeRender()content with project-specific logic - Add
createComponentContact()andcreateComponentSearch()if needed - Add
paginate(),createComponentVisualPaginator(),lang()methods - Keep the
Translatortrait from Demoweb - Remove unused Demoweb dependencies:
WebMutations,CookieSettings,Instagram, etc.
Presenters to remove (unless used by the project)
AccountPresenterEshopPresenterGoPayPresenterInstagramPresenterPohodaPresenterTypeaheadPresenter
RegisterAndLoginPresenter
Use Demoweb's version. Override beforeRender() to NOT call parent::beforeRender() — set $web directly from settings instead.
Step 6 — Move Front Latte Templates
rm -rf app/presenters/Front/templates
cp -r {ProjectName}_old/app/presenters/Front/templates app/presenters/Front/templates
cp ~/projects/81/Demoweb/app/presenters/Front/templates/RegisterAndLogin/*.latte \
app/presenters/Front/templates/RegisterAndLogin/
Latte 2.11 Compatibility Fixes
Apply to ALL .latte files in app/presenters/Front/templates/:
| Old syntax | New syntax |
|---|---|
{!$var} |
{$var|noescape} |
{!$var|filter} |
{$var|filter|noescape} |
{? expr} |
{do expr} |
{$template->elixir('path')} |
{='path'|elixir} |
{$template->imageExist('path', 'default')} |
{('path'|imageExist:'default')} |
Use a PHP script for replacements — sed/perl often breaks with quotes in Latte syntax.
<?php
$files = glob('app/presenters/Front/templates/**/*.latte');
foreach ($files as $file) {
$c = file_get_contents($file);
$c = preg_replace('/\{!\$([^}|]+)\|([^}]+)\}/', '{$$1|$2|noescape}', $c);
$c = preg_replace('/\{!\$([^}]+)\}/', '{$$1|noescape}', $c);
$c = str_replace("{? ", "{do ", $c);
file_put_contents($file, $c);
}
Front Filters — required fixes
| Old | New |
|---|---|
extends Nette\Object |
use Nette\SmartObject; trait |
$item['supplements']['X'] ?? $fb |
$item['supplements']['X'] ?: $fb |
Html::add() |
Html::addHtml() |
->class('x') |
Html::el('div class="x"') |
->href('x') |
->setAttribute('href', 'x') |
Step 7 — Convert LESS to SASS
- Copy old LESS files to
resources/sass/{project}-front/ - Rename
.less→.scss, add_prefix for partials - Install
bootstrap-sass@3.3.7 - Replace Bootstrap LESS with:
@import "~bootstrap-sass/assets/stylesheets/bootstrap";
Syntax conversion table
| LESS | SCSS |
|---|---|
@variable |
$variable (not @media, @import, @keyframes) |
@{variable} |
#{$variable} |
~"string" |
string |
.mixin-name() { } |
@mixin mixin-name() { } |
.mixin-name; |
@include mixin-name; |
spin() |
adjust-hue() |
@import (inline) "file" |
@import "file" |
@import "file.less" |
@import "file" |
Step 8 — Update webpack.mix.js
// front - {ProjectName}
mix.sass('resources/sass/{project}-front/app.scss', 'assets/css/front.css')
.combine([
'resources/js/jquery.js',
'resources/js/bootstrap.js',
'resources/js/netteForms.js',
// add project-specific JS from old Gulpfile.js
], 'www/assets/js/front.js');
// admin (keep Demoweb config)
mix.sass('resources/sass/admin/app.scss', 'assets/admin/app.css');
fnm use 18 && npm install && npx mix --production
Step 9 — Update config.neon
- Use Demoweb's
config.neonas base - Update database credentials for production
- Quote passwords with special characters
- Add project-specific
Front\Filter\*services - Keep all Demoweb admin services
Step 10 — Update .gitlab-ci.yml
| Variable | Value |
|---|---|
PROJECT_NAME |
Must match the old project name exactly |
DEVELOP_DOMAIN |
czechdevelo |
DEVELOP_DIR |
/www/hosting/czechdevelo.cz |
chown |
$DEVELOP_USER:$DEVELOP_USER and $PRODUCTION_USER:$PRODUCTION_USER |
CRITICAL: Verify DEVELOP_DOMAIN and DEVELOP_DIR against the real server before the first push. Wrong values here caused 2 of the 3 fix commits in penzionusynka.
Step 11 — Testing
Verify all pages return HTTP 200 with no Tracy errors.
# Front pages
for page in "/" "cs/m-{id}-{name}"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" \
"https://{project}.rafa.lat04.vas-server.cz/$page" -u claude:radegast)
echo "$STATUS → /$page"
done
# Admin pages
for page in "admin/login" "admin" "admin/menu" "admin/fotogalerie" \
"admin/eshop" "admin/nastaveni" "admin/uzivatele"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" -L \
"https://{project}.rafa.lat04.vas-server.cz/$page" -u claude:radegast)
echo "$STATUS → /$page"
done
# Check Tracy errors
curl -sk "URL" -u claude:radegast | grep -c "tracy-section--error"
Expected result: 0 Tracy errors on every page.
Step 12 — Commit and Push
feat({ticket-id}): migrate project to Demoweb template services
Steps 0–11 completed. Database aligned, templates converted to
Latte 2.11, LESS migrated to SCSS, CI deploy configured.
Refs: https://app.freelo.io/task/{ticket-id}
After pushing, verify the deploy to czechdevelo completes without errors and the site loads correctly.
Common Gotchas
1. Cache permissions
Apache runs as www-data, CLI as rafa. When cache breaks after deploy, recreate the temp directory:
mv temp temp_old && mkdir -p temp/cache && chmod 777 temp temp/cache
2. NEON password parsing
Passwords containing =, :, or special characters must be quoted in .neon files or the parser will fail silently.
3. Supplements empty strings vs null
Old databases store "" instead of null. Use ?: (elvis), never ?? (null coalescing) when accessing supplement values.
4. Html::add() removed in Nette 3.x
Use addHtml() or addText() instead. Html::add() will throw a fatal error.
5. Html magic methods
->class(), ->href() cause errors in Nette 3.x. Use Html::el('div class="x"') or ->setAttribute('href', 'x').
6. Latte {!} noescape
In Latte 2.11, {!} means boolean negation inside filters, NOT noescape. Always use |noescape explicitly.
7. $template->filter() in Latte 2.11
Does not work. Use {='value'|filter} or {$var|filter} syntax instead.
8. Route::$defaultFlags removed
Delete the line entirely — it does not exist in Nette 3.x.
9. Route constants renamed
Route::FILTER_IN/OUT → Route::FilterIn/FilterOut. Using RouteAlias::FILTER_IN also works.
10. Security classes renamed
Nette\Security\Identity → SimpleIdentity. IAuthenticator → Authenticator. Passwords must be injected, not used statically.
Lessons from penzionusynka fix commits
- temp/log dirs in CI deploy — Add
mkdir -p temp/cache log && chmod 777 temp temp/cache logto the deploy job, not just locally. - chown must match reference project — Check SynekAuto's
.gitlab-ci.ymlfor the exactchownsyntax before writing your own. - DEVELOP_DOMAIN and DEVELOP_DIR — Verify against the real server before the first push.
Migration Checklist
Step 0 — Project Setup
- Git clone URL obtained
- Project name confirmed
- PHP version checked in
composer.json - Project placed in
~/projects/81/{ProjectName}/
Step 1 — Backup
{ProjectName}_old/created.git,.idea,node_modulesremoved from backup
Step 2 — Copy Demoweb
- Demoweb copied to project folder
.gitrestored from original projectconfig.local.neonrestored.gitlab-ci.ymlrestored from old projecttemp/cachecreated withchmod 777
Step 3 — Database
config.local.neonpoints to correct DB- Tables compared against
demoweb_services - Missing tables imported
- Missing columns added via
ALTER TABLE settings_configrows importedsettings_translationrows importedmodulerows imported
Step 4 — Supplement IDs
- IDs in
$supp_x_configverified against DB - All
??replaced with?:in ApiPresenter and filters
Step 5 — Front Presenters
- BasePresenter adapted from Demoweb base
- HomepagePresenter copied and updated
- Unused Demoweb presenters removed
- RegisterAndLoginPresenter uses Demoweb version
Step 6 — Latte Templates
- Templates copied from old project
- RegisterAndLogin templates from Demoweb
{!$var}→|noescapeapplied{? expr}→{do expr}applied- Elixir and imageExist filter syntax updated
- Front filters updated (SmartObject, Html methods)
Step 7 — LESS → SASS
- LESS files copied to
resources/sass/{project}-front/ - Files renamed to
.scss bootstrap-sass@3.3.7installed- LESS syntax converted to SCSS
Step 8 — webpack.mix.js
- SASS entry point configured
- JS files combined correctly
npx mix --productionsucceeds without errors
Step 9 — config.neon
- Based on Demoweb config
- Production DB credentials set
- Project-specific filters registered
Step 10 — GitLab CI
PROJECT_NAMEmatches old projectDEVELOP_DOMAIN=czechdeveloDEVELOP_DIR=/www/hosting/czechdevelo.czchownmatches SynekAuto referencemkdir -p temp/cache login deploy job
Step 11 — Testing
- All front pages return HTTP 200
- All admin pages return HTTP 200
- All zeroadmin pages return HTTP 200
- Zero Tracy errors on all pages
Step 12 — Commit & Push
- Conventional commit message with ticket ID
- Pushed to feature branch
- Deploy to czechdevelo verified
- Site loads correctly on czechdevelo domain
Reference Projects
SynekAuto
First successfully migrated project. Primary reference for CI/CD configuration and server ownership.
.gitlab-ci.yml— exactchownsyntax and deploy variables- File ownership on the server — match this exactly
app/presenters/Front/BasePresenter.php— structure reference
penzionusynka
Second migrated project. Useful for understanding what can go wrong in CI deploy (branch: 28992001-new-admin).
.gitlab-ci.yml—mkdir -p temp/cachein deploy jobapp/model/Supplement.php—?:operator usageapp/presenters/Admin/ApiPresenter.php— empty string guard pattern
Commit history reference (penzionusynka)
| # | Commit | What it fixed |
|---|---|---|
| 1 | feat(28993490): migrate project to Demoweb template services |
Steps 0–11 |
| 2 | fix(28993490): update develop deploy domain to czechdevelo |
Wrong DEVELOP_DOMAIN |
| 3 | fix(28993490): fix deploy config and ensure temp/log dirs exist |
Missing mkdir in CI |
| 4 | fix(28993490): fix file ownership in CI deploy to match SynekAuto |
Wrong chown |
Claude Code Prompt
Copy and paste this prompt into Claude Code to start a new migration. The prompt will guide Claude through all 12 steps in order.
You are migrating a legacy Nette Framework project to the new Demoweb template services architecture (Nette 3.1, PHP 8.1).
Follow these steps IN ORDER. Do not skip steps. Ask the user before proceeding to the next major step.
## Step 0: Project Setup
Ask the user:
1. What is the Git clone URL of the project?
2. What is the project name? (e.g., autosynek, penzionusynka)
Then:
- Clone the project to a temporary location
- Check composer.json for the PHP version requirement (require.php)
- If PHP >= 8.1 → folder ~/projects/81/
- If PHP >= 7.4 → folder ~/projects/74/
- If PHP >= 7.1 → folder ~/projects/71/
- Check if the project already exists in any of these folders
- The project will be migrated to ~/projects/81/{ProjectName}/ regardless of its current PHP version
- If it exists in another folder (e.g., 74/), that becomes the "old" reference
## Step 1: Backup
- Copy the old project to {ProjectName}_old (without .git, .idea, .vscode, node_modules)
cp -r ~/projects/{version}/{ProjectName} ~/projects/{version}/{ProjectName}_old
rm -rf ~/projects/{version}/{ProjectName}_old/.git ~/projects/{version}/{ProjectName}_old/.idea ~/projects/{version}/{ProjectName}_old/node_modules
## Step 2: Erase and Copy Demoweb
- Save .git directory and app/config/config.local.neon from the project
- Erase everything in the project directory
- Copy Demoweb template services as the new base:
cp -r ~/projects/81/Demoweb/* ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.htaccess ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.gitignore ~/projects/81/{ProjectName}/
cp ~/projects/81/Demoweb/.deploy_ignore ~/projects/81/{ProjectName}/
- Restore .git and config.local.neon
- Do NOT copy .git, .idea, node_modules from Demoweb
- Create temp directory: mkdir -p temp/cache && chmod 777 temp temp/cache
- Restore the project's .gitlab-ci.yml from the old backup
## Step 3: Connect Database and Compare Structure
Local database connection — config.local.neon should point to:
database:
dsn: 'mysql:host=mysql-rafa;dbname={ProjectName}'
user: dev_rafa
password: 'WEKdHaYVsUyO/09aTSZKQQ=='
IMPORTANT: If password contains special chars (=, :, etc.), wrap in quotes.
Compare project DB against demoweb_services:
diff <(mysql ... {ProjectName} -e "SHOW TABLES" | sort) <(mysql ... demoweb_services -e "SHOW TABLES" | sort)
- Import missing tables from demoweb_services (use --single-transaction, SET FOREIGN_KEY_CHECKS=0)
- ALTER TABLE to add missing columns
- Import missing settings_config rows (ADMIN_TAB_*, ADMIN_LOGO_SRC, IS_ESHOP, etc.)
- Import missing settings_translation rows
- Import missing module rows
## Step 4: Check MenuRepository Supplement IDs
- The menuRepository has a $supp_x_config array that maps supplement names to IDs
- Compare the old project's mapping with the actual supplement table in the database
- Verify that supplement IDs match between the code and the database
- CRITICAL: Demoweb code uses ?? (null coalescing) for supplement values, but old databases often have empty strings "" instead of null. Replace ?? with ?: (elvis operator) in:
- app/presenters/Admin/ApiPresenter.php — all $names[...] ?? ... patterns
- All front filters that access $item['supplements'][...]
## Step 5: Move Front Presenters (BasePresenter, HomepagePresenter)
DO NOT replace Demoweb's BasePresenter with the old one. Instead:
BasePresenter:
- Start from Demoweb's app/presenters/Front/BasePresenter.php
- Modify the $menus and $pages static arrays to match the old project's values
- Add the project-specific injected services (filters, repositories)
- Replace the beforeRender() method content with the project-specific logic
- Add createComponentContact() and createComponentSearch() if needed
- Add paginate(), createComponentVisualPaginator(), lang() methods
- Keep the Translator trait from Demoweb
- Remove Demoweb-specific dependencies the project doesn't need (WebMutations, CookieSettings, Instagram, etc.)
HomepagePresenter:
- Copy from old project
- Update self::$item references to self::$pages
- Verify all repository method calls exist in the current repositories
Other Front Presenters:
- Copy FeederPresenter.php, RegisterAndLoginPresenter.php from Demoweb
- Remove Demoweb-only presenters: AccountPresenter, EshopPresenter, GoPayPresenter, InstagramPresenter, PohodaPresenter, TypeaheadPresenter (unless the project uses them)
RegisterAndLoginPresenter:
- Use Demoweb's version (has proper traits)
- Override beforeRender() to NOT call parent::beforeRender() — instead set $web directly from settings
## Step 6: Move Front Latte Templates
rm -rf app/presenters/Front/templates
cp -r {ProjectName}_old/app/presenters/Front/templates app/presenters/Front/templates
cp ~/projects/81/Demoweb/app/presenters/Front/templates/RegisterAndLogin/*.latte app/presenters/Front/templates/RegisterAndLogin/
Fix Latte 2.11 Compatibility — apply to ALL .latte files:
1. Noescape output: {!$var} → {$var|noescape}, {!$var|filter} → {$var|filter|noescape}
2. Do macro: {? expr} → {do expr}
3. Elixir filter: {$template->elixir('path')} → {='path'|elixir}
4. imageExist filter: {$template->imageExist('path', 'default')} → {('path'|imageExist:'default')}
Use PHP script for reliable replacement:
<?php
$c = preg_replace('/\{!\$([^}|]+)\|([^}]+)\}/', '{$$1|$2|noescape}', $c);
$c = preg_replace('/\{!\$([^}]+)\}/', '{$$1|noescape}', $c);
$c = str_replace("{? ", "{do ", $c);
Fix Front Filters — all filters in app/presenters/Front/filters/ need:
- extends Nette\Object → use Nette\SmartObject; trait
- Guard clauses: if (!$item || !is_array($item)) return '';
- $item['supplements']['X'] ?: $fallback (NOT ??)
- Html::add() → Html::addHtml()
- ->class('x') → use Html::el('div class="x"') syntax
- ->href('x') → ->setAttribute('href', 'x')
## Step 7: Convert LESS to SASS
1. Copy old LESS files to resources/sass/{project}-front/
2. Rename .less → .scss, add _ prefix for partials
3. Install bootstrap-sass@3.3.7
4. Replace Bootstrap LESS with: @import "~bootstrap-sass/assets/stylesheets/bootstrap";
5. Convert custom/theme files:
- @variable → $variable (but NOT @media, @import, @keyframes)
- @{variable} → #{$variable}
- ~"string" → string
- .mixin-name() { } → @mixin mixin-name() { }
- .mixin-name; → @include mixin-name;
- spin() → adjust-hue()
- @import (inline) "file" → @import "file"
- @import "file.less" → @import "file"
6. CSS libs (.css) → rename to _name.scss
7. Update webpack.mix.js to compile the new SASS
## Step 8: Update webpack.mix.js
mix.sass('resources/sass/{project}-front/app.scss', 'assets/css/front.css')
.combine([
'resources/js/jquery.js',
'resources/js/bootstrap.js',
'resources/js/netteForms.js',
], 'www/assets/js/front.js');
mix.sass('resources/sass/admin/app.scss', 'assets/admin/app.css');
Run: fnm use 18 && npm install && npx mix --production
## Step 9: Update config.neon
- Use Demoweb's config.neon as base
- Update database credentials for production
- IMPORTANT: Quote passwords with special characters
- Add project-specific Front\Filter\* services
- Keep all Demoweb admin services
## Step 10: Update .gitlab-ci.yml
- Set correct PROJECT_NAME (must match the old project)
- Set DEVELOP_DOMAIN: "czechdevelo"
- Set DEVELOP_DIR: "/www/hosting/czechdevelo.cz"
- Use proper chown with $DEVELOP_USER:$DEVELOP_USER and $PRODUCTION_USER:$PRODUCTION_USER
## Step 11: Testing
for page in "/" "cs/m-{id}-{name}"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" "https://{project}.rafa.lat04.vas-server.cz/$page" -u claude:radegast)
echo "$STATUS → /$page"
done
for page in "admin/login" "admin" "admin/menu" "admin/fotogalerie" "admin/eshop" "admin/nastaveni" "admin/uzivatele"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" -L "https://{project}.rafa.lat04.vas-server.cz/$page" -u claude:radegast)
echo "$STATUS → /$page"
done
for page in "zeroadmin" "zeroadmin/user" "zeroadmin/lang" "zeroadmin/translation" "zeroadmin/config" "zeroadmin/supplement" "zeroadmin/form" "zeroadmin/image"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" -L "https://{project}.rafa.lat04.vas-server.cz/$page" -u claude:radegast)
echo "$STATUS → /$page"
done
Check Tracy errors: curl -sk "URL" -u claude:radegast | grep -c "tracy-section--error"
## Step 12: Commit and Push
- Use conventional commits with the ticket ID as scope
- Push to the feature branch
- Verify deploy to czechdevelo works
## Common Gotchas
1. Cache permissions: mv temp temp_old && mkdir -p temp/cache && chmod 777 temp temp/cache
2. NEON parsing: Passwords with =, : or special chars MUST be quoted
3. Supplements empty strings: Old databases have "" instead of null — use ?: not ??
4. Html::add() doesn't exist in Nette 3.x — use addHtml() or addText()
5. Html magic methods like ->class(), ->href() cause errors — use Html::el('tag class="x"') or setAttribute()
6. Latte {!} in Latte 2.11 means boolean negation inside filters, not noescape — always use |noescape
7. $template->filter() doesn't work in Latte 2.11 — use {='value'|filter} or {$var|filter}
8. Route::$defaultFlags removed in Nette 3.x — delete the line
9. Route::FILTER_IN/OUT renamed to Route::FilterIn/FilterOut — but using RouteAlias::FILTER_IN works too
10. Nette\Security\Identity → SimpleIdentity, IAuthenticator → Authenticator, Passwords must be injected (not static)
Additional Resources
- Nette 3.x migration guide: doc.nette.org/en/migration-3-0
- Latte 2.x docs: latte.nette.org
- Conventional Commits: conventionalcommits.org
- bootstrap-sass: github.com/twbs/bootstrap-sass