ULWBYMSXYZEE7BJQ2B4HTX2JAABBNJWZTUFIQ56NF5RWXODXRTHQC YHDO5ELECDAMD4Q2LIFQQJA3QVEEMUP7VCUGHIH2UR6P5VKKMDYAC DBOROCRFD6A5SJBMFYFEJI5S5M77X4EFEK6KDQWA5QDMQJKIHRWQC U6JEEU5O477ZOJ5UMRMOJSGPEJEU6Q7KMPKUSDF56CYVUJWL7QBQC 3XZAHT6PTML33YSBVF5OSL7KLCRARTHRYPT3N7GUYJKSZKWHBIDQC SDBVLSDDRPQF62XXKJKM2RQLMXOKKHOYRVUF6DIUDFRYCGL2DW3QC EW7VBNMGWFBC73ZUDLB4LIK2HWFKA74ZUTUDG4J575ZQHEFHW4UQC FRY33K6EGWLU3F5NJJ66AT5RBV6OEOWSDKY3JH2CPKKGPAHOMM6AC W3A2EECCD23SVHJZN6MXPH2PAVFHH5CNFD2XHPQRRW6M4GUTG3FAC 2IURSWW3ZXRBH3DPJO437YE2FGMG54MPFL6W6UCEF5HODPVWVVTQC }}func TestLabelComment(t *testing.T) {// Test parsing comment from .data filecontent := `[{"Operator": "Test", "Duration": 60.0},[10.0, 20.0, 0, 0, [{"species": "Kiwi", "certainty": 100, "filter": "M", "comment": "Good call"}]]]`tmpfile, err := os.CreateTemp("", "test*.data")if err != nil {t.Fatal(err)}defer os.Remove(tmpfile.Name())if _, err := tmpfile.Write([]byte(content)); err != nil {t.Fatal(err)}tmpfile.Close()df, err := ParseDataFile(tmpfile.Name())if err != nil {t.Fatal(err)}if df.Segments[0].Labels[0].Comment != "Good call" {t.Errorf("expected Comment='Good call', got '%s'", df.Segments[0].Labels[0].Comment)}// Test writing commentdf.Segments[0].Labels[0].Comment = "Updated comment"tmpfile2, err := os.CreateTemp("", "test2*.data")if err != nil {t.Fatal(err)}tmpfile2.Close()defer os.Remove(tmpfile2.Name())if err := df.Write(tmpfile2.Name()); err != nil {t.Fatal(err)
}return m, nil}}// handleCommentKey handles key presses in comment modefunc (m Model) handleCommentKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {key := msg.Key()// Enter: save commentif key.Code == tea.KeyEnter {m.state.SetComment(m.commentText)if err := m.state.Save(); err != nil {m.err = err.Error()}m.commentMode = falsereturn m, nil}// Escape: cancelif key.Code == tea.KeyEscape || key.Code == tea.KeyEsc {m.commentMode = falsereturn m, nil}// Handle via string representationswitch msg.String() {case "backspace":if len(m.commentText) > 0 {m.commentText = m.commentText[:len(m.commentText)-1]}return m, nilcase "ctrl+u":m.commentText = ""return m, nil}// Printable ASCII characters := msg.String()if len(s) == 1 && s[0] >= 32 && s[0] <= 126 {if len(m.commentText) < 140 {m.commentText += s
// renderCommentDialog renders the comment input dialogfunc (m Model) renderCommentDialog(b *strings.Builder) {// Build dialog contentinputLine := m.commentText + "█" // cursorcharCount := fmt.Sprintf("%d/140", len(m.commentText))helpLine := "[enter]save [esc]cancel [ctrl+u]clear"// Render boxcontent := fmt.Sprintf("Comment:\n%s\n%s\n%s", inputLine, charCount, helpLine)b.WriteString(commentBoxStyle.Render(content))}
// SetComment sets the comment on the current segment's filter label.// Returns the previous comment (for undo) or empty string if none.func (s *ClassifyState) SetComment(comment string) string {seg := s.CurrentSegment()if seg == nil {return ""}df := s.CurrentFile()if df == nil {return ""}// Set reviewerdf.Meta.Reviewer = s.Config.Reviewer// Get labels matching filterfilterLabels := seg.GetFilterLabels(s.Config.Filter)var oldComment stringif len(filterLabels) == 0 {// No matching labels, add new one with commentlabel := &utils.Label{Species: "Don't Know",Certainty: 0,Filter: s.Config.Filter,Comment: comment,}seg.Labels = append(seg.Labels, label)} else {// Set comment on first matching labeloldComment = filterLabels[0].CommentfilterLabels[0].Comment = comment}s.Dirty = truereturn oldComment}// GetCurrentComment returns the comment on the current segment's filter label.func (s *ClassifyState) GetCurrentComment() string {seg := s.CurrentSegment()if seg == nil {return ""}filterLabels := seg.GetFilterLabels(s.Config.Filter)if len(filterLabels) == 0 {return ""}return filterLabels[0].Comment}
**Changes:**- `utils/data_file.go` — Added `Comment` field to `Label` struct, parse/write handling- `tools/calls_classify.go` — Added `SetComment()` and `GetCurrentComment()` methods, `Comment` field in `BindingResult`- `tui/classify.go` — Added `commentMode`/`commentText` state, spacebar opens dialog, text input handling, dialog rendering**AviaNZ spec compliance:** The spec allows "any additional attributes defined for this call" as key-value pairs. Comments are stored as `"comment": "text"` in the label object.**Usage:**- `[space]` — Open comment dialog (pre-fills existing comment)- Type comment (max 140 chars, ASCII only)- `[enter]` — Save comment- `[esc]` — Cancel (discard changes)- `[backspace]` — Delete last character- `[ctrl+u]` — Clear all**Help text:** `[esc]quit [,]prev [.]next [space]comment [enter]play [shift+enter]½speed`