Instead of crashing (PHP 8.2 TypeError) or silently failing on duplicate hash,
the API now:
- Checks for any existing ticket with the same hash (no 24h limit)
- If open/pending/in-progress: returns Duplicate ticket with existing ID
- If closed: reopens the ticket, posts a recurrence comment, returns action=reopened
- If new: creates the ticket as before
- Wraps INSERT in try/catch for mysqli_sql_exception to handle race conditions
gracefully when multiple nodes POST simultaneously
Also improves the hash function:
- Ceph issues now include a subtype (bluestore_slow, clock_skew, osd_down, etc.)
so different Ceph warnings get distinct tickets instead of colliding
- LXC storage issues include the container ID so each container gets its own ticket
- Fixed potential null-subject issue in preg_match for missing titles
- Added early input validation (400 + JSON error) before any processing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
generateTicketHash() passed $data['title'] to preg_match() before any
input validation. In PHP 8.2, preg_match() with null subject throws
TypeError, causing HTTP 500 with empty body. hwmonDaemon saw this as
"Expecting value: line 1 column 1 (char 0)" and failed to create tickets
on all nodes.
Moved input validation before the hash call: missing or empty title now
returns HTTP 400 with proper JSON error instead of crashing. Also removed
the redundant late URL-encoded fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Line 1575 used .replace(/</g, '<').replace(/>/g, '>') to set
the comment-raw edit textarea content, missing '&' → '&'. Replaced
with lt.escHtml() which escapes all five special HTML characters (&, <,
>, ", ') consistently with the rest of the codebase.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- manage_recurring.php calculateNextRun(): expand monthly cap from 28→31
with proper last-day-of-month clamping (matches model fix); use split
with ':00' append to handle malformed time strings without crashing;
fix weekly day array to start at index 1 (not 0) so day=0 never maps
to empty string and blows up DateTime
- RecurringTicketModel::calculateNextRunTime(): same weekly day array fix
(start at index 1) to eliminate '' → DateTime exception on day=0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rawurlencode($username) was called on line 38 (encoding the username),
then rawurlencode($matrixId) was called on line 39 encoding the already-
encoded string — causing %20 to become %2520 for usernames with special
characters. Fixed by building $matrixId with the plain username and only
encoding the full Matrix ID once in the URL path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TicketView.php: Show 3 lt-skeleton-card placeholders in the comment list
while "Load more" fetches; skeletons are removed on resolve or error
- ticket.css: Add .comment-skeleton margin spacing
- WorkflowDesignerView.php + manage_workflows.php: Prevent creating/editing
status transitions where from_status === to_status (client + server check)
- RecurringTicketsView.php: Expand monthly day picker from 28 to 31 days
(days 29-31 labelled "last day in short months")
- RecurringTicketModel.php: Clamp monthly schedule day to last day of target
month using format('t') instead of hard-capping at 28
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- download_attachment.php: path traversal check used strpos() without
trailing DIRECTORY_SEPARATOR, allowing /uploads_evil/* to pass when
upload dir is /uploads — now checks realPath + DIRECTORY_SEPARATOR prefix
- bulk_operation.php: $conn->close() was called before StatsModel($conn)
construction; moved close() inside each branch to after all DB use
- upload_attachment.php: ticket ID validated as /^\d{9}$/ (exactly 9
digits) breaking all tickets below ID 1,000,000,000 — changed to
/^\d+$/ for any positive integer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added session_status() === PHP_SESSION_NONE guard to six API files
(custom_fields, revoke_api_key, manage_templates, generate_api_key,
get_template, manage_recurring) that called bare session_start() after
RateLimitMiddleware had already started the session
- Registered /api/notifications.php and /api/user_avatar.php in index.php
router (were missing, served only by direct file access)
- Complete README rewrite: remove all Discord references (Matrix/hookshot
is the only external notification method), add hwmonDaemon API docs,
document all TDS v1.2 features (kanban, charts, SLA, command palette,
notification bell, watcher avatars, @mention, etc.), fix keyboard
shortcuts table, add Matrix/LDAP env vars to setup section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract expression args to local variables before bind_param (PHP 8 requirement)
- Guard session_start with session_status check in manage_workflows
- Remove redundant session_start from bulk_operation (RateLimitMiddleware starts it)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
StatsModel::invalidateCache() was never called from any API, so the
60s cached stats persisted after bulk assign/status/priority changes,
ticket updates, assignments, and clones. Dashboard tiles showed stale
counts until the TTL expired.
Added invalidation to the four APIs that affect dashboard stat tiles:
- bulk_operation.php: after successful bulk assign/status/priority
- assign_ticket.php: after successful reassignment
- update_ticket.php: after any successful ticket update
- clone_ticket.php: after successful clone (open_tickets changes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The combobox modal used lt-combobox-list but lt.combobox looks for
lt-combobox-dropdown — it returned immediately, wiring nothing.
Replaced with lt.typeahead which is correct for single-select search:
- Filters users client-side as you type (minChars:1, debounced 150ms)
- Shows display_name (username) with highlight on match
- onSelect stores user ID and shows "✓ Name" confirmation below input
- Input auto-focuses when modal opens
- Enter key now selects first result even without arrow-key navigation
(same fix applied to lt.combobox Enter handler)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes inline max-width/nowrap from title td, moves to CSS with
width:99% so the title column absorbs all available space freed by
hiding other columns. max-width:0 trick ensures overflow ellipsis
still works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TicketView: ticket age was measuring from last update not creation;
fixed to always use created_at
- dashboard.js: bulk assign used non-existent onSelect callback (no
selection was ever stored); fixed to onChange with selected[0],
added max:1 to enforce single-select
- base.js: lt.combobox Enter key only fired when focusedIdx >= 0;
now falls back to first filtered result when no arrow key used
- DashboardView + dashboard.js + dashboard.css: add COLS ▾ button on
table header that opens a checkbox panel to show/hide optional
columns (Ticket ID, Category, Type, Created By, Assigned To,
Created, Updated); state persisted in localStorage, Reset button
restores all; core columns (Priority, Title, Status, Actions) always
visible; data-col attributes added to all th/td for CSS targeting
Notifications bell: was functional all along — was broken by the
notifications.php 500 error (now fixed). Avg resolution: correct,
tickets genuinely take ~158 days average on this dataset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ticket.js: status change requiring a comment now shows an inline
modal with a textarea — comment is actually posted before the status
changes, instead of just warning the user and changing anyway
- layout_header.php: add ⌘K button in header so users can discover
the command palette; also removes inline onclick in favor of JS
(CSP-safe via nonce script block already present)
- TicketView.php: upgrade breadcrumb to lt-breadcrumb markup with
ticket title preview (truncated at 45 chars) and aria-current
- ticket.js + ticket.css: image attachments now render as clickable
thumbnails (3rem×3rem) that open in lt.lightbox; non-image files
keep the icon display unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds lt-cmd-overlay HTML to layout_header.php and initializes
lt.cmdPalette with commands for: navigation (Dashboard, New Ticket),
filters (My Tickets, Unassigned, P1 Critical), admin pages (if admin),
and recent tickets (last 5 viewed, stored in localStorage).
TicketView.php records each viewed ticket ID to localStorage under
lt_recent_tickets so the command palette can surface them as Recent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- notifications.php: audit_log PK is audit_id not log_id; alias all
three queries with audit_id AS log_id to fix 500 error
- DashboardView: avg resolution time now picks best unit automatically
(min < 1h, hr < 48h, days < 14d, wks otherwise) with full hours
shown in title tooltip; adds lt-stat-unit CSS for the suffix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The $statusSql double-quoted string contained '%"status":%' which caused
PHP to terminate the string at the inner double quotes, resulting in a
parse error (unexpected identifier 'status') on the beta server.
Also cleared stale stats cache that stored by_assignee in old name=>count
map format instead of the current array-of-objects format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both keys were silently dropped on batch save (the for-loop just
continued on unknown keys). timezone is sent by saveSettings() and
notif_last_seen is written by the notifications mark-read endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DashboardView.php: wrap performAdvancedSearch in a closure so it is
resolved at event-fire time rather than listener-registration time
(advanced-search.js loads later via pageScripts so the bare identifier
reference caused ReferenceError).
DashboardView.php: reset sort URL to page=1 so sorting all pages
instead of staying on the current page.
dashboard.js: add missing save-settings and close-settings cases to
the click delegation handler (were removed in a prior session under
the assumption they were in dashboard.js, but they were not).
notifications.php: replace JSON_EXTRACT-based comment join (not
universally supported) with a two-step PHP filter: fetch owner/watcher
ticket IDs first, then filter raw comment rows in PHP. Also fix the
status change LIKE pattern to match the actual logTicketUpdate format
{"status": {"from": ..., "to": ...}}.
SecurityHeadersMiddleware.php: add https://cdn.jsdelivr.net to
connect-src so Chart.js source maps load without CSP violations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notifications.php: comment notifications never fired because the query
used action_type='comment'/entity_type='ticket' but logCommentCreate
logs action_type='create'/entity_type='comment'. Fix query to match
actual log format and extract ticket_id from details JSON.
notifications.php: status change notification titles always showed
"? → ?" because code read details.old_value/new_value but logTicketUpdate
stores the delta as {"status": {"from": ..., "to": ...}}.
base.css: move .is-hidden to base.css (global) — it was only defined in
ticket.css, so on the dashboard the ticket-preview popup had no hide
rule applied and was visible in the DOM at all times.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
markdown.js already calls renderMarkdownElements() on DOMContentLoaded
for all [data-markdown] elements; ticket.js only processes plain-text
comments to avoid double-rendering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate edit-comment/delete-comment cases from TicketView.php inline
script — ticket.js already handles them. Double-call of editComment() would
immediately open then close the edit form (second call sees .editing → cancels)
- Fix keyboard shortcut 1-4 status change: dispatchEvent(new Event('change'))
was non-bubbling (default), so the document-level change delegation in TicketView
never received it. Now uses { bubbles: true } so updateTicketStatus() fires correctly
- Fix saved filter status type: getCurrentFilterCriteria() was saving status as a
joined string "Open,Pending" but pill-click handler called .join() expecting an array
(TypeError swallowed by try/catch → status filter silently not applied). Now saves
as array; applySavedFilterCriteria handles both arrays and legacy strings
- Pill-click handler also updated to handle both array and string status formats
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add nonce to charts and ticket-preview drawer inline <script> blocks in
DashboardView.php (both were CSP-blocked — charts never rendered)
- Add .lt-modal-xs (280px) to base.css — used by quickStatus/quickAssign
modals but was undefined, causing them to use full modal width
- Fix showConfirmModal in utils.js: class="text-center" → "lt-text-center"
(undefined class); escape newlines as <br> so multi-line messages render
- Remove duplicate click-handler cases from DashboardView.php inline script
that were already handled by dashboard.js, preventing double-firing
(export-tickets, open-settings, remove-filter, etc. were all called twice)
- Fix manual-refresh action to use lt.autoRefresh.now() instead of bare
window.location.reload() so modal/focus guards are respected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- position:fixed popup was adding window.scrollX/scrollY to viewport coords
from getBoundingClientRect(), making it appear far below link when scrolled
- Off-screen check compared against innerHeight + scrollY instead of innerHeight
- Added clamp to prevent negative coords (popup clipped off top/left edge)
- Hide preview on scroll, modal open, and pagination clicks (capture phase)
so stale popup doesn't linger after user navigates away
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .lt-modal-sm (max 360px) and .lt-modal-header--danger variant used
in JS-generated bulk delete confirmation modal (no CSS = unstyled header)
- Add .lt-badge-sm for compact inline badges (comment counts, group tags)
- Add .lt-kv-row { display:contents } with .lt-kv-label/.lt-kv-value rules
(was missing from previous commit — added in base.css)
- Replace style="text-align:center" with .lt-text-center in JS modal body
- Replace style="flex-direction:column" with .lt-flex-col on .lt-btn-group
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kanban restore bug:
- set-view-mode click handler called populateKanbanCards() directly but never
called setViewMode(), so ticketViewMode was never saved to localStorage
- DOMContentLoaded restore checked ticketViewMode (never written) — it should
check lt_activeTab_<path> which lt.tabs.init() actually saves
- Fix: delegate to setViewMode() from the click handler; DOMContentLoaded
reads lt_activeTab_<path> and calls populateKanbanCards() when tab-kanban
Settings modal horizontal scroll:
- .lt-modal-body was missing overflow-x: hidden; content wider than the modal
(e.g. kbd elements with white-space: nowrap) caused horizontal scrollbar
- Added overflow-x: hidden + min-width: 0 to .lt-modal-body
Missing lt-kv-row / lt-kv-label / lt-kv-value CSS:
- These classes were used in TicketView, DashboardView, admin views but had
no primary CSS rules (only a light-theme color override existed)
- Without rules, lt-kv-row divs were block-level grid children consuming one
grid cell each, making lt-kv-label/value stack inside wrong columns
- Added display:contents on lt-kv-row so children participate directly in
the lt-kv-grid 2-column grid; lt-kv-label/value get padding, border, and
min-width:0 + overflow-wrap:break-word to prevent grid column blowout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User input containing MySQL boolean operators (+, -, (, ), ~, *, ", @)
was passed directly to MATCH...AGAINST in BOOLEAN MODE, causing MySQL to
parse them as search operators rather than literals. Input like '(test)'
or '-keyword' would result in a MySQL syntax error / empty results.
Strip boolean mode special chars before building the FULLTEXT term;
the raw search string is still used unchanged for the LIKE fallback parts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Avatar img tag was missing closing > — the endif fired before the tag
closed, causing the initials span to be parsed as an attribute value;
this would silently break the avatar fallback when image fails to load
- Replace style="width:100%;text-align:center" on notif footer link with
lt-w-full lt-text-center utility classes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next_run_at was typed 'i' (int) but stores a datetime string → should be 's'.
is_active was typed 's' (string) but stores 0/1 boolean → should be 'i'.
Positions 10-11 were swapped: 'ssssiiisssis' → 'ssssiiisssii'.
The UPDATE method already had the correct types; only INSERT was affected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DashboardController: handle assigned_to='unassigned' before validateUserId()
which discarded the string, causing the filter to never reach TicketModel;
model already correctly converts 'unassigned' to IS NULL in SQL
- dashboard.js: add null guards before .value access on dynamically-created
modal selects (bulkPriority, bulkStatus, quickStatusSelect)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace style="text-align:center" with .lt-text-center utility class in
WorkflowDesignerView, CustomFieldsView, error_403, error_404, DashboardView JS string
- Replace style="margin-top:..." with .lt-mt-sm utility in WorkflowDesignerView
- Switch comment-edit-raw data-store textareas to .is-hidden class (TicketView PHP
+ JS-rendered; ticket.js template literal) — these are never shown, only read via .value
- Add aria-describedby="visibilityGroupsHint" + id on hint <p> in CreateTicketView
- Fix bind_param type string bug in manage_workflows.php PUT handler: 'ssiiiii' → 'ssiiii'
(7 type chars for 6 params caused binding error on workflow transition updates)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove all onerror="this.style.display='none'" from avatar imgs in
layout_header.php, DashboardView.php, and TicketView.php (PHP + JS)
- Replace onclick SLA dismiss with data-action="dismiss-priority-banner"
attribute; handler wired via existing click delegation in TicketView.php
- Global capture-phase error delegation in layout_footer.php handles all
avatar image failures by adding .lt-avatar-img-err class (CSS display:none)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avatar bug:
- base.css: .lt-avatar now position:relative; img is position:absolute inset:0
so a loaded image covers the initials span (fixes img+initials shown together)
- base.css: .lt-avatar img.lt-avatar-img-err { display:none } — CSS hook for error state
- layout_footer.php: capture-phase error event delegation on .lt-avatar imgs
replaces blocked inline onerror handlers (CSP has no unsafe-inline in script-src)
Chart bug:
- DashboardView: replaced display:flex section-body containers with a
position:relative; width:100%; height:170px div wrapper for each canvas
(Chart.js responsive:true reads parentNode dimensions; flex containers
give canvas zero intrinsic width causing 0×0 render = empty charts)
- Removed has-lt-overlay from chart frames (no overlay div was injected)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DashboardView: Charts row with 3 panels (priority donut, status donut, category bar)
using Chart.js from CDN; data passed inline from PHP stats; TDS color palette
- DashboardView: Flatpickr date picker on advanced search date fields with TDS theme overrides
- dashboard.js: showTableSkeleton() shows lt-skeleton-row during filter-triggered reloads
and auto-refresh; called before all location.reload() with delay
- dashboard.css: Flatpickr TDS theme overrides (dark BG, monospace font, TDS accent colors)
- SecurityHeadersMiddleware: Added cdn.jsdelivr.net to script-src and style-src CSP
to allow Chart.js and Flatpickr from CDN
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dependencies tab: auto-loads potential duplicates via /api/check_duplicates.php
on first activation; shows 'Mark duplicate' button per result which POSTs to
ticket_dependencies with type=duplicates and refreshes the dependencies list
- Settings modal: replaced checkboxes with lt-toggle switches for
notifications_enabled and sound_effects; loads current user prefs on modal open
and saves via /api/user_preferences.php on SAVE button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TicketView: SLA banner now shows live HH:MM:SS elapsed + countdown via JS setInterval
(previously showed static hours from PHP)
- TicketView: Markdown toggles in comment form replaced with lt-toggle switches
- layout_header: In-app notification bell (🔔) with dropdown panel for all users
- layout_footer: Notification JS — polls /api/notifications.php every 60s, badge count,
mark-all-read, panel open/close with Escape/outside-click
- api/notifications.php (new): Returns assign/comment/status-change events from audit_log
for current user's tickets and watched tickets; mark-read via user_preferences
- DashboardView: Ticket preview right drawer — Ctrl+click title or ⊙ peek button
opens lt-drawer-right with ticket summary extracted from table row DOM
- DashboardView: lt.sortable wired on all 4 kanban columns (group='kanban')
Cross-column drag = status change via POST /api/update_ticket.php with optimistic UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- updateTicketField() now targets .lt-frame-ticket[data-priority] (TDS v1.2)
instead of old .priority-indicator / .ticket-container selectors
- All 7 admin views: keyboard-shortcuts.js now uses dynamic ?v={$_v}
instead of hardcoded unversioned path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dashboard: saved filter pills row above active filters bar — loads from API,
click applies criteria as URL params, hidden when no saved filters exist
- ticket.css: add TDS-styled CSS for @mention autocomplete dropdown (was unstyled)
- Dashboard table: data-tooltip on Title and Assigned To columns for truncated text
(lt.tooltip.init() auto-called by lt.init(), zero extra JS needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dashboard stat cards now show lt-dot trend indicators (up/warn/idle) based on
created_today vs closed_today flow — no extra DB query needed
- Add collapsible Team Workload panel showing assignee open ticket counts with
progress bars (green/cyan/red by load), avatar, and name
- StatsModel.getTicketsByAssignee() now returns proper objects with user_id,
display_name, open_count (was name-keyed flat array); limit raised to 8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- watch_ticket.php GET now returns watcher list (up to 6 users) for avatar group
- TicketView: watcher avatar group rendered next to WATCH button, refreshes on toggle
- Rewrite renderDependencies/renderDependents to use TDS lt-kv-grid/lt-badge/lt-btn classes
- renderDependencies: show lt-alert--warning blocker banner when blocked_by has open tickets
- Fix ALL hardcoded ?v=20260327 asset version strings in CreateTicketView + all admin views
- base.css: fix .lt-nav-dropdown-menu hardcoded background → var(--bg-overlay)
- base.css: add light-theme overrides for nav dropdown menu (background, links, hover)
- ticket.css: add .lt-avatar-group and .lt-avatar--overflow styles for watcher display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix DashboardView asset version (was hardcoded 20260327, now uses config ASSET_VERSION)
- Add lt-dot status indicators on dashboard table rows and ticket view toolbar
- Add lt-tag display for Category/Type in ticket read mode (swaps to select in edit mode)
- Add P1/P2 SLA alert banner with elapsed time, progress bar, per-session dismiss
- Wire command palette (Ctrl+K): global nav + admin links via lt.cmdPalette.init()
- Fix cmdPalette.init() call format (flat array, not nested group objects)
- Improve activity timeline: richer formatAction(), better color coding by event type,
inline status transitions shown in meta row, icon column added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Production base.css has per-breakpoint .lt-main.lt-container rules that
explicitly set padding-top with tighter spacing at SM/XS viewports. Adding
these to beta to match — ensures header clearance is bulletproof at all sizes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CSS cascade fixes were correct but browser was serving cached base.css.
Inline style cannot be cached separately and bypasses all cascade issues.
CSS variables still respect media query :root overrides so --header-height
resolves to the correct value (50px SM, 46px XS) at each breakpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The TDS v1.2 sync removed the .lt-main.lt-container combined selector that
was already in the project's base.css. That selector has specificity (0,2,0)
vs single-class (0,1,0), so it always wins over .lt-container padding
shorthand at every breakpoint without needing per-breakpoint overrides.
Also restored flex:1, width:100%, min-width:0 on .lt-main that were dropped.
Removed the incorrect per-breakpoint .lt-main and #main-content hacks added
today which were the wrong approach to the same problem.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use #main-content (specificity 1,0,0,0) to set padding-top at each breakpoint.
This cannot be overridden by any class-based rule regardless of cascade order,
permanently fixing the fixed header overlapping page content.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only dashboard/ticket assets were tracked, so changes to base.css
and base.js were never reflected in the cache-busting version string. Browsers
served stale cached copies, meaning the header padding-top fix never reached
users. Touch base files to bump mtime and force a cache miss immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every media query that overrides .lt-container { padding } with a shorthand
was clobbering .lt-main { padding-top } because both selectors have equal
specificity and the container rule came later in the file. Added .lt-main
padding-top restores after each affected breakpoint (LG 1024-1279px, MD
768-1023px, 1920px+). The laptop range (LG) was the likely culprit on desktop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In the SM (≤767px) and XS (≤479px) media queries, .lt-container { padding }
shorthand appeared after .lt-main { padding-top } with equal specificity,
causing the shorthand to clobber the header-clearance padding-top. Swap order
so .lt-main always wins.
Also remove redundant lt-scanlines div — body::before in base.css already
renders the scanline overlay globally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
body::before and body::after are used for background grid/gradient effects.
Adding lt-scanlines to body caused ::after conflict (higher specificity) and
put the scanline overlay at z-index 9998, above the header at z-index 300.
Move lt-scanlines to a dedicated fixed div so pseudo-elements don't conflict
and the header remains fully visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>