Phase 20: Dedicated tag field — chip input, explicit tags array

Tags are now a first-class field rather than being auto-extracted from
the note body. A new TagInput.vue chip component handles tag entry in
both editor views with autocomplete, Enter/comma/backspace UX, and
space-to-hyphen sanitization.

Backend:
- routes/notes.py: create reads tags from JSON; update accepts explicit
  tags (omit = keep existing); append_tag writes to tags array with
  dedup; suggest-tags accepts current_tags filter; remove extract_tags
- routes/tasks.py: same — explicit tags on create/update; remove extract_tags
- services/tag_suggestions.py: current_tags param replaces body extraction
- services/tools.py: create_note tool schema adds tags param; executor passes it
- services/llm.py: system prompt tells LLM to use tags param, not embed #tag in body

Frontend:
- components/TagInput.vue: new chip-based tag input (autocomplete, keyboard UX)
- NoteEditorView.vue / TaskEditorView.vue: tags ref loaded from note.tags;
  TagInput placed between title and body; save/autosave include tags; suggest
  now adds chips; fetchTagSuggestions passes current_tags; dirty tracks tags
- TiptapEditor.vue: remove fetchTags prop and TagSuggestion extension;
  keep TagDecoration for legacy inline #tag highlighting

No DB migration needed — tags column already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 06:36:35 -05:00
parent 7947134e22
commit 316a85e13b
12 changed files with 315 additions and 55 deletions
-6
View File
@@ -9,18 +9,15 @@ import DOMPurify from "dompurify";
import { serializeToMarkdown } from "@/utils/markdownSerializer";
import { TagDecoration } from "@/extensions/TagDecoration";
import { WikilinkDecoration } from "@/extensions/WikilinkDecoration";
import { TagSuggestion } from "@/extensions/TagSuggestion";
import { WikilinkSuggestion } from "@/extensions/WikilinkSuggestion";
const props = withDefaults(
defineProps<{
modelValue: string;
placeholder?: string;
fetchTags?: (query: string) => Promise<string[]>;
}>(),
{
placeholder: "",
fetchTags: undefined,
}
);
@@ -72,9 +69,6 @@ try {
}),
TagDecoration,
WikilinkDecoration,
TagSuggestion.configure({
fetchTags: props.fetchTags ?? (async () => []),
}),
WikilinkSuggestion,
],
onUpdate({ editor: ed }) {