fix(triage): queue folds closed again with animation; deck follows changes from other devices

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-31 09:36:50 +01:00
1 parent 508717cc02
commit 597b6816a7
5 files changed
+77 -39

No files matched your search

+8 -1
View File
@@ -6,6 +6,12 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased]
## [0.15.1] - 2026-10-31
### Fixed
- Triage am Handy: Die Warteschlange lässt sich wieder zuklappen („Warteschlange ausblenden“) und fährt beim Auf- und Zuklappen weich auf bzw. zu.
- Triage auf dem Desktop: Notizen, die auf einem anderen Gerät einsortiert oder gelöscht wurden, verschwinden jetzt sofort auch hier (bisher wurde nur der Aufgaben-Tab aktualisiert). Kommt der Tab wieder in den Vordergrund, lädt er außerdem neu, falls Änderungen im Hintergrund verpasst wurden.
## [0.15.0] - 2026-10-31
### Changed
@@ -358,7 +364,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
- Geteilte Listen (Einkaufs-, Pack- und Checklisten) mit `#Kategorie`-Gruppierung, Mengenangaben und Echtzeit-Abhaken.
- Benutzer-, Gruppen- und Admin-Verwaltung (Ersteinrichtungs-Assistent, Einladungscodes, Profilbilder, persönliche API-Keys & Webhook-Ingest `/api/v1/webhooks/ingest`, JSON-Datenexport) sowie CSS-Theme-Engine mit 4 Themes (`Dark`, `Light`, `High Contrast`, `Soft`) und PWA-Unterstützung.
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.0...dev
[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.1...dev
[0.15.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.0...v0.15.1
[0.15.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.3...v0.15.0
[0.14.3]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.2...v0.14.3
[0.14.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.1...v0.14.2
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mindsift-frontend",
"version": "0.15.0",
"version": "0.15.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mindsift-frontend",
"version": "0.15.0",
"version": "0.15.1",
"license": "Unlicense",
"dependencies": {
"@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mindsift-frontend",
"private": true,
"version": "0.15.0",
"version": "0.15.1",
"license": "Unlicense",
"type": "module",
"scripts": {
+18 -2
View File
@@ -628,8 +628,20 @@ export const App: React.FC = () => {
}, 5000);
}, []);
// Refresh stats on task/doc/list real-time events (counters in the navigation)
// Refresh stats on task/doc/list real-time events (counters in the navigation). Task events also
// reload the triage deck: a note sorted on another device must leave this deck too.
useEffect(() => {
let deckTimer: number | undefined;
const reloadDeck = () => {
window.clearTimeout(deckTimer);
deckTimer = window.setTimeout(() => {
api.getTriageDeck().then((res) => setDeck(Array.isArray(res) ? res : [])).catch(() => {});
}, 300);
};
// Events missed while the tab slept (desktop in the background, connection dropped)
const onVisible = () => {
if (document.visibilityState === 'visible' && getToken()) refreshAll();
};
const handleWs = (e: any) => {
const msg = e.detail;
if (!msg) return;
@@ -644,13 +656,17 @@ export const App: React.FC = () => {
api.getMe().then((res) => {
setStats(res.stats);
}).catch(() => {});
if (msg.event.startsWith('TASK_')) reloadDeck();
}
};
window.addEventListener('mindsift:ws', handleWs);
document.addEventListener('visibilitychange', onVisible);
return () => {
window.clearTimeout(deckTimer);
window.removeEventListener('mindsift:ws', handleWs);
document.removeEventListener('visibilitychange', onVisible);
};
}, []);
}, [refreshAll]);
// Close profile dropdown on outside click
useEffect(() => {
+48 -33
View File
@@ -916,41 +916,56 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
{cards.length > 1 && (
<div className="pt-2" ref={queueRef}>
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */}
{!showQueueMobile && (
<button
type="button"
onClick={() => {
setShowQueueMobile(true);
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
}}
className="sm:hidden w-full min-h-[48px] px-3 rounded-xl border border-border bg-panel text-sm font-medium text-main flex items-center justify-center gap-2 touch-manipulation cursor-pointer"
>
<Layers className="w-4 h-4 text-accent" />
<span>Warteschlange anzeigen ({cards.length - 1})</span>
<ChevronDown className="w-4 h-4 text-muted" />
</button>
)}
<div className={`${showQueueMobile ? 'flex' : 'hidden sm:flex'} items-center justify-between mb-2 px-1`}>
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-accent" />
Warteschlange ({cards.length - 1})
<button
type="button"
aria-expanded={showQueueMobile}
onClick={() => {
const opening = !showQueueMobile;
setShowQueueMobile(opening);
if (opening) {
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 320);
}
}}
className="sm:hidden w-full min-h-[48px] px-3 rounded-xl border border-border bg-panel text-sm font-medium text-main flex items-center justify-center gap-2 touch-manipulation cursor-pointer"
>
<Layers className="w-4 h-4 text-accent" />
<span>
Warteschlange {showQueueMobile ? 'ausblenden' : 'anzeigen'} ({cards.length - 1})
</span>
<span className="text-[11px] text-faint">
Tippen = Vorholen · 👈 Wegwerfen · 👉 Aufgabe
</span>
</div>
<ChevronDown
className={`w-4 h-4 text-muted transition-transform duration-300 ${showQueueMobile ? 'rotate-180' : ''}`}
/>
</button>
{/* Folds open/closed on phones; always open from sm upwards */}
<div
className={`grid transition-[grid-template-rows,opacity,visibility] duration-300 ease-out sm:grid-rows-[1fr] sm:opacity-100 sm:visible ${
showQueueMobile ? 'grid-rows-[1fr] opacity-100 visible' : 'grid-rows-[0fr] opacity-0 invisible'
}`}
>
<div className="overflow-hidden min-h-0">
<div className="flex items-center justify-between mb-2 px-1 pt-2 sm:pt-0">
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
<Layers className="w-3.5 h-3.5 text-accent" />
Warteschlange ({cards.length - 1})
</span>
<span className="text-[11px] text-faint">
Tippen = Vorholen · 👈 Wegwerfen · 👉 Aufgabe
</span>
</div>
<div className={`${showQueueMobile ? 'block' : 'hidden sm:block'} panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden`}>
{cards.slice(1).map((item) => (
<SwipeableQueueItem
key={item.id}
item={item}
lists={writableLists}
onBringToFront={handleBringToFront}
onOpenActionSheet={openActionSheetForCard}
onAction={handleAction}
/>
))}
<div className="panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden">
{cards.slice(1).map((item) => (
<SwipeableQueueItem
key={item.id}
item={item}
lists={writableLists}
onBringToFront={handleBringToFront}
onOpenActionSheet={openActionSheetForCard}
onAction={handleAction}
/>
))}
</div>
</div>
</div>
</div>
)}