design(chrome): nav-bar redesign — glass pageheader, gradient tabs, Tasks board summary#3680
design(chrome): nav-bar redesign — glass pageheader, gradient tabs, Tasks board summary#3680gloriafolaron wants to merge 12 commits into
Conversation
Modernizes the app-wide page header: the pageheader becomes a proper glass card (like the content card below it) instead of transparent text floating on the page's gradient background. Mechanism (Marcel's "just update the pageheader CSS"): the old design used a 150px invisible padding cushion on .pageheader plus a -95px .maincontent overlap so the header title floated transparently on the gradient. That's replaced by a boxed header — background: var(--glass-background), border-radius, shadow, real padding, and .maincontent margin-top back to 0. The gradient itself (on .rightpanel::before, untouched) still shows around the cards. Header text flips white → dark to read on the new light card: .pagetitle h1 and .pageicon move from --main-titles-color (#fff) to --primary-font-color. Both are tokens, so dark mode adapts for free (glass-background → dark panel, primary-font-color → light text). The --main-titles-color token is unchanged, so its other consumers (menu, etc.) are unaffected. Applied globally so every page's header is consistent — no per-page overrides, no scoped body-class hacks. Login/registration keep their own look via the more-specific .regpanelinner .pageheader rules. Verified live (cache-busted) on two page types: - To-Dos (/tickets/showKanban): white glass box, dark readable title, the existing "To-Dos // All To-Dos" breadcrumb-switcher sits cleanly inside it, no horizontal scroll. - Dashboard: white box, dark "Strategy" title, no scroll, left menu intact. PR 1 of the chrome-modernization plan. This is the foundation; pages (report, RA, Tasks) then pass richer slot content — breadcrumb + subject switcher — into this modernized header. Follow-up validation before merge: broader page sweep (Settings, project views), dark mode, and mobile (mobile.css has its own .pagetitle h1 rule). Plugin pages (RA, reports) validate once they rebase onto this. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit 020f75c4a8c6df208c45baae741db1cddee59442)
The "Parent // Current ▾" page-title dropdown (header-title-dropdown) was
hand-rolled inline across ~18 templates — To-Dos sprint switcher, canvas
boards, wiki, ideas, goals, projects, etc. This consolidates the chrome
into one <x-global::subjectSwitcher> component (Marcel's "move/optimize
existing, don't build new"), and migrates the first consumer (To-Dos) as
proof.
The component owns the structure — <h1> + optional parent crumb +
separator + a Bootstrap dropdown whose toggle shows the current subject.
The MENU ITEMS are the slot, so each consumer keeps its own domain
options (sprints here carry their own onclick) while sharing the chrome.
It keeps the existing classes (.header-title-dropdown, .dropdown,
.dropdown-menu), so the established CSS (dropdowns.css) and Bootstrap
data-toggle behavior apply unchanged — migrating a consumer is a
zero-visual-change swap, verified on To-Dos: renders "To-Dos //
All To-Dos ▾", menu items intact, dropdown opens, no regressions.
A `switchStyle` prop ('legacy' default | 'pill') is included so the
modern pill treatment is a one-line flip once that call is made — no
rebuild.
First of an incremental consolidation: the other ~17 inline consumers
(timelineHeader, canvas boards, wiki, …) migrate to this component in
follow-ups, each a mechanical swap. The report and RA will consume it
for their subject switchers too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
(cherry picked from commit b036505ea55820a19751577775288e61240a80db)
- pageheader: drop the 8px bottom margin so the gap to the next block is the single 10px from .maincontent's padding-top. Vertical rhythm (top-nav→header, header→nav/content) is now an even 10px everywhere (was 8+10 = an uneven 18px between header and the block below it). - --glass-background 0.85 → solid white: the header/content cards were translucent, so nested report surfaces read as mismatched whites. Solid white makes every card one cohesive surface. - dropdown pill / pageicon refinements to match the white-card header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit 5e6208dd23829196f194f9b832c5cddfbe7e9c22)
…jectSwitcher - subline slot: a light meta/metrics line under the title (report uses it for "Strategy report · updated …"; Tasks will use it for task count + last-updated once the board summary is wired). - breadcrumb parent: subtle gray, no underline (was a heavy link). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit 6083fb6b9b3ca81d31786189a2084e40fe19ac2f)
Adds a light metrics line under the "To-Dos // <board>" title (same slot the report uses): total tasks · unassigned · due this week · last updated. - TicketService::getBoardSummary() computes the metrics from the already- fetched grouped ticket set, so counts reflect the current board filters with no extra query. Wired into getTicketTemplateAssignments() so all three board views (Kanban/Table/List) get it. - BoardSummary model holds total/unassigned/dueThisWeek/lastUpdated. - "Due this week" compares calendar dates in the user's timezone so a date-only dateToFinish lands in the intended week; "last updated" is the max ticket modified date (the board's most recent change). - Header renders it via the subjectSwitcher subline slot; zero-value segments (unassigned / due-this-week) are hidden to keep the line tidy. - Unit tests cover the count math, the due-this-week window, last-updated, and the empty-board case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit a1960b8d541a4f58714fd85d0812f3f250f0e9c1)
Restyles the shared .maincontentinner.tabs (Tasks Kanban/Table/List switcher, timeline tabs, plugin tabs) to the same gradient nav bar as the stakeholder report's Option C — one model for every horizontal-nav page, no per-template edits. Active tab = white pill, inactive = muted white; sticky behavior kept. - Bar uses --accent1/--accent2 so it adapts to theme + dark mode. - Active-pill text is fixed #004666 (the pill is always white, so a light user accent via --primary-color would be illegible). Verified in light and dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit ec3aa630e226537406d20dfb5b06d79675b21f4a)
Hidden optional segments (unassigned / due-this-week) left their surrounding
spaces behind, so the line rendered with stray gaps ("6 tasks · updated")
instead of the report header's single "· " separators. Build the present
segments in PHP and implode(' · ') so spacing is clean regardless of which
metrics show.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
(cherry picked from commit b0203c519bcfe510f11604ed611a283d38da9547)
Reworks the Tasks board chrome so the header and nav read as one compact, connected unit: - One-line header: breadcrumb/title on the left; board stats and (for real sprint views only) the sprint edit/delete ⋮ in a right cluster. Drops the separate sub-line row (header 83px → ~53px). - Board actions (New / Filter / Group By) move out of their own toolbar row into the right of the nav bar — like the report's picker — so there's no extra band and tasks start higher. Removed from showKanban/showAll/showList; rendered in the shared ticketBoardTabs (guarded on $searchCriteria). showAll keeps only its DataTables buttons row. - Tabs are a framed segmented control (accent-gradient grid + soft outline + white active segment); New is the standard Leantime button, Filter/Group By are light text controls — nav vs actions read distinctly. - Removed the nav-bar drop shadow so it sits connected to the content. - Stacking fixes: header above the nav bar (z-index 12) so the breadcrumb and sprint ⋮ dropdowns aren't clipped/covered; nav bar above the sticky column headers (z-index 11) so Filter/Group By open on top; right-anchored the action panels/menus so they never fall off the right edge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit f9e7a9f9fd019069965e3c130152b6a3c4264cb1)
House-style divider defaults to a single chevron (›) instead of the double slash (//), which read as URL-ish. Applies everywhere the switcher is used (Tasks, report, canvas boards, …). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit 6e83a8931b6b260e4b41438f09b97cce16d9d5ee)
- +New becomes a filled dark chip: reads as an action, distinct from both the white/framed tabs and the light text organizers (Filter / Group By). No divider — keeps the bar's negative space. - Conditional contrast scrim: white nav controls fail WCAG AA on a light accent (a mid-tone). The header computes each theme accent's luminance and sets --nav-scrim (0 for dark accents → fully vivid; 0.22 for light accents → white controls clear 4.5:1). Dark-accent/default themes are unaffected. - Filter / Group By go full white (was 0.85) so they pass on the scrimmed bright end. - Visible keyboard focus rings on every tab + action (white ring on the gradient; accent ring on the white active tab). Follow-ups logged (pre-existing / minor): the Filter toggle is an <a onclick> (not keyboard-focusable) — a ticketFilter fix; active tab could carry aria-current and the toggles aria-expanded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit a66934a1eb90fa899fd19a437375dfd6531b19bd)
Pages using the global .tabs nav without a .tabs-actions cluster (timeline — roadmap/calendar/milestones — and the plugin tabs) were rendering the framed tab group on the left with ~1000px of empty gradient to the right. Hug the bar to its content there (:not(:has(.tabs-actions)) → width: fit-content) so it reads as a compact nav; boards with actions stay full-width so the actions can sit at the right edge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85 (cherry picked from commit 27eba6ad110e5bc514a57ed289138ba2cf466ed8)
There was a problem hiding this comment.
Pull request overview
Introduces a reusable “chrome” update (page header + gradient tab bar + shared subject switcher) and applies it to the Tickets Tasks board by adding an on-screen board summary (counts + last-updated) that reflects current filters.
Changes:
- Redesign global page header and horizontal tabs nav (glass/white card header, gradient segmented tabs, action area on the nav bar).
- Extract a shared
<x-global::subjectSwitcher>Blade component for the “Parent › Current ▾” breadcrumb dropdown. - Add
BoardSummary+Tickets::getBoardSummary()and unit tests; render board metrics in the Tickets header.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Unit/app/Domain/Tickets/Services/TicketsServiceTest.php | Adds unit tests covering board-summary counts and last-updated behavior. |
| public/theme/default/css/light.css | Adjusts glass background token for the updated header card styling. |
| public/assets/css/components/text-styles.css | Updates page title text styling for the new light header surface. |
| public/assets/css/components/style.default.css | Updates icon/button styling and layout behavior for the new header flex layout. |
| public/assets/css/components/structure.css | Converts .pageheader into a card-like header and removes the legacy overlap spacing. |
| public/assets/css/components/nav.css | Redesigns horizontal .tabs as a gradient segmented control and adds right-aligned actions area. |
| public/assets/css/components/dropdowns.css | Restyles header dropdown toggle for readability on the new header surface; adds subline styling. |
| app/Views/Templates/sections/header.blade.php | Computes and sets --nav-scrim based on accent luminance for consistent tab contrast. |
| app/Views/Templates/components/subjectSwitcher.blade.php | Adds the new shared subject switcher component used by header breadcrumbs. |
| app/Language/en-US.ini | Adds new strings for board-summary metrics in the header. |
| app/Domain/Tickets/Templates/submodules/ticketHeader.blade.php | Migrates sprint selector to the shared subject switcher and renders board-summary meta in the header. |
| app/Domain/Tickets/Templates/submodules/ticketBoardTabs.blade.php | Moves board actions (new/filter/group) into the tabs/nav bar as a right-side cluster. |
| app/Domain/Tickets/Templates/showList.blade.php | Removes the old toolbar row since actions are now in the nav bar. |
| app/Domain/Tickets/Templates/showKanban.blade.php | Removes the old toolbar row since actions are now in the nav bar. |
| app/Domain/Tickets/Templates/showAll.blade.php | Removes left toolbar row; keeps table-specific controls right-aligned. |
| app/Domain/Tickets/Services/Tickets.php | Adds getBoardSummary() and injects boardSummary into template assignments. |
| app/Domain/Tickets/Models/BoardSummary.php | Introduces the BoardSummary model to carry computed metrics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $due = is_object($ticket) ? ($ticket->dateToFinish ?? null) : ($ticket['dateToFinish'] ?? null); | ||
| if (! empty($due) && ! str_starts_with((string) $due, '0000-00-00')) { | ||
| $dueDate = dtHelper()->parseDbDateTime((string) $due)->setToUserTimezone()->format('Y-m-d'); | ||
| if ($dueDate >= $weekStartDate && $dueDate <= $weekEndDate) { | ||
| $summary->dueThisWeek++; | ||
| } | ||
| } |
| $modified = is_object($ticket) ? ($ticket->modified ?? null) : ($ticket['modified'] ?? null); | ||
| if (! empty($modified) && ! str_starts_with((string) $modified, '0000-00-00')) { | ||
| $modifiedDate = dtHelper()->parseDbDateTime((string) $modified); | ||
| if ($summary->lastUpdated === null || $modifiedDate->greaterThan($summary->lastUpdated)) { | ||
| $summary->lastUpdated = $modifiedDate; | ||
| } | ||
| } |
- subjectSwitcher: escape $parent/$separator ({!! !!} → {{ }}). A shared
component echoing a crumb label unescaped is an XSS footgun; the only
caller passes __() (plain text), so escaping is a zero-behavior-change
hardening. $current was already escaped.
- getBoardSummary: guard date parsing with dtHelper()->isValidDateString()
instead of only filtering 0000-00-00. parseDbDateTime() throws on the
1969-12-31 sentinel too, which would have broken the board header on any
ticket carrying that stamp. Added a regression test.
- Remove orphaned @throws BindingResolutionException docblock above
getBoardSummary (it no longer throws).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
| * @param array $groupedTickets Grouped tickets as returned by getAllGrouped() | ||
| * (each group holds its rows under 'items'). | ||
| * | ||
| * @api | ||
| */ | ||
| public function getBoardSummary(array $groupedTickets): BoardSummary | ||
| { |
| $due = is_object($ticket) ? ($ticket->dateToFinish ?? null) : ($ticket['dateToFinish'] ?? null); | ||
| // isValidDateString() is exactly what parseDbDateTime() guards on | ||
| // (rejects empty, 0000-00-00, and the 1969-12-31 sentinel), so this | ||
| // can never throw on a sentinel/invalid value. | ||
| if (dtHelper()->isValidDateString($due !== null ? (string) $due : null)) { | ||
| $dueDate = dtHelper()->parseDbDateTime((string) $due)->setToUserTimezone()->format('Y-m-d'); | ||
| if ($dueDate >= $weekStartDate && $dueDate <= $weekEndDate) { | ||
| $summary->dueThisWeek++; | ||
| } | ||
| } |
| $modified = is_object($ticket) ? ($ticket->modified ?? null) : ($ticket['modified'] ?? null); | ||
| if (dtHelper()->isValidDateString($modified !== null ? (string) $modified : null)) { | ||
| $modifiedDate = dtHelper()->parseDbDateTime((string) $modified); | ||
| if ($summary->lastUpdated === null || $modifiedDate->greaterThan($summary->lastUpdated)) { | ||
| $summary->lastUpdated = $modifiedDate; | ||
| } | ||
| } |
|
Status: triaged / needs visual review · Priority: P2 (broad CSS blast radius) · Next action: eyeball the nav across themes + confirm no Automated maintainer first-response (advisory only — not an approval; merge authority stays with @marcelfolaron / @broskees). First review, at open ( 1. Intent: The reusable chrome/nav design system + its first application on the Tasks board — 2. Change requests:
3. Acceptance checklist (must pass before merge):
4. CI status: Advisory only — not an approval, not marking ready, not merging. Merge authority stays with @marcelfolaron / @broskees. |
Carved cleanly off
masterfrom theintegrate/report-chromeline (11 commits cherry-picked, no conflicts). This is the reusable chrome/nav design system + its first application on the Tasks board.What's in it
Chrome design system (CSS + shared component)
pageheaderbecomes a glass card with even 10px block rhythm; solid-white cards (structure/style/text-styles/dropdowns CSS)..tabshorizontal nav (nav.css), with a WCAG-driven conditional scrim:header.blade.phpcomputes--nav-scrimfrom accent luminance (0 for dark accents, 0.22 for light) so tab contrast stays AA on every theme.<x-global::subjectSwitcher>(the "Parent › Current ▾" breadcrumb) with an optional subline slot; separator//→›.:not(:has(.tabs-actions))).Tasks board application
BoardSummarymodel +getBoardSummary()service (TicketsServiceTest, 32 tests).Checks (local)
pint ✓ · phpstan ✓ (BoardSummary, Tickets) · 32 board-summary tests green. No
dist/committed — CI builds assets.🤖 Generated with Claude Code