From 26fd7d35d2e2b086627641a232234a86b7340fe7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 15:50:42 +0000 Subject: [PATCH 1/2] Add Done button and input-accepted indicator to JSON drawer - Adds a "Done" button at the bottom of the JSON panel to close it - Flashes a checkmark indicator whenever a typed edit is successfully parsed and applied to the draft - Empty blocks now render a blank textarea instead of "[]", and a blank/whitespace-only textarea is treated as an empty block list rather than a parse error --- src/components/json-drawer.tsx | 49 ++++++++++++++++++++++++++++++++-- test/json-drawer.test.tsx | 35 ++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 test/json-drawer.test.tsx diff --git a/src/components/json-drawer.tsx b/src/components/json-drawer.tsx index 7ea94de..c742b3e 100644 --- a/src/components/json-drawer.tsx +++ b/src/components/json-drawer.tsx @@ -49,10 +49,12 @@ export function JsonDrawer({ const [parseError, setParseError] = useState(null); const [validationErrors, setValidationErrors] = useState([]); const [copied, setCopied] = useState(false); + const [accepted, setAccepted] = useState(false); const textareaRef = useRef(null); const gutterRef = useRef(null); const copyResetRef = useRef | null>(null); + const acceptResetRef = useRef | null>(null); // Hold the latest blocks in a ref so the open-seed effect can read them // without listing them as a dependency. While the drawer is open the @@ -64,19 +66,24 @@ export function JsonDrawer({ useEffect(() => { if (open) { - setValue(JSON.stringify(blocksRef.current, null, 2)); + const current = blocksRef.current; + setValue(current.length === 0 ? '' : JSON.stringify(current, null, 2)); setParseError(null); setValidationErrors([]); setCopied(false); + setAccepted(false); } }, [open]); - // Clear the "copied" reset timer on unmount. + // Clear the "copied" and "accepted" reset timers on unmount. useEffect( () => () => { if (copyResetRef.current) { clearTimeout(copyResetRef.current); } + if (acceptResetRef.current) { + clearTimeout(acceptResetRef.current); + } }, [] ); @@ -103,6 +110,20 @@ export function JsonDrawer({ setValidationErrors([]); return; } + // A blank textarea (or one the user cleared entirely) is treated as an + // empty block list rather than a parse error, so clearing the box to + // paste something new doesn't flash red first. + if (next.trim() === '') { + setParseError(null); + setValidationErrors([]); + onApply([]); + setAccepted(true); + if (acceptResetRef.current) { + clearTimeout(acceptResetRef.current); + } + acceptResetRef.current = setTimeout(() => setAccepted(false), 900); + return; + } let parsed: unknown; try { parsed = JSON.parse(next); @@ -123,6 +144,11 @@ export function JsonDrawer({ } setParseError(null); onApply(blocks); + setAccepted(true); + if (acceptResetRef.current) { + clearTimeout(acceptResetRef.current); + } + acceptResetRef.current = setTimeout(() => setAccepted(false), 900); const result = validateBlockKit(blocks, { target: 'blocks', surface: 'message' @@ -159,6 +185,16 @@ export function JsonDrawer({ > {copied ? : } +
+ + Input accepted +
- -
- - Input accepted +
+
+ + Input accepted +
+
)}
- +