From 7015148edfdafbaa71734e0479df4377d87491a6 Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Wed, 22 Jul 2026 21:28:12 -0400 Subject: [PATCH 1/2] Let plop notes be written from the UI (#85 item 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit journal_entries.planting_id was modelled, accepted by the API, and the JournalPanel already rendered a "planting" badge for such entries โ€” but nothing in the UI ever created one. A badge for a state the UI couldn't produce. Give the plop the same "add note" affordance the bed inspector has: - PlopInspector gains an onAddNote button ("๐Ÿ““ Add a note about this plant"), shown only to an editor (a viewer can't write notes). - The editor store gains a journalPlantingId scope beside journalObjectId. The two are mutually exclusive โ€” each setter clears the other โ€” so the journal filter is never double-scoped. - JournalPanel filters by plantingId when that scope is set, shows a "Notes about one planting ยท Show all" banner, and its composer attaches new notes to the plop. Empty-state copy updated to match. Two taps from a selected plant to typing, mirroring the bed flow. No backend change โ€” the API already accepted plantingId. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ --- web/src/editor/JournalPanel.tsx | 49 +++++++++++++++++++++++++----- web/src/editor/PlopInspector.tsx | 10 ++++++ web/src/editor/store.ts | 13 +++++++- web/src/pages/GardenEditorPage.tsx | 11 +++++++ 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/web/src/editor/JournalPanel.tsx b/web/src/editor/JournalPanel.tsx index fba7d63..5b6ec4d 100644 --- a/web/src/editor/JournalPanel.tsx +++ b/web/src/editor/JournalPanel.tsx @@ -37,6 +37,8 @@ export function JournalPanel({ objects, scopeObjectId, onScopeChange, + scopePlantingId = null, + onScopePlantingChange, }: { gardenId: number canEdit: boolean @@ -46,13 +48,23 @@ export function JournalPanel({ /** Which bed the panel is filtered to, if any. */ scopeObjectId: number | null onScopeChange: (id: number | null) => void + /** Which single plop the panel is filtered to, if any (#85). Mutually + * exclusive with scopeObjectId โ€” the editor store enforces that. */ + scopePlantingId?: number | null + onScopePlantingChange?: (id: number | null) => void }) { // Date-range narrowing (#85): the backend and JournalFilter already supported // from/to; they just had no UI. Empty inputs don't filter. const [from, setFrom] = useState('') const [to, setTo] = useState('') + // A plop scope wins over a bed scope โ€” the two are mutually exclusive in the + // store, but guard here too so the filter is never double-scoped. const filter = { - ...(scopeObjectId != null ? { objectId: scopeObjectId } : {}), + ...(scopePlantingId != null + ? { plantingId: scopePlantingId } + : scopeObjectId != null + ? { objectId: scopeObjectId } + : {}), ...(from ? { from } : {}), ...(to ? { to } : {}), } @@ -83,6 +95,21 @@ export function JournalPanel({ )} + {scopePlantingId != null && ( +
+ Notes about one planting + {onScopePlantingChange && ( + + )} +
+ )} +
)} @@ -150,9 +149,7 @@ export function JournalPanel({ gardenId={gardenId} objectId={scopePlantingId != null ? null : scopeObjectId} plantingId={scopePlantingId} - scopeLabel={ - scopePlantingId != null ? 'this planting' : scopedObject ? objectDisplayName(scopedObject) : null - } + scopeLabel={scopeLabel} /> )} diff --git a/web/src/editor/PlopInspector.tsx b/web/src/editor/PlopInspector.tsx index 5442427..08b2dcd 100644 --- a/web/src/editor/PlopInspector.tsx +++ b/web/src/editor/PlopInspector.tsx @@ -35,7 +35,9 @@ export function PlopInspector({ onChangePlant: () => void onClose: () => void /** Scope the journal to this plop and open it โ€” the plop parallel of the bed - * inspector's "add note" (#85). Absent for a viewer, who can't write notes. */ + * inspector's "add note" (#85). Offered to viewers too (to READ the plop's + * notes, like the bed inspector does); the journal's composer is separately + * gated on edit rights, so a viewer just sees the entries. */ onAddNote?: () => void readOnly?: boolean }) { @@ -176,9 +178,9 @@ export function PlopInspector({ /> - {!readOnly && onAddNote && ( + {onAddNote && ( )} -- 2.54.0