Existing systems used to have no automatic mechanism for the
db/updates/*.sql idempotent updates after `git pull` — devs had to
remember which files were already applied and run new ones manually
with `mariadb < ...`. Mirror module:migrate to fix this:
- New `bin/console db:migrate` walks db/updates/*.sql in alphabetical
order, skipping anything already recorded in the new core_migrations
tracking table. Each file runs in its own transaction; failure
rolls back so the file is retried on the next run.
- New `db:migrate --status` lists applied vs. pending files without
running anything. Useful for ops debugging "schema seems stale"
symptoms.
- module:sync now runs db:migrate as its first step, so the standard
post-pull command stays a single invocation. README's 3-step setup
is unchanged — module:sync now also covers core schema updates.
CoreMigrationRepository owns its own mysqli connection (using the
same DB credentials as MintyPHP\DB) and runs raw `$mysqli->query()`
instead of going through DB::query's prepared statement path —
MariaDB rejects some DDL (e.g. ALTER TABLE … ADD CONSTRAINT CHECK)
in the prepared protocol, which the existing 18 db/updates files
trip on. ModuleMigrationRepository is left untouched (no module
migration uses such DDL today and changing the vendor pattern is
out of scope).
End-to-end verified: against an existing DB the first run applies
all 19 idempotent files as no-ops and records them, second run
reports "all updates already applied". Against a freshly init'd
DB the same thing happens — no double work, no surprises. All
encrypted seed secrets keep decrypting because APP_CRYPTO_KEY
is unchanged.
Tests: tests/Console/CoreCommandsTest covers success/failure/status
output shapes against a FakeModuleRunner (mirror of the existing
ModuleCommandsTest pattern). 5 stale baseline entries in
phpstan-baseline removed (covered by the new @api annotation on
the test fixture class).
Docs: CLAUDE.md and docs/reference-cli-commands.md document the
new command + --status flag; docs/howto-fehlerbehebung.md gains
a "schema seems stale after git pull" section pointing at
db:migrate --status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
config/config.php was gitignored and devs had to copy it from
config/config.php.example on every fresh clone. Since the bootstrap
config reads everything from .env via $envString() / $envInt() /
$envBool() defaults, there is nothing developer-specific in the file —
the cp step was dead ceremony.
- Remove config/config.php from .gitignore and track it directly
- Delete config/config.php.example
- Drop the example-existence + per-doc reference checks from
bin/docs-drift-check.sh; add a legacy-pattern check that flags
any new mention of the removed example file
- Update ConfigContractsTest to require config.php and forbid the
example
- Clean stale references in CLAUDE.md, README.md, six docs files,
and the core-guardrails skill
Same commit slims README down from 294 to 47 lines: one setup
checklist (now 3 commands instead of 4), the seeded login table,
the three quality-gate commands, and pointers to CLAUDE.md and
docs/index.md for everything else.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New core partial templates/partials/app-input-copy.phtml renders a
standard label + input with a compact copy button overlaid on the
right edge of the input. Used for privacy/imprint URLs on the tenant
form; reusable for any field where a one-click copy is helpful.
- Extend the shared copy-field component (web/js/components/app-copy-field.js)
with data-copy-target support — the button now reads the target input's
.value at click-time, so users can edit a field and still copy the
current value. Static data-copy-value keeps working unchanged.
- New component CSS web/css/components/app-input-copy.css positions the
button absolute/inset + margin-block:auto (robust vertical centering
regardless of input height) and uses a descendant selector (0,2,0)
so the button wins over the global [data-tooltip] position:relative.
- Also register app-input-copy.css + app-tenant-logo.css in core.css
@import list — they were only in the shared asset group before, which
default-template admin pages don't load, so tenant-logo styles were
effectively missing on admin tenant edit (topbar size etc.).
- Document the Copy-to-Clipboard + Copyable Input standards in
docs/reference-frontend-javascript.md so future consumers don't
re-invent the markup/selectors.
- File-upload preview: pending-file block restructured to a compact
list-item row (small square thumb + filename/size stack + clear X)
and removed the transparency checker pattern on the current-image
preview in favour of a flat --app-preview-bg surface.
- Tenant edit page title + breadcrumb now show the tenant description
("Acme GmbH") instead of the generic "Mandant bearbeiten" when one
is present.
- i18n: add "Copy to clipboard" / "In Zwischenablage kopieren" across
both locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the single tenant avatar with a pair of theme-scoped brand logos.
Render only the theme-matching <img> server-side and swap src on theme
toggle via a JS hook — no reload, no double request, no CSS tricks.
Tenant logos
- TenantLogoService (ImageUploadTrait) with theme whitelist and per-theme
storage storage/tenants/{uuid}/logo/{light|dark}/, SIZES 128/256/512
- Public serving endpoint auth/tenant-logo-file so login can show the
logo pre-auth; matching authenticated admin preview endpoint
- appTenantLogoUrl(?size, ?theme) with 4-step fallback cascade; PDF +
mail always request 'light'
- Admin tenant edit: avatar block replaced by "Tenant logos" details
block inside the Master-data tab, two side-by-side slots via Pico
.grid with the core app-file-upload partial
- Policy rename ABILITY_ADMIN_TENANTS_AVATAR_VIEW -> LOGO_VIEW, action
routes logo / logo-delete / logo-file with theme body/query param
- API endpoint path kept (backward compat), internals on new service
- CLI tenant:logo-migrate-avatars moves legacy avatar/ -> logo/light/
idempotently (--dry-run, --yes, --cleanup)
- i18n "Tenant image" removed, 12 new keys synced across de/en
File upload component
- Full-width preview + filename/actions below (3D stack layout)
- Fixed 16:9 aspect ratio with 1rem inner padding for consistent
preview size across any logo aspect
- Transparency checker pattern as background so black logos stay
visible on dark mode and white logos on light mode
- form="" + deleteFormId support so the partial works with barrier
forms inside another form
Buttons
- width:100% dropped from button[type="submit"]; scoped back via
.login-main for the auth-flow primary CTA
- .outline base rule now tints background via color-mix of --app-color
so secondary/primary/danger outlines all gain a subtle surface
- .outline.secondary restyled Stripe-style in both themes: solid white
chip with soft shadow in light, solid elevated dark chip with white
text in dark; neutral border replaces role-colored border
- .app-action-success/.app-action-danger outlines get color-mix bg +
theme-aware outline-text tokens for stronger contrast
- Filled .primary/.app-action-success/.app-action-danger get raised
box-shadow (inset highlight + drop) — opt-in via class so chrome
buttons stay flat
- Dropped the legacy .secondary utility that was clobbering the
custom-property cascade with a hardcoded muted color
Theme swap
- Logo img carries data-src-light + data-src-dark; theme-toggle JS
swaps src when data-theme changes, keeping the topbar/login logo in
sync without a page reload
Quality gates: PHPUnit (2045), PHPStan L5, CS-Fixer, docs link/drift,
codex skills sync — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the extensible theme catalog (config/themes.php + ThemeConfigGateway)
in favor of a two-entry const on SettingsAppGateway. appThemes() now returns
the list directly — no container lookup, no file include. Drop the
dark-green theme assets, narrow [data-theme^="dark"] selectors to
[data-theme="dark"], and tighten isDarkTheme() to an exact match. Ship an
idempotent migration that corrects any leftover 'dark-green' rows on users
and tenants to safe defaults (light / NULL).
Tenant scoping (default_theme, allow_user_theme) and per-user override stay
intact; only the catalog extensibility and the third theme are removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unifies the breakpoint system and fixes cross-layer CSS bugs that kept
the old mobile hide rules from ever taking effect.
Breakpoints:
- Document canonical xs/sm/md/lg/xl/2xl scale in variables.base.css
- Replace 968px one-offs with 1024px (app-details, app-page-editor)
- Make details tab-panel max-width responsive (min(70ch, 100%))
Topbar (< md):
- Hide breadcrumb in components layer so cross-layer overrides actually win
- Dock search icon alongside right-column icons via flex layout
- Add xs (400px) tightening for hit-area preservation
- Hide brand divider alongside hidden brand name
- Switch hover states to theme-aware --app-dropdown-hover-background-color
Mobile drawer:
- Icon-bar now vertical like desktop (was horizontal top strip, effectively invisible)
- Sidebar shifted 52px right so icon-bar and sidebar slide in as one 280px unit
- Add breathing room above first nav item (20px top padding)
List tabs:
- Rewrite .app-list-tabs to horizontal scroll with hidden scrollbar, snap,
and mask-image gradient fades; add initListTabs to scroll active into view
- Register as runtime component in app-init.js
List toolbar:
- Stack wide controls (multi-select, search/text inputs) full-width below sm,
keep compact controls at natural width
Footer:
- New dedicated app-footer.css with mobile-first stacking, border-top separator,
theme-aware hover; remove redundant rules from app-shell.css
Notification dropdown:
- Width with min(360px, 100vw - 2rem) and min-width: 0 override global
details > ul rule; switch to position: fixed below md so it hugs the
viewport right edge instead of the non-rightmost bell button
Content spacing:
- Add padding-block-start to .app-main-content on mobile (was 0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement handover protocols as a new entity in the helpdesk module,
allowing users to create fillable protocol records from admin-defined
software product schemas.
Key additions:
- DB migration (helpdesk_handovers table) with tenant scope
- HandoverService with status workflow (draft/in_progress/completed/archived)
- Three-tier permissions (view/create/manage)
- Two-step creation wizard (Stripe-style assistant)
- Grid.js list page with search and status filter
- Edit/detail page with aside metadata and status controls
- Reusable core autocomplete lookup component (app-lookup-field)
- Debitor lookup data endpoint for autocomplete
- Dynamic form rendering from schema snapshots
- 11 PHPUnit tests for HandoverService
- DE+EN i18n translations (48 keys each)
Also includes: PHPStan fixes (dead code removal, stale baseline cleanup),
software product edit title improvement, fieldset simplification,
and Stripe-style hover for schema preview.
Workflow: HD-HANDOVERS-001 (.agents/runs/HD-HANDOVERS-001/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bare <input type="file"> across all upload locations with a
card-based dropzone component via shared partial. Three visual states:
current server file (thumbnail + Replace/Delete), empty dropzone, and
pending file preview (local FileReader thumbnail + metadata). Delete
actions use data-confirm-message for confirmation dialog.
Centralized as templates/partials/app-file-upload.phtml to prevent
markup drift — documented as deliberate exception to plain-HTML inputs
convention in CLAUDE.md and developer checklist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- add helpdesk module pages, services, settings and tests
- standardize debtor list on drawer/grid contracts and robust filter drawer behavior
- add helpdesk aside panel navigation and settings visibility provider
- switch primary list slug to helpdesk/debitor and remove helpdesk/search compatibility
- include required core contract updates for list contracts and detail/drawer integration
New help-center module adds a question-mark icon to the sidebar with
an aside panel containing three sections: Documentation (permission-
gated links to docs viewer and API docs), Keyboard Shortcuts (inline
reference from HotkeyService with Mac/Win variants), and About
(application info). Visible to all logged-in users — individual
resource links respect existing permissions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the Swagger UI viewer from core (pages/admin/api-docs/) into
modules/api-docs/ as a self-contained module. Create module manifest
with routes, permission, sidebar.admin_nav_item slot, asset group,
and authorization policy. Remove API docs ability from core
OperationsAuthorizationPolicy and UiCapabilityMap. Remove hardcoded
sidebar nav item — now contributed via module slot. Add
admin-automation group meta to sidebar template for module nav items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-app notification system with topbar bell icon, dropdown panel,
mark-as-read/dismiss actions, and scheduled cleanup of old read
notifications. Includes event listeners for user.created and
user.deleted events, authorization policy, and full test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a module kernel (ModuleManifest, ModuleRegistry) that allows modules to
contribute routes, UI slots, search providers, layout context, session
lifecycle, and permissions. Modules are activated via config/modules.php or
APP_ENABLED_MODULES env variable. Conflicts (duplicate routes, permissions,
slot keys) cause a fail-fast with a clear error message.
Extract the address book from hardcoded Core integration points into the
first module (modules/addressbook/). The module provides:
- Aside icon-bar tab + People panel via UI slot system
- Global search resource via AddressBookSearchProvider
- Layout context data via AddressBookLayoutProvider
- Session lifecycle via AddressBookSessionProvider
Core cleanup removes address-book hardcodings from SearchSqlResourceProvider,
SearchUiMetaProvider, SearchItemMapperProvider, appBuildLayoutNavContext(),
and the aside templates. Permissions (ADDRESS_BOOK_VIEW) and business logic
(AddressBookService) remain in Core as they gate general user visibility.
Includes 38 new tests (894 total), PHPStan level 5 clean, and architecture
tests verifying zero hardcoded address-book references in search/templates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>