Skip to content

mitmweb: make flow table columns resizable - #8358

Open
sleeyax wants to merge 14 commits into
mitmproxy:mainfrom
sleeyax:feat/web-column-resize
Open

mitmweb: make flow table columns resizable#8358
sleeyax wants to merge 14 commits into
mitmproxy:mainfrom
sleeyax:feat/web-column-resize

Conversation

@sleeyax

@sleeyax sleeyax commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

Adds drag handles to the flow table's column headers. Widths are held in FlowTable state, persisted to localStorage and restored on page load.

Two notes on the diff:

  • The table is table-layout: fixed, which spreads leftover space over every column that has no width of its own, so dragging one column moved the rest and the handle tore away from the cursor. A drag now pins all columns and leaves one of them flexible to take the leftover space (path, or quickactions once everything else is pinned). That column is the reason for the <colgroup>, and it is neither sortable nor resizable.
  • The quick-actions column is now headed Actions, since a resize handle beside an unlabelled column looks odd. Header labels clip with an ellipsis and the table scrolls horizontally instead of clipping.

Part of #7789, closes #4107.

Demo

Kooha-2026-08-03-16-20-11.webm

Checklist

  • I have updated tests where applicable.
  • I have added an entry to the CHANGELOG.

@sleeyax
sleeyax marked this pull request as ready for review August 3, 2026 16:58
sleeyax and others added 11 commits August 4, 2026 16:59
Render a colgroup driven by a per-column widths map and add drag handles to the column headers.
Widths are held in FlowTable component state, persisted to localStorage, and restored on load.
Resizing does not trigger a sort, and row height is untouched so virtualization is unaffected.

Part of mitmproxy#7789, mitmproxy#4107.
Fixed table layout hands the space that sized columns leave over to every column without a width of its own, so setting one width while the others stayed flexible made the browser inflate all of them: columns set to 100px rendered at 195px and the handle tore away from the cursor.
Every column is now pinned when a drag starts and quickactions absorbs the slack, so the width follows the pointer exactly, and widening past the viewport scrolls horizontally instead of being clipped.
Pointer moves are coalesced into one update per animation frame and localStorage is written once at the end of the drag rather than on every event.
Adds pointer capture, a pointercancel teardown, and a body class that blocks text selection while dragging.
The handle of the last column sat flush against the right edge of the table, where the scrollbar and the pane splitter leave nothing to grab, and the column that takes the width the others leave over carried no header at all, so it read as a stray divider beside an unlabelled column.
That column is now headed Actions after what its cells hold, and it keeps absorbing the slack, which is why it is neither sortable nor resizable.
A narrow filler column keeps the last handle clear of the scrollbar for as long as the actions column is still collapsed, and the rows carry a cell for it because a column without cells is not covered by the row background.
Widths may only sit on the col elements: fixed layout falls back to the first row's cell whenever a col is auto, which would pin the one column that has to stay flexible.
The last onResize and onResizeEnd both ran synchronously from the pointerup listener, where React batches state updates, so the width that got stored and the layout that got re-measured were still the ones from the previous animation frame.
A drag that finished inside a single frame left nothing behind at all.
The end callback now carries the final width, and the table stores and re-measures it from the setState completion callback.
jsdom does not implement PointerEvent, so fireEvent.pointerDown and friends fall back to a bare Event and every clientX the test passed was dropped.
The drag arithmetic ran on undefined throughout and the test could only assert which keys came back, never a width.
Dispatching MouseEvents under the pointer event types carries the coordinates through, and stubbing offsetWidth gives the columns a width to start from, so the reported pixels can be asserted.
The 20px floor a drag is clamped to sits above the 10px the TLS column ships at, so pressing and releasing on its handle without moving doubled its width.
Any restored width below the floor jumped the same way.
A column narrower than the minimum now keeps its own width as the floor, and a gesture that never moved the pointer reports no new width at all.
Comparing the last pointer position against the starting one treated a drag that happened to end on the pixel it began from as a gesture that never moved, so the pending frame was cancelled and no width was reported.
The column stayed at whatever width the last frame before that had applied, and that width was the one persisted.
An explicit flag set on the first pointermove answers the question the comparison was standing in for.
The actions column ships collapsed until it is the one taking the slack, but its header carries the Actions label all the same, and a th never clipped its content the way the body cells do.
The label rendered past the right edge of the table, so the default state, before any width has been dragged, opened with a horizontal scrollbar over 28px of overflow.
Clipping the header content keeps every label inside its own column, which also covers a column dragged narrower than the word it is headed by.
The row click handler bailed out of selection for any click inside the quickactions cell, not just on its buttons.
That was invisible while the column was collapsed to the width of its buttons, but a resized column leaves empty space that stopped selecting, and rows with no quickaction at all were unselectable across the column.
Guard on the buttons themselves instead.
The width state, its localStorage round trip and the resize handle's click guard had no test, so those lines showed up as a patch coverage drop.
@sleeyax
sleeyax force-pushed the feat/web-column-resize branch from 8487b8d to 86285bb Compare August 4, 2026 15:03

@lups2000 lups2000 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks :) Overall, this looks good to me! I noticed a few things while testing:

  1. The resize-handle hover state is effectively invisible in dark mode. There seems to be a darker background on the divider, but the contrast is too subtle to see clearly.
  2. There is a lower bound when shrinking a column, but apparently no upper bound when expanding one. I can drag columns far outside the visible area, after which restoring the layout becomes difficult.
  3. Is there a way to reset the column widths to their defaults? I think that would be useful, although I’m not sure what the best UX would be. Any thoughts?

sleeyax added 3 commits August 5, 2026 09:56
The hover fill was a black overlay, which all but vanishes on the dark header surface.
A drag had a floor but no ceiling, so a column could be pulled well past the right edge and the layout it left behind was hard to drag back.
A column that is already wider, restored from a wider window, keeps its width as the ceiling and may only shrink.
There was no way back to the default layout once columns had been dragged.
The widths move from FlowTable's own state into a ui/columnWidths slice so the Flow List menu can reach them, which also puts the localStorage round trip in one place.
@sleeyax

sleeyax commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author
  1. Oof, glad you caught this. I noticed this as well but forgot to implement or note it down 😅
  2. Good catch, fixed.
  3. Good idea, I added a "Reset Widths" button in a new Columns group in FlowListMenu.tsx, disabled until something is resized.

@sleeyax
sleeyax requested a review from lups2000 August 8, 2026 19:35

@lups2000 lups2000 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would move the button to reset the column widths outside the Flow List tab, since this option has nothing to do with the other settings there (search, highlight, intercept). Maybe we could move it to the Options tab instead. @emanuele-em what do you think?

@lups2000
lups2000 requested a review from emanuele-em August 13, 2026 15:11
@emanuele-em

Copy link
Copy Markdown
Member

@sleeyax I agree with @lups2000, reserving 1/3 of the space just for a button that is used only when the user resizes the columns is a little bit too much. I'd put that button in the options -> Appearance tab.
Other two possible UX improvements:

  • You can also add the double-click action on the columns separator to adapt the column to the largest element in the column.
  • Add a multi-select dropdown above the "reset column size" button to check/uncheck the columns making them visible/unvisible.

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.

Mitmweb: Make columns configurable/customizable

3 participants