Merge ticket branch t092

This commit is contained in:
Hugh Kaznowski
2026-09-07 10:47:01 +01:00
2 changed files with 65 additions and 97 deletions
+17 -97
View File
@@ -8,7 +8,7 @@ import {
saveLogSortSettings, saveLogSortSettings,
type LogSortSettings, type LogSortSettings,
} from './components/LogTimeline' } from './components/LogTimeline'
import { TIME_RANGE_PRESETS, computePresetRange, type TimeRange, type TimeRangePresetId } from './lib/timeRange' import { TIME_RANGE_PRESETS, type TimeRange, type TimeRangePresetId } from './lib/timeRange'
import { LogPreview } from './components/LogPreview' import { LogPreview } from './components/LogPreview'
import { Params } from './components/Params' import { Params } from './components/Params'
import { Metrics } from './components/Metrics' import { Metrics } from './components/Metrics'
@@ -26,6 +26,7 @@ import {
type TimelineBucket, type TimelineBucket,
} from './lib/api' } from './lib/api'
import { DocsNavProvider } from './lib/docsNav' import { DocsNavProvider } from './lib/docsNav'
import { useRangeSelection } from './lib/useRangeSelection'
import { useSettingsPanel } from './lib/useSettingsPanel' import { useSettingsPanel } from './lib/useSettingsPanel'
import { POLL_MS } from './lib/polling' import { POLL_MS } from './lib/polling'
@@ -186,47 +187,18 @@ function StandaloneRanged({
initialFixedRange: TimeRange | null initialFixedRange: TimeRange | null
}) { }) {
const [buckets, setBuckets] = useState<TimelineBucket[]>([]) const [buckets, setBuckets] = useState<TimelineBucket[]>([])
const [range, setRange] = useState<TimeRange | null>(
initialPresetId ? computePresetRange(initialPresetId) : initialFixedRange,
)
// No preset and no fixed range means the popout was opened with nothing carried over (e.g. a bare URL) — default to 'all', matching FullApp's own default. // No preset and no fixed range means the popout was opened with nothing carried over (e.g. a bare URL) — default to 'all', matching FullApp's own default.
const [activePreset, setActivePreset] = useState<TimeRangePresetId | null>( const { range, activePreset, selectPreset, handleManualRangeSelect, resolveEffectiveRange } = useRangeSelection({
initialPresetId ?? (initialFixedRange ? null : 'all'), preset: initialPresetId ?? (initialFixedRange ? null : 'all'),
) range: initialFixedRange,
})
const rangeRef = useRef(range)
useEffect(() => {
rangeRef.current = range
}, [range])
function selectPreset(id: TimeRangePresetId) {
setActivePreset(id)
setRange(computePresetRange(id))
}
function handleManualRangeSelect(next: TimeRange | null) {
if (next === null) {
selectPreset('all')
} else {
setActivePreset(null)
setRange(next)
}
}
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
async function poll() { async function poll() {
// Rolling presets re-resolve on this same polling cadence instead of freezing the window at selection time — this is what makes a carried-over preset independently slide rather than track the source tab. // Rolling presets re-resolve on this same polling cadence instead of freezing the window at selection time — this is what makes a carried-over preset independently slide rather than track the source tab.
const effectiveRange = const effectiveRange = resolveEffectiveRange()
activePreset && activePreset !== 'all' ? computePresetRange(activePreset) : rangeRef.current
if (activePreset && activePreset !== 'all') {
setRange((current) =>
current && effectiveRange && current.startMs === effectiveRange.startMs && current.endMs === effectiveRange.endMs
? current
: effectiveRange,
)
}
try { try {
const nextBuckets = await fetchTimeline({ const nextBuckets = await fetchTimeline({
startMs: effectiveRange?.startMs, startMs: effectiveRange?.startMs,
@@ -295,8 +267,10 @@ function StandaloneLog() {
const [logs, setLogs] = useState<LogEntry[]>([]) const [logs, setLogs] = useState<LogEntry[]>([])
const [params, setParams] = useState<ParamDeclaration[]>([]) const [params, setParams] = useState<ParamDeclaration[]>([])
const [buckets, setBuckets] = useState<TimelineBucket[]>([]) const [buckets, setBuckets] = useState<TimelineBucket[]>([])
const [range, setRange] = useState<TimeRange | null>(null) const { range, activePreset, selectPreset, handleManualRangeSelect, resolveEffectiveRange } = useRangeSelection({
const [activePreset, setActivePreset] = useState<TimeRangePresetId | null>('all') preset: 'all',
range: null,
})
const [filter, setFilter] = useState('') const [filter, setFilter] = useState('')
const [filterError, setFilterError] = useState<string | null>(null) const [filterError, setFilterError] = useState<string | null>(null)
const [sortSettings, setSortSettings] = useState<LogSortSettings>(loadLogSortSettings) const [sortSettings, setSortSettings] = useState<LogSortSettings>(loadLogSortSettings)
@@ -307,11 +281,6 @@ function StandaloneLog() {
saveLogSortSettings(next) saveLogSortSettings(next)
} }
const rangeRef = useRef(range)
useEffect(() => {
rangeRef.current = range
}, [range])
useEffect(() => { useEffect(() => {
fetchParamDeclarations() fetchParamDeclarations()
.then(setParams) .then(setParams)
@@ -320,33 +289,11 @@ function StandaloneLog() {
}) })
}, []) }, [])
function selectPreset(id: TimeRangePresetId) {
setActivePreset(id)
setRange(computePresetRange(id))
}
function handleManualRangeSelect(next: TimeRange | null) {
if (next === null) {
selectPreset('all')
} else {
setActivePreset(null)
setRange(next)
}
}
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
async function poll() { async function poll() {
const effectiveRange = const effectiveRange = resolveEffectiveRange()
activePreset && activePreset !== 'all' ? computePresetRange(activePreset) : rangeRef.current
if (activePreset && activePreset !== 'all') {
setRange((current) =>
current && effectiveRange && current.startMs === effectiveRange.startMs && current.endMs === effectiveRange.endMs
? current
: effectiveRange,
)
}
try { try {
const [nextBuckets, nextLogs, nextParams] = await Promise.all([ const [nextBuckets, nextLogs, nextParams] = await Promise.all([
fetchTimeline({ fetchTimeline({
@@ -436,8 +383,10 @@ function FullApp() {
const [logs, setLogs] = useState<LogEntry[]>([]) const [logs, setLogs] = useState<LogEntry[]>([])
const [params, setParams] = useState<ParamDeclaration[]>([]) const [params, setParams] = useState<ParamDeclaration[]>([])
const [buckets, setBuckets] = useState<TimelineBucket[]>([]) const [buckets, setBuckets] = useState<TimelineBucket[]>([])
const [range, setRange] = useState<TimeRange | null>(null) const { range, activePreset, selectPreset, handleManualRangeSelect, resolveEffectiveRange } = useRangeSelection({
const [activePreset, setActivePreset] = useState<TimeRangePresetId | null>('all') preset: 'all',
range: null,
})
const [filter, setFilter] = useState('') const [filter, setFilter] = useState('')
const [filterError, setFilterError] = useState<string | null>(null) const [filterError, setFilterError] = useState<string | null>(null)
const [logViewerCollapsed, setLogViewerCollapsed] = useSectionCollapsed('log-viewer') const [logViewerCollapsed, setLogViewerCollapsed] = useSectionCollapsed('log-viewer')
@@ -461,12 +410,6 @@ function FullApp() {
}) })
} }
// Read via ref inside the poll loop instead of depending on `range` directly, which would restart the interval every rolling-preset tick.
const rangeRef = useRef(range)
useEffect(() => {
rangeRef.current = range
}, [range])
async function loadParams() { async function loadParams() {
try { try {
setParams(await fetchParamDeclarations()) setParams(await fetchParamDeclarations())
@@ -479,35 +422,12 @@ function FullApp() {
loadParams() loadParams()
}, []) }, [])
function selectPreset(id: TimeRangePresetId) {
setActivePreset(id)
setRange(computePresetRange(id))
}
// A drag always yields a fixed (non-rolling) range, overriding any active preset.
function handleManualRangeSelect(next: TimeRange | null) {
if (next === null) {
selectPreset('all')
} else {
setActivePreset(null)
setRange(next)
}
}
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
async function poll() { async function poll() {
// Rolling presets re-resolve on this same polling cadence instead of freezing the window at selection time. // Rolling presets re-resolve on this same polling cadence instead of freezing the window at selection time.
const effectiveRange = const effectiveRange = resolveEffectiveRange()
activePreset && activePreset !== 'all' ? computePresetRange(activePreset) : rangeRef.current
if (activePreset && activePreset !== 'all') {
setRange((current) =>
current && effectiveRange && current.startMs === effectiveRange.startMs && current.endMs === effectiveRange.endMs
? current
: effectiveRange,
)
}
try { try {
const [nextStats, nextBuckets, nextLogs, nextParams] = await Promise.all([ const [nextStats, nextBuckets, nextLogs, nextParams] = await Promise.all([
fetchStats(), fetchStats(),
+48
View File
@@ -0,0 +1,48 @@
import { useEffect, useRef, useState } from 'react'
import { computePresetRange, type TimeRange, type TimeRangePresetId } from './timeRange'
// `resolveEffectiveRange` is meant to be called once at the top of each variant's own poll
// callback: it re-resolves a rolling preset to "now" on every tick (instead of freezing the
// window at selection time) while reading a manually-selected fixed range via a ref, so the poll
// loop's own `useEffect` doesn't need `range` itself as a dependency and restart its interval on
// every tick.
export function useRangeSelection(initial: { preset: TimeRangePresetId | null; range: TimeRange | null }) {
const [range, setRange] = useState<TimeRange | null>(
initial.preset ? computePresetRange(initial.preset) : initial.range,
)
const [activePreset, setActivePreset] = useState<TimeRangePresetId | null>(initial.preset)
const rangeRef = useRef(range)
useEffect(() => {
rangeRef.current = range
}, [range])
function selectPreset(id: TimeRangePresetId) {
setActivePreset(id)
setRange(computePresetRange(id))
}
// A drag always yields a fixed (non-rolling) range, overriding any active preset.
function handleManualRangeSelect(next: TimeRange | null) {
if (next === null) {
selectPreset('all')
} else {
setActivePreset(null)
setRange(next)
}
}
function resolveEffectiveRange(): TimeRange | null {
const effectiveRange = activePreset && activePreset !== 'all' ? computePresetRange(activePreset) : rangeRef.current
if (activePreset && activePreset !== 'all') {
setRange((current) =>
current && effectiveRange && current.startMs === effectiveRange.startMs && current.endMs === effectiveRange.endMs
? current
: effectiveRange,
)
}
return effectiveRange
}
return { range, activePreset, selectPreset, handleManualRangeSelect, resolveEffectiveRange }
}