Extends the toIntIds() rollout to service and repository layers:
- Drops a third private duplicate (UserCustomFieldValueService::normalizeTenantIds)
- Collapses the two-line intval+filter pattern inside UserAssignmentService,
UserAuthorizationPolicy, SsoUserLinkService, DepartmentService, and
UserCustomFieldValueService
- Replaces inline patterns in 4 repositories (RolePermissionRepository,
RoleAssignableRoleRepository, UserWriteRepository, DepartmentRepository,
UserCustomFieldValueRepository, UserCustomFieldValueOptionRepository)
- Simplifies the notifications sanitizeTenantIds trait body
Tenant-area files deliberately untouched per parallel ongoing work on
the tenant-logo refactor.
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>
- Add missing i18n key "LDAP attribute map could not be encoded" (de+en)
- Replace raw font-weight/line-height with design tokens in app-topbar.css
- Fix import ordering in SchedulerRunService.php (php-cs-fixer)
- Fix PHPStan mock chain errors in DebitorDetailServiceTest and TenantSsoServiceTest (PHPUnit 12 API)
- Simplify redundant comparison in NotificationService dedupe window
- Add PHPStan type hint for mock callback array in SchedulerJobFailedNotificationListenerTest
- Fix undefined variable and add flow-analysis suppression in login().php
- Increase dev PHP memory_limit to 1G for PHPStan (prod stays 256M)
All 6 quality gates now pass: QG-001 (1618 tests), QG-002 (0 errors),
QG-003 (6630 assertions), QG-006 (0 fixable), QG-008, QG-009.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use Stripe pattern: outline icons by default, filled for active state
(bookmarks bookmarked, notifications unread). Remove chevron arrows
from tenant/user dropdowns. Add max-width with ellipsis to breadcrumb
items to prevent overflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
createForTenantAdminUsersFromMessage relies on listActiveUserIdsByTenantId
which returns empty results in CLI context (scheduler). Instead, iterate
admin users directly and call createFromMessage per user per tenant.
Verified end-to-end: notification created, dedup blocks duplicates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change DEDUPE_TYPES from bool to int|true — int value overrides the
default 30-min window. scheduler.job_failed uses 1440 min (24h) so
admins receive at most one notification per failed job per day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable).
Dispatch scheduler.job_failed event when a job fails with payload:
job_id, job_key, label, error_code, error_message, trigger_type.
New SchedulerJobFailedNotificationListener in notifications module
creates in-app admin notifications for users with JOBS_MANAGE
permission, scoped per tenant, with 30-min dedup per job_key.
Also fixes audit module manifest: system_audit_purge job_key was
mismatched (audit_system_purge vs system_audit_purge in DB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove NotificationRepositoryFactory, NotificationServicesFactory, and
NotificationRepositoryInterface — all single-consumer abstractions with
no polymorphism benefit. Simplify container registrar to wire services
directly. Extract shared listener logic (sanitizeTenantIds,
resolveDisplayName, resolveUuid) into NotificationListenerTrait,
eliminating duplication across 4 listeners.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Module pages must use a unique top-level directory (audit/) instead of
nesting under admin/ which collides with core's pages/admin/ during
module:build symlink creation. Routes still map admin/* URLs to the
audit/ page directory via manifest route targets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NotificationsAuthorizationPolicy was never registered in the container,
so ModuleClassResolver could not instantiate it (requires PermissionService).
This silently prevented the notifications.view permission from being granted,
hiding the bell icon in the topbar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add bin/ scripts to PHPStan scanFiles, suppress property.onlyWritten for
by-ref test properties, add exhaustive default match arm, use explicit
expects() on mock stubs, fix data provider return type, and simplify
redundant null check in NotificationServiceTest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set defaultTestSuite="CoreCore" to prevent 222 duplicate file warnings,
and replace deprecated isType() with isArray()/isString() in notification
listener tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass AppContainer to SessionProvider::clear() so all module providers use
SessionStoreInterface consistently instead of raw $_SESSION. Also add
loading and error states to the notification bell dropdown (GR-UI-014).
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>
Each module can now ship its own i18n/ directory with translation files
(default_de.json, default_en.json). At boot time, ModuleI18nLoader
preloads core translations then merges module translations on top via
Reflection into I18n::$strings — the t() helper stays unchanged.
- Add i18nPath property to ModuleManifest
- Add ModuleI18nLoader with Reflection-based preload
- Extract 59 module-exclusive keys from core i18n into module files
(notifications: 8, bookmarks: 41, addressbook: 10)
- Expand TranslationKeysTest to scan modules/ and validate against
merged core + module translations
- Add ModuleI18nContractTest for per-module de/en parity and completeness
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>