fix(search): open tasks and list items before completed ones
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m36s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 18:36:45 +01:00
1 parent 2bb0dec744
commit 558fc46cf4
6 files changed
+38 -7

No files matched your search

+7 -1
View File
@@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased] ## [Unreleased]
## [0.49.2] - 2026-11-01
### Fixed
- Globale Suche: Offene Aufgaben stehen vor erledigten, verworfene Gedanken ganz hinten; offene Listeneinträge vor abgehakten. Bisher zählte nur, wie gut der Text passte, sodass erledigte Aufgaben offene verdrängen konnten.
## [0.49.1] - 2026-11-01 ## [0.49.1] - 2026-11-01
### Fixed ### Fixed
@@ -765,7 +770,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.49.1...dev [Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.49.2...dev
[0.49.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.49.1...v0.49.2
[0.49.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.49.0...v0.49.1 [0.49.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.49.0...v0.49.1
[0.49.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.48.0...v0.49.0 [0.49.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.48.0...v0.49.0
[0.48.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.47.0...v0.48.0 [0.48.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.47.0...v0.48.0
+1 -1
View File
@@ -6,7 +6,7 @@
[![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#)
[![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart) [![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart)
[![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir) [![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir)
[![Version](https://img.shields.io/badge/Version-v0.49.1-16a34a?style=flat-square)](CHANGELOG.md) [![Version](https://img.shields.io/badge/Version-v0.49.2-16a34a?style=flat-square)](CHANGELOG.md)
**MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server. **MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.49.1", "version": "0.49.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.49.1", "version": "0.49.2",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.3.0", "@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"private": true, "private": true,
"version": "0.49.1", "version": "0.49.2",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
+3 -2
View File
@@ -273,7 +273,8 @@ func (s *Store) SearchGlobal(userID, query string) (*SearchResults, error) {
JOIN thoughts t ON t.id = f.thought_id JOIN thoughts t ON t.id = f.thought_id
WHERE thoughts_fts MATCH ? WHERE thoughts_fts MATCH ?
AND (t.user_id = ? OR t.task_list_id IN (`+s.accessibleTaskListIDsQuery()+`)) AND (t.user_id = ? OR t.task_list_id IN (`+s.accessibleTaskListIDsQuery()+`))
ORDER BY score ASC -- Open before completed, discarded thoughts last; within each by relevance
ORDER BY t.status = 'discarded' ASC, t.is_completed ASC, score ASC
LIMIT 40 LIMIT 40
`, ftsQuery, userID, userID, userID, userID) `, ftsQuery, userID, userID, userID, userID)
if err == nil { if err == nil {
@@ -330,7 +331,7 @@ func (s *Store) SearchGlobal(userID, query string) (*SearchResults, error) {
FROM list_items_fts f FROM list_items_fts f
JOIN list_items li ON li.id = f.item_id JOIN list_items li ON li.id = f.item_id
WHERE list_items_fts MATCH ? WHERE list_items_fts MATCH ?
ORDER BY score ASC ORDER BY li.is_completed ASC, score ASC
LIMIT 40 LIMIT 40
`, ftsQuery) `, ftsQuery)
if err == nil { if err == nil {
+24
View File
@@ -212,3 +212,27 @@ func TestSearchPrefixAndSpecialCharacters(t *testing.T) {
} }
} }
} }
// Open tasks come before completed ones, even when a completed one matches the words better.
func TestSearchListsOpenTasksFirst(t *testing.T) {
store, userA, _ := setupTestStore(t)
defer store.Close()
done := doneTask(t, store, userA.ID, "", "Heizung Heizung Heizung entlüften", "")
open, _ := store.CreateThoughtWithDetails(userA.ID, "Heizung warten lassen, Termin mit dem Installateur ausmachen", "normal", "manual", "", "", "todo", "")
_, _ = store.UpdateThoughtTriage(userA.ID, open.ID, "keep", "task", "", "", "")
res, err := store.SearchGlobal(userA.ID, "Heizung")
if err != nil {
t.Fatal(err)
}
var order []string
for _, h := range res.Hits {
if h.Type == "thought" {
order = append(order, h.ID)
}
}
if len(order) != 2 || order[0] != open.ID || order[1] != done {
t.Fatalf("open task should come first: %v (open %s, done %s)", order, open.ID, done)
}
}