summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/ui/HomeTileGrid.svelte
AgeCommit message (Collapse)Author
11 daysfeat(ui): raised (popped-out) home tiles, the inverse of the inset well; ↵Adam Malczewski
shared navbar look on the home header
2026-09-07fix(workspaces): mobile (tall) grid can't scroll — root height collapse + ↵Adam Malczewski
touch capture Two blockers for scrolling the tall grid on a phone: 1. HomeTileGrid's root was `flex-1` inside a BLOCK parent (the workspace region), so flex-1 did nothing and the root sized to its content; the scroller's `h-full` collapsed to auto and overflow-y-auto never engaged — extra tiles were clipped by the shell's `overflow-hidden` with no way to scroll. The root is now `h-full`, so the scroller gets a definite height and tall mode scrolls (wide mode was unaffected: horizontal overflow scrolls regardless of container height). 2. `setPointerCapture` was called for TOUCH pointers at pointerdown, which can suppress native panning in WebKit. Touch now skips explicit capture: the browser's implicit capture already retargets the press, and after the long-press lift the moved events bubble up to the grid container where the handlers live — scroll gestures keep working and touch drags are intact. Verification: svelte-check 0 errors/0 warnings; vitest 1694/1694; build ok. Needs a human on a real phone: swipe the tall grid when tiles overflow — it must scroll; long-press-and-drag a tile must still reorder.
2026-09-07feat(workspaces): content-driven canvas while dragging — scrollbar ↵Adam Malczewski
grows/shrinks with the gesture Revert the fixed append-canvas reservation (9ede1f0): pre-reserving the 3 spaces at drag start made the view jump right and created far too much free canvas. The grid is back to hugging its content (width: max-content / auto), so the preview extends the canvas as the tile reaches toward the edge and it shrinks back when the gesture ends — the scrollbar moves with the drag, as intended. The drag-target clamp (dragAppendLimit, +3 past the committed edge) still bounds how far the preview can extend, so the "endless growth" problem stays solved without reserving anything up front. Dots restore the one-pitch padding extension for the append slot; the scroll-measure effect still re-runs on drag start/end (the canvas shrinks back on cancel). Test reverted to the content-driven assertions. Verification: svelte-check 0 errors/0 warnings; vitest 1693/1693; biome clean on touched files; build ok.
2026-09-07feat(workspaces): reserve the append canvas for the whole drag — no ↵Adam Malczewski
scrollbar change mid-drag Previously the grid kept `width: max-content` during a drag, so the canvas grew/shrunk as the preview placed the tile at each hovered cell — the scrollbar resized mid-drag, making placement harder. Now the moment a drag starts the grid's growth axis becomes a FIXED reservation: the append limit (committed content edge + 3, span-aware) computed at drag start, so the 3 spaces "pop in" immediately, the scroller size is constant for the entire gesture, and the unused reservation disappears when the drag ends (the canvas returns to max-content / auto). The dragged tile's span room is included so its body always stays inside the reserved canvas. Also: the scroll-measure effect now tracks `dragging`, so canPan and the scroll indicator re-measure when the reservation appears and disappears. Test: the drag test now asserts the grid's style is a fixed px width while dragging (never max-content) and back to max-content after the drop. (This caught a missing `px` unit that would have silently voided the reservation.) Update the handoff bullet to describe the reservation. Verification: svelte-check 0 errors/0 warnings; vitest 1693/1693; biome clean on touched files; build ok.
2026-09-07feat(workspaces): bound the drag append area — 3 columns past the content edgeAdam Malczewski
Dragging a tile far right grew the wide canvas endlessly (the target cell resolved from the pointer with no upper bound), making precise placement impossible. Add a pure `dragAppendLimit(placements, geometry, extra = 3)`: the drag target may aim at most 3 columns (wide) / rows (tall) past the current span-aware content edge. The bound is measured on the COMMITTED layout — never the live preview — so it can't ratchet mid-drag; it steps forward only when a drop commits new content. cellAt applies it on the growth axis (wide: col, tall: row); the fixed cross-axis clamps are unchanged. Exported via the unit index. Verification: svelte-check 0 errors/0 warnings; vitest 1693/1693 (4 new dragAppendLimit tests); biome clean on touched files; build ok.
2026-09-07feat(workspaces): FLIP reflow eases OUT — fast start, decelerating arrivalAdam Malczewski
The tile-organization animation used power2.inOut (eases INTO the motion too). Switch to power3.out so the sweep starts fast and settles slowly into the destination — the Windows-8-style snap-to-place feel. Verification: svelte-check 0 errors/0 warnings; vitest 1689/1689; build ok.
2026-09-07fix(workspaces): tile-grid drag bugs — stuck drop, grab-offset follow, ↵Adam Malczewski
empty-cell hover Three drag-interaction bugs on the Win8 tile home, one shared root cause (capture loss from DOM reordering) plus two wiring defects: - DROP NEVER COMMITS (stuck tile): displacePlace returned the displacement preview SORTED BY STREAM ORDER, so crossing stream positions moved the dragged tile's DOM node in the keyed {#each} and the browser dropped its pointer capture — pointerup never landed, phase stayed "dragging", the follow transform never cleared. displacePlace now preserves the INPUT render order (coordinates change only); placeAtCell re-sorts to stream order itself, so the commit's order-renumbering semantics are unchanged. - FOLLOW TRANSFORM OFFSET: the follow translate was raw pointer displacement, so a grab away from the tile's top-left made the tile jump (its ORIGIN rode the pointer). gridPointerDown records the grab offset (pointer minus the tile's rect) and followStyle cancels the tile's true grid anchor (targetCell origin), so the GRAB POINT tracks the pointer. - CAN'T HOVER EMPTY CELLS: pointer handling moved from per-tile elements to the GRID CONTAINER, which captures the pointer and is never re-parented, never pointer-events:none, never unmounted — move/up stream to it over empty cells and off-canvas, and capture can never be lost to a DOM move. Tests updated: the four order-sensitive displacePlace assertions now expect input render order (all coordinates unchanged — the plan's stream examples still hold); HomeTileGrid drag tests still pass via event bubbling. Verification: svelte-check 0 errors/0 warnings; vitest 1689/1689; biome clean on touched files; vite build succeeds. Pointer capture is browser-only — human check at the page: (1) drag a tile across columns, release — it must drop, not stay stuck; (2) grab a tile by its corner/center — the grabbed point must stay under the cursor; (3) hover/drag over empty cells past the last tile — the ghost + drop must keep tracking.
2026-09-07feat(workspaces): pinned tiles with displacement reflow + FLIP drag previewAdam Malczewski
Tiles can now be dropped onto specific cells; empty cells are real estate that persists (notes/pinned-tiles-plan.md). Data (v2): each tile carries an optional pinned anchor in a UNIVERSAL 4-row frame (tall folds its 2 columns onto frame column 1; a wide col>1 pin reads as unpinned in tall). v1 documents migrate in memory to all-unpinned (render-identical) and write back as v2 on the next change. sanitizeLayout drops garbage anchors. Packing: pinned-first (first-wins on overlap, one-ply), then unpinned tiles first-fit into the REMAINING cells — holes between pins count and never compact. displacePlace: the user-specified semantics — the dragged tile leaves a gap at its origin (nothing slides backward), anchors at the drop cell, and the contiguous filled run pushes FORWARD until the first empty cell absorbs it (X_X + Y@1 → YXX; _XZ_ + Y@2 → _YXZ). placeAtCell commits the displacement and pins every tile; pinAtFirstEmpty pins the create-flow tile at the first truly-empty slot; sortByRecentActivity now also clears pins (an explicit arrangement reset — the menu item says so). Drag UX: pointer → cell hit-math (empty cells are drop targets); a faint dot marks every cell center during a drag (CSS radial-gradient overlay, one extra pitch of end padding for the append slot); the dragged tile element itself follows the pointer (lifted via transform, pointer-events none); a dashed tone-colored ghost marks the landing footprint. The preview recomputes from the COMMITTED layout on every hover-cell change (as if the user moved straight from the original to there) while GSAP Flip animates from wherever the tiles currently are (re-entrant — mid-flight positions captured). Escape/pointercancel discards the preview and flies the tile home; a drop settles it onto its new cell. Tests: displacement (both user examples + internal moves + spans + tall stream), pinned pack (anchors, gaps kept, first-wins, universal-frame mapping, deleted-leaves-holes), v1→v2 migration + anchor sanitization, store placeAtCell/no-op write guard, Escape-cancels-drag component test. 1689 green.
2026-09-07feat(workspaces): rework tile-grid visuals — Start-screen polishAdam Malczewski
The home canvas now reads as a real Windows 8 Start screen instead of a cramped, edge-hugging list: - Geometry: the wide unit is DERIVED from the canvas height (4 rows fill it: unit=height/5 clamped to 112-184px, gap=unit/9, margin=unit/3 clamped 24-64px) — tiles are no longer tiny, and the tile field always carries a proportional outer margin (never compressed against the left edge). Tall mode keeps fluid columns with a taller 148px row + 12px gap. - Tiles take a per-workspace SATURATED tone (new pure tileTone(id): FNV-1a hash → one of the six theme-semantic colors) with tone-content text — the Win8 colored-tile identity. Starred is now a ★ badge (a tile keeps its own color), not a gray-tint override. - The tile face is the Win8 layout: small uppercase slug caption at the top, a bold span-aware title (lg → 3xl across 1×1 → 4×2), and a single-line stat row pinned to the bottom (count compacts to 'conv.' on 1×1 tiles, truncation-safe so narrow tiles never clip). - Selection is a high-contrast offset ring (visible on any tile color); tiles lift on hover; the pan arrow is a solid elevated circle. - Header gains a 'WORKSPACES' kicker; empty/loading states are centered and styled for the Start-screen canvas. Persisted data (order + spans only) and all interaction semantics (drag/FLIP, long-press, action bar, resize picker, create popover) are untouched. Tests: geometry derivation/clamping, tileTone determinism, wide-vs-compact count label — 1664 green.
2026-09-07feat(workspaces): Win8 Start-style tile-grid home pageAdam Malczewski
Rebuild the workspace home as a Windows 8 Start-screen-style tile grid, replacing the max-w-3xl vertical card list. Pure core (zero DOM/Svelte/fetch, fully unit-tested): - logic/tile-layout.ts — geometryFor(width, height) resolves WIDE (4 fixed ~120px rows, column-wise flow, 4x2 span cap) or TALL (2 fixed columns, row-wise flow, 2x2 cap); pack() is the ONLY order->coordinates transform (greedy first-fit in fill order, hole-free, render-clamped per geometry); firstEmptySlot/insertAtFirstEmptySlot/mergeLayout/resizeTile/moveTile/ sortByRecentActivity/sanitizeLayout. Persisted data is orientation-independent {version:1, tiles:[{id,colSpan,rowSpan,order}]} — order + spans only, never positions; the same data renders in either geometry (spans clamp for the render, persisted values untouched). - logic/drag-controller.ts — pure pointer/press state machine: mouse threshold-drag + tap-open; touch long-press lifts + selects, pre-lift movement cancels so vertical scrolling never fights the gesture. Shell: - tile-layout-store.svelte.ts — thin reactive wrapper persisting through the injected createLocalStore adapter (dispatch.workspaces.tiles), ops: sync/resize/move/placeFirstEmpty/sortByRecentActivity. - ui/HomeTileGrid.svelte — CSS-grid tile canvas (wide: horizontal scroll + snap + wheel pan + pan arrow + scroll indicator; tall: native vertical scroll), FLIP-animated reflow, drag ghost, transient resize preview. - ui/HomeHeader.svelte — fixed chrome: Dispatch display title, New workspace popover (same slug validation; creates via create-on-miss and does NOT navigate — the new tile lands in the first empty slot in fill order and enters rename mode), global Plugins button, sort-by-recent overflow; compact icon buttons + bottom sheet on tall screens. - ui/WorkspaceTile.svelte — tile face (title, /slug, conversation count, relative last-activity, live active dots, starred tint); inline rename with the old card's semantics. - ui/TileActionBar.svelte — slides up on select (right-click / long-press): Open, Rename, Resize, Star/Unstar, Details, Plugins, Delete (old confirm + store call preserved). - ui/ResizePicker.svelte — Word-style insert-table hover grid (wide 4x2 / tall 2x2) with arrow/Enter/Escape support and ghost preview on commit. - ui/WorkspaceDetails.svelte — flyout holding the old card's default-cwd input + default-computer select (same store calls + error states). - WorkspacesHome.svelte orchestrates; App.svelte untouched (same props). - WorkspaceCard.svelte is removed (replaced by the tile system). Manual tile order rules; starred workspaces get a tint (star is no longer a sort key); workspaces unknown to the layout append at the end. vitest: jsdom gains PointerEvent (MouseEvent-based) + Element.animate stubs — the pointer logic is the outermost edge for the drag tests. Verification: svelte-check 0/0, vitest 1655 passed (102 files, twice), biome clean, vite build OK.