Add contextual "?" help icons linking into Docs (ticket 021)
New HelpIcon component + a DocsNavProvider context (webapp/src/lib/docsNav.tsx) let any component request "open Docs to section X", bumping a nonce each time so a repeat click re-triggers even if Docs is already open to that section. NavMenu forwards that request into DocsPanel as a `target` prop, which expands the right accordion section, scrolls it into view, and flashes it (a brief accent-tinted background/border, settling back after 1.2s) — verified visually with a live server + Playwright. Opening Docs via the plain NavMenu button explicitly clears any pending target, so that path is unchanged: no forced section, no flash. Added a "?" icon to FilterBar, LogTimeline, LogPreview, Metrics, Dashboards, and NavMenu's own Settings/Presets panels, plus new Docs sections for Timeline, Log preview, Settings, and Presets (previously undocumented). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ce711ab70e
commit
abc061a7b1
@@ -1,4 +1,5 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
||||
import type { DocsTarget } from '../lib/docsNav'
|
||||
|
||||
/// One accordion section: a title plus its body, rendered as plain JSX so
|
||||
/// each page can mix prose, lists, and inline code without pulling in a
|
||||
@@ -122,6 +123,32 @@ const SECTIONS: DocSection[] = [
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'timeline',
|
||||
title: 'Timeline',
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The timeline above the log table renders log volume per bucket over the active window. Drag across it to
|
||||
select a fixed time range — it narrows the log table, metrics, and dashboards below to that window; click
|
||||
"Clear selection" to go back to the default (rolling "all time") view.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'log-preview',
|
||||
title: 'Log preview',
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The log table lists ingested lines newest-last, each with its sequence number, timestamp, and raw text. The
|
||||
column picker (top-right) adds a column per declared param, showing that line's derived value for it (or a
|
||||
blank if it didn't match) — a quick way to eyeball params without switching to the Metrics panel.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'metrics',
|
||||
title: 'Metrics',
|
||||
@@ -200,17 +227,75 @@ const SECTIONS: DocSection[] = [
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'Settings',
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The Settings panel controls process-wide state: colour scheme, the maximum number of logs kept in memory
|
||||
(blank means unlimited), and imported/linked CSS. These apply immediately and persist to the single
|
||||
settings file on disk — no save button needed.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'presets',
|
||||
title: 'Presets',
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The Presets panel manages profiles: create, switch, and delete them. Switching activates a profile's
|
||||
params/metrics in the running engine immediately, with no restart needed — see the <em>General idea</em>{' '}
|
||||
section above for what a profile stores.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
export function DocsPanel() {
|
||||
/** How long a flashed section stays highlighted before settling back to normal. */
|
||||
const FLASH_DURATION_MS = 1200
|
||||
|
||||
export function DocsPanel({ target }: { target?: DocsTarget | null }) {
|
||||
const [openId, setOpenId] = useState<string | null>(SECTIONS[0].id)
|
||||
const [flashId, setFlashId] = useState<string | null>(null)
|
||||
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
||||
|
||||
// Keyed on `target?.nonce` (not `target?.sectionId`) so clicking the same
|
||||
// "?" icon twice in a row re-triggers the flash rather than being a no-op
|
||||
// because the panel is already open to that section.
|
||||
useEffect(() => {
|
||||
if (!target) return
|
||||
setOpenId(target.sectionId)
|
||||
const raf = requestAnimationFrame(() => {
|
||||
sectionRefs.current[target.sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
||||
})
|
||||
setFlashId(target.sectionId)
|
||||
const timeout = setTimeout(() => setFlashId(null), FLASH_DURATION_MS)
|
||||
return () => {
|
||||
cancelAnimationFrame(raf)
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [target?.nonce])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 text-sm text-text-secondary">
|
||||
{SECTIONS.map((section) => {
|
||||
const isOpen = openId === section.id
|
||||
const isFlashing = flashId === section.id
|
||||
return (
|
||||
<div key={section.id} className="rounded border border-border">
|
||||
<div
|
||||
key={section.id}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.id] = el
|
||||
}}
|
||||
className={`rounded border transition-colors duration-500 ${
|
||||
isFlashing ? 'border-accent bg-accent-soft/25' : 'border-border'
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpenId(isOpen ? null : section.id)}
|
||||
|
||||
Reference in New Issue
Block a user