Skip to content

Commit 80a25ee

Browse files
quic-boyucclaude
andcommitted
observatory: fix sidebar/header usability
Two UI improvements addressing feedback that the left panel requires hovering a hard-to-find 12px strip to open: Header - Remove hover-only reveal (no more invisible 10px trigger strip) - Make header permanently visible with `position: sticky` - Slightly compact padding so it uses less vertical space Sidebar toggle tab - Add a visible 22px-wide tab fixed at the left edge (always visible) - Shows ›/‹ chevron; click to pin panel open or close - Tab slides to left:300px when sidebar is pinned open - State persisted in localStorage (os_sidebar_pinned) so it survives page refresh - Hover-to-reveal still works as a secondary mechanism Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ac88080 commit 80a25ee

2 files changed

Lines changed: 78 additions & 23 deletions

File tree

devtools/observatory/templates/css/main.css

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -87,42 +87,34 @@
8787
header {
8888
background: var(--bg-secondary);
8989
color: var(--text-primary);
90-
padding: 1rem 2rem;
90+
padding: 0.55rem 1.5rem;
9191
display: flex;
9292
justify-content: space-between;
9393
align-items: center;
9494
flex-shrink: 0;
9595
border-bottom: 1px solid var(--border-color);
96-
position: fixed;
96+
position: sticky;
9797
top: 0;
9898
left: 0;
9999
right: 0;
100-
transform: translateY(-100%);
101-
transition: transform 0.25s ease, box-shadow 0.25s ease;
102100
z-index: 200;
103101
}
104102

105-
.header-trigger:hover ~ header,
106-
header:hover {
107-
transform: translateY(0);
108-
box-shadow: 0 4px 12px var(--shadow);
109-
}
110-
111103
header.hidden {
112-
transform: translateY(-100%);
104+
display: none;
113105
}
114-
106+
115107
[data-theme="dark"] header {
116108
background: var(--header-bg);
117109
color: var(--text-inverse);
118110
border-bottom: none;
119111
}
120112

121-
.header-content h1 { font-size: 1.2rem; margin: 0; }
122-
.header-meta { font-size: 0.85rem; opacity: 0.7; margin-top: 0.2rem; }
113+
.header-content h1 { font-size: 1.05rem; margin: 0; }
114+
.header-meta { font-size: 0.8rem; opacity: 0.7; margin-top: 0.1rem; }
123115
.header-meta span { margin-right: 1.5rem; }
124116
.header-meta code { background: var(--bg-tertiary); padding: 0.2rem 0.4rem; border-radius: 3px; border: 1px solid var(--border-color); }
125-
117+
126118
[data-theme="dark"] .header-meta code { background: rgba(255,255,255,0.1); border: none; }
127119

128120
/* Theme Toggle */
@@ -131,30 +123,69 @@
131123
border: 1px solid var(--border-color);
132124
color: var(--text-primary);
133125
border-radius: 6px;
134-
padding: 0.5rem 0.75rem;
126+
padding: 0.4rem 0.65rem;
135127
cursor: pointer;
136-
font-size: 1.2rem;
128+
font-size: 1.1rem;
137129
transition: all 0.2s;
138130
display: flex;
139131
align-items: center;
140132
justify-content: center;
141133
}
142134
.theme-toggle:hover { background: var(--hover-color); transform: scale(1.05); }
143-
135+
144136
[data-theme="dark"] .theme-toggle {
145137
background: rgba(255,255,255,0.1);
146138
border: 1px solid rgba(255,255,255,0.2);
147139
color: white;
148140
}
149141
[data-theme="dark"] .theme-toggle:hover { background: rgba(255,255,255,0.2); }
150142

143+
/* Sidebar toggle tab — always-visible click target on left edge */
144+
.sidebar-toggle-btn {
145+
position: fixed;
146+
left: 0;
147+
top: 50%;
148+
transform: translateY(-50%);
149+
z-index: 210;
150+
width: 22px;
151+
height: 60px;
152+
background: var(--bg-secondary);
153+
border: 1px solid var(--border-color);
154+
border-left: none;
155+
border-radius: 0 8px 8px 0;
156+
cursor: pointer;
157+
display: flex;
158+
align-items: center;
159+
justify-content: center;
160+
box-shadow: 2px 0 8px var(--shadow);
161+
transition: left 0.25s ease, background 0.2s, box-shadow 0.2s;
162+
padding: 0;
163+
color: var(--text-secondary);
164+
font-size: 13px;
165+
line-height: 1;
166+
user-select: none;
167+
}
168+
.sidebar-toggle-btn:hover {
169+
background: var(--hover-color);
170+
color: var(--text-primary);
171+
box-shadow: 3px 0 12px var(--shadow);
172+
width: 26px;
173+
}
174+
.sidebar-toggle-btn.open { left: 300px; }
175+
[data-theme="dark"] .sidebar-toggle-btn {
176+
background: var(--header-bg);
177+
border-color: rgba(255,255,255,0.15);
178+
color: rgba(255,255,255,0.7);
179+
}
180+
[data-theme="dark"] .sidebar-toggle-btn:hover { background: rgba(255,255,255,0.12); color: white; }
181+
151182
.container {
152183
display: flex;
153184
flex: 1;
154185
overflow: hidden;
155186
}
156187

157-
/* Sidebar trigger zone — invisible strip on left edge */
188+
/* Sidebar trigger zone — invisible strip on left edge (hover fallback) */
158189
.index-pane-trigger {
159190
position: fixed;
160191
left: 0;
@@ -182,9 +213,10 @@
182213
box-shadow: none;
183214
}
184215

185-
/* Show when trigger zone or pane itself is hovered */
216+
/* Show when trigger zone or pane itself is hovered, or pinned open */
186217
.index-pane-trigger:hover ~ .index-pane,
187-
.index-pane:hover {
218+
.index-pane:hover,
219+
.index-pane.pinned {
188220
transform: translateX(0);
189221
box-shadow: 4px 0 16px var(--shadow);
190222
}

devtools/observatory/templates/js/02_layout.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
const state = OBS.state;
44
const { escapeHtml } = OBS.utils;
55

6+
// Restore pinned state from localStorage
7+
let sidebarPinned = false;
8+
try { sidebarPinned = localStorage.getItem('obs_sidebar_pinned') === '1'; } catch(e) {}
9+
610
function renderLayout() {
711
const icon = state.theme === 'dark' ? '☀️' : '🌙';
812
OBS.app.innerHTML = `
9-
<div class="header-trigger"></div>
1013
<header>
1114
<div class="header-content">
1215
<h1>${escapeHtml(state.data.title || 'Observatory Report')}</h1>
@@ -22,7 +25,13 @@
2225
</header>
2326
<div class="container">
2427
<div class="index-pane-trigger"></div>
25-
<nav class="index-pane">
28+
<button class="sidebar-toggle-btn ${sidebarPinned ? 'open' : ''}"
29+
id="sidebar-toggle-btn"
30+
onclick="toggleSidebar()"
31+
title="${sidebarPinned ? 'Close panel' : 'Open panel'}">
32+
${sidebarPinned ? '&#8249;' : '&#8250;'}
33+
</button>
34+
<nav class="index-pane ${sidebarPinned ? 'pinned' : ''}" id="index-pane">
2635
<div class="index-header" id="index-header">
2736
<h2>Collected Graphs (${(state.data.records || []).length})</h2>
2837
</div>
@@ -34,6 +43,20 @@
3443
updateIndexHeader();
3544
}
3645

46+
window.toggleSidebar = function() {
47+
sidebarPinned = !sidebarPinned;
48+
try { localStorage.setItem('obs_sidebar_pinned', sidebarPinned ? '1' : '0'); } catch(e) {}
49+
50+
const pane = document.getElementById('index-pane');
51+
const btn = document.getElementById('sidebar-toggle-btn');
52+
if (pane) pane.classList.toggle('pinned', sidebarPinned);
53+
if (btn) {
54+
btn.classList.toggle('open', sidebarPinned);
55+
btn.innerHTML = sidebarPinned ? '&#8249;' : '&#8250;';
56+
btn.title = sidebarPinned ? 'Close panel' : 'Open panel';
57+
}
58+
};
59+
3760
function updateIndexHeader() {
3861
const header = document.getElementById('index-header');
3962
if (!header) return;

0 commit comments

Comments
 (0)