relay UI overhaul, favicon, settings, admin last-seen, fixes

Relay:
  - Inbox/shared/sent split into own view-inbox (nav button + poll).
    Compose is now single-column, centered.
  - Header logo links to compose. Inbox badge stays fresh on all
    views via a quiet loadInbox on every view switch.
  - Mobile menu: full-height sheet below header, larger buttons
    (15px font, 14px padding), full width.
  - Login form wrapped in <form> with name attributes + for/id on
    labels so password managers can autofill.
  - Admin user list shows "last seen <relative time>" per user.
    last_seen_at column on tenants, updated on every authenticated
    request (session + client API).
  - Cache-busting on style.css + app.js via filemtime.
  - View persistence in localStorage (survives reload).
  - Shared-bucket cards show title only (preview line removed).

Favicon:
  - favicon.svg added to project root + copied to public/, relay/,
    website/. Linked via <link rel="icon" type="image/svg+xml"> in
    all three <head> sections.

Settings:
  - New toggle: "Show shopping list items in task collector"
    (default off). collectTasks skips #einkauf/#shopping cards when
    disabled.

Fixes:
  - card-spinner gets display:inline-block so it rotates outside
    flex containers (shopping save spinner).
  - Tim tab test opens the plugin before checking active state.
  - Compose view centered with margin:auto on desktop.

Bumps to 1.9.29.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-17 13:16:23 +02:00
1 parent 8e738ed9d4
commit d50f9fe789
19 files changed
+209 -101

No files matched your search

+35 -27
View File
@@ -7,6 +7,7 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#1e293b">
<title>hyprcrd relay</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="fonts/phosphor.css">
<link rel="stylesheet" href="ui/style.css?v=<?= filemtime(__DIR__ . '/style.css') ?>">
</head>
@@ -14,14 +15,17 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
<header>
<span class="dot"></span>
<i class="ph ph-broadcast brand-icon"></i>
<h1>hyprcrd relay</h1>
<a onclick="showView('compose')" style="display:flex;align-items:center;gap:8px;text-decoration:none;cursor:pointer;">
<i class="ph ph-broadcast brand-icon"></i>
<h1>hyprcrd relay</h1>
</a>
<button id="header-status" class="header-status pixel" onclick="jumpToInbox()" title="jump to inbox"></button>
<span class="spacer"></span>
<button id="nav-burger" class="nav-btn hidden" aria-label="menu" onclick="toggleNavMenu()"><i class="ph ph-list"></i></button>
<div id="nav-buttons" class="hidden">
<span id="nav-user" class="user"></span>
<button id="nav-compose" class="nav-btn active" onclick="showView('compose')"><i class="ph ph-pencil-line"></i> compose</button>
<button id="nav-inbox" class="nav-btn" onclick="showView('inbox')"><i class="ph ph-tray"></i> inbox</button>
<button id="nav-chat" class="nav-btn" onclick="showView('chat')"><i class="ph ph-chat-circle-dots"></i> chat</button>
<button id="nav-shopping" class="nav-btn" onclick="showView('shopping')"><i class="ph ph-shopping-cart"></i> shopping</button>
<button id="nav-account" class="nav-btn" onclick="showView('account')"><i class="ph ph-user-circle"></i> account</button>
@@ -58,15 +62,17 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
<div id="view-login" class="auth-form hidden">
<i class="ph ph-key auth-icon"></i>
<h2>login</h2>
<div class="field">
<label>username</label>
<input type="text" id="login-username" autocomplete="username">
</div>
<div class="field">
<label>password</label>
<input type="password" id="login-password" autocomplete="current-password">
</div>
<button class="btn btn-primary" onclick="doLogin()"><i class="ph ph-sign-in"></i> unlock</button>
<form id="login-form" autocomplete="on" onsubmit="event.preventDefault(); doLogin();">
<div class="field">
<label for="login-username">username</label>
<input type="text" id="login-username" name="username" autocomplete="username">
</div>
<div class="field">
<label for="login-password">password</label>
<input type="password" id="login-password" name="password" autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary"><i class="ph ph-sign-in"></i> unlock</button>
</form>
<div id="login-err" class="err"></div>
</div>
@@ -106,22 +112,24 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
</div>
<div id="compose-status" class="compose-status-bar"></div>
</div>
<div id="compose-sidebar" class="compose-sidebar">
<div id="inbox-section" class="section">
<h2><i class="ph ph-tray"></i> your inbox <span class="count" id="inbox-count">0</span>
<span style="flex:1"></span>
<button id="inbox-delete-all" class="btn btn-danger hidden" style="padding:3px 10px;font-size:9px;" onclick="deleteAllMessages()"><i class="ph ph-trash"></i> delete all</button>
</h2>
<div id="inbox-list"></div>
</div>
<div id="shared-section" class="section">
<h2><i class="ph ph-users-three"></i> shared buckets <span class="count" id="shared-count">0</span></h2>
<div id="shared-list"></div>
</div>
<div id="outbox-section" class="section">
<h2><i class="ph ph-paper-plane-tilt"></i> sent <span class="count" id="outbox-count">0</span></h2>
<div id="outbox-list"></div>
</div>
</div>
<!-- ── Inbox view (inbox + shared buckets + sent) ── -->
<div id="view-inbox" class="hidden">
<div id="inbox-section" class="section">
<h2><i class="ph ph-tray"></i> your inbox <span class="count" id="inbox-count">0</span>
<span style="flex:1"></span>
<button id="inbox-delete-all" class="btn btn-danger hidden" style="padding:3px 10px;font-size:9px;" onclick="deleteAllMessages()"><i class="ph ph-trash"></i> delete all</button>
</h2>
<div id="inbox-list"></div>
</div>
<div id="shared-section" class="section">
<h2><i class="ph ph-users-three"></i> shared buckets <span class="count" id="shared-count">0</span></h2>
<div id="shared-list"></div>
</div>
<div id="outbox-section" class="section">
<h2><i class="ph ph-paper-plane-tilt"></i> sent <span class="count" id="outbox-count">0</span></h2>
<div id="outbox-list"></div>
</div>
</div>