super-buckets: split tab click (activate) from caret click (dropdown)

Clicking the super-bucket tab now activates the last-shown child
directly. Only the caret arrow on the right opens the dropdown popover.
Caret has its own hit area with accent hover highlight.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.6 committed 2026-04-12 19:37:38 +02:00
1 parent 04b52ecc19
commit 527305355c
5 files changed
+64 -16

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
<?php
define('APP_NAME', 'hyprcrd');
define('APP_VERSION', '1.2.0');
define('APP_VERSION', '1.3.0');
// Database path — writable directory, outside webroot
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hyprcrd",
"version": "1.2.0",
"version": "1.3.0",
"description": "hyprcrd — self-hosted card manager",
"main": "main.js",
"scripts": {
+18
View File
@@ -821,6 +821,24 @@ body.anim-slow .board-card.card-pulse-local {
/* Super-bucket dropdown popover. Positioned via inline styles by
openSuperDropdown() in buckets.js. Lives on document.body (not
inside #bucket-bar) so it can escape the bar's overflow:auto. */
.super-caret {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2px;
padding: 2px 4px;
border-radius: 3px;
opacity: 0.6;
transition: opacity 0.15s, background 0.15s;
cursor: pointer;
}
.super-caret:hover {
opacity: 1;
background: color-mix(in srgb, var(--t-accent) 25%, transparent);
}
.bucket-tab.active .super-caret:hover {
background: rgba(0,0,0,0.25);
}
#bucket-super-popover {
z-index: 220;
background: var(--t-surface);
+16 -6
View File
@@ -309,7 +309,7 @@ function renderBar() {
const shown = tileFor(b);
const c = shown.color || '#64748b';
const groupActive = state.activeBucket == b.id || kids.some(k => k.id == state.activeBucket);
return `<button class="${tab(groupActive)} bucket-super" data-bucket-super="${b.id}" data-super-shown="${shown.id}" draggable="true" data-bucket-drag="${b.id}" style="border-top:3px solid ${c};${groupActive ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-stack text-sm"></i><span class="font-pixel text-[10px]">${esc(shown.name)}</span><i class="ph ph-caret-down text-[10px] ml-0.5 opacity-70"></i></button>`;
return `<button class="${tab(groupActive)} bucket-super" data-bucket-super="${b.id}" data-super-shown="${shown.id}" draggable="true" data-bucket-drag="${b.id}" style="border-top:3px solid ${c};${groupActive ? `background:${c};color:var(--t-bg);border-color:${c}` : ''}"><i class="ph ph-stack text-sm"></i><span class="font-pixel text-[10px]">${esc(shown.name)}</span><span class="super-caret" data-super-caret="${b.id}"><i class="ph ph-caret-down text-[10px]"></i></span></button>`;
}
const c = b.color || '#64748b';
const active = state.activeBucket == b.id;
@@ -462,11 +462,21 @@ function renderBar() {
const isUser = isSuperTile || !SPECIAL.includes(bid);
if (isSuperTile) {
// Click opens the dropdown popover instead of activating the
// parent directly. The dropdown lists parent + every child.
t.addEventListener('click', (e) => {
e.stopPropagation();
openSuperDropdown(t, parseInt(bid, 10));
// Caret arrow opens the dropdown; clicking the rest of the
// tab activates the last-shown child (or parent).
const caret = t.querySelector('[data-super-caret]');
if (caret) {
caret.addEventListener('click', (e) => {
e.stopPropagation();
openSuperDropdown(t, parseInt(bid, 10));
});
}
t.addEventListener('click', () => {
const shownId = t.dataset.superShown;
setActiveBucket(shownId || bid);
state.activeGroup = null;
renderBar();
window._renderBoard();
});
} else {
t.addEventListener('click', () => { setActiveBucket(bid); state.activeGroup = null; renderBar(); window._renderBoard(); });
+28 -8
View File
@@ -180,7 +180,7 @@ describe('Super-buckets: Bar rendering', () => {
expect(superTile.textContent).toContain('__sb_active_child__');
});
it('clicking the super-tile opens a popover with parent + child entries', async () => {
it('clicking the caret opens a popover with parent + child entries', async () => {
const parent = await makeBucket('__sb_pop_parent__');
const child1 = await makeBucket('__sb_pop_child1__');
const child2 = await makeBucket('__sb_pop_child2__');
@@ -191,15 +191,15 @@ describe('Super-buckets: Bar rendering', () => {
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
if (!superTile) return;
click(superTile);
const caret = superTile.querySelector('[data-super-caret]');
expect(caret).toExist();
click(caret);
await sleep(100);
const popover = document.getElementById('bucket-super-popover');
expect(popover).toExist();
// Parent + 2 children = 3 entries
const entries = popover.querySelectorAll('[data-bucket]');
expect(entries.length).toBe(3);
// All three names appear
const text = popover.textContent;
expect(text).toContain('__sb_pop_parent__');
expect(text).toContain('__sb_pop_child1__');
@@ -215,7 +215,8 @@ describe('Super-buckets: Bar rendering', () => {
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
if (!superTile) return;
click(superTile);
const caret = superTile.querySelector('[data-super-caret]');
click(caret);
await sleep(100);
const childEntry = document.querySelector(`#bucket-super-popover [data-bucket="${child.id}"]`);
@@ -226,7 +227,25 @@ describe('Super-buckets: Bar rendering', () => {
expect(String(state.activeBucket)).toBe(String(child.id));
});
it('clicking the super-tile twice toggles the popover closed', async () => {
it('clicking the super-tile activates the shown bucket', async () => {
const parent = await makeBucket('__sb_direct_parent__');
const child = await makeBucket('__sb_direct_child__');
await patchBucket(child.id, { parent_id: parent.id });
await window._reloadAll?.();
await sleep(200);
state.activeBucket = 'all';
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
if (!superTile) return;
click(superTile);
await sleep(150);
const shownId = superTile.dataset.superShown;
expect(String(state.activeBucket)).toBe(String(shownId));
expect(document.getElementById('bucket-super-popover')).toBe(null);
});
it('clicking the caret twice toggles the popover closed', async () => {
const parent = await makeBucket('__sb_toggle_parent__');
const child = await makeBucket('__sb_toggle_child__');
await patchBucket(child.id, { parent_id: parent.id });
@@ -235,10 +254,11 @@ describe('Super-buckets: Bar rendering', () => {
const superTile = document.querySelector(`#bucket-bar [data-bucket-super="${parent.id}"]`);
if (!superTile) return;
click(superTile);
const caret = superTile.querySelector('[data-super-caret]');
click(caret);
await sleep(100);
expect(document.getElementById('bucket-super-popover')).toExist();
click(superTile);
click(caret);
await sleep(100);
expect(document.getElementById('bucket-super-popover')).toBe(null);
});