Skip to content

Improve selection behavior of the Logs tab#5756

Open
Karakatiza666 wants to merge 2 commits intomainfrom
better-logs
Open

Improve selection behavior of the Logs tab#5756
Karakatiza666 wants to merge 2 commits intomainfrom
better-logs

Conversation

@Karakatiza666
Copy link
Copy Markdown
Contributor

userSelect.ts now contains a complex but working implementation for consistent user selection within a virtual list. In the future I will try to simplify the implementation while preserving the UX. This fixes many issues like drag-to-select not working properly, etc.

Also, made it so that the Logs tab does not lose state when leaving it, avoiding having to re-load logs when switching between tabs of the same pipeline. When switching between pipeline logs are still reloaded.

Manual testing: extensively tested selection behavior using mouse in Firefox and Chromium-based browser. Tested the Logs tab correctly preserve and reload state when needed while switching the tabs and pipelines

Signed-off-by: Karakatiza666 <bulakh.96@gmail.com>
… the tabs

Signed-off-by: Karakatiza666 <bulakh.96@gmail.com>
@Karakatiza666 Karakatiza666 requested a review from mihaibudiu March 5, 2026 01:15
Copy link
Copy Markdown

@mythical-fred mythical-fred left a comment

Choose a reason for hiding this comment

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

The userSelect.ts refactor is a real improvement — data-rowindex anchors + Range.toString() for offsets beats the old recursive sibling-walking by a mile. The visited SvelteSet in TabsPanel and the IntersectionObserver in useReverseScrollContainer are coherent, necessary follow-ups to enabling keepAlive for Logs.

Ongoing web-console gap: the selection logic in particular (drag-extend, shift-click, DOM recycling edge cases) is exactly what Vitest + @testing-library/svelte tests would catch before they reach production.

Copy link
Copy Markdown
Contributor

@mihaibudiu mihaibudiu left a comment

Choose a reason for hiding this comment

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

I have actually stopped reviewing midway. First I would like to make sure that this is the right way to solve this problem. Maybe having a "pause" button for the logs is a better and much simpler solution.

}

export const injectOnCopyAll = (node: HTMLElement, props: { value: string }) => {}
type RowLocation = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This type seems to be misnamed, unless you have a matrix with ROW values in every cell.

} else if (node.nodeType === Node.ELEMENT_NODE && node instanceof HTMLElement) {
textLen += node.innerText.length
/** Walk up the DOM to find the nearest ancestor with a `data-rowindex` attribute */
const findRowAncestor = (node: Node): HTMLElement | null => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This function name is very generic, it does not really reflect what it is doing.

row: number
col: number
/** Get the character offset from the start of a row element to a specific DOM position */
const getCharOffset = (rowElement: HTMLElement, node: Node, offset: number): number => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same for this function name.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is a row element?

index: number[] // [firstChild, ...restChildren] firstChild - index in the list of .children, restChildren - indices in the list of .childNodes
offsetLocal: number // Location in the leaf Node.TEXT_NODE
offsetAbsolute: number // Location in the root.item(i).innerText
/** Convert a DOM selection point to a logical {row, col} position */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

position within what?
Function named "toLogical", but result is RowLocation. Maybe it should be called toRowLocation?

): Location | null => {
if (!node.parentNode) {
return null
/** Iterate over row elements in the virtualizer root, calling fn for each row index and its wrapper */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't know what a virtualizer is, maybe I missed it.

// Track when the mouse leaves the container during a drag-selection, so the MO
// callback knows to extend the focus toward newly visible rows.
let dragOutside: 'above' | 'below' | null = null
// Set when handleSelectionChange detects that Firefox placed the anchor on the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just Firefox?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, unfortunately the behavior between differs between it and Chrome-based browsers. I am actually yet to test Safari.

// This signals the MO callback that the browser can't maintain the selection
// natively, so we must reapply and extend focus ourselves even during a drag
// inside the container. Cleared on pointerup when the drag ends.
let browserSelectionBroken = false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems weird to me that you have to build this whole machinery to handle gestures. You seem to imply that the dom keeps changing while you handle the gestures. Maybe there is a different solution, like a way to pause updates to the view while user performs these actions. Could be implicit (start of a gesture) or explicit (through some "pause" button).


/** Like getVisibleRange but only includes rows actually within the scroll container's viewport,
* excluding virtua's overscan/buffer rows that are in the DOM but scrolled off-screen. */
function getViewportVisibleRange() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you write a type for the return value?


/**
* Merge browser-reported anchor/focus with tracked state to handle off-screen endpoints.
* When virtua recycles DOM nodes, the browser creates fallback endpoints at visible boundaries.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

virtua?

/**
* Merge browser-reported anchor/focus with tracked state to handle off-screen endpoints.
* When virtua recycles DOM nodes, the browser creates fallback endpoints at visible boundaries.
* This detects those fallbacks and substitutes the tracked logical positions.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

substitutes the position into what?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what does the function return?

@Karakatiza666
Copy link
Copy Markdown
Contributor Author

Maybe having a "pause" button for the logs is a better and much simpler solution.

The code in userSelect.ts is about simulating mouse selection behavior of a plain text in the context of shifting, re-purposed and deleted HTML nodes in a virtual list. This us unrelated to the logs content changing or not.

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.

3 participants