Skip to content

refactor(log-viewer): give a tab one wiring call to the inspector - #977

Merged
lcottercertinia merged 1 commit into
certinia:mainfrom
lukecotter:refactor-event-source
Sep 1, 2026
Merged

refactor(log-viewer): give a tab one wiring call to the inspector#977
lcottercertinia merged 1 commit into
certinia:mainfrom
lukecotter:refactor-event-source

Conversation

@lukecotter

Copy link
Copy Markdown
Collaborator

📝 PR Overview

Three events reach every tab's view and each view answered only its own, so the same detail.source === '<tab>' test sat in nine places. Behind that was a bigger repeat: all four views subscribe to the same three events, hold three nullable unsubscribe fields, and undo them one at a time in teardown. ApexLogTimeline spent twelve lines on it.

The bus gains onSource(event, source, callback), which delivers only what names a tab. On top of that, wireInspectorTab names the set of three once and returns one unsubscribe, so a view supplies only what it does differently. Net 90 insertions against 291 deletions.

🛠️ Changes made

  • eventBus.onSource — a SourcedEvent type admits only the payloads that name a tab, so the source test lives on the bus. No cast is needed: EventMap[K] already resolves to the union of sourced payloads.
  • wireInspectorTab(source, emphasis, sync) — one call per view, one unsubscribe. A view gives mark, reveal, clear, and movesToMergedPick where a picked row that merges occurrences should also move.
  • One reveal for both kinds of pick. The Call Tree and Analysis views were passing the same method twice, once for a single frame and once for the first of several. movesToMergedPick now says when it moves, and the helper catches a rejection, so no view needs void on a promise.
  • inspectorLocate.ts is gone, absorbed into inspectorTab.ts. Its doc no longer has to ask the caller to subscribe it a particular way, since the module does the subscribing.
  • Twelve unsubscribe fields become four, and four teardown blocks lose about thirty lines between them.

🧩 Type of change (check all applicable)

  • 🐛 Bug fix - something not working as expected
  • ✨ New feature – adds new functionality
  • ♻️ Refactor - internal changes with no user impact
  • ⚡ Performance Improvement
  • 📝 Documentation - README or documentation site changes
  • 🔧 Chore - dev tooling, CI, config
  • 💥 Breaking change

📷 Screenshots / gifs / video [optional]

N/A. Nothing changes on screen.

🔗 Related Issues

Follows #974, #975 and #976, which built the inspector mark this wiring carries.

✅ Tests added?

  • 👍 yes
  • 🙅 no, not needed
  • 🙋 no, I need help

Nine wireInspectorTab cases driven through the real bus, and three for onSource. Three guards were proven by reverting the code they cover: returning only the first unsubscribe, dropping the movesToMergedPick gate, and swapping mark with move each fail a test. The last needed an ordered log, since two separate lists cannot show order; the ordering had no test before this.

📚 Docs updated?

  • 🔖 README.md
  • 🔖 CHANGELOG.md
  • 📖 help site
  • 🧪 Marked any pre-release-only features
  • 🙅 not needed

Nothing user-visible changes.

Anything else we need to know? [optional]

Where to start. log-viewer/src/components/inspectorTab.ts is the whole mechanism, then any one view to see what a caller now looks like.

What was deliberately left. SourcedEvent also admits detail:select, detail:view and detail:locate. Those must not be filtered by source: the inspector records every tab's selection, so filtering would lose the tab it is not showing. The type's doc says so, since the constraint belongs to the caller rather than the type.

Test plan.

  • `pnpm lint` and `pnpm test`.
  • In each of the four tabs, with the Inspector open: hover an inspector row and the tab marks; pick one and the Call Tree and Analysis grids also move, while the Database grids and the flame chart only mark.
  • Pick a row, then `Escape`: the tab's own selection and the mark both go.
  • Switch tabs with a mark set: the tab you left stops marking, and the one you arrive at answers.
  • Both themes, Inspector docked at the side and at the bottom.

Known unrelated failure locally. `lana/src/services/tests/servicesRuntime.test.ts` cannot resolve `effect` in a worktree not installed since #951. No `lana` file is touched here, and it passes in CI.

Follow-ups, not in this PR.

  • Two subscriptions still carry no source. `DatabaseTimeTree.ts:205` and `:210` answer `detail:locate` and `selection:clear` from every tab, so a Timeline hover marks rows in the Database tab's time tree and Escape anywhere drops that table's pick. `onSource` makes the fix one word, but narrowing it changes behaviour, so it wants its own change.
  • All four views subscribe in the constructor and release in `disconnectedCallback`, so a Lit element that is detached and re-attached comes back with dead subscriptions. Pre-existing, and unchanged here.
  • A stale move is not abandoned. Clicking two inspector rows quickly lets the earlier move settle last and scroll away from the newer row.

