From f96a4ab3ad8afa7c19da4affd2bd5c27e0dd109d Mon Sep 17 00:00:00 2001 From: Olaf Date: Mon, 24 Aug 2026 20:17:46 +0200 Subject: [PATCH] Header improvements --- VIBE/CHAT_LOG.md | 30 +++++++++ VIBE/PROMPTS.md | 5 ++ backend/tests/test_api.py | 16 +++-- backend/tests/test_user_config.py | 8 ++- frontend/static/admin.js | 8 --- frontend/static/auth-header.js | 35 +++++----- frontend/static/style.css | 95 ++++++++++++++++++---------- frontend/templates/admin.html | 21 +++--- frontend/templates/feed.html | 19 ++++-- frontend/templates/login.html | 19 ++++-- frontend/templates/user_profile.html | 20 +++--- 11 files changed, 183 insertions(+), 93 deletions(-) diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index e6b0b51..866d8fd 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -295,3 +295,33 @@ Pressing signout (on the home page) does not log the user out ### Assistant outcome Found that the home page rendered the shared authenticated session header but did not load `logout.js`, so its sign-out button had no event handler. Added the logout handler to the home and login pages with cache-busted asset references. Verified real token revocation and the full strict backend suite with 12 passing tests. + +### User +Instead of the buttons on the top of the page create a 'hamburger' dropdown menu. The menu items are sign in, profile, admin, and sign out with authenticated visibility rules. + +### Assistant outcome +Replaced top-level authentication buttons on all web pages with a shared hamburger menu. The menu contains Sign in for signed-out users, Profile and Sign out for authenticated users, and Admin only for administrators. Added accessible toggle state, avatar/username identity display, responsive Mocha styling, and updated route tests. The strict backend suite passed with 12 tests and all browser scripts passed syntax checks. + +### User +The menu items on the dropdown render behind
+ +### Assistant outcome +Fixed the dropdown stacking context by removing header overflow clipping, elevating the header above main content, and raising the menu z-index. Verified the served CSS rules, page rendering, browser scripts, and strict backend suite with 12 passing tests. + +### User +The menu should not show the profile, admin and signout items when nobody is authenticated. + +### Assistant outcome +Fixed CSS specificity so `.hidden` works inside the hamburger menu. The menu now shows only `Sign in` when no valid session exists; Profile, Admin, and Sign out remain hidden. Served CSS and authentication script checks passed, followed by the strict backend suite with 12 tests. + +### User +Add one entry on top of the hamburger menu 'home' that always directs to the home page. + +### Assistant outcome +Added `Home` as the first item in the shared hamburger menu on feed, user, login, profile, and admin pages. It always remains visible and links to `/`, independent of authentication state. All page smoke checks and the strict backend suite passed with 12 tests. + +### User +where the hamburger menu shows the avatar and username, replace by username only and link to the /user/ page + +### Assistant outcome +Replaced the hamburger menu avatar/username row with a username-only link. After authentication, the link dynamically targets `//`; the separate Profile, Admin, and Sign out menu items retain their session-based visibility. Removed the old menu avatar dependency, bumped the shared header asset to `v3`, and verified all pages plus the strict backend suite with 12 tests. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index da0eb8d..a6623da 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -52,6 +52,11 @@ 48. execute next todo 49. execute final todo (nummer 3) 50. Pressing signout (on the home page) does not log the user out +51. Instead of the buttons on the top of the page create a 'hamburger' dropdown menu. The menu items are sign in, profile, admin, and sign out with authenticated visibility rules. +52. The menu items on the dropdown render behind
+53. The menu should not show the profile, admin and signout items when nobody is authenticated. +54. Add one entry on top of the hamburger menu 'home' that always directs to the home page. +55. where the hamburger menu shows the avatar and username, replace by username only and link to the /user/ page ## Future entries diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index f9897c3..95cd9c3 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -172,8 +172,14 @@ def test_logout_revokes_token_and_admin_can_list_plugins(): def test_public_and_admin_pages_render_html(): root_page = client.get('/') assert 'LinkLog' in root_page.text - assert 'class="login-button" href="/login"' in root_page.text - assert 'id="auth-session" class="auth-session hidden"' in root_page.text + assert 'class="menu-toggle"' in root_page.text + assert 'id="auth-menu" class="auth-menu hidden"' in root_page.text + assert 'id="auth-home-link" href="/">Home' in root_page.text + assert '' in root_page.text + assert 'id="auth-avatar"' not in root_page.text + assert 'id="auth-login-button" href="/login"' in root_page.text + assert 'id="auth-profile-link" class="hidden"' in root_page.text + assert 'id="auth-admin-link" class="hidden"' in root_page.text assert 'logout.js?v=3' in root_page.text assert client.get('/alice').status_code == 200 assert client.get('/alice/').status_code == 200 @@ -193,8 +199,10 @@ def test_public_and_admin_pages_render_html(): assert 'Admin' in admin_page assert 'id="admin-controls" class="hidden"' in admin_page assert 'id="admin-auth-notice" class="auth-notice hidden"' in admin_page - assert 'id="admin-login-button" class="login-button" href="/login"' in admin_page - assert 'id="auth-session" class="auth-session hidden"' in admin_page + assert 'id="auth-menu" class="auth-menu hidden"' in admin_page + assert 'id="auth-home-link" href="/">Home' in admin_page + assert '' in admin_page + assert 'admin.js?v=3' in admin_page feed_script = client.get('/static/feed.js?v=5').text assert 'if (item.is_owner)' in feed_script diff --git a/backend/tests/test_user_config.py b/backend/tests/test_user_config.py index 8ed6885..69fbe5d 100644 --- a/backend/tests/test_user_config.py +++ b/backend/tests/test_user_config.py @@ -35,9 +35,11 @@ def test_user_config_api_and_profile_page(): assert 'name="avatar_url"' not in page_response.text assert 'value="mastodon.social"' in page_response.text assert 'From my #LinkLog: "' in page_response.text - assert 'id="admin-link"' in page_response.text - assert 'class="login-button hidden"' in page_response.text - assert 'id="auth-session" class="auth-session hidden"' in page_response.text + assert 'id="auth-menu" class="auth-menu hidden"' in page_response.text + assert 'id="auth-home-link" href="/">Home' in page_response.text + assert 'id="auth-profile-link" class="hidden"' in page_response.text + assert '' in page_response.text + assert 'id="auth-avatar"' not in page_response.text bob_login = client.post('/api/auth/login', json={ 'username': 'bob', diff --git a/frontend/static/admin.js b/frontend/static/admin.js index d90d92c..f7c5d49 100644 --- a/frontend/static/admin.js +++ b/frontend/static/admin.js @@ -4,8 +4,6 @@ const userList = document.querySelector('#user-list'); const userForm = document.querySelector('#user-form'); const adminControls = document.querySelector('#admin-controls'); const adminAuthNotice = document.querySelector('#admin-auth-notice'); -const adminLoginButton = document.querySelector('#admin-login-button'); -const adminLogoutButton = document.querySelector('#logout-button'); const accessToken = localStorage.getItem('linklogAccessToken'); function authHeaders(includeJson = false) { @@ -66,22 +64,16 @@ async function loadUsers() { function showAdminState(isAdmin) { adminControls.classList.toggle('hidden', !isAdmin); adminAuthNotice.classList.toggle('hidden', isAdmin); - adminLoginButton.classList.toggle('hidden', isAdmin); - adminLogoutButton.classList.toggle('hidden', !accessToken); } function showSignedOutState() { showAdminState(false); adminAuthNotice.innerHTML = 'Administrator sign-in required. Sign in'; - adminLoginButton.classList.remove('hidden'); - adminLogoutButton.classList.add('hidden'); } function showUnauthorizedState() { showAdminState(false); adminAuthNotice.textContent = 'You are signed in, but you are not authorized to access this page.'; - adminLoginButton.classList.add('hidden'); - adminLogoutButton.classList.remove('hidden'); } async function loadAdminState() { diff --git a/frontend/static/auth-header.js b/frontend/static/auth-header.js index 4727272..f2b89cd 100644 --- a/frontend/static/auth-header.js +++ b/frontend/static/auth-header.js @@ -1,34 +1,39 @@ (() => { const loginButton = document.querySelector('#auth-login-button'); + const profileLink = document.querySelector('#auth-profile-link'); + const adminLink = document.querySelector('#auth-admin-link'); const session = document.querySelector('#auth-session'); - const avatar = document.querySelector('#auth-avatar'); const username = document.querySelector('#auth-username'); const token = localStorage.getItem('linklogAccessToken'); - if (!loginButton || !session || !avatar || !username) return; + const menu = document.querySelector('#auth-menu'); + const menuToggle = document.querySelector('.menu-toggle'); + const logoutButton = document.querySelector('#logout-button'); + + if (!loginButton || !profileLink || !adminLink || !session || !username || !menu || !menuToggle || !logoutButton) return; + + menuToggle.addEventListener('click', () => { + const isOpen = !menu.classList.contains('hidden'); + menu.classList.toggle('hidden', isOpen); + menuToggle.setAttribute('aria-expanded', String(!isOpen)); + }); function showSignedOut() { loginButton.classList.remove('hidden'); + profileLink.classList.add('hidden'); + adminLink.classList.add('hidden'); session.classList.add('hidden'); + logoutButton.classList.add('hidden'); } function showSignedIn(user) { loginButton.classList.add('hidden'); + profileLink.classList.remove('hidden'); + adminLink.classList.toggle('hidden', !user.is_admin); session.classList.remove('hidden'); + logoutButton.classList.remove('hidden'); username.textContent = user.username || ''; - const initial = (user.username || 'U').slice(0, 1).toUpperCase(); - avatar.textContent = initial; - if (user.avatar_url) { - const image = document.createElement('img'); - image.src = user.avatar_url; - image.alt = `${user.username || 'User'} avatar`; - image.addEventListener('error', () => { - image.remove(); - avatar.textContent = initial; - }); - avatar.textContent = ''; - avatar.appendChild(image); - } + username.href = `/${encodeURIComponent(user.username || '')}/`; } if (!token) { diff --git a/frontend/static/style.css b/frontend/static/style.css index f49378e..cec3175 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -51,7 +51,7 @@ body::selection { .site-header { position: relative; - overflow: hidden; + z-index: 20; padding: 56px 0 48px; background: linear-gradient(115deg, rgba(203, 166, 247, 0.18), transparent 45%), @@ -103,18 +103,67 @@ body::selection { flex: 0 0 auto; align-items: center; gap: 8px; + position: relative; +} + +.menu-toggle { + min-width: 0; + padding: 10px 13px; + border-color: var(--surface-2); + background: var(--surface-0); + color: var(--text); +} + +.menu-toggle::before { + content: '\2630'; + margin-right: 7px; + font-size: 1.1em; +} + +.auth-menu { + position: absolute; + z-index: 30; + top: calc(100% + 10px); + right: 0; + display: grid; + min-width: 190px; + gap: 4px; + padding: 8px; + background: var(--surface-0); + border: 1px solid var(--surface-1); + border-radius: 10px; + box-shadow: var(--shadow); +} + +.auth-menu a, +.auth-menu button { + display: block; + width: 100%; + min-width: 0; + padding: 9px 10px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--text); + text-align: left; + text-decoration: none; +} + +.auth-menu a:hover, +.auth-menu button:hover { + background: var(--surface-1); + color: var(--lavender); +} + +.auth-menu .hidden { + display: none; } .auth-session { display: flex; align-items: center; - gap: 9px; -} - -.auth-session .avatar { - width: 32px; - height: 32px; - flex-basis: 32px; + padding: 7px 10px; + border-bottom: 1px solid var(--border); } .auth-session .user-name { @@ -124,28 +173,8 @@ body::selection { white-space: nowrap; } -.login-button { - flex: 0 0 auto; - margin-top: 2px; - padding: 10px 16px; - border: 1px solid var(--surface-2); - border-radius: 8px; - background: var(--surface-0); - color: var(--text); - font-weight: 700; - text-decoration: none; - transition: background 160ms ease, border-color 160ms ease; -} - -.login-button:hover { - border-color: var(--lavender); - background: var(--surface-1); - color: var(--text); -} - .logout-button { min-width: 0; - padding: 10px 13px; border-color: rgba(243, 139, 168, 0.45); background: transparent; color: var(--red); @@ -158,6 +187,8 @@ body::selection { } main.container { + position: relative; + z-index: 1; padding-top: 28px; padding-bottom: 64px; } @@ -462,21 +493,19 @@ button:disabled { } .header-actions { - align-items: stretch; - flex-direction: column; + align-items: flex-end; } .auth-session { - flex-direction: column; + align-items: flex-start; } - .login-button { + .menu-toggle { padding: 8px 11px; font-size: 0.9rem; } .logout-button { - padding: 8px 11px; font-size: 0.9rem; } diff --git a/frontend/templates/admin.html b/frontend/templates/admin.html index d8d7f1b..4f406f9 100644 --- a/frontend/templates/admin.html +++ b/frontend/templates/admin.html @@ -15,12 +15,17 @@

Manage users and plugin configuration

- - + +
@@ -60,8 +65,8 @@
- + - + diff --git a/frontend/templates/feed.html b/frontend/templates/feed.html index d48e0b6..b0dbd15 100644 --- a/frontend/templates/feed.html +++ b/frontend/templates/feed.html @@ -15,12 +15,17 @@

Public link feed

- - + +
@@ -61,7 +66,7 @@
- + diff --git a/frontend/templates/login.html b/frontend/templates/login.html index 4d1713c..ef231f1 100644 --- a/frontend/templates/login.html +++ b/frontend/templates/login.html @@ -15,12 +15,17 @@

Access your LinkLog settings

- - + +
@@ -41,7 +46,7 @@ - + diff --git a/frontend/templates/user_profile.html b/frontend/templates/user_profile.html index f998b8d..9ce461e 100644 --- a/frontend/templates/user_profile.html +++ b/frontend/templates/user_profile.html @@ -12,19 +12,23 @@

Profile

- - + +
-

Sign in at /login to manage your settings.

- +