dashboard: full-height inbox + drag-to-resize side panel (#450 #451)

two related flyout improvements — both small, single PR.

#450 — full-height inbox

dropped the .inbox max-height: 24em cap. the side-panel-body
already provides overflow: auto, so the cap was just clamping the
inbox list short of the available panel height on tall viewports.
the inbox now fills as much of the panel as it needs and the
panel itself scrolls.

#451 — drag-to-resize side panel

added a 6px hit-strip on the drawer's left edge. mousedown/move/
up handlers track the drag and update --side-panel-w on the
drawer; the CSS variable defaults to min(760px, 94vw) (preserving
pre-#451 behaviour) and is clamped to [320px, 96vw] so a bad
stored value can never wedge the drawer off-screen.

ergonomics

- handle is invisible at rest, mauve glow on hover + during drag
  so the affordance is discoverable but doesn't compete with the
  2px mauve border-left for the visual boundary.
- body.side-panel-resizing class forces ew-resize cursor + kills
  user-select page-wide so the cursor doesn't flip back to
  default the moment it leaves the 6px band during a fast drag.
- final width persists to localStorage (key
  hyperhive:side-panel-width) so it survives reload. window
  resize re-clamps so a stored width that exceeds the new 96vw
  shrinks accordingly.
- handle is a separator role with aria-orientation: vertical +
  aria-label for screen readers.

files

- frontend/packages/dashboard/src/dashboard.css
  - .inbox: dropped max-height (#450).
  - .side-panel-drawer: width = var(--side-panel-w, min(760px,
    94vw)) + min/max clamp (#451).
  - new .side-panel-resize + body.side-panel-resizing rules.
- frontend/packages/dashboard/src/common.js
  - Panel.bind now also calls applyStoredWidth + bindResize.
  - resize handle is prepended to the drawer at bind time so
    every flyout (inbox, file preview, diff, journal) gets it.

validation

- npm run build --workspace=@hive/dashboard clean. CSS 40.9kb →
  41.3kb. app.js + flow.js grew ~0.5kb each (resize handler).
- browser smoke test isn't possible from inside iris's container;
  the resize math is straightforward (width = innerWidth -
  clientX, clamped) and the CSS variable + localStorage
  persistence are standard patterns.
This commit is contained in:
iris 2026-05-26 01:20:29 +02:00 committed by Mara
commit f4635cc256
2 changed files with 124 additions and 3 deletions

View file

@ -1111,8 +1111,10 @@ summary:hover { color: var(--purple); }
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.5em 0.8em;
max-height: 24em;
overflow-y: auto;
/* #450: no max-height cap let the inbox grow to fill the
side-panel-body which already scrolls (`overflow: auto`). The
pre-#450 24em cap clamped the list well short of the available
panel height even on tall viewports. */
}
.inbox li {
padding: 0.25em 0;
@ -1297,7 +1299,19 @@ footer .banner-thin {
top: 0;
right: 0;
bottom: 0;
width: min(760px, 94vw);
/* #451: width is a CSS variable so the drag handle (added by
Panel.bind) can update it live, and so localStorage-persisted
widths apply on first paint. Default min(760px, 94vw) preserves
the pre-#451 behaviour for operators who never drag. The body
adds `data-spw="<px>"` once persisted; CSS doesn't read it
directly JS sets `--side-panel-w` via inline style on the
drawer. */
width: var(--side-panel-w, min(760px, 94vw));
/* Clamp so a stored width can never push the drawer off-screen
or shrink it past readability. min content width matches the
panel-head's natural width with the close button visible. */
min-width: 320px;
max-width: 96vw;
display: flex;
flex-direction: column;
background: var(--bg-elev);
@ -1309,6 +1323,36 @@ footer .banner-thin {
.side-panel.open { pointer-events: auto; }
.side-panel.open .side-panel-backdrop { opacity: 1; }
.side-panel.open .side-panel-drawer { transform: translateX(0); }
/* #451: drag-to-resize handle on the drawer's left edge. The handle
itself is invisible until hover/drag so it doesn't compete with the
2px mauve `border-left` for the visual boundary. Pointer-cursor
tells the operator the edge is grabbable; the brighter glow during
drag (`body.side-panel-resizing`) is the affordance the eye
tracks. */
.side-panel-resize {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 6px;
margin-left: -3px;
cursor: ew-resize;
z-index: 1;
background: transparent;
transition: background 120ms ease;
}
.side-panel-resize:hover,
body.side-panel-resizing .side-panel-resize {
background: rgba(203, 166, 247, 0.55);
}
/* While dragging: kill text selection + force the resize cursor
page-wide so the cursor doesn't flip back to default the moment it
leaves the 6px handle band. */
body.side-panel-resizing {
cursor: ew-resize !important;
user-select: none;
}
body.side-panel-resizing * { cursor: ew-resize !important; }
.side-panel-head {
flex: 0 0 auto;
display: flex;