ui: version-display via JS, undo-toast centering, ctrl+enter save, test progress polish

* version: every span that shows v<APP_VERSION> in the page chrome now
  carries data-app-version, and checkAuth() patches them in from the
  /api/auth/status response so the GUI version doesn't go stale when
  PHP is cached behind anything that revalidates lazily.

* undo: tailwind v4 compiles `-translate-x-1/2` to the *new* CSS
  `translate` property, which then stacks with the inline `transform`
  the toast set — the box ended up shifted ~ -100% and looked left-
  anchored. Drop the tailwind utility, set `translate` directly inline
  so there's one source of truth for the offset.

* editor: ctrl/cmd+enter while the editor modal is open now invokes
  saveCard() (= save + close), pairing with the existing ctrl+s
  (save only) and ctrl+shift+s (save+close) shortcuts.

* collab buckets: drop the inset left edge from .bucket-tab-collab and
  the matching pulse-keyframe shadow — the bar tab now reads as a
  normal bucket tab, just with the unread dot + 3-cycle pulse on update.

* collab create dialog: drop the box border / background from
  .collab-toggle-row and .collab-members so the toggle and the user
  list sit directly on the modal background instead of looking like
  nested input boxes.

* test progress strip: ensureProgressBar() now also pushes the body
  down by 30px AND shrinks it via height: calc(100vh - 30px) so the
  fixed-top bar doesn't clip the footer (body is h-screen +
  overflow-hidden in the core layout). hideProgress() clears both
  inline styles when the run finishes.

