Skip to content

Commit a3f3bbe

Browse files
committed
fix: loading spinner positioning
1 parent 7058507 commit a3f3bbe

2 files changed

Lines changed: 61 additions & 20 deletions

File tree

static/css/style.css

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ a { color: var(--link); text-decoration: none; }
106106
a:hover { text-decoration: underline; }
107107

108108
/* ---------- Layout ---------- */
109-
.container { max-width: 1200px; margin: 0 auto; padding: 1.5rem 1rem; flex: 1; }
109+
/* width: 100% is required here: body is display:flex + flex-direction:column,
110+
* and auto horizontal margins (margin: 0 auto) suppress align-items:stretch,
111+
* so without an explicit width the container collapses to content width. */
112+
.container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 1.5rem 1rem; flex: 1; }
110113

111114
/* ---------- Navbar ---------- */
112115
.navbar {
@@ -297,10 +300,24 @@ h3 { font-size: 1.15rem; font-weight: 600; }
297300
.dropdown-item:hover { background: var(--bg-hover); text-decoration: none; }
298301

299302
/* ---------- Loading ---------- */
300-
.loading { display: flex; flex-direction: column; align-items: center; padding: 3rem 0; gap: 1rem; }
303+
.loading,
304+
.loading-center {
305+
display: flex;
306+
flex-direction: column;
307+
align-items: center;
308+
justify-content: center;
309+
width: 100%;
310+
gap: 1rem;
311+
text-align: center;
312+
}
313+
.loading { padding: 3rem 0; }
314+
.loading-center { color: var(--text-muted); font-size: 0.9rem; }
301315
.spinner {
316+
display: block;
317+
flex-shrink: 0;
302318
width: 2rem;
303319
height: 2rem;
320+
margin-inline: auto;
304321
border: 3px solid var(--border);
305322
border-top-color: var(--spinner);
306323
border-radius: 50%;
@@ -324,7 +341,17 @@ h3 { font-size: 1.15rem; font-weight: 600; }
324341
* URL inside .main-content would push the column wider than 1fr — overflowing
325342
* the viewport on the right. min-width: 0 lets the column shrink and lets the
326343
* existing overflow-x: auto on .prose pre handle the scroll inside the bubble. */
327-
.main-content { min-width: 0; }
344+
/* min-height prevents the right column from collapsing during lazy-load (issue #84). */
345+
.main-content { min-width: 0; min-height: 60vh; }
346+
347+
/* Loading state: full-width cell; .loading-center centers spinner + label. */
348+
.main-content.is-loading {
349+
width: 100%;
350+
min-height: 60vh;
351+
}
352+
.main-content.is-loading .loading-center {
353+
min-height: 60vh;
354+
}
328355

329356
/* ---------- Sidebar ---------- */
330357
.sidebar {
@@ -358,6 +385,13 @@ h3 { font-size: 1.15rem; font-weight: 600; }
358385
.sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
359386
.sidebar::-webkit-scrollbar-track { background: transparent; }
360387

388+
/* Placeholder shown in the sidebar column while summary tabs are fetching */
389+
.sidebar-loading {
390+
width: 100%;
391+
padding: 1.5rem 0;
392+
opacity: 0.5;
393+
}
394+
361395
/* Scroll hint at the bottom */
362396
.sidebar-scroll-hint {
363397
text-align: center;

templates/workspace.html

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ <h2 id="project-name">Loading...</h2>
3939
<p class="text-sm text-muted" id="convo-count"></p>
4040
</div>
4141

42-
<div id="loading" class="loading">
43-
<div class="spinner"></div>
44-
<p>Loading conversations...</p>
45-
</div>
46-
47-
<!-- Content grid -->
48-
<div id="content-grid" class="workspace-grid" style="display:none">
49-
<div class="sidebar" id="sidebar"></div>
50-
<div class="main-content" id="main-content">
51-
<div class="card" style="padding:2rem;text-align:center">
52-
<p class="text-muted">No conversation selected</p>
42+
<!-- Content grid — always in layout so column widths never collapse -->
43+
<div id="content-grid" class="workspace-grid">
44+
<div class="sidebar" id="sidebar">
45+
<div class="sidebar-loading loading-center">
46+
<div class="spinner" style="width:1.25rem;height:1.25rem;border-width:2px"></div>
47+
</div>
48+
</div>
49+
<div class="main-content is-loading" id="main-content">
50+
<div class="loading-center">
51+
<div class="spinner"></div>
52+
<p>Loading conversations…</p>
5353
</div>
5454
</div>
5555
</div>
@@ -78,8 +78,6 @@ <h2 id="project-name">Loading...</h2>
7878
document.getElementById('project-hash').textContent = WORKSPACE_ID;
7979
}
8080

81-
document.getElementById('loading').style.display = 'none';
82-
document.getElementById('content-grid').style.display = 'grid';
8381
document.getElementById('convo-count').textContent = `${allTabs.length} conversations`;
8482

8583
renderSidebar();
@@ -92,7 +90,11 @@ <h2 id="project-name">Loading...</h2>
9290
selectTab(allTabs[0].id);
9391
}
9492
} catch (e) {
95-
document.getElementById('loading').innerHTML = '<p class="text-danger">Failed to load workspace.</p>';
93+
const main = document.getElementById('main-content');
94+
main.classList.remove('is-loading');
95+
main.innerHTML =
96+
'<div class="card" style="padding:2rem;text-align:center"><p class="text-danger">Failed to load workspace.</p></div>';
97+
document.getElementById('sidebar').innerHTML = '';
9698
}
9799
});
98100

@@ -143,8 +145,10 @@ <h2 id="project-name">Loading...</h2>
143145
selectedTab = summary;
144146
tabCache[id] = summary;
145147
} else {
146-
document.getElementById('main-content').innerHTML =
147-
'<div class="card" style="padding:2rem;text-align:center"><div class="spinner"></div><p>Loading conversation…</p></div>';
148+
const main = document.getElementById('main-content');
149+
main.classList.add('is-loading');
150+
main.innerHTML =
151+
'<div class="loading-center" style="min-height:60vh"><div class="spinner"></div><p>Loading conversation…</p></div>';
148152
try {
149153
const res = await fetch(`/api/workspaces/${WORKSPACE_ID}/tabs/${id}`);
150154
if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -154,7 +158,9 @@ <h2 id="project-name">Loading...</h2>
154158
tabCache[id] = data.tab;
155159
selectedTab = data.tab;
156160
} catch (e) {
157-
document.getElementById('main-content').innerHTML =
161+
const main = document.getElementById('main-content');
162+
main.classList.remove('is-loading');
163+
main.innerHTML =
158164
`<div class="card" style="padding:2rem;text-align:center"><p class="text-danger">Failed to load conversation: ${escapeHtml(e.message)}</p></div>`;
159165
return;
160166
}
@@ -170,6 +176,7 @@ <h2 id="project-name">Loading...</h2>
170176

171177
function renderChat(tab) {
172178
const main = document.getElementById('main-content');
179+
main.classList.remove('is-loading');
173180
const bubbles = (tab.bubbles || []).filter(b => b.text && b.text.trim());
174181

175182
let html = `<div class="card" style="padding:1.5rem">

0 commit comments

Comments
 (0)