fix: avatar image overlays initials, chart canvas responsive sizing

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>
This commit is contained in:
2026-04-04 18:25:27 -04:00
parent bfe00ea0f6
commit 1ab374531c
3 changed files with 37 additions and 16 deletions
+12 -1
View File
@@ -4490,8 +4490,19 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
overflow: hidden;
flex-shrink: 0;
user-select: none;
position: relative; /* needed so img can overlay initials absolutely */
}
.lt-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Image overlays initials — hidden by JS (.lt-avatar-img-err) when broken */
.lt-avatar img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* When the image fails, JS adds .lt-avatar-img-err to hide it, revealing initials */
.lt-avatar img.lt-avatar-img-err { display: none; }
/* Sizes */
.lt-avatar--xs { width: 1.5rem; height: 1.5rem; font-size: 0.55rem; }
.lt-avatar--sm { width: 2rem; height: 2rem; font-size: 0.65rem; }
+15 -15
View File
@@ -177,25 +177,31 @@ include __DIR__ . '/layout_header.php';
CHARTS ROW (Chart.js loaded from CDN on this page only)
═══════════════════════════════════════════════════════════ -->
<div class="lt-grid-3" style="margin-bottom:0.75rem" id="chartsRow">
<div class="lt-frame has-lt-overlay" id="chartPriorityWrap">
<div class="lt-frame" id="chartPriorityWrap">
<span class="lt-frame-bl"></span><span class="lt-frame-br"></span>
<div class="lt-section-header">Priority Distribution</div>
<div class="lt-section-body" style="height:180px;display:flex;align-items:center;justify-content:center">
<canvas id="chartPriority" aria-label="Priority distribution donut chart" role="img"></canvas>
<div class="lt-section-body" style="padding:0.5rem">
<div style="position:relative;width:100%;height:170px">
<canvas id="chartPriority" aria-label="Priority distribution donut chart" role="img"></canvas>
</div>
</div>
</div>
<div class="lt-frame has-lt-overlay" id="chartStatusWrap">
<div class="lt-frame" id="chartStatusWrap">
<span class="lt-frame-bl"></span><span class="lt-frame-br"></span>
<div class="lt-section-header">Status Breakdown</div>
<div class="lt-section-body" style="height:180px;display:flex;align-items:center;justify-content:center">
<canvas id="chartStatus" aria-label="Status breakdown donut chart" role="img"></canvas>
<div class="lt-section-body" style="padding:0.5rem">
<div style="position:relative;width:100%;height:170px">
<canvas id="chartStatus" aria-label="Status breakdown donut chart" role="img"></canvas>
</div>
</div>
</div>
<div class="lt-frame has-lt-overlay" id="chartCategoryWrap">
<div class="lt-frame" id="chartCategoryWrap">
<span class="lt-frame-bl"></span><span class="lt-frame-br"></span>
<div class="lt-section-header">Category Breakdown</div>
<div class="lt-section-body" style="height:180px;display:flex;align-items:center;justify-content:center">
<canvas id="chartCategory" aria-label="Category breakdown bar chart" role="img"></canvas>
<div class="lt-section-body" style="padding:0.5rem">
<div style="position:relative;width:100%;height:170px">
<canvas id="chartCategory" aria-label="Category breakdown bar chart" role="img"></canvas>
</div>
</div>
</div>
</div>
@@ -289,12 +295,6 @@ include __DIR__ . '/layout_header.php';
var pColors = { 'P1': COLORS.red, 'P2': COLORS.amber, 'P3': COLORS.cyan, 'P4': COLORS.green, 'P5': COLORS.muted };
var sColors = { 'Open': COLORS.green, 'Pending': COLORS.amber, 'In Progress': COLORS.cyan, 'Closed': COLORS.muted };
// Remove loading overlays
['chartPriorityWrap','chartStatusWrap','chartCategoryWrap'].forEach(function(id) {
var el = document.getElementById(id);
if (el) { el.classList.remove('has-lt-overlay'); var ov = el.querySelector('.lt-loading-overlay'); if (ov) ov.remove(); }
});
makeDonut('chartPriority', priorityData, pColors);
makeDonut('chartStatus', statusData, sColors);
makeBar('chartCategory', categoryData.slice(0, 8));
+10
View File
@@ -250,6 +250,16 @@
})();
<?php endif ?>
// ── Avatar image error fallback (CSP blocks inline onerror) ──────
// Uses capture-phase error delegation: if an img inside .lt-avatar
// fails to load, add .lt-avatar-img-err to hide it (CSS display:none),
// revealing the initials span underneath.
document.addEventListener('error', function(e) {
if (e.target.tagName === 'IMG' && e.target.closest('.lt-avatar')) {
e.target.classList.add('lt-avatar-img-err');
}
}, true);
// Footer hint bar actions (keyboard help + settings — work on all pages)
document.addEventListener('click', function(e) {
var btn = e.target.closest('[data-action]');