feat: replace hard-to-grab resize tabs with card edge/corner resizing#70
Conversation
Replace the tiny CSS resize: vertical browser-native grab handles on chart containers with a proper edge/corner resize system at the card level. - New CardEdgeResizeDirective handles mousedown/move/up on card edges (bottom: height, right: full-width toggle, corner: both) - Visual edge indicators appear on hover showing resize affordances - Double-click bottom edge resets height; double-click right toggles width - Card height is persisted to localStorage (_tb_card_sizes.v1) - Applied to both regular cards and superimposed cards - Removed CSS resize: vertical from scalar and superimposed chart containers Co-authored-by: Samuel <samuel@knutsen.co>
Since resize: vertical was removed from chart containers (now handled by the card-level edge resize directive), the persistResize directives on chart containers are no longer needed. Data table persistResize directives are preserved as those still have resize: vertical. Co-authored-by: Samuel <samuel@knutsen.co>
Preview Deployment
Details
|
Replace the binary full-width toggle with smooth grid-column span resizing. Dragging the right edge of a card now smoothly snaps between grid column spans (span 1, span 2, span 3, ..., full-width). - Directive reads grid column count, width, and gap from the parent grid element to compute the target span during drag - Column span is applied via inline grid-column style (span N or 1/-1) - Both height and column span are persisted to localStorage - Double-click right edge resets span to 1 - Dragging to max columns auto-sets full-width (1/-1) - Fix prettier formatting on all changed files Co-authored-by: Samuel <samuel@knutsen.co>
During right-edge drag, the card now resizes smoothly in real pixels (following the cursor exactly) instead of jumping between column spans. On mouse-up, it snaps to the nearest grid column span for clean alignment. - On drag start: set explicit pixel width, clear grid-column, raise z-index so the card overlaps neighbors during the drag - During drag: update width in pixels for every mousemove - On release: compute nearest column span from the final width, clear pixel overrides, apply grid-column span - Added edge-resizing-width class with subtle shadow during drag - Min width clamped to 200px, max width clamped to full grid width Co-authored-by: Samuel <samuel@knutsen.co>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Restore path bypasses column count clamping logic
- Changed the ngOnInit restore path to call snapshotGrid() then applyColSpan(saved.colSpan) so the span is clamped to the current grid column count instead of being set directly.
- ✅ Fixed: Inline gridColumn style overrides full-width CSS class permanently
- Added ngDoCheck lifecycle hook that clears the inline gridColumn style whenever the element has the full-width CSS class and is not mid-drag, allowing the CSS class rule to take effect.
- ✅ Fixed: Field
startColSpanis assigned but never read- Removed the unused startColSpan field declaration and its sole assignment in handleDown.
Or push these changes by commenting:
@cursor push bd234634ff
Preview (bd234634ff)
diff --git a/tensorbored/webapp/widgets/edge_resize_directive.ts b/tensorbored/webapp/widgets/edge_resize_directive.ts
--- a/tensorbored/webapp/widgets/edge_resize_directive.ts
+++ b/tensorbored/webapp/widgets/edge_resize_directive.ts
@@ -14,6 +14,7 @@
==============================================================================*/
import {
Directive,
+ DoCheck,
ElementRef,
EventEmitter,
Input,
@@ -98,7 +99,7 @@
standalone: false,
selector: '[cardEdgeResize]',
})
-export class CardEdgeResizeDirective implements OnInit, OnDestroy {
+export class CardEdgeResizeDirective implements OnInit, OnDestroy, DoCheck {
@Input('cardEdgeResize') persistKey = '';
@Output() columnSpanChanged = new EventEmitter<number>();
@@ -108,7 +109,6 @@
private startY = 0;
private startHeight = 0;
private startWidth = 0;
- private startColSpan = 1;
private gridColWidth = 0;
private gridGap = 0;
private gridTotalCols = 1;
@@ -138,7 +138,8 @@
this.el.style.height = `${saved.height}px`;
}
if (saved?.colSpan && saved.colSpan > 1) {
- this.el.style.gridColumn = `span ${saved.colSpan}`;
+ this.snapshotGrid();
+ this.applyColSpan(saved.colSpan);
}
}
this.zone.runOutsideAngular(() => {
@@ -149,6 +150,13 @@
});
}
+ ngDoCheck() {
+ if (this.dragging) return;
+ if (this.el.classList.contains('full-width') && this.el.style.gridColumn) {
+ this.el.style.gridColumn = '';
+ }
+ }
+
ngOnDestroy() {
this.el.removeEventListener('mousemove', this.onHover);
this.el.removeEventListener('mousedown', this.onDown);
@@ -251,7 +259,6 @@
const rect = this.el.getBoundingClientRect();
this.startHeight = rect.height;
this.startWidth = rect.width;
- this.startColSpan = this.getCurrentColSpan();
this.snapshotGrid();
if (edge & ResizeEdge.RIGHT) {This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| } | ||
| if (saved?.colSpan && saved.colSpan > 1) { | ||
| this.el.style.gridColumn = `span ${saved.colSpan}`; | ||
| } |
There was a problem hiding this comment.
Restore path bypasses column count clamping logic
Medium Severity
The ngOnInit restore path sets gridColumn to span ${saved.colSpan} directly, bypassing applyColSpan which clamps the span to the current grid column count (using '1 / -1' when span >= total columns). If a user saves a span of 4 on a wide screen then reopens on a narrower screen with 2 columns, span 4 creates implicit grid columns, breaking the layout with overflow or misaligned cards.
Additional Locations (1)
| } else { | ||
| this.el.style.gridColumn = `span ${span}`; | ||
| } | ||
| } |
There was a problem hiding this comment.
Inline gridColumn style overrides full-width CSS class permanently
Medium Severity
After an edge resize to span > 1, the directive leaves an inline gridColumn style on the element. This permanently overrides the .full-width CSS class (grid-column-start: 1; grid-column-end: -1), breaking the full-size toggle for histogram cards (via cardStateMap.fullWidth) and image cards (via cardsAtFullWidth). The toggle dispatches to the store and adds the CSS class, but the inline style silently wins.
Additional Locations (1)
| private startY = 0; | ||
| private startHeight = 0; | ||
| private startWidth = 0; | ||
| private startColSpan = 1; |
There was a problem hiding this comment.
Field startColSpan is assigned but never read
Low Severity
startColSpan is declared at line 111 and assigned in handleDown at line 254 via getCurrentColSpan(), but is never read anywhere in the directive. This is dead code that adds confusion about whether span-relative logic was intended but forgotten.