Four views each answered three source-keyed events, so the same source test
sat in nine places and the same three unsubscribe fields in four. The bus
gains onSource, and wireInspectorTab names the set once: a view supplies
only what it does differently and gets one unsubscribe.
@lcottercertinia
lcottercertinia merged commit 181c74c into certinia:main Sep 1, 2026
7 checks passed
lcottercertinia pushed a commit that referenced this pull request Sep 1, 2026
…de (#980)

> **Stacked on #977.** Its commit shows in this diff until that merges.
Review from `be6412ea` onward.

# 📝 PR Overview

Two bugs in the inspector's row mark, both about which rows light up.

**A grid row told the inspector the wrong thing.** A row under the
pointer emitted every call it counts, so a Bottom Up caller row marked
the inspector rows for the leaf calls underneath it rather than for the
caller itself. The forward direction stopped doing that in #973; the
reverse direction was still on the old rule, so the two disagreed
depending on which side you pointed at.

**A mark could come back after being dropped.** The sweep reaches only
the rows a table has attached. A row lit while on screen, then scrolled
out, kept the class when the mark moved away, and the renderer
re-attaches such a row without running the row formatter again, so the
stale highlight returned with it.

## 🛠️ Changes made

- **`rowFrames(row, root, direction)`** — a bottom-up caller row climbs
to its own depth, `depthOf(_pathId) - 1` hops above each call it counts.
A top-down row already sits at its frames' depth, so it climbs nothing.
- **`LogStore.framesAbove`** — the climb, next to `stackByEventIndex`,
which already owned this parent-pointer walk. The inspector's
`frameEventIndexes` now reads it too, so the two sides cannot drift.
- **The direction is read at hover time**, from
`directionOf(this.viewMode)`, which the sibling `_emitDetailSelection`
already uses. No new parameter and no second source of truth.
- **`litByHost`** — what each table's mark has lit, whichever half lit
it, so a new mark can un-light an element the renderer has since
detached.
- **The `detail:locate` doc** described the old rule; it now says what
the protocol carries.

## 🧩 Type of change (check all applicable)

- [x] 🐛 Bug fix - something not working as expected
- [x] ♻️ Refactor - internal changes with no user impact
- [ ] ✨ New feature – adds new functionality
- [ ] ⚡ Performance Improvement
- [ ] 📝 Documentation - README or documentation site changes
- [ ] 🔧 Chore - dev tooling, CI, config
- [ ] 💥 Breaking change

## 📷 Screenshots / gifs / video [optional]

N/A. What changes is which rows carry the highlight.

## 🔗 Related Issues

Follows #973, which changed the forward direction, and #975, which
shipped the mark mechanism the second fix corrects.

## ✅ Tests added?

- [x] 👍 yes
- [ ] 🙅 no, not needed
- [ ] 🙋 no, I need help

Three `rowFrames` cases and one for the detached row. Each guard was
proven by reverting the code it covers: dropping the direction check
makes a top-down row climb; removing the shared climb fails one
`rowFrames` test **and** two `frameEventIndexes` tests, which also shows
the inspector test runs the real method rather than a copy of it;
restoring the old clearing fails the detached-row test.

## 📚 Docs updated?

- [ ] 🔖 README.md
- [x] 🔖 CHANGELOG.md
- [ ] 📖 help site
- [ ] 🧪 Marked any pre-release-only features
- [ ] 🙅 not needed

No entry: both fixes correct the unreleased Inspector, so they belong to
its existing entry. Ticked to record that it was considered.

## Anything else we need to know? [optional]

**Where to start.** `LogStore.framesAbove` is the mechanism; `rowFrames`
is the grid's use of it and `frameEventIndexes` the inspector's.

**Why the mark has to remember what it lit.** Tabulator builds a row's
element once (`Row.create()` is guarded by \`this.created\`) and
\`Row.initialize()\` re-uses it, and both \`deinitialize()\` calls in
Tabulator are inside \`reinitializeRows()\`, a column-layout path. So an
ordinary scroll neither rebuilds the element nor re-runs the formatter:
the class persists, and clearing it has to reach elements the query
cannot see.

**On the dedupe.** A merged row aggregates distinct caller frames that
share a signature, so the climb is many-to-many and the answer can be as
long as what was asked about. It is not safe to climb from one call and
assume the rest agree.

**Test plan.**

- \`pnpm lint\` and \`pnpm test\`.
- Call Tree, **Bottom Up**, Inspector open. Hover a bucket row, then a
caller row one level down: the inspector mark moves up the stack with
you rather than staying on the leaf calls.
- Step two and three levels up: one frame per level.
- Analysis: same, hovering a row under a method bucket.
- **Aggregated** and **Time Order**: unchanged, since a row there
already sits at its own frames' depth.
- Hover an inspector row so a grid row lights, scroll that row out of
view, move the pointer off the inspector row, then scroll back: no
highlight. That is the second fix.
- Both themes, Inspector docked at the side and at the bottom.

**Known unrelated failure locally.**
\`lana/src/services/__tests__/servicesRuntime.test.ts\` cannot resolve
\`effect\` in a worktree not installed since #951. It passes in CI.

**Follow-up, not in this PR.** `deriveCalls` already walks each call's
parents inside `chainReaches` and stops exactly at the frame the row is,
then keeps the index and throws the frame away, so `framesAbove`
re-walks the same edges. Fusing the two means having `chainReaches`
return the node it stopped at.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants