Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions changelogs/CHANGELOG-annotation-panel-overlay-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Fix: Annotation Thread Panel Renders With Empty Overlay Box

- Fixed the annotation Q&A panel opening with its content squeezed to the left and a large empty white box covering the right portion
- Root cause: the right-side Threads dock was created and left visible (320px wide) even when no thread was docked, painting an invisible overlay over every floating panel. Dock visibility was managed via inconsistent inline `display` styles.
- Fix: the dock is now `display:none` by default in CSS and shown only via `body.ai-tag-dock-open`, which is added exclusively when a panel is actually docked (and removed when the dock empties)
- Also: messages area sizes to content (`flex:0 1 auto`) instead of growing to fill an oversized panel; floating-panel positioning now hard-clamps to the viewport on all four edges; removed `backdrop-filter: blur()` (compositing artifacts on a fixed/draggable element) for a solid background; mobile panel height floored (`min-height`, `max-height:70vh`) instead of a collapsing `60vh`

---

## Summary

A regression in the new reading-companion thread panels: an empty dock element overlaid the right side of every floating Q&A panel, clipping the conversation into a narrow left strip. The dock is now hidden unless it actually holds a docked thread, and several panel-sizing/positioning robustness fixes were made. Verified at desktop resolution via Playwright (the live preview iframe is 1px tall and can't validate layout heights).

---

## Testing
- Verified at 1280×800 via Playwright: panel renders full-width with header/toolbar/messages/input visible; hit-test at the previously-empty region now returns a message element, not the dock; docking still reflows the document (1280 → 960) and shows the dock only when used.
- Smoke suite 22/22; build clean.
29 changes: 19 additions & 10 deletions css/ai-tags.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,12 @@ body.ai-tags-hidden .ai-tag-pill-anchor {
z-index: 10001;
width: 400px;
max-height: 520px;
background: rgba(13, 17, 23, 0.96);
/* Solid background — backdrop-filter:blur on a fixed, draggable element caused
a compositing artifact that clipped content and painted an empty overlay box. */
background: #161b22;
border: 1px solid rgba(99, 110, 123, 0.3);
border-radius: 14px;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.03);
backdrop-filter: blur(16px);
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
overflow: hidden;
Expand All @@ -175,7 +176,7 @@ body.ai-tags-hidden .ai-tag-pill-anchor {
to { opacity: 1; transform: translateY(0) scale(1); }
}
[data-theme="light"] .ai-tag-thread-panel {
background: rgba(255, 255, 255, 0.97);
background: #ffffff;
border-color: rgba(0, 0, 0, 0.12);
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
}
Expand Down Expand Up @@ -280,13 +281,15 @@ body.ai-tags-hidden .ai-tag-pill-anchor {

/* Thread messages area */
.ai-tag-thread-messages {
flex: 1;
/* Size to content (scroll past 300px) rather than flex-growing to fill an
oversized panel — otherwise a short thread leaves a large empty void. */
flex: 0 1 auto;
overflow-y: auto;
padding: 10px 14px;
display: flex;
flex-direction: column;
gap: 10px;
min-height: 80px;
min-height: 60px;
max-height: 300px;
}
.ai-tag-thread-msg {
Expand Down Expand Up @@ -558,12 +561,17 @@ body.ai-tags-hidden .ai-tag-pill-anchor {
width: var(--ai-tag-dock-width);
height: 100vh;
z-index: 9000;
display: flex;
/* Hidden unless the dock actually holds a thread — body.ai-tag-dock-open shows it.
This prevents an empty dock from painting an overlay box over the document. */
display: none;
flex-direction: column;
background: rgba(13, 17, 23, 0.98);
background: #161b22;
border-left: 1px solid rgba(99, 110, 123, 0.3);
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.35);
}
body.ai-tag-dock-open .ai-tag-thread-dock {
display: flex;
}
[data-theme="light"] .ai-tag-thread-dock {
background: rgba(250, 251, 252, 0.99);
border-left-color: rgba(0, 0, 0, 0.1);
Expand Down Expand Up @@ -632,8 +640,9 @@ body.ai-tag-dock-open .app-container {
@media (max-width: 640px) {
.ai-tag-thread-panel {
width: 100vw;
height: 60vh;
max-height: 60vh;
height: auto;
max-height: 70vh;
min-height: 240px;
bottom: 0;
left: 0 !important;
right: 0;
Expand Down
34 changes: 21 additions & 13 deletions js/ai-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -1115,22 +1115,32 @@
}

// Place a floating panel near its anchor, cascading so multiples don't stack
// exactly on top of each other.
// exactly on top of each other. Clamps to the viewport on all four edges so a
// panel never opens partly off-screen or taller than the window.
function positionFloatingPanel(panel, anchorEl) {
var vw = window.innerWidth, vh = window.innerHeight;
var panelWidth = Math.min(400, vw - 24);
var panelHeight = Math.min(520, vh - 24);
var n = Object.keys(openPanels).length;
var offset = ((n - 1) % 6) * 28;
var panelWidth = 400, panelHeight = 520;

var left, top;
if (anchorEl) {
var rect = anchorEl.getBoundingClientRect();
left = Math.min(rect.right + 8, window.innerWidth - panelWidth - 16);
top = Math.min(rect.top - 20, window.innerHeight - panelHeight - 16);
// Prefer to the right of the anchor; if no room, sit to its left.
left = rect.right + 8;
if (left + panelWidth > vw - 16) left = rect.left - panelWidth - 8;
top = rect.top - 20;
} else {
left = (window.innerWidth - panelWidth) / 2;
top = (window.innerHeight - panelHeight) / 2;
left = (vw - panelWidth) / 2;
top = (vh - panelHeight) / 2;
}
left = Math.max(16, left - offset);
top = Math.max(16, top + offset);
// Apply cascade, then hard-clamp inside the viewport on every edge.
left = left - offset;
top = top + offset;
left = Math.max(12, Math.min(left, vw - panelWidth - 12));
top = Math.max(12, Math.min(top, vh - panelHeight - 12));

panel.style.position = 'fixed';
panel.style.left = left + 'px';
panel.style.top = top + 'px';
Expand Down Expand Up @@ -1238,18 +1248,16 @@
var total = Object.keys(openPanels).length;
var countEl = threadDock.querySelector('.ai-tag-dock-count');
if (countEl) countEl.textContent = total;
// hide the dock chrome when nothing is docked
threadDock.style.display = docked > 0 ? '' : 'none';
if (docked > 0) document.body.classList.add('ai-tag-dock-open');
// Visibility is driven purely by the body class (CSS shows the dock only then).
document.body.classList.toggle('ai-tag-dock-open', docked > 0);
}

// Remove the dock (and un-reflow the document) once it has no docked panels.
// Un-reflow the document once the dock has no docked panels.
function maybeCollapseDock(force) {
if (!threadDock) return;
var docked = threadDock.querySelectorAll('.ai-tag-thread-panel').length;
if (docked === 0 || force) {
document.body.classList.remove('ai-tag-dock-open');
if (docked === 0) { threadDock.style.display = 'none'; }
}
}

Expand Down
Loading