Sitelet https://github.com/feldera/feldera/pull/6952
Skip to content

[profiler] Make the corner chips pressable - #6952

Open
Karakatiza666 wants to merge 1 commit into
redesign-profiler-diagram-11from
redesign-profiler-diagram-12
Open

[profiler] Make the corner chips pressable#6952
Karakatiza666 wants to merge 1 commit into
redesign-profiler-diagram-11from
redesign-profiler-diagram-12

Conversation

@Karakatiza666

@Karakatiza666 Karakatiza666 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Part 12 of 15 of #6895, split one commit per PR. Based on redesign-profiler-diagram-11; merge in order.

The stack (this is 12 of 15)
  1. [profiler] Remove the unused hierarchical table #6941 [profiler] Remove the unused hierarchical table
  2. [profiler] Select the whole SQL range a node came from #6942 [profiler] Select the whole SQL range a node came from
  3. [profiler] Count the primitive operators inside every region #6943 [profiler] Count the primitive operators inside every region
  4. [profiler] Add the diagram palettes, stylesheet and corner chips #6944 [profiler] Add the diagram palettes, stylesheet and corner chips
  5. [profiler] Size an expanded region for its name and its counter #6945 [profiler] Size an expanded region for its name and its counter
  6. [profiler] Draw the diagram from the palette-driven stylesheet #6946 [profiler] Draw the diagram from the palette-driven stylesheet
  7. [profiler] Draw a collapsed region nested inside an expanded one #6947 [profiler] Draw a collapsed region nested inside an expanded one
  8. [profiler] Paint a node's id and its operator as two text runs #6948 [profiler] Paint a node's id and its operator as two text runs
  9. [profiler] Mark the node the metrics are about, and trace its edges #6949 [profiler] Mark the node the metrics are about, and trace its edges
  10. [profiler] Show a picture of the circuit on the minimap, and steer from it #6950 [profiler] Show a picture of the circuit on the minimap, and steer from it
  11. [profiler] Move the diagram's lifecycle reactions into observers #6951 [profiler] Move the diagram's lifecycle reactions into observers
  12. [profiler] Make the corner chips pressable #6952 [profiler] Make the corner chips pressable <-- this PR
  13. [profiler] Follow the application theme #6953 [profiler] Follow the application theme
  14. [profiler] Add a browser harness for the diagram, and pin what it paints #6954 [profiler] Add a browser harness for the diagram, and pin what it paints
  15. [profiler] Pin what a pointer on the diagram does #6955 [profiler] Pin what a pointer on the diagram does

The two corner chips are the diagram's controls: the code chip opens the
SQL the node came from, the counter expands or collapses the region it
reports on. Cytoscape knows nothing about them - they are background
images - so chipButtons.ts computes each chip's box from the resolved
style and hit-tests presses against it, which is also what gives the
cursor and the hover highlight.

Both actions land where a pointer already went: a code chip press reports
on its node and then asks for its source, the same lookup a double click
on an operator does, so a consumer sees one callback either way. A counter
press is the double click on the region.

Describe Manual Test Plan

Press a node's code chip to open the SQL it came from; press a region's counter chip to expand or collapse it. The cursor turns over both, and they highlight on hover.

Verified at this commit, not just at the tip of the stack: checked out detached with js-packages/profiler-lib/dist deleted and rebuilt from this commit's source, then profiler-lib bun run check and bun run test, and profiler-layout bun run check and bun run test (all three vitest projects, browser suites included). All four green.

Checklist

  • Unit tests added/updated
  • Integration tests added/updated
  • Documentation updated
  • Changelog updated

Breaking Changes?

Mark if you think the answer is yes for any of these components:

  • OpenAPI / REST HTTP API / feldera-types / manager
  • Feldera SQL (Syntax, Semantics)
  • feldera-sqllib (incl. dependencies fxp, etc.)
  • Python SDK
  • fda (CLI arguments)
  • Adapters (including configuration)
  • Storage Format / Checkpoints
  • Others (specify)

Describe Incompatible Changes

None. The change is confined to js-packages/.

let pressed: ChipHit | null = null;
// Both bound on the container in the capture phase, ahead of cytoscape's own press handling, which
// is bound on that same container in the bubble phase.
container.addEventListener('mousedown', (event) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two listeners are added to the container and never removed, and installChipButtons returns no teardown, so CytographRendering.dispose() cannot drop them. ProfilerDiagram.svelte disposes and recreates a Visualizer on the same graphContainer whenever the profile data changes, so after one remount the container carries two pairs of handlers, and the older pair holds a destroyed cycytoscape.destroyRenderer() sets _private.renderer = null, so toGraphPoint dereferences null.

Reproduced against this branch (temporary test, real headless cy, renderer() stubbed to null exactly as destroyRenderer leaves it):

TypeError: Cannot read properties of null (reading 'projectIntoViewport')
 ❯ toGraphPoint src/chipButtons.ts:170:18
 ❯ chipAtMouse  src/chipButtons.ts:233:23
 ❯ mousedown    src/chipButtons.ts:241:53

So every mousedown in the diagram throws after the first profile reload, and the live handlers still hit-test against the stale instance. Suggest returning a disposer (or binding an AbortController signal) and calling it from dispose(), plus a test that a torn-down instance no longer reacts.

Comment on lines +220 to +227
// Touch and pen only: cytoscape routes those through its own touch handling and reports a tap. A
// mouse press never reaches here, being stopped below before cytoscape sees it.
cy.on('tap', (event: EventObject) => {
const hit = hitTestChips(cy, event.position.x, event.position.y);
if (hit !== null) {
dispatch(hit);
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Touch and pen only" doesn't hold for a press that starts off a chip. Cytoscape emits tap from its mouseup path too (triggerEvents(down, ["click", "tap", "vclick"], e, {x: pos[0], y: pos[1]}), gated only on !didDrag && !dragged && !selecting && !isOverThresholdDrag, desktopTapThreshold = 4px), with the position of the release. The mousedown handler below only calls stopPropagation() when the press landed on a chip; otherwise cytoscape sets hoverData.capture and the tap fires normally.

So: press on the canvas 1–3px above a code chip (or on a region's title row just under its counter), release on the chip → this handler dispatches. That defeats the cancel rule the mouseup handler implements and that 'cancels a press let go of anywhere but the chip it started on' tests. Consider gating on the pointer type (e.g. only dispatch here when the tap came from touch/pen), or tracking a "cytoscape saw the press" flag.

},
// The code chip asks for the same source lookup a double click on an operator does, so
// consumers see one callback either way.
onShowSource: (node) => this.config.callbacks.onNodeDoubleClick?.(node, 'leaf')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code chip is drawn on any node with has_source, composites included, so this reports 'leaf' for a group node. Both consumers (ProfilerLayout.handleNodeDoubleClick, SupportBundleViewerLayout) only branch on type !== 'leaf', so it works today, but the second argument is documented as the node's kind and this makes it not one. A 'source' type — or narrowing the callback to onShowSource at the public boundary — would keep it honest.

@mythical-fred-oss

Copy link
Copy Markdown

Ran locally at this commit: bun install --frozen-lockfile, profiler-lib bun run check + bun run test (13 files / 251 tests green, chipButtons.test.ts 23/23 on 5 consecutive runs — no flake), bun run build:deps:profiler-layout and profiler-layout bun run check (0 errors). Cross-checked chipBox against cytoscape 3.33.1 drawInscribedImage: the nodeTW = width + 2*padding / percent-vs-px position+offset arithmetic matches, background-fit: none and background-width-relative-to: include-padding are the defaults it relies on, and bounds-expansion really is folded into boundingBox(), so the hit boxes are derived correctly. Tests are thorough and well written; two real defects and one API nit are inline.

gate verdict
unit tests for changed behaviour pass (23 new tests)
flaky tests pass
manual test plan pass
docs / breaking / deps / unsafe / pull_request_target n/a
correctness fail — see the two inline findings

Uncovered cases I derived that the suite does not exercise: teardown (nothing asserts the instance stops reacting after dispose(), which is how the leak below hides); a tap arriving from a mouse release whose press started off-chip; mouseup outside the container after an on-chip press (leaves pressed set); a chip on a node inside a display: none parent (shown() only reads the node's own style); and hitTestChips walking every node on every mousemove — cytoscape's own hit test is O(n) too, so this is a constant factor rather than a regression, but it also allocates a fresh cy.nodes().toArray() per move, which is easy to hoist. Requesting changes for the disposal leak in particular — it throws a TypeError on every mousedown after the diagram is remounted.

The two corner chips are the diagram's controls: the code chip opens the
SQL the node came from, the counter expands or collapses the region it
reports on. Cytoscape knows nothing about them - they are background
images - so `chipButtons.ts` computes each chip's box from the resolved
style and hit-tests presses against it, which is also what gives the
cursor and the hover highlight.

Both actions land where a pointer already went: a code chip press reports
on its node and then asks for its source, the same lookup a double click
on an operator does, so a consumer sees one callback either way. A counter
press is the double click on the region.

Signed-off-by: Karakatiza666 <bulakh.96@gmail.com>
@Karakatiza666
Karakatiza666 force-pushed the redesign-profiler-diagram-12 branch from c8b08aa to c52ff18 Compare August 26, 2026 07:38
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.

1 participant