Resolve tickets 058, 062-066: frontend UI fixes
- 058: poll loop now refetches param declarations so auto-declared params appear live without a page reload - 062: browser tab title says "ottyl" instead of "webapp" - 063: declared params list omits the redundant capture-group suffix when it matches the param name - 064: dashboard autosave no longer clobbers in-flight PromQL edits; reconciliation is guarded against edits made after the save request was sent - 065: filter bar placeholder/help icons lead with filter-bar syntax instead of regex - 066: filter bar surfaces the active time-range preset/sort order and resyncs its draft when the value prop changes externally Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1ZJ3SrayzLzVndhVq9BmS
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a03ec1fb93
commit
4ace654019
@@ -153,6 +153,9 @@ export function Dashboards({ range, activePreset }: { range: TimeRange | null; a
|
||||
const [saveError, setSaveError] = useState<string | null>(null)
|
||||
// Last-persisted shape for the current draft, so the autosave effect can skip when nothing actually changed.
|
||||
const lastSavedRef = useRef<string | null>(null)
|
||||
// Ticket 064: latest `draft`, read inside the autosave timeout's async callback so a save response can tell
|
||||
// whether the user kept typing after the request went out, instead of unconditionally overwriting `draft`.
|
||||
const draftRef = useRef<Dashboard | null>(draft)
|
||||
const [collapsed, setCollapsed] = useSectionCollapsed('dashboards')
|
||||
|
||||
async function loadDashboards(profileName: string, selectAfterLoad?: string) {
|
||||
@@ -181,11 +184,21 @@ export function Dashboards({ range, activePreset }: { range: TimeRange | null; a
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
draftRef.current = draft
|
||||
}, [draft])
|
||||
|
||||
// Deliberately keyed only on `selectedId`, not `dashboards` — this effect's job is to load a *newly selected*
|
||||
// dashboard's content into `draft`. Keying it on `dashboards` too meant every autosave's own `setDashboards`
|
||||
// call (below) re-ran this effect and clobbered whatever the user had typed since the save was kicked off
|
||||
// (ticket 064). Reading `dashboards` from the closure here still sees the just-loaded array, since
|
||||
// `setSelectedId`/`setDashboards` from `loadDashboards` land in the same render.
|
||||
useEffect(() => {
|
||||
const found = dashboards.find((d) => d.id === selectedId) ?? null
|
||||
setDraft(found)
|
||||
lastSavedRef.current = found ? JSON.stringify(dashboardSaveShape(found)) : null
|
||||
}, [selectedId, dashboards])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedId])
|
||||
|
||||
// Skips entirely once the draft matches what's already persisted — covers both re-selecting an unchanged dashboard and this effect's own post-save re-render.
|
||||
useEffect(() => {
|
||||
@@ -198,9 +211,14 @@ export function Dashboards({ range, activePreset }: { range: TimeRange | null; a
|
||||
setSaveStatus('saving')
|
||||
try {
|
||||
const saved = await saveDashboard(activeProfile, dashboardId, dashboardSaveShape(draft))
|
||||
lastSavedRef.current = JSON.stringify(dashboardSaveShape(saved))
|
||||
lastSavedRef.current = snapshot
|
||||
setDashboards((prev) => prev.map((d) => (d.id === saved.id ? saved : d)))
|
||||
setDraft(saved)
|
||||
// Only reconcile `saved` into `draft` if nothing newer was typed while the request was in flight —
|
||||
// otherwise this stale response would overwrite those in-progress keystrokes (ticket 064).
|
||||
const current = draftRef.current
|
||||
if (current && current.id === dashboardId && JSON.stringify(dashboardSaveShape(current)) === snapshot) {
|
||||
setDraft(saved)
|
||||
}
|
||||
setSaveStatus('saved')
|
||||
setSaveError(null)
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user