chore: bump version to 0.9.2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-11 20:56:59 +02:00
1 parent fe3399a44b
commit 2059e50ce3
7 files changed
+39 -26

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
<?php
define('APP_NAME', 'hyprcrd');
define('APP_VERSION', '0.9.1');
define('APP_VERSION', '0.9.2');
// Database path — writable directory, outside webroot
define('DB_PATH', __DIR__ . '/data/hyprcrd.db');
+3 -3
View File
@@ -1185,7 +1185,7 @@ if (str_starts_with($route, '/api/')) {
<div class="flex items-center gap-3">
<i class="ph ph-terminal-window text-t-accent text-xl"></i>
<h1 class="brand font-pixel text-t-accent text-lg tracking-wider">hyprcrd</h1>
<span class="text-[10px] text-t-dim font-pixel ml-1">v<?= APP_VERSION ?></span>
<span class="text-[10px] text-t-dim font-pixel ml-1" data-app-version>v<?= APP_VERSION ?></span>
<button id="search-btn" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors ml-3">
<i class="ph ph-magnifying-glass text-sm"></i><span class="font-pixel text-[10px]">Search</span>
<kbd class="text-[9px] text-t-dim border border-t-border rounded px-1 py-0.5 ml-1 font-pixel">ctrl+k</kbd>
@@ -1478,7 +1478,7 @@ if (str_starts_with($route, '/api/')) {
</section>
<div class="pt-4 border-t border-t-border text-center">
<span class="font-pixel text-[10px] text-t-dim">© <?= date('Y') ?> hyprcrd · v<?= APP_VERSION ?></span>
<span class="font-pixel text-[10px] text-t-dim">© <?= date('Y') ?> hyprcrd · <span data-app-version>v<?= APP_VERSION ?></span></span>
</div>
</div>
</div>
@@ -1509,7 +1509,7 @@ if (str_starts_with($route, '/api/')) {
<div class="flex flex-col items-center justify-center py-12 text-center">
<i class="ph ph-terminal-window text-t-accent text-5xl mb-4"></i>
<h1 class="brand font-pixel text-2xl text-t-accent tracking-wider mb-2">hyprcrd</h1>
<p class="font-pixel text-[10px] text-t-dim mb-6">v<?= APP_VERSION ?></p>
<p class="font-pixel text-[10px] text-t-dim mb-6" data-app-version>v<?= APP_VERSION ?></p>
<p class="text-xs text-t-muted leading-relaxed max-w-sm">Self-hosted card manager with markdown, tags, calendar, CalDAV sync, encrypted WebDAV notes, and Obsidian-compatible export.</p>
<div class="flex items-center gap-4 mt-8 text-t-dim">
<div class="text-center"><div id="stats-cards" class="font-pixel text-lg text-t-bright">—</div><div class="font-pixel text-[8px] uppercase">cards</div></div>
+8
View File
@@ -158,6 +158,13 @@ async function checkAuth() {
const res = await fetch('api/auth/status');
const data = await res.json();
if (data.csrf) { setCsrf(data.csrf); window.hyprcrd?.util?.setCsrf?.(data.csrf); }
// Refresh version-displaying spans from the live API response so the
// GUI doesn't show a stale APP_VERSION when the PHP page is cached.
if (data.version) {
for (const el of document.querySelectorAll('[data-app-version]')) {
el.textContent = 'v' + data.version;
}
}
if (data.setup) {
let pw = null;
@@ -223,6 +230,7 @@ document.addEventListener('keydown', e => {
return;
}
if (e.key === 's' && (e.ctrlKey || e.metaKey) && modal.classList.contains('open')) { e.preventDefault(); e.shiftKey ? saveCard() : saveOnly(); return; }
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && modal.classList.contains('open')) { e.preventDefault(); saveCard(); return; }
if (e.key === 'z' && (e.ctrlKey || e.metaKey) && !modal.classList.contains('open') && canUndo()) { e.preventDefault(); undo(); return; }
if (e.key === '/' && ![modal, detail, search, tasks].some(el => el?.classList.contains('open')) && document.activeElement === document.body) { e.preventDefault(); openSearch(); }
+11 -3
View File
@@ -66,21 +66,29 @@ export function pushBatch(label, patches) {
}
// Toast hint
//
// Centering note: tailwind v4 compiles `-translate-x-1/2` to the *new*
// CSS `translate` property, which then stacks with any inline `transform`
// we set — the box ended up shifted ~ -100% (utility translate + JS
// transform translate both applied) and looked left-anchored. Fix: drop
// the tailwind translate utility and centre purely via the modern
// `translate` property in inline styles, which is one source of truth.
let hintTimer = null;
function showHint(text) {
let el = document.getElementById('undo-hint');
if (!el) {
el = document.createElement('div');
el.id = 'undo-hint';
el.className = 'fixed bottom-16 left-1/2 -translate-x-1/2 z-[95] bg-t-surface border border-t-border px-4 py-2 text-xs font-pixel text-t-muted shadow-lg transition-all';
el.className = 'fixed bottom-16 left-1/2 z-[95] bg-t-surface border border-t-border px-4 py-2 text-xs font-pixel text-t-muted shadow-lg transition-all';
el.style.translate = '-50% 0';
document.body.appendChild(el);
}
el.textContent = text + ' (ctrl+z undo)';
el.style.opacity = '1';
el.style.transform = 'translateX(-50%) translateY(0)';
el.style.translate = '-50% 0';
clearTimeout(hintTimer);
hintTimer = setTimeout(() => {
el.style.opacity = '0';
el.style.transform = 'translateX(-50%) translateY(8px)';
el.style.translate = '-50% 8px';
}, 2500);
}
@@ -8,8 +8,6 @@
.bucket-tab.bucket-tab-collab {
position: relative;
/* Subtle left emphasis so collab tabs read differently at a glance */
box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent);
}
.bucket-tab.bucket-tab-collab .collab-tab-dot {
@@ -35,14 +33,8 @@ body.anim-slow .bucket-tab.bucket-tab-collab.pulsing {
animation-duration: 1.2s !important;
}
@keyframes collab-bucket-pulse {
0%, 100% {
box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent),
0 0 0 0 color-mix(in srgb, var(--t-danger) 60%, transparent);
}
50% {
box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent),
0 0 0 4px color-mix(in srgb, var(--t-danger) 0%, transparent);
}
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--t-danger) 60%, transparent); }
50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--t-danger) 0%, transparent); }
}
/* ── Create / edit modal (used by the + Bucket button when the plugin
@@ -136,26 +128,20 @@ body.anim-slow .bucket-tab.bucket-tab-collab.pulsing {
display: flex !important;
align-items: center;
gap: 8px;
padding: 8px 10px;
background: var(--t-bg);
border: 1px solid var(--t-border);
border-radius: var(--ui-radius-input);
padding: 6px 0;
cursor: pointer;
font-size: 12px;
color: var(--t-text);
user-select: none;
}
.collab-toggle-row input { accent-color: var(--t-accent); cursor: pointer; }
.collab-toggle-row:hover { border-color: var(--t-accent); color: var(--t-bright); }
.collab-toggle-row:hover { color: var(--t-bright); }
.collab-members-wrap { margin-top: 10px; }
.collab-members {
border: 1px solid var(--t-border);
border-radius: var(--ui-radius-input);
background: var(--t-bg);
max-height: 200px;
overflow-y: auto;
padding: 4px;
padding: 0;
}
.collab-members-empty {
font-size: 11px;
+6
View File
@@ -43,6 +43,12 @@ const RUNNER_V = '?v=2';
function ensureProgressBar() {
if (document.getElementById('test-progress')) return;
// Push the whole document down by 30px AND shrink the body by the
// same amount so the footer doesn't get clipped (body is h-screen +
// overflow-hidden, so without the height shrink the bottom 30px get
// pushed off-viewport). hideProgress() reverts both.
document.body.style.marginTop = '30px';
document.body.style.height = 'calc(100vh - 30px)';
const bar = document.createElement('div');
bar.id = 'test-progress';
const s = bar.style;
+5
View File
@@ -234,6 +234,11 @@ function updateProgress(current, total, name, failed) {
}
function hideProgress() {
getProgressBar()?.remove();
// ensureProgressBar() in loader.js pushed the document down by 30px and
// shrunk it by the same amount so the footer wouldn't get clipped.
// Restore both inline overrides here.
document.body.style.marginTop = '';
document.body.style.height = '';
}
async function runAll() {