Introduces a reusable core detail-drawer primitive that slides in from the
right and loads any view via a `*-fragment(none).phtml` endpoint. Bundles the
address book list overhaul that is its first consumer.
Core additions:
- `app-detail-drawer.js` — generic drawer with stepper, focus trap, body
scroll-lock, URL-hash deep-linking, session-expiry detection
- `app-fragment-init.js` — auto-wires tabs/lookups/confirm/file-upload/
fslightbox inside injected HTML; consumers do not re-initialize components
- `app-focus-trap.js` — shared focus-trap + refcounted scroll-lock, used by
both filter-drawer and detail-drawer
- `getHtml()` in `app-http.js` + `SessionExpiredError`; drawer reloads the
page on auth redirect instead of rendering the login form in the panel
- `DetailDrawerFragmentContractTest` enforces that every `initDetailDrawer`
consumer ships matching `*-fragment($id).php` + `*-fragment(none).phtml`
Address book list:
- Grid collapses from 9 columns to 4 (identity / context / phone / actions)
with a two-line identity cell (avatar + name + email)
- Tenant register tabs above the grid using the `app-list-tabs` partial;
tenant filter wired via hidden toolbar field so grid.js forwards it on
every data fetch
- Profile body extracted to a shared partial so the full-page view and the
new drawer fragment share the same markup
- New i18n keys for the drawer/list labels
Also refactors `app-filter-drawer` to reuse the shared focus-trap and
scroll-lock instead of maintaining its own copy, and documents the
detail-drawer convention in CLAUDE.md.
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>
Move topbar outside the grid container as a standalone sticky header
(Shopify/Stripe pattern). Sidebar and icon bar use position:sticky
relative to topbar height. Mobile uses drawer pattern with backdrop,
focus trap, ESC close, and scroll lock. Tenant branding moved from
sidebar header into the topbar.
Co-Authored-By: Claude Opus 4.6 <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>
Move breadcrumb rendering from individual page templates into the core
topbar. Each page now sets $breadcrumbs in its action .php file; the
topbar renders it automatically via the shared partial.
- Remove global back/forward buttons and app-nav-history.js component
- Remove Alt+Arrow keyboard shortcuts for history navigation
- Render breadcrumb in topbar-left section (replaces button area)
- Clean up breadcrumb CSS: context-neutral base (flex, no margin)
- Recalculate sticky titlebar offset in details container
- Migrate all 41 pages (core + helpdesk, audit, addressbook, api-docs)
- Add missing breadcrumbs to addressbook detail view
- Update architecture contract tests (nav-history references removed)
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
Hide LDAP connection, search/bind, and attribute mapping cards until
the "Enable LDAP login" toggle is activated — same UX pattern as the
Microsoft SSO section using data-tenant-ldap-* attributes and a
mirrored JS toggle component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Custom-built error handler for the web channel that shows a tabbed debug
dashboard (exception, request context, environment, SQL queries) in dev mode
and a clean branded error page with copyable request ID in prod mode.
All errors are logged as JSON lines to storage/logs/errors/ for lookup by
request ID. Uses the project's design tokens for visual consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace prominent outlined chevron buttons with borderless ghost icons
and gradient fade masks for a cleaner overflow indicator. Reset shell-level
button custom properties to prevent global button styles from bleeding through.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs in the toast redesign:
1. Variant backgrounds (success/warning/info/error) used rgba with 12-20%
opacity, making toasts semi-transparent over page content. Fixed by
layering the tint over a solid background via linear-gradient compositing.
2. Flash messages with scoped paths including query strings (e.g.
edit pages with ?return=...) were not matched by Flash::peek(Request::path())
since path() strips the query. Added fallback to pathWithQuery() matching.
Also moved flash partial before JS init scripts to ensure DOM is present
when the component runtime mounts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents session timeout for users who stay active on a single page
(scrolling, reading) without making requests. Pings every 5 min only
when interaction was detected since last sync — no false keep-alive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert app-global-search.js from a self-executing module to an
exported initGlobalSearch() function following the established
component pattern. Extract 6 inline handlers to named variables,
add destroy() for proper listener cleanup, double-bind guard,
and return a public API (destroy, openSearch, focusSearch).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace innerHTML assignments in app-tabs.js and app-password-toggle.js
with createElement/appendChild to prevent potential XSS vectors. Add
destroy() methods to app-tabs, app-filter-drawer, and
app-toggle-aside-panels so document/window-level listeners can be
properly removed when components are torn down.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DRY duplicated helpers (escapeAttr, belongsToForm, isSubmitControl,
isEditableTarget) into web/js/core/app-form-utils.js and update 7
consumers. Add aria-live="polite" to flash messages, async messages,
and search results for screen reader announcements. Add
prefers-reduced-motion support to CSS tooltips.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Session timeout redirected to /de/login?return_to=... but the remember-me
cookie was not cleared by logoutDueToSessionTimeout(). On the next request,
autoLoginFromCookie() silently re-logged the user in, causing the MintyPHP
router to resolve the login route to pages/index().php (dashboard) instead
of the login action — the intended URL redirect logic never executed.
Fix: intercept ?return_to= immediately after successful auto-login in
index.php and redirect to the sanitized route path. Also harden the login
flow with hidden return_to form fields and POST body fallback for cases
without remember-me.
Additional improvements in this commit:
- Convert stored REQUEST_URI to router-compatible path (strip leading
slash and locale prefix) via IntendedUrlService::toRoutePath()
- Consolidate duplicate CSRF data attributes into shared data-csrf-key
and data-csrf-token on <html> element
- Add tenant branding (logo) to auth pages via appAuthLogoUrl() helper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a session expires, the user's current URL is now captured and restored
after re-authentication (via remember-me cookie or manual login), instead of
always redirecting to the dashboard. A JavaScript session warning dialog
appears ~2 minutes before idle timeout, allowing users to extend their session
with a single click. Includes open-redirect prevention via URL validation,
Microsoft SSO callback support, and 33 unit tests.
Refs: SESSION-REDIRECT-PRESERVE-001
Co-Authored-By: Claude Opus 4.6 <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>