frontend: unify dashboard + agent side panel into shared hive-side-panel
The dashboard's Panel singleton and the per-agent UI's own inline Panel IIFE each had their own near-identical implementation of the right-side slide-in drawer used for file previews, diffs, logs, and inbox/todo lists. Both are now thin wrappers around a new <hive-side-panel> shadow-DOM custom element in @hive/shared, following the same house pattern as <hive-menu>: the element owns and builds all its structural chrome itself (backdrop, drawer, resize handle, header, title, close button) in connectedCallback, and only the caller's opaque content node is projected in via a default <slot> so each package's own content-type-specific CSS keeps reaching it. Public API is the union of both originals: open(title, content), openNamed(name, title, content), refresh(name, title, content), close(), and currentOwner(). Drag-to-resize + localStorage width persistence (ported verbatim from the dashboard's original implementation, the only one of the two that had it) is now available to both consumers by default — a deliberate behavior widening for the agent UI, which didn't have resize before. Along the way, fixed a latent bug in the ported CSS: the resize handle was setting a --side-panel-w custom property that no width rule ever consumed, so dragging never actually resized the drawer even though it looked wired up; the new shared stylesheet's width rule reads it properly. Each package's own global stylesheet keeps its content-specific rules (common.css's .side-panel-body .md, agent.css's .side-panel-body .agent-inbox) exactly where they were — those can never be reached from the shared element's shadow tree, same architectural floor as <hive-menu>'s item-row styling. Each wrapper applies a plain 'side-panel-body' compatibility class to its own <hive-side-panel> instance so those existing selectors keep matching by ordinary light-DOM descendant matching, with the shared element itself having no knowledge of what that class name means. Panel.bind() is gone from both packages' public API — the shared element wires its own listeners in connectedCallback, so there's no bind step left to call. tabs.js's one call site (the only bind() caller in either package) was updated to drop it. The two original chrome CSS blocks disagreed on several purely visual details beyond the resize-handle rules (z-index, backdrop color, drawer border/box-shadow, title typography) — the dashboard's values (the more feature-complete of the two) were kept as canonical, which is a small visible style change for the agent UI's panel chrome (thinner border, no box-shadow, no bold purple title). Flagged for visibility since nothing in the original two implementations called this out explicitly. Verified with a real headless-Chromium/CDP harness (bundled the actual component + built page CSS, served statically, drove via raw CDP) for both usage shapes: open/close, backdrop-click dismiss, Escape dismiss, refresh() owner-matching (no-op on wrong owner, applies on matching owner), and drag-to-resize (drawer width updates live during drag and persists to localStorage on release).
This commit is contained in:
parent
d10eebd455
commit
c5610b075a
10 changed files with 388 additions and 358 deletions
|
|
@ -891,73 +891,24 @@ pre.diff {
|
|||
/* Row + pill + details styling moved to hive-fr0nt::TERMINAL_CSS. */
|
||||
|
||||
/* ─── side panel (singleton drawer) ────────────────────────────────
|
||||
Inbox + loose-ends details open here instead of expanding inline.
|
||||
Copy of the dashboard's side-panel pattern — candidate for
|
||||
extraction into @hive/shared once both surfaces stabilize. */
|
||||
.side-panel {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
/* Closed: ignore pointer events so the agent page underneath stays
|
||||
interactive; `.open` flips it back on. */
|
||||
pointer-events: none;
|
||||
}
|
||||
.side-panel-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.side-panel-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: min(640px, 92vw);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-elev);
|
||||
border-left: 2px solid var(--purple);
|
||||
box-shadow: -10px 0 30px rgba(0, 0, 0, 0.45);
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.side-panel.open { pointer-events: auto; }
|
||||
.side-panel.open .side-panel-backdrop { opacity: 1; }
|
||||
.side-panel.open .side-panel-drawer { transform: translateX(0); }
|
||||
.side-panel-head {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1em;
|
||||
padding: 0.7em 1em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.side-panel-title {
|
||||
color: var(--purple);
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.05em;
|
||||
word-break: break-all;
|
||||
}
|
||||
.side-panel-close {
|
||||
flex: 0 0 auto;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
font-family: inherit;
|
||||
font-size: 1em;
|
||||
line-height: 1;
|
||||
padding: 0.25em 0.55em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.side-panel-close:hover { border-color: var(--red); color: var(--red); }
|
||||
.side-panel-body {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
padding: 0.8em 1em;
|
||||
Inbox + loose-ends details open here instead of expanding inline. The
|
||||
drawer chrome itself (backdrop, drawer, resize handle, header, title,
|
||||
close button) is now the shared `<hive-side-panel>` element
|
||||
(@hive/shared/side-panel.js — this also picks up drag-to-resize,
|
||||
which this page didn't have before); what's left here is the drag
|
||||
cursor-override escape hatch (has to be a global, light-DOM rule — it
|
||||
reaches every element on the page during a drag, not just the shared
|
||||
element's own shadow tree) and the styling for the panel's own
|
||||
content types, which the shared element's shadow tree can never
|
||||
reach (see @hive/shared/side-panel/hive-side-panel.css's header
|
||||
comment). `.side-panel-body` below is the compatibility class
|
||||
app.js's `Panel` wrapper puts directly on its `<hive-side-panel>`
|
||||
instance so these rules keep reaching the slotted content by
|
||||
ordinary light-DOM descendant matching. */
|
||||
body.side-panel-resizing {
|
||||
user-select: none;
|
||||
}
|
||||
body.side-panel-resizing * { cursor: ew-resize !important; }
|
||||
/* Inbox / loose-ends lists rendered into the side-panel body. The
|
||||
legacy <details>-collapsible variant of .agent-inbox is gone, so
|
||||
here we strip the inbox-only chrome (background, border-left) and
|
||||
|
|
|
|||
Loading…
Reference in a new issue