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:
Hugh Kaznowski
2026-09-06 13:04:26 +01:00
co-authored by Claude Sonnet 5
parent a03ec1fb93
commit 4ace654019
11 changed files with 83 additions and 258 deletions
@@ -1,42 +0,0 @@
# 058 — Newly auto-declared params don't appear in the UI until a manual refresh
## Summary
`auto_declare_params` / `auto_declare_log_stream_params` (`src/engine.rs:383`,
`:451`) can add new param declarations on any ingested line, but the
frontend only ever fetches the param list once, on mount
(`webapp/src/App.tsx:278-284`, `fetchParamDeclarations().then(setParams)`).
The main poll loop (`App.tsx:300-350`, interval from
`webapp/src/lib/polling.ts`) refetches timeline entries and logs every
tick but never re-fetches param declarations — the only other place
`setParams` is called is the `onDeclared` callback fired by a user's own
manual edit in `Params.tsx`. So a param that gets auto-declared mid-run
from freshly ingested lines is invisible in the Params panel until the
page is reloaded.
There is no push infrastructure (SSE/websocket) anywhere in the
codebase; all live-refresh today is plain polling.
## Scope
- Add a periodic (or piggybacked on the existing poll loop) refetch of
param declarations in `App.tsx`, e.g. call `fetchParamDeclarations()`
alongside the existing timeline/log poll in the loop at
`App.tsx:300-350`, or on its own lighter-weight interval if refetching
every tick is wasteful.
- Only update state when the declaration list actually changed, to avoid
needlessly re-rendering `Params.tsx` on every poll tick.
- Out of scope: introducing SSE/websocket push infrastructure — polling
is consistent with how the rest of the app already refreshes.
## Acceptance criteria
- Ingesting new lines that trigger auto-declaration of a previously
unseen param causes that param to appear in the Params UI within one
poll interval, without a page reload.
- Existing manual param declaration/edit flows (`Params.tsx`'s
`onDeclared`) are unaffected.
## Depends on
None.
-24
View File
@@ -1,24 +0,0 @@
# 062 — Browser tab title says "webapp" instead of "ottyl"
## Summary
`webapp/index.html:7` has `<title>webapp</title>`. Nothing else sets a
user-visible title — no `document.title` assignment exists in
`App.tsx`, and `webapp/package.json`'s `"name": "webapp"` is a separate
dev-tooling field not surfaced to users. The only fix needed is the
`index.html` title tag.
## Scope
- Change `<title>webapp</title>` to `<title>ottyl</title>` in
`webapp/index.html`.
- Out of scope: renaming the `webapp` directory or `package.json` name
field — those are internal and not user-facing.
## Acceptance criteria
- The browser tab shows "ottyl" when the dashboard is open.
## Depends on
None.
@@ -1,38 +0,0 @@
# 063 — Declared params list shows a redundant "group name" alongside type
## Summary
`webapp/src/components/Params.tsx:331-337` renders each param as
`{p.name} ({p.kind}, ...)`, where the third segment is one of `elapsed
since previous`, nothing (for booleans), `json field {path}`, or `group
{p.capture_group}`. There is no distinct "group" concept in the domain
model — `src/dto.rs`, `src/profile.rs`, and `src/engine.rs` have no
`group` field at all. What the UI calls "group" is actually
`capture_group` (`dto.rs:22`, `profile.rs:69`), the regex named-capture
group used to extract the param's value. By convention, params are
declared with `(?P<name>...)`, so `capture_group` is almost always
identical to `p.name` — displaying it is redundant noise in the common
case (`name (type, name)`).
## Scope
- Change the default rendering to `name (type)`.
- Only append the extra suffix when it's actually informative:
- Keep `json field {path}` and `elapsed since previous` suffixes —
these carry information not implied by `name`/`type`.
- For the capture-group case, only show it when
`p.capture_group !== p.name` (i.e. the regex group name genuinely
differs from the declared param name); otherwise omit it.
- Out of scope: changing the underlying `capture_group` field or param
declaration model — this is a display-only fix.
## Acceptance criteria
- A param whose capture group matches its name renders as `name (type)`.
- A param whose capture group differs from its name still shows that
distinction (e.g. `name (type, group other_name)`).
- `json field` and `elapsed since previous` suffixes are unchanged.
## Depends on
None.
@@ -1,48 +0,0 @@
# 064 — Typing a panel's PromQL query gets clobbered by in-flight autosave responses
## Summary
In `webapp/src/components/Dashboards.tsx`, the panel query `<input>`
(`:408-411`) is bound directly to `draft.panels[...].promql` /
`updatePanel`, and `draft` is autosaved via a 600ms-debounced effect
(`AUTOSAVE_DEBOUNCE_MS = 600`, line 34; effect at `:190-214`). On save
success, `setDraft(saved)` (`:203`) unconditionally replaces the entire
local draft with the server's response — including whatever `promql`
string was in flight at request time. If the user types more characters
after the debounce fires but before the save response returns, those
keystrokes exist only in local `draft` state and get overwritten the
instant the stale `saved` response resolves, producing the reported
"characters inserted then removed" behavior. A second effect
(`Dashboards.tsx:184-188`) also resets `draft` whenever `dashboards`
changes — which the same save's `setDashboards` call triggers —
compounding the race.
## Scope
- Stop unconditionally overwriting in-flight-edited fields on autosave
response. Options (pick one, document the choice):
- Track which panel/field is actively focused/being edited and skip
overwriting that specific field when reconciling `saved` into
`draft`.
- Only merge server-assigned fields (ids, timestamps) from `saved`
into `draft` rather than replacing `draft` wholesale.
- Guard the reconciliation with a "pending edits since request was
sent" check, applying `saved` only when no newer local edit
occurred after the request was issued.
- Also fix the related staleness effect at `Dashboards.tsx:184-188` so
it doesn't clobber in-progress edits when `dashboards` changes as a
side effect of the same save.
- This does not require adopting Redux or another state library — the
fix is local to this component's save/edit reconciliation.
## Acceptance criteria
- Typing continuously in a panel's PromQL input while an autosave
round-trip is in flight never drops or reorders characters.
- Autosave still persists the query to the backend and other
server-assigned fields (ids, etc.) still get reconciled into local
state correctly.
## Depends on
None.
@@ -1,41 +0,0 @@
# 065 — Filter bar's help/prompt foregrounds regex syntax instead of the filter bar's own syntax
## Summary
A comprehensive filter-bar syntax reference already exists — operators,
`[param]` comparisons, datetime formats, worked examples — behind the
`<HelpIcon sectionId="filtering">` in `webapp/src/components/Docs.tsx:76-140`,
wired up at `FilterBar.tsx:32-34`. The problem is emphasis, not missing
docs: the input's `placeholder` (`FilterBar.tsx:38`) leads with
`regex("error") and ottyl_seq<5 or ottyl_ts>12:00`, making regex look
like the primary syntax, and a second `<RegexHelpIcon>` (a pure regex
cheat-sheet, `webapp/src/components/RegexHelpIcon.tsx`) sits more
prominently next to the input than the actual filter-syntax help icon.
A user skimming the bar sees "regex" twice before noticing the filter
bar has its own broader query language.
## Scope
- Change the input `placeholder` at `FilterBar.tsx:38` to lead with a
non-regex filter-bar example (e.g.
`ottyl_ts>-15m and requests[duration]>200`), keeping a `regex(...)`
clause further in the example rather than first if desired.
- Reorder/re-emphasize the two help icons so the general filter-syntax
`HelpIcon` (→ `Docs.tsx`'s filtering section) reads as the primary
affordance, with the regex-specific cheat sheet secondary/clearly
labeled as being about regex specifically, not the filter bar as a
whole.
- Out of scope: rewriting the content of `Docs.tsx`'s filtering section
— it's already accurate and thorough; this is about surfacing it
correctly, not authoring new documentation.
## Acceptance criteria
- The filter bar's placeholder text no longer leads with a bare
`regex(...)` example as the first thing a user sees.
- The general filter-syntax help is at least as discoverable/prominent
as the regex-only cheat sheet.
## Depends on
None.
@@ -1,53 +0,0 @@
# 066 — Filter bar text doesn't reflect the active time-range preset or sort order
## Summary
In `webapp/src/App.tsx`, `filter` (the raw typed filter text, `:264`),
`activePreset`/`range` (`:262-263`, set via `selectPreset` /
`handleManualRangeSelect`, `:286-298`), and `sortSettings` (`:266`) are
three entirely separate pieces of state.
`<FilterBar value={filter} onChange={setFilter} .../>` (`:355`) only
ever reflects the manually-typed `filter` string — selecting a
quick-pick time-range preset or changing the sort order never touches
`filter`, so the filter bar can show empty/stale text even though a
preset or sort order is actively constraining or ordering what's
displayed. Separately, `FilterBar.tsx:22`
(`const [draft, setDraft] = useState(value)`) initializes its internal
`draft` from `value` only once on mount and never resyncs if `value`
changes externally afterward (e.g. a programmatic clear) — a related
staleness bug worth fixing alongside this.
## Scope
- Decide and implement one of:
- Synthesize a read-only textual summary of the active preset/sort
state and display it in or alongside the filter bar when the user
hasn't typed a manual filter (e.g. showing the equivalent
`ottyl_ts>-Xm`-style clause for the active preset).
- Make preset-selection and sort-order actions actually write into
the `filter` query string itself (e.g. `selectPreset` appends/
updates an `ottyl_ts` clause), so `filter` is always the single
source of truth for what's displayed in the bar.
Document the choice in the PR — this is a real design decision, not
just a bug fix.
- Fix `FilterBar.tsx:22` so the internal `draft` resyncs when the
`value` prop changes externally (not just on mount), so programmatic
filter changes (from whichever approach above, or an external clear)
are reflected in the input.
- Out of scope: changing how presets/sort settings are computed or
stored server-side — this is about surfacing existing state in the
filter bar's displayed value.
## Acceptance criteria
- Selecting a quick-pick time-range preset causes the filter bar to
visibly reflect that a time constraint is active (either via a
synthesized display or an actual written filter clause).
- Changing sort order is likewise visible via the filter bar's
displayed state.
- The filter bar's input value stays in sync with externally-driven
changes to the active filter, not just the user's own typing.
## Depends on
None.