Add tickets and ideas

This commit is contained in:
Hugh Kaznowski
2026-09-07 12:16:36 +01:00
parent 0c41048e0f
commit 49bfd6b020
15 changed files with 1188 additions and 12 deletions
@@ -0,0 +1,35 @@
# 093 — Log-line JSON viewer should default to collapsed, not expanded
## Summary
When a log line's text parses as a JSON object, the frontend renders it
via a collapsible tree view — but every node starts expanded, so a
line with a large JSON payload dumps its entire structure into the
timeline by default instead of showing a collapsed summary the user
opts into expanding.
## Where
- `webapp/src/components/LogPreview.tsx:97-107` — `JsonValueView`
recursively renders JSON values, delegating objects/arrays to
`JsonNode`.
- `webapp/src/components/LogPreview.tsx:109-156` — `JsonNode` renders
a collapsible subtree with a ▾/▸ disclosure toggle. Default state is
set at line 116: `useState(true)`, i.e. expanded. Each nested
object/array has its own independent `expanded` state.
- `webapp/src/components/LogPreview.tsx:160-164` — `LogMessage` is the
per-row entry point: parses the line via `tryParseJsonObject`
(`:82-94`) and renders through `JsonValueView` when it succeeds.
- Empty objects/arrays already short-circuit to plain `{}`/`[]` with
no toggle (`:118-125`) — unaffected by this change.
## Scope
- Flip the default in `JsonNode` (`LogPreview.tsx:116`) from
`useState(true)` to `useState(false)` so JSON subtrees render
collapsed until the user clicks to expand.
- Since expand state is per-node (not shared across the tree), this
changes every node in a JSON log line's tree independently — confirm
that's the intended look (top-level object collapsed with `{...}`
rather than only nested children collapsing) rather than needing a
"expand root, collapse children" split default.
+34
View File
@@ -0,0 +1,34 @@
# 094 — Adopt rustfmt and format the codebase
## Summary
The Rust backend has no `rustfmt.toml` and isn't currently formatted
to rustfmt's defaults: `cargo fmt -- --check` exits non-zero, reporting
diffs across 24 files (352 diff hunks total).
## Where
- No `rustfmt.toml` / `.rustfmt.toml` exists at the repo root.
- `cargo fmt -- --check` reports diffs in `src/api/{dto,mcp_server,mod}.rs`,
`src/cli/{mod,sessions}.rs`, `src/config/{mod,profile}.rs`, and most
of `src/engine/**` (`alerts.rs`, `data_value.rs`, `filter_datetime.rs`,
`filter_parser.rs`, `ingest.rs`, `logfmt.rs`, `message.rs`, `mod.rs`,
`promql.rs`, `rate.rs`, `snapshot/{filter,metrics,mod,params,query,sort}.rs`,
`tests.rs`). Heaviest: `src/engine/snapshot/mod.rs` (~65 hunks),
`src/config/profile.rs` (~38), `src/api/mod.rs` (~35),
`src/engine/alerts.rs` (~28).
- `Makefile:26` (`lint-be`) currently only runs `cargo clippy
--all-targets` — no `cargo fmt --check` step.
## Scope
- Add a `rustfmt.toml` if any defaults should be overridden (otherwise
an empty/absent file is fine and rustfmt defaults apply — decide
whether to keep the file empty for documentation purposes or omit
it).
- Run `cargo fmt` across the whole `src/` tree as a single,
non-functional formatting commit (don't mix with logic changes, to
keep the diff reviewable and `git blame`-friendly).
- Add a `cargo fmt --check` step to `make lint` (or a new `make
fmt-check` wired into CI) so future changes can't drift out of
format again.
+41
View File
@@ -0,0 +1,41 @@
# 095 — Resolve clippy warnings and make lint enforce a clean run
## Summary
`make lint`'s backend target runs `cargo clippy --all-targets` with no
`-D warnings` (or equivalent), so clippy warnings accumulate silently
instead of failing the lint step. There are currently 23 unique
warnings across 5 lint categories.
## Where
- `Makefile:26` (`lint-be:`) — `cargo clippy --all-targets`, no denial
flag.
- `cargo clippy --all-targets` currently reports, by lint:
- `clippy::cloned_ref_to_slice_refs` — 10 occurrences, mostly in
`src/engine/alerts.rs` test code (e.g. around lines 469, 472, 493,
534).
- `clippy::collapsible_if` — 8 occurrences, concentrated in
`src/engine/snapshot/mod.rs` (lines 608, 940, 961) and
`src/engine/alerts.rs` (around lines 510, 512).
- `clippy::type_complexity` — 3 occurrences.
- `clippy::single_match` — 1 occurrence.
- `clippy::unnecessary_get_then_check` — 1 occurrence,
`src/engine/tests.rs:2234`
(`entries[1].params.get("level").is_none()`).
## Scope
- Fix all 23 current warnings (mechanical for most: `from_ref` for the
slice-ref clone cases, collapsing nested `if`s, `is_none()` →
`!contains_key`/`get().is_none()` equivalent simplification per
clippy's suggestion, breaking up the complex types clippy flags or
adding a type alias, converting the single-arm `match` to `if let`).
- Once clean, change `Makefile:26` to deny warnings (e.g. `cargo
clippy --all-targets -- -D warnings`) so the target fails on any
future regression instead of just printing them.
- Don't add blanket `#[allow(...)]`s to silence these — fix the
underlying code per clippy's suggestion, matching the existing
`#[allow(dead_code)]` uses elsewhere in the codebase which are for
genuinely-unused-for-now items, not a precedent for suppressing
active lints.
+34
View File
@@ -0,0 +1,34 @@
# 096 — Add single-character short flags to clap CLI args
## Summary
None of the CLI's clap-defined flags have a `short` form — every
option is long-only, including ones that are typed often
(`--file`, `--metrics-url`).
## Where
- `src/cli/mod.rs:20-39` — top-level `Args` struct, all fields
`#[arg(long, ...)]` with no `short`:
- `port: u16` — `:25-26`, `default_value_t = 0`
- `settings_path: Option<PathBuf>` — `:28-29`
- `file: Option<PathBuf>` — `:31-32`
- `metrics_url: Option<String>` — `:34-35`
- `name: Option<String>` — `:37-38`
- `src/cli/mod.rs:41-50` — `Command::Mcp` subcommand, also long-only:
- `session: Option<String>` — `:44-45`
- `port: Option<u16>` — `:46-47`
## Scope
- Add `short` to each flag where the initial letter is unambiguous
within its own `Args`/subcommand struct: `-p` (port), `-f` (file),
`-m` (metrics-url), `-n` (name), `-s` (settings-path or session,
whichever reads more naturally per subcommand — they're in separate
structs so both can use `-s` without conflict).
- `Mcp`'s `port` can reuse `-p` since it's a distinct struct from the
top-level `Args`.
- Verify with `cargo run -- --help` (and the `mcp` subcommand's
`--help`) that clap doesn't reject any of these for collision, and
update `README.md`'s CLI usage section if it documents flags
explicitly.
@@ -0,0 +1,50 @@
# 097 — Metrics have no gauge/counter distinction, so `rate()`/`sum()`/`avg()` can be applied without a semantics guard
## Summary
`MetricPrimitiveKind` only distinguishes data *type*
(`String`/`Number`/`Timestamp`/`Duration`/`Boolean`), not metric
*semantics* (counter vs. gauge). PromQL's `gauge()` op is a bespoke
query-time "take the last point" function rather than something tied
to how a metric was declared, and nothing stops calling `rate()` on a
metric that's conceptually a gauge, or `sum()`/`avg()` on a
monotonically-increasing counter without rating it first.
## Where
- `src/engine/data_value.rs:70-76` — `MetricPrimitiveKind`:
`String, Number, Timestamp, Duration, Boolean`. No `Counter`/`Gauge`
variant.
- `src/engine/metric_primitive.rs:8-14` — `MetricPrimitive` mirrors
the same type-only distinction (`String, Number(Number), Timestamp,
Duration(i64), Boolean`).
- `src/engine/promql.rs:9-16` — `AggOp` includes `Gauge` as one of six
query-time aggregation ops (`Sum, Avg, Count, Rate, Histogram,
Gauge`), evaluated in `src/engine/snapshot/mod.rs:993-1001` by
taking only `points.last()` per series — a query-side convenience,
not a property of the declared metric.
- The only type guard on aggregation today is
`src/engine/snapshot/mod.rs:888-896`, which rejects `sum`/`avg` on
`MetricPrimitiveKind::String` — there's no equivalent guard for
applying `rate()` to a non-monotonic value or `sum()`/`avg()` to an
un-rated counter.
## Scope
- Add a semantics field to metric declarations (e.g. extend
`MetricPrimitiveKind` with `Counter`/`Gauge` variants, or add a
separate `MetricSemantics` enum alongside the existing type field —
decide which based on whether type and semantics should ever vary
independently for the same declaration).
- Thread that through `DeclareMetricRequest`/`MetricDeclarationRecord`/
`MetricDeclarationDto` (the same three-places pattern noted in this
repo's `CLAUDE.md` for declaration features: `engine.rs` message +
handler, `settings.rs`/`profile.rs` persistence, `api.rs` route,
plus a webapp UI affordance in `Params.tsx`/`Metrics`'s declaration
form).
- Use it to validate PromQL queries at evaluation time (e.g. warn or
reject `rate()` on a declared gauge, or `sum()`/`avg()` directly on a
declared counter without an inner `rate()`), and consider whether
the current ad hoc `gauge()` PromQL wrapper becomes redundant once
the declaration itself carries this information (a bare selector on
a declared gauge could just work the way `gauge()` does today).
+41
View File
@@ -0,0 +1,41 @@
# 098 — No histogram_quantile()-style percentile query
## Summary
The PromQL layer's `histogram()` op buckets raw sample values by a
fixed width and returns per-bucket counts — it's unrelated to
Prometheus's classic histogram bucket/quantile model, and there's no
way to ask for p50/p95/p99 out of a metric today.
## Where
- `src/engine/promql.rs:9-16` — `AggOp::Histogram` is one of the six
supported ops.
- `src/engine/snapshot/mod.rs:1002` onward — `Histogram` evaluation:
buckets values by `bucket_width` into counts, grouped by
`by_labels`. This answers "how many samples fell in each bucket",
not "what value is the Nth percentile".
- No `histogram_quantile()` function or equivalent exists anywhere in
`promql.rs`'s parser (`parse_query`, `:261-324` recognizes only
`sum`, `avg`, `count`, `rate`, `histogram`, `gauge`).
- Depends on ticket 097 (gauge/counter semantics) for the broader
question of whether a metric needs a declared "this holds raw
observations for quantile purposes" flag, but quantile computation
itself doesn't strictly require that — it can operate on whatever
raw values are already stored in a metric's buckets.
## Scope
- Decide the quantile computation model: interpolate over the raw
stored sample values in a bucket/window (simplest, exact for the
data actually retained) vs. building a genuine bucketed-histogram
representation with configurable bucket boundaries (closer to
Prometheus's classic histograms, more work, needed only if retained
raw values are later dropped in favor of pre-aggregated buckets).
- Add a `histogram_quantile(p, metric[range])`-shaped PromQL construct
(or extend `histogram()` with a quantile argument) to
`promql.rs`'s parser and `AggOp`, plus an evaluator in
`snapshot/mod.rs` alongside the existing `Histogram` handling.
- Add dashboard/panel UI support for selecting a quantile once the
query-language side exists (`webapp/src/components/Dashboards.tsx`
or wherever panel query construction lives).
+45
View File
@@ -0,0 +1,45 @@
# 099 — Remove `test_handle`'s duplicate-of-`spawn` test stub
## Summary
`test_handle` is a `#[cfg(test)]` production-file function that exists
only to give tests access to the engine's internal `mpsc::Receiver` —
it duplicates `spawn()` almost line-for-line rather than exposing that
receiver through `spawn()`/`EngineHandle` itself. Per this repo's
`IDEAS.md` guidance: prefer changing a function's scope/visibility (or
the tests) over maintaining a parallel stub.
## Where
- `src/engine/handle.rs:113-128` — `#[cfg(test)] pub(crate) fn
test_handle(channel_capacity: usize) -> (EngineHandle,
mpsc::Receiver<EngineMessage>)`, duplicating `spawn()`
(`src/engine/handle.rs:103-111`) except that `spawn()` discards the
receiver after wiring the actor loop, while `test_handle` returns it
raw.
- Re-exported at `src/engine/mod.rs:21`: `pub(crate) use
handle::test_handle;`.
- Consumed only by tests in `src/engine/ingest.rs:228` (`use
crate::engine::test_handle;`) and called at lines 248, 264, 281, 302
inside `#[cfg(test)] mod tests`.
## Scope
- Remove `test_handle` and instead give tests access via `spawn()`
itself — e.g. have `spawn()` take an optional channel-capacity
override and always hand back the receiver alongside the handle (or
the actor join handle), with production call sites (`main.rs`)
simply ignoring what they don't need, rather than keeping a second
constructor whose only difference is what it returns.
- Update the four call sites in `src/engine/ingest.rs`'s test module
to use the adjusted `spawn()` signature.
- Out of scope: the other test-fixture helpers in `src/engine/tests.rs`
(`metric_request`, `declare_test_param`, `ingest_log`, `logs_query`,
`logs_query_sorted`, etc.) are ordinary test-data builders, not
visibility workarounds — leave those alone. Also leave the
`#[allow(dead_code)]` items (`src/engine/message.rs:11,29`,
`src/api/mcp_server.rs:254`, `src/config/mod.rs:27`, and similar in
`metric_primitive.rs`/`data_value.rs`/`timestamp.rs`/`index_key.rs`)
untouched — they weren't confirmed to be test-visibility stubs and
need individual inspection before deciding whether they're even
related to this ticket's pattern.