diff --git a/backend/app/core/tree_validation.py b/backend/app/core/tree_validation.py index 47afed51..8d079a1f 100644 --- a/backend/app/core/tree_validation.py +++ b/backend/app/core/tree_validation.py @@ -115,7 +115,7 @@ def _validate_children(children: list[dict[str, Any]], path: str, errors: list[d # --- Procedural Tree Validation --- -VALID_STEP_TYPES = {"procedure_step", "procedure_end"} +VALID_STEP_TYPES = {"procedure_step", "procedure_end", "section_header"} VALID_CONTENT_TYPES = {"action", "informational", "verification", "warning"} diff --git a/frontend/src/components/common/TagInput.tsx b/frontend/src/components/common/TagInput.tsx index c6c0e805..76f21b59 100644 --- a/frontend/src/components/common/TagInput.tsx +++ b/frontend/src/components/common/TagInput.tsx @@ -106,10 +106,14 @@ export function TagInput({ } else if (e.key === 'Escape') { setShowSuggestions(false) setSelectedIndex(-1) - } else if (e.key === ',' || e.key === 'Tab') { + } else if (e.key === ',' || e.key === ';' || e.key === 'Tab') { if (inputValue.trim()) { e.preventDefault() - addTag(inputValue) + // Support multiple tags separated by commas or semicolons + const parts = inputValue.split(/[,;]/).map(s => s.trim()).filter(Boolean) + for (const part of parts) { + addTag(part) + } } } } @@ -157,7 +161,20 @@ export function TagInput({ ref={inputRef} type="text" value={inputValue} - onChange={(e) => setInputValue(e.target.value)} + onChange={(e) => { + const val = e.target.value + // If pasted text contains delimiters, split into tags immediately + if (val.includes(',') || val.includes(';')) { + const parts = val.split(/[,;]/).map(s => s.trim()).filter(Boolean) + const last = parts.pop() + for (const part of parts) { + addTag(part) + } + setInputValue(last || '') + } else { + setInputValue(val) + } + }} onKeyDown={handleKeyDown} onFocus={() => { if (inputValue.length >= 1 && suggestions.length > 0) { @@ -221,7 +238,7 @@ export function TagInput({ {/* Helper text */}
- {tags.length}/{maxTags} tags. Press Enter or comma to add. + {tags.length}/{maxTags} tags. Press Enter, Tab, comma, or semicolon to add.
) diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx index 3ae94adc..f1347f89 100644 --- a/frontend/src/pages/ProceduralEditorPage.tsx +++ b/frontend/src/pages/ProceduralEditorPage.tsx @@ -1,10 +1,11 @@ -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { Save, ArrowLeft, ListOrdered } from 'lucide-react' import { treesApi } from '@/api/trees' import { useProceduralEditorStore } from '@/store/proceduralEditorStore' import { IntakeFormBuilder } from '@/components/procedural-editor/IntakeFormBuilder' import { StepList } from '@/components/procedural-editor/StepList' +import { TagInput } from '@/components/common/TagInput' import { toast } from '@/lib/toast' export function ProceduralEditorPage() { @@ -33,8 +34,6 @@ export function ProceduralEditorPage() { getTreeForSave, } = useProceduralEditorStore() - const [tagInput, setTagInput] = useState('') - // Load tree or init new useEffect(() => { if (isEditMode && id) { @@ -95,18 +94,6 @@ export function ProceduralEditorPage() { } } - const handleAddTag = () => { - const tag = tagInput.trim() - if (tag && !tags.includes(tag)) { - setTags([...tags, tag]) - setTagInput('') - } - } - - const handleRemoveTag = (tag: string) => { - setTags(tags.filter((t) => t !== tag)) - } - if (isLoading) { return (