diff --git a/frontend/src/lib/components/blockshell/BlockShell.svelte b/frontend/src/lib/components/blockshell/BlockShell.svelte
index 934f53f..41e18b4 100644
--- a/frontend/src/lib/components/blockshell/BlockShell.svelte
+++ b/frontend/src/lib/components/blockshell/BlockShell.svelte
@@ -40,6 +40,8 @@
receiver?: BlockReceiver;
/** Whether to show close button */
closable?: boolean;
+ /** Whether panel is minimized (collapsed to header only) */
+ minimized?: boolean;
/** Extra CSS class */
class?: string;
/** Panel content */
@@ -54,12 +56,14 @@
constraints = DEFAULT_CONSTRAINTS,
receiver,
closable = true,
+ minimized = false,
class: extraClass = '',
children,
onClose,
onDragMove,
onResize,
onFullscreenChange,
+ onMinimizeChange,
onDrop,
}: Props = $props();
@@ -127,6 +131,15 @@
onFullscreenChange?.(isFullscreen);
}
+ // --- Minimize toggle (double-click header) ---
+ let hasDragged = false;
+
+ function handleHeaderDblClick() {
+ if (hasDragged) return;
+ if (isFullscreen) return;
+ onMinimizeChange?.(!minimized);
+ }
+
// --- Drag (repositioning via header) ---
function handleDragStart(e: PointerEvent) {
// Only left button, not on buttons
@@ -134,6 +147,7 @@
if ((e.target as HTMLElement).closest('button')) return;
isDragging = true;
+ hasDragged = false;
dragStartX = e.clientX;
dragStartY = e.clientY;
@@ -146,6 +160,9 @@
if (!isDragging) return;
const dx = e.clientX - dragStartX;
const dy = e.clientY - dragStartY;
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
+ hasDragged = true;
+ }
dragStartX = e.clientX;
dragStartY = e.clientY;
onDragMove?.(dx, dy);
@@ -289,12 +306,13 @@
bind:this={containerEl}
class="blockshell {extraClass}"
class:blockshell-fullscreen={isFullscreen}
+ class:blockshell-minimized={minimized && !isFullscreen}
class:blockshell-dragging={isDragging}
class:blockshell-resizing={isResizing}
class:blockshell-drop-compatible={dropZoneState === 'compatible'}
class:blockshell-drop-incompatible={dropZoneState === 'incompatible'}
style:width={isFullscreen ? undefined : `${clampedWidth}px`}
- style:height={isFullscreen ? undefined : `${clampedHeight}px`}
+ style:height={isFullscreen ? undefined : minimized ? undefined : `${clampedHeight}px`}
ondragenter={handleDragEnter}
ondragover={handleDragOver}
ondragleave={handleDragLeave}
@@ -309,6 +327,7 @@
onpointerdown={handleDragStart}
onpointermove={handleDragMove}
onpointerup={handleDragEnd}
+ ondblclick={handleHeaderDblClick}
>
+ {#if !isFullscreen}
+
+ {/if}
-
-
- {#if children}
- {@render children()}
- {:else}
-
Ingen innhold ennå.
- {/if}
-
+
+ {#if !minimized || isFullscreen}
+
+ {#if children}
+ {@render children()}
+ {:else}
+
Ingen innhold ennå.
+ {/if}
+
+ {/if}
{#if dropZoneState !== 'idle'}
@@ -361,8 +392,8 @@
{/if}
-
- {#if !isFullscreen && !isMobile}
+
+ {#if !isFullscreen && !isMobile && !minimized}
{#each resizeDirections as dir}
void;
/** Panel fullscreen state changed */
onFullscreenChange?: (isFullscreen: boolean) => void;
+ /** Panel minimize state changed (double-click header to toggle) */
+ onMinimizeChange?: (isMinimized: boolean) => void;
/** Drop received on this panel */
onDrop?: (payload: DragPayload) => void;
}
diff --git a/frontend/src/lib/workspace/types.ts b/frontend/src/lib/workspace/types.ts
index efef069..e1d5143 100644
--- a/frontend/src/lib/workspace/types.ts
+++ b/frontend/src/lib/workspace/types.ts
@@ -9,6 +9,9 @@
* Ref: docs/retninger/arbeidsflaten.md § "Tre lag"
*/
+/** Height of the BlockShell header — used as minimized panel height */
+export const PANEL_HEADER_HEIGHT = 36;
+
/** Position and size of a single panel on the canvas */
export interface PanelLayout {
/** Trait name this panel represents */
@@ -21,6 +24,8 @@ export interface PanelLayout {
width: number;
/** Panel height */
height: number;
+ /** Whether panel is minimized (collapsed to header only) */
+ minimized?: boolean;
}
/** Full workspace layout state */
diff --git a/frontend/src/routes/collection/[id]/+page.svelte b/frontend/src/routes/collection/[id]/+page.svelte
index 6537db3..7e39ec8 100644
--- a/frontend/src/routes/collection/[id]/+page.svelte
+++ b/frontend/src/routes/collection/[id]/+page.svelte
@@ -16,6 +16,7 @@
getPanelInfo,
generateDefaultLayout,
resolveLayout,
+ PANEL_HEADER_HEIGHT,
} from '$lib/workspace/types.js';
// Context header
@@ -139,7 +140,7 @@
x: p.x,
y: p.y,
width: p.width,
- height: p.height,
+ height: p.minimized ? PANEL_HEADER_HEIGHT : p.height,
}))
);
@@ -197,6 +198,16 @@
persistLayout();
}
+ /** Handle panel minimize/restore toggle */
+ function handlePanelMinimize(trait: string, isMinimized: boolean) {
+ const idx = layout.panels.findIndex(p => p.trait === trait);
+ if (idx >= 0) {
+ layout.panels[idx] = { ...layout.panels[idx], minimized: isMinimized };
+ layout = { ...layout };
+ persistLayout();
+ }
+ }
+
/** Handle adding a new panel from the tool menu */
function handleAddPanel(trait: string) {
// Don't add duplicate panels
@@ -369,8 +380,10 @@
icon={info.icon}
width={panel?.width ?? obj.width}
height={panel?.height ?? obj.height}
+ minimized={panel?.minimized ?? false}
onResize={(w, h) => handlePanelResize(trait, w, h)}
onClose={() => handlePanelClose(trait)}
+ onMinimizeChange={(m) => handlePanelMinimize(trait, m)}
>
{#if knownTraits.has(trait)}
{#if trait === 'editor'}
diff --git a/tasks.md b/tasks.md
index 0d31437..5d9d245 100644
--- a/tasks.md
+++ b/tasks.md
@@ -216,8 +216,7 @@ Ref: `docs/retninger/arbeidsflaten.md`, `docs/features/canvas_primitiv.md`
- [x] 19.2 BlockShell wrapper-komponent: header med tittel + fullskjerm/resize/lukk-knapper, drag-handles for repositionering, resize-handles, drop-sone rendering (highlight ved drag-over). Responsivt (min-size, max-size).
- [x] 19.3 Arbeidsflaten layout: skriv om `/collection/[id]` fra vertikal stack til Canvas + BlockShell. Last brukerens lagrede arrangement eller bruk defaults fra samlingens traits. Persist arrangement i bruker-edge metadata. Desktop: spatial canvas, mobil: stacked/tabs. Ref: `docs/retninger/arbeidsflaten.md` § "Tre lag".
- [x] 19.4 Kontekst-header: header tilhører flaten, viser gjeldende node som nedtrekksmeny/kontekst-velger. Mest brukte noder øverst (frekvens/recency), søkbart. Verktøymeny for å instansiere nye paneler. Ref: `docs/retninger/arbeidsflaten.md` § "Kontekst-header".
-- [~] 19.5 Snarveier: paneler kan minimeres til kompakt ikon/fane. Dobbeltklikk → minimer/gjenopprett. Bevarer posisjon og størrelse. Ref: `docs/retninger/arbeidsflaten.md` § "Snarveier".
- > Påbegynt: 2026-03-18T07:39
+- [x] 19.5 Snarveier: paneler kan minimeres til kompakt ikon/fane. Dobbeltklikk → minimer/gjenopprett. Bevarer posisjon og størrelse. Ref: `docs/retninger/arbeidsflaten.md` § "Snarveier".
- [ ] 19.6 Personlig flate: brukerens standard arbeidsflate (node_kind: 'workspace'). Vises når ikke koblet til en annen node. Persistent layout.
## Fase 20: Universell overføring + panelrework