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:
1 parent
508717cc02
commit
597b6816a7
5 files changed
+77
-39
No files matched your search
+8
-1
@@ -6,6 +6,12 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.15.0] - 2026-10-31
|
||||||
|
|
||||||
### Changed
|
### 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.
|
- 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.
|
- 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.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.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
|
[0.14.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.14.1...v0.14.2
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"version": "0.15.0",
|
"version": "0.15.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"version": "0.15.0",
|
"version": "0.15.1",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "5.3.0",
|
"@fontsource-variable/inter": "5.3.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mindsift-frontend",
|
"name": "mindsift-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.15.0",
|
"version": "0.15.1",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+18
-2
@@ -628,8 +628,20 @@ export const App: React.FC = () => {
|
|||||||
}, 5000);
|
}, 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(() => {
|
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 handleWs = (e: any) => {
|
||||||
const msg = e.detail;
|
const msg = e.detail;
|
||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
@@ -644,13 +656,17 @@ export const App: React.FC = () => {
|
|||||||
api.getMe().then((res) => {
|
api.getMe().then((res) => {
|
||||||
setStats(res.stats);
|
setStats(res.stats);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
if (msg.event.startsWith('TASK_')) reloadDeck();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('mindsift:ws', handleWs);
|
window.addEventListener('mindsift:ws', handleWs);
|
||||||
|
document.addEventListener('visibilitychange', onVisible);
|
||||||
return () => {
|
return () => {
|
||||||
|
window.clearTimeout(deckTimer);
|
||||||
window.removeEventListener('mindsift:ws', handleWs);
|
window.removeEventListener('mindsift:ws', handleWs);
|
||||||
|
document.removeEventListener('visibilitychange', onVisible);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [refreshAll]);
|
||||||
|
|
||||||
// Close profile dropdown on outside click
|
// Close profile dropdown on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -916,41 +916,56 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
{cards.length > 1 && (
|
{cards.length > 1 && (
|
||||||
<div className="pt-2" ref={queueRef}>
|
<div className="pt-2" ref={queueRef}>
|
||||||
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */}
|
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */}
|
||||||
{!showQueueMobile && (
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
aria-expanded={showQueueMobile}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowQueueMobile(true);
|
const opening = !showQueueMobile;
|
||||||
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
|
setShowQueueMobile(opening);
|
||||||
}}
|
if (opening) {
|
||||||
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"
|
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 320);
|
||||||
>
|
}
|
||||||
<Layers className="w-4 h-4 text-accent" />
|
}}
|
||||||
<span>Warteschlange anzeigen ({cards.length - 1})</span>
|
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"
|
||||||
<ChevronDown className="w-4 h-4 text-muted" />
|
>
|
||||||
</button>
|
<Layers className="w-4 h-4 text-accent" />
|
||||||
)}
|
<span>
|
||||||
<div className={`${showQueueMobile ? 'flex' : 'hidden sm:flex'} items-center justify-between mb-2 px-1`}>
|
Warteschlange {showQueueMobile ? 'ausblenden' : 'anzeigen'} ({cards.length - 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})
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-faint">
|
<ChevronDown
|
||||||
Tippen = Vorholen · 👈 Wegwerfen · 👉 Aufgabe
|
className={`w-4 h-4 text-muted transition-transform duration-300 ${showQueueMobile ? 'rotate-180' : ''}`}
|
||||||
</span>
|
/>
|
||||||
</div>
|
</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`}>
|
<div className="panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden">
|
||||||
{cards.slice(1).map((item) => (
|
{cards.slice(1).map((item) => (
|
||||||
<SwipeableQueueItem
|
<SwipeableQueueItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
lists={writableLists}
|
lists={writableLists}
|
||||||
onBringToFront={handleBringToFront}
|
onBringToFront={handleBringToFront}
|
||||||
onOpenActionSheet={openActionSheetForCard}
|
onOpenActionSheet={openActionSheetForCard}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in new issue
Block a user