Address #123 review: viewers can read plop notes; tighten the API
Build image / build-and-push (push) Successful in 11s
Build image / build-and-push (push) Successful in 11s
- The "add note" affordance no longer hides from viewers (it claimed parity with the bed inspector but gated on !readOnly). A viewer now sees "📓 Notes about this plant" and can open the plop's journal to read it; the composer stays edit-gated, so they can't write. Real parity now. - onScopePlantingChange is required, matching onScopeChange (its bed twin), so a caller can't pass a plop scope with no way to clear it. Dropped the now-dead guard on the "Show all" button. - Pulled the plop-over-bed scope-label priority into one `scopeLabel`, shared by the filter's sibling logic and the composer, so they can't drift; trimmed the invariant comment that was restated a third time. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -37,7 +37,7 @@ export function JournalPanel({
|
||||
objects,
|
||||
scopeObjectId,
|
||||
onScopeChange,
|
||||
scopePlantingId = null,
|
||||
scopePlantingId,
|
||||
onScopePlantingChange,
|
||||
}: {
|
||||
gardenId: number
|
||||
@@ -48,17 +48,19 @@ 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
|
||||
/** Which single plop the panel is filtered to, if any (#85). The store keeps
|
||||
* this mutually exclusive with scopeObjectId. Required like its bed twin. */
|
||||
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 scopedObject = objects.find((o) => o.id === scopeObjectId) ?? null
|
||||
// One source of scope priority — plop over bed — for both the filter and the
|
||||
// composer's label, so they can't drift.
|
||||
const scopeLabel = scopePlantingId != null ? 'this planting' : scopedObject ? objectDisplayName(scopedObject) : null
|
||||
const filter = {
|
||||
...(scopePlantingId != null
|
||||
? { plantingId: scopePlantingId }
|
||||
@@ -70,7 +72,6 @@ export function JournalPanel({
|
||||
}
|
||||
const journal = useJournal(gardenId, filter)
|
||||
const entries = journal.data?.pages.flatMap((p) => p.entries) ?? []
|
||||
const scopedObject = objects.find((o) => o.id === scopeObjectId) ?? null
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -98,15 +99,13 @@ export function JournalPanel({
|
||||
{scopePlantingId != null && (
|
||||
<div className="flex items-center justify-between gap-2 rounded-md bg-accent/10 px-2 py-1 text-xs">
|
||||
<span className="text-accent-strong">Notes about one planting</span>
|
||||
{onScopePlantingChange && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onScopePlantingChange(null)}
|
||||
className="rounded px-1 text-muted outline-none hover:text-fg focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||
>
|
||||
Show all
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onScopePlantingChange(null)}
|
||||
className="rounded px-1 text-muted outline-none hover:text-fg focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||
>
|
||||
Show all
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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({
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
{!readOnly && onAddNote && (
|
||||
{onAddNote && (
|
||||
<Button variant="ghost" className="justify-start px-2 py-1 text-sm" onClick={onAddNote}>
|
||||
📓 Add a note about this plant
|
||||
📓 {readOnly ? 'Notes about this plant' : 'Add a note about this plant'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user