From 32cbe36817c91cfaff67cdcb528c2a2370445166 Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Tue, 21 Jul 2026 01:54:11 -0400 Subject: [PATCH] Address Gadfly review on the journal UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prop names were backwards. `canEdit` meant "may delete" and `canWrite` meant "may rewrite the text" — which is the opposite of how either word reads, on a component where the distinction is the entire permission model. They are now canDelete and canRewrite. An open edit draft never re-synced with its entry, so a refetch after someone else's edit left a stale draft and a Cancel that restored the wrong text. It now resyncs when the entry changes underneath, but only while not editing, so a background refetch can't clobber what's being typed — the version guard is what catches a genuine collision. A 409 from an edit had no handling, so the component kept its stale version and every retry conflicted again: a button that just doesn't work. A conflict now invalidates, so the fresh version arrives. Saving an empty edit returned silently, which also reads as a broken button. It says why nothing happened, and points at Delete if that's what was meant. A failed delete's error now clears on retry rather than making a second attempt look like a second failure. formatObservedAt let out-of-range parts roll over — 2026-13-45 rendering as February 2027, confidently wrong. It shows the raw string instead. Also: the scope select guards against a non-numeric value becoming a NaN objectId and a cryptic 400; the journal total is memoized rather than spread-and-reduced every render; EditorRail's doc comment no longer calls the journal "later" now that it's here; and a static class list stopped being split across two cn() arguments for no reason. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ --- web/src/editor/EditorRail.tsx | 6 ++-- web/src/editor/JournalPanel.tsx | 51 ++++++++++++++++++++---------- web/src/lib/journal.ts | 12 +++++-- web/src/pages/GardenEditorPage.tsx | 6 +++- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/web/src/editor/EditorRail.tsx b/web/src/editor/EditorRail.tsx index 2aa76b4..a836274 100644 --- a/web/src/editor/EditorRail.tsx +++ b/web/src/editor/EditorRail.tsx @@ -4,9 +4,9 @@ import { cn } from '@/lib/cn' /** * The editor's side rail, and the answer to "four things want one rail". * - * The inspector, history, and (later) the journal and chat panel all want the - * same strip of screen. Rather than each bolting on its own chrome, they are - * tabs in one rail — which keeps the canvas one width instead of a different + * The inspector, history, journal, and (later) the chat panel all want the same + * strip of screen. Rather than each bolting on its own chrome, they are tabs in + * one rail — which keeps the canvas one width instead of a different * width per panel, and means adding the journal or chat is adding a tab. * * Two constraints shaped it: diff --git a/web/src/editor/JournalPanel.tsx b/web/src/editor/JournalPanel.tsx index a9827e9..d37c193 100644 --- a/web/src/editor/JournalPanel.tsx +++ b/web/src/editor/JournalPanel.tsx @@ -4,7 +4,6 @@ import { Button } from '@/components/ui/Button' import { TextArea } from '@/components/ui/TextArea' import { TextField } from '@/components/ui/TextField' import { errorMessage } from '@/lib/api' -import { cn } from '@/lib/cn' import { formatObservedAt, today, @@ -55,7 +54,10 @@ export function JournalPanel({ {objects.length > 0 && (