Log row visual treatment: zebra striping, severity tint and stripe #42

Open
opened 2026-11-12 17:26:57 +01:00 by omp-deck · 0 comments
Collaborator

Part of the dynamic-columns work. Depends on #39 (grid layout, cell model, header) — specifically on detail panels being nested inside the row rather than beside it, and on the nowrap row-width fix from the scroller change.

Independent of #40 and #41; can land in any order relative to them.

Why

Rows are currently separated by the hover highlight alone (assets/scss/main.scss), which is deliberate — a rule per row would draw a horizontal line every 18 pixels down the page. A subtle alternating background reads better at density, and severity is the single most useful thing to spot while scrolling.

Scope

Three visual layers, plus hover:

  • Zebra — a subtle alternating row background.
  • Severity tint — a subtle background tint for warn and above.
  • Severity stripe — a brighter leading-edge border per row.
  • Hover — must stay legible on top of all of the above.

Composition. Only one background-color exists per element, so compose with color-mix() in oklab over the Bulma theme variables rather than hardcoding colours: zebra base, mixed with severity tint, mixed with a hover increment. Mixing against --bulma-scheme-main is what makes the same rules work in both light and dark themes — a tint hand-picked against white typically disappears on a dark background.

Contrast case to check: a hovered untinted row and a non-hovered tinted row must not land on the same perceived lightness, or hover stops reading as hover.

Severity mapping. src/views/mod.rs deliberately refuses to map severities, because that would mean guessing a shipper's vocabulary. That decision stands for the content: the level cell stays verbatim. Styling can still be vocabulary-aware — emit a best-effort normalised class alongside the verbatim text, where an unrecognised value simply gets no class and no colour. Nothing is guessed in what the user reads.

Parity must not come from :nth-child. Appending rows in tail is fine, but a DOM cap evicting rows from the top flips nth-child parity for every remaining row — the whole list re-stripes on every eviction, which is a strobing background rather than a subtle alternation. Instead carry parity explicitly: emit a class on alternate rows (its absence encoding the other state), so eviction cannot disturb it. The state is one bit, flipped per row; the tail path reads the starting bit off the last backfilled row and toggles from there. No counter, nothing to keep in sync.

This also makes nested detail panels safe, which a positional selector would not be.

Prerequisite from the scroller change. In nowrap, rows are currently sized max-content with min-width: 100%, so rows have different widths and a row's background stops at its own content. Scrolled right, shorter rows show no background at all. The hover highlight has this today and it is easy to miss; zebra striping makes it glaring. The max-content sizing has to move up to the list so every row is as wide as the widest.

Acceptance

  • Alternating backgrounds, severity tint and stripe, and hover are all simultaneously legible, in both light and dark themes.
  • Parity is stable across row eviction and across expanded detail panels.
  • Backgrounds span the full scroll width in nowrap mode.
  • An unrecognised level value renders verbatim and uncoloured.

Out of scope

Row detail expansion itself. Any change to what the level cell displays.

Verification note

None of this is observable to the current test harness and there is no browser in CI. The parity class is assertable structurally; the colour composition is not.

Part of the dynamic-columns work. **Depends on** #39 (grid layout, cell model, header) — specifically on detail panels being nested inside the row rather than beside it, and on the nowrap row-width fix from the scroller change. Independent of #40 and #41; can land in any order relative to them. ## Why Rows are currently separated by the hover highlight alone (`assets/scss/main.scss`), which is deliberate — a rule per row would draw a horizontal line every 18 pixels down the page. A subtle alternating background reads better at density, and severity is the single most useful thing to spot while scrolling. ## Scope Three visual layers, plus hover: - **Zebra** — a subtle alternating row background. - **Severity tint** — a subtle background tint for warn and above. - **Severity stripe** — a brighter leading-edge border per row. - **Hover** — must stay legible on top of all of the above. **Composition.** Only one `background-color` exists per element, so compose with `color-mix()` in `oklab` over the Bulma theme variables rather than hardcoding colours: zebra base, mixed with severity tint, mixed with a hover increment. Mixing against `--bulma-scheme-main` is what makes the same rules work in both light and dark themes — a tint hand-picked against white typically disappears on a dark background. Contrast case to check: a hovered untinted row and a non-hovered tinted row must not land on the same perceived lightness, or hover stops reading as hover. **Severity mapping.** `src/views/mod.rs` deliberately refuses to map severities, because that would mean guessing a shipper's vocabulary. That decision stands for the *content*: the level cell stays verbatim. Styling can still be vocabulary-aware — emit a best-effort normalised class alongside the verbatim text, where an unrecognised value simply gets no class and no colour. Nothing is guessed in what the user reads. **Parity must not come from `:nth-child`.** Appending rows in tail is fine, but a DOM cap evicting rows from the top flips `nth-child` parity for every remaining row — the whole list re-stripes on every eviction, which is a strobing background rather than a subtle alternation. Instead carry parity explicitly: emit a class on alternate rows (its absence encoding the other state), so eviction cannot disturb it. The state is one bit, flipped per row; the tail path reads the starting bit off the last backfilled row and toggles from there. No counter, nothing to keep in sync. This also makes nested detail panels safe, which a positional selector would not be. **Prerequisite from the scroller change.** In nowrap, rows are currently sized `max-content` with `min-width: 100%`, so rows have different widths and a row's background stops at its own content. Scrolled right, shorter rows show no background at all. The hover highlight has this today and it is easy to miss; zebra striping makes it glaring. The `max-content` sizing has to move up to the list so every row is as wide as the widest. ## Acceptance - Alternating backgrounds, severity tint and stripe, and hover are all simultaneously legible, in both light and dark themes. - Parity is stable across row eviction and across expanded detail panels. - Backgrounds span the full scroll width in nowrap mode. - An unrecognised level value renders verbatim and uncoloured. ## Out of scope Row detail expansion itself. Any change to what the level cell displays. ## Verification note None of this is observable to the current test harness and there is no browser in CI. The parity class is assertable structurally; the colour composition is not.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/logview#42
No description provided.