list: a named task list is not a grocery item (V-520)

"добавь в список" was a marker in two places: task_phrases.json for task
capture, and listCapturePrefixes for the grocery list. ListGrammars is wired
before TaskCaptureGrammar in buildRouter, so the list claimed every one of
them, and takeListTag does not know "дел" as a list name — "добавь в список
дел хлеб" filed a grocery item called "дел хлеб".

The bare marker stays a grocery item, because an unnamed list already defaults
to покупки and the task side always names its list. A named task list now
declines in ParseListCapture, ParseListQuery and ParseListRemove, so the turn
falls through to task capture. The bare forms are gone from task_phrases.json,
so the data says what the code does rather than being shadowed by grammar
order.

Reversible if he asks for the other default: move the two bare phrases back and
the list will need to decline them instead.
This commit is contained in:
2026-08-05 11:44:07 +04:00
parent c586346a60
commit 7b4fb6229a
3 changed files with 95 additions and 3 deletions
+38
View File
@@ -108,3 +108,41 @@ func TestListTagIsAWordNotAPrefix(t *testing.T) {
}
}
}
// TestNamedTaskListIsNotAGroceryItem — "добавь в список" is a marker on both
// sides, and ListGrammars is wired before TaskCaptureGrammar, so the list used
// to claim every one of them (Vikunja #520). takeListTag does not know "дел" as
// a list name, so the item filed was "дел хлеб". The bare marker stays a
// grocery item; a named task list falls through to task capture.
func TestNamedTaskListIsNotAGroceryItem(t *testing.T) {
for _, in := range []string{
"добавь в список дел хлеб",
"добавь в список задач позвонить врачу",
"запиши в список дел купить молоко",
"add to the list of todos milk",
} {
if got, ok := ParseListCapture(in); ok {
t.Errorf("ParseListCapture(%q) claimed it as list %q item %q, want a pass to task capture",
in, got.List, got.Item)
}
}
// The bare form is still the grocery list, and the tagged forms are untouched.
for _, tc := range []struct{ in, list, item string }{
{"добавь в список хлеб", "покупки", "хлеб"},
{"добавь в список покупок молоко", "покупки", "молоко"},
{"добавь в список аптеку витамины", "аптека", "витамины"},
} {
got, ok := ParseListCapture(tc.in)
if !ok || got.List != tc.list || got.Item != tc.item {
t.Errorf("ParseListCapture(%q) = (%q, %q, %v), want (%q, %q, true)",
tc.in, got.List, got.Item, ok, tc.list, tc.item)
}
}
// A read-back and a cross-off name the same lists and must split the same way.
if _, ok := ParseListQuery("покажи список дел"); ok {
t.Error(`ParseListQuery("покажи список дел") claimed a task list`)
}
if got, ok := ParseListRemove("вычеркни из списка дел хлеб"); ok {
t.Errorf("ParseListRemove claimed a task list: %q / %q", got.List, got.Item)
}
}