agent-canvas 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,9 +59,7 @@ export function AnnotationEditor({
59
59
  const openFilePicker = useCallback(() => fileInputRef.current?.click(), []);
60
60
 
61
61
  // Expose file picker to parent
62
- useEffect(() => {
63
- if (openFilePickerRef) openFilePickerRef.current = openFilePicker;
64
- }, [openFilePickerRef, openFilePicker]);
62
+ if (openFilePickerRef) openFilePickerRef.current = openFilePicker;
65
63
 
66
64
  const setRef = useCallback((el: HTMLTextAreaElement | null) => {
67
65
  internalRef.current = el;
@@ -48,7 +48,6 @@ function clearPersisted(sessionId: string, revision: number) {
48
48
  }
49
49
 
50
50
  export function AnnotationProvider({ sessionId, revision, isReadOnly, children }: AnnotationProviderProps) {
51
- const prevKeyRef = useRef(`${sessionId}:${revision}`);
52
51
  const [annotations, setAnnotations] = useState<Annotation[]>(() => {
53
52
  const saved = loadPersisted(sessionId, revision);
54
53
  return saved?.annotations ?? [];
@@ -67,20 +66,6 @@ export function AnnotationProvider({ sessionId, revision, isReadOnly, children }
67
66
  return saved?.feedbackEntries ? new Map(saved.feedbackEntries) : new Map();
68
67
  });
69
68
 
70
- // Reset state when session/revision changes
71
- useEffect(() => {
72
- const key = `${sessionId}:${revision}`;
73
- if (key === prevKeyRef.current) return;
74
- prevKeyRef.current = key;
75
-
76
- const saved = loadPersisted(sessionId, revision);
77
- setAnnotations(saved?.annotations ?? []);
78
- setGeneralNote(saved?.generalNote ?? "");
79
- setResponses(saved?.responses ? new Map(saved.responses) : new Map());
80
- setFeedbackEntries(saved?.feedbackEntries ? new Map(saved.feedbackEntries) : new Map());
81
- setActiveAnnotationId(null);
82
- }, [sessionId, revision]);
83
-
84
69
  // Persist to localStorage (debounced)
85
70
  const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
86
71
  useEffect(() => {
@@ -564,7 +564,11 @@ function ResponseBanner({ markdown }: { markdown: string }) {
564
564
  const responseAnnotations = useMemo(() => annotations.filter((a) => a.filePath === RESPONSE_ANNOTATION_PATH), [annotations]);
565
565
 
566
566
  // Reset dismissed state when markdown changes
567
- useEffect(() => { setDismissed(false); }, [markdown]);
567
+ const [prevMarkdown, setPrevMarkdown] = useState(markdown);
568
+ if (markdown !== prevMarkdown) {
569
+ setPrevMarkdown(markdown);
570
+ setDismissed(false);
571
+ }
568
572
 
569
573
  // Restore marks after content renders
570
574
  useEffect(() => {
@@ -153,7 +153,12 @@ export function AnnotationPopover({
153
153
  return idx + row;
154
154
  };
155
155
 
156
- useEffect(() => { setSelectedIdx(null); setSuppressed(isEditMode); }, [note]);
156
+ const [prevNote, setPrevNote] = useState(note);
157
+ if (note !== prevNote) {
158
+ setPrevNote(note);
159
+ setSelectedIdx(null);
160
+ setSuppressed(isEditMode);
161
+ }
157
162
 
158
163
  const applySuggestion = useCallback((s: string) => {
159
164
  setNote(s);
@@ -11,17 +11,32 @@ interface ResponsePreviewProps {
11
11
 
12
12
  export function ResponsePreview({ open, onClose, onSubmit }: ResponsePreviewProps) {
13
13
  const { annotations, generalNote, responses, feedbackEntries } = useAnnotations();
14
- const [text, setText] = useState("");
14
+ const [editedText, setEditedText] = useState("");
15
15
  const [manuallyEdited, setManuallyEdited] = useState(false);
16
16
  const [editMode, setEditMode] = useState(false);
17
17
  const [validationError, setValidationError] = useState<string | null>(null);
18
18
  const textareaRef = useRef<HTMLTextAreaElement>(null);
19
+ const [prevOpen, setPrevOpen] = useState(false);
19
20
 
20
- useEffect(() => {
21
- if (!open) { setManuallyEdited(false); setEditMode(false); setValidationError(null); return; }
22
- if (manuallyEdited) return;
23
- setText(generateMarkdown(annotations, generalNote, responses, feedbackEntries));
24
- }, [annotations, generalNote, responses, feedbackEntries, manuallyEdited, open]);
21
+ if (open && !prevOpen) {
22
+ setManuallyEdited(false);
23
+ setEditMode(false);
24
+ setValidationError(null);
25
+ }
26
+ if (!open && prevOpen) {
27
+ setManuallyEdited(false);
28
+ setEditMode(false);
29
+ setValidationError(null);
30
+ }
31
+ if (open !== prevOpen) {
32
+ setPrevOpen(open);
33
+ }
34
+
35
+ const generatedText = useMemo(
36
+ () => generateMarkdown(annotations, generalNote, responses, feedbackEntries),
37
+ [annotations, generalNote, responses, feedbackEntries]
38
+ );
39
+ const text = manuallyEdited ? editedText : generatedText;
25
40
 
26
41
  useEffect(() => {
27
42
  if (open && editMode && textareaRef.current) {
@@ -86,7 +101,7 @@ export function ResponsePreview({ open, onClose, onSubmit }: ResponsePreviewProp
86
101
  <textarea
87
102
  ref={textareaRef}
88
103
  value={text}
89
- onChange={(e) => { setText(e.target.value); setManuallyEdited(true); }}
104
+ onChange={(e) => { setEditedText(e.target.value); setManuallyEdited(true); }}
90
105
  className="flex-1 w-full bg-bg-base p-5 text-[13px] font-mono text-text-code resize-none focus:outline-none leading-relaxed placeholder:text-text-disabled"
91
106
  placeholder="Your feedback will appear here..."
92
107
  />
@@ -18,11 +18,9 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
18
18
  const [showNote, setShowNote] = useState(false);
19
19
  const wrapperRef = useRef<HTMLDivElement>(null);
20
20
 
21
- useEffect(() => {
22
- if (!responses.has(id)) {
23
- setResponse(id, { id, type: "radio", label, value: null, options, required });
24
- }
25
- }, [id]);
21
+ if (!responses.has(id)) {
22
+ setResponse(id, { id, type: "radio", label, value: null, options, required });
23
+ }
26
24
 
27
25
  const handleSelect = (opt: string) => {
28
26
  setResponse(id, { ...current!, value: opt });
@@ -39,7 +37,7 @@ export function Choice({ id, label, options, required }: ChoiceProps) {
39
37
  };
40
38
  el.addEventListener("kb-select", handler);
41
39
  return () => el.removeEventListener("kb-select", handler);
42
- });
40
+ }, [options, handleSelect]);
43
41
 
44
42
  const showError = current?.required && !selected;
45
43
 
@@ -95,11 +93,9 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
95
93
  const [showNote, setShowNote] = useState(false);
96
94
  const wrapperRef = useRef<HTMLDivElement>(null);
97
95
 
98
- useEffect(() => {
99
- if (!responses.has(id)) {
100
- setResponse(id, { id, type: "checkbox", label, value: [], options, required });
101
- }
102
- }, [id]);
96
+ if (!responses.has(id)) {
97
+ setResponse(id, { id, type: "checkbox", label, value: [], options, required });
98
+ }
103
99
 
104
100
  const toggle = (opt: string) => {
105
101
  const next = selected.includes(opt) ? selected.filter((o) => o !== opt) : [...selected, opt];
@@ -117,7 +113,7 @@ export function MultiChoice({ id, label, options, required }: MultiChoiceProps)
117
113
  };
118
114
  el.addEventListener("kb-select", handler);
119
115
  return () => el.removeEventListener("kb-select", handler);
120
- });
116
+ }, [options, toggle]);
121
117
 
122
118
  const showError = current?.required && selected.length === 0;
123
119
 
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, { useRef, useState } from "react";
2
2
  import { useAnnotations } from "#canvas/runtime";
3
3
  import { ResponseNote } from "./ResponseNote";
4
4
  import { autoResizeTextarea } from "../utils";
@@ -20,11 +20,9 @@ export function UserInput({ id, label, placeholder, multiline, required }: UserI
20
20
  const [showNote, setShowNote] = useState(false);
21
21
  const ref = useRef<HTMLTextAreaElement>(null);
22
22
 
23
- useEffect(() => {
24
- if (!responses.has(id)) {
25
- setResponse(id, { id, type: "text", label, value: "", required });
26
- }
27
- }, [id]);
23
+ if (!responses.has(id)) {
24
+ setResponse(id, { id, type: "text", label, value: "", required });
25
+ }
28
26
 
29
27
  const handleChange = (v: string) => {
30
28
  setResponse(id, { ...current!, value: v });
@@ -75,11 +73,9 @@ export function RangeInput({ id, label, min = 1, max = 10, step = 1, required, m
75
73
  const note = current?.note || "";
76
74
  const [showNote, setShowNote] = useState(false);
77
75
 
78
- useEffect(() => {
79
- if (!responses.has(id)) {
80
- setResponse(id, { id, type: "range", label, value: null, required });
81
- }
82
- }, [id]);
76
+ if (!responses.has(id)) {
77
+ setResponse(id, { id, type: "range", label, value: null, required });
78
+ }
83
79
 
84
80
  const handleChange = (v: number) => {
85
81
  setResponse(id, { ...current!, value: v });