Motivation for features / changes
The small resize tabs (browser-native CSS
resize: verticalhandles) in the bottom-right corner of chart containers are very hard to grab, especially on smaller cards. Users have to precisely target a tiny area to resize cards. This PR replaces that mechanism with full edge-based and corner-based resizing directly on the card wrapper, making it much easier to adjust card dimensions.Technical description of changes
New:
CardEdgeResizeDirective(edge_resize_directive.ts)1/-1)grid-column: span NlocalStorage(_tb_card_sizes.v1) and restored on initconst enum ResizeEdgewith bitwise operationsVisual indicators
::afterpseudo-elements on.card-spaceand.card-wrappershow:edge-resizing-widthclass adds a subtle shadow during width drag for visual feedbackRemoved: CSS
resize: verticalandpersistResizeresize: verticalfrom.chart-containerin both scalar and superimposed card SCSS[persistResize]from chart containers (data table containers retain theirs)Integration
[cardEdgeResize]directive applied to.card-spaceincard_grid_component.ng.html(regular cards)[cardEdgeResize]directive applied to.card-wrapperinsuperimposed_cards_view_component.ts(superimposed cards)EdgeResizeModuleimported inMainViewModulecolumnSpanChangedevent handled in bothCardGridComponentandSuperimposedCardsViewComponentBUILDupdated for both widgets and main_view targetsScreenshots of UI changes (or N/A)
N/A (cursor-based interaction — smooth pixel resize during drag, grid-snap on release, subtle edge highlights)
Detailed steps to verify changes work correctly (as executed by you)
ns-resize, subtle bar appearsew-resizenwse-resizeyarn lint)Alternate designs / implementations considered (or N/A)
span Nduring drag. Still jumpy — columns are 335+ px wide, so changes are coarse.