UI: resizable sidebar and side panels by dragging the edge (no handle) #66

Open
opened 2026-09-24 15:54:58 +00:00 by kayg · 5 comments
Owner

Owner rule (2026-09-24): "make sure the sidebar is resizable by simple drag and drop (no drag handle required)".

  • The FloatingSidebar (and every other side panel: the Day view inspector, the search preview pane, the Quick Look linked-notes panel, settings nav) resizes by dragging its edge itself: the cursor turns to col-resize on a generous invisible hit zone (≈8 px, straddling the edge), no visible grabber.
  • Live resize at 60 fps (transform/width via rAF, no layout thrash), min/max widths, snap-to-collapse when dragged below the minimum (hides it fully, reopening restores the last width), double-click the edge to reset to the default width.
  • Width is a per-user preference (<home>/.calternal/settings.json, per panel), restored on load with no layout shift.
  • Keyboard and assistive tech: the edge is a focusable role="separator" with aria-orientation="vertical" and aria-valuenow/min/max; ←/→ resize in steps, Home/End for min/max.
  • Touch: long-press-free edge drag on iPad; on phones the sidebar is a sheet and does not resize.
  • calternal.js visual language; no border or handle appears at rest (owner has rejected invented borders).
    Read CLAUDE.md, docs/DESIGN.md §25, §29, §33. Comment on this issue as you work; never close it.
Owner rule (2026-09-24): "make sure the sidebar is resizable by simple drag and drop (no drag handle required)". - The FloatingSidebar (and every other side panel: the Day view inspector, the search preview pane, the Quick Look linked-notes panel, settings nav) resizes by dragging its **edge itself**: the cursor turns to `col-resize` on a generous invisible hit zone (≈8 px, straddling the edge), no visible grabber. - Live resize at 60 fps (transform/width via rAF, no layout thrash), min/max widths, snap-to-collapse when dragged below the minimum (hides it fully, reopening restores the last width), double-click the edge to reset to the default width. - Width is a per-user preference (`<home>/.calternal/settings.json`, per panel), restored on load with no layout shift. - Keyboard and assistive tech: the edge is a focusable `role="separator"` with `aria-orientation="vertical"` and `aria-valuenow/min/max`; ←/→ resize in steps, Home/End for min/max. - Touch: long-press-free edge drag on iPad; on phones the sidebar is a sheet and does not resize. - calternal.js visual language; no border or handle appears at rest (owner has rejected invented borders). Read CLAUDE.md, docs/DESIGN.md §25, §29, §33. Comment on this issue as you work; never close it.
Author
Owner

Started (Claude files UI job). Branch worktree-agent-ac5763a033ac4a951, base afe8c7a. Scope: Files mode UI (Finder-style browser, Shared, Recent, Trash, shares and public links, Quick Look viewer, Sonner upload progress, public link page, resizable sidebar edge). Small Files API additions needed by the UI: item lookup by ID for /f/<item-id> deep links, a people list for picking share recipients, recent files, original path in Trash listings, owner name/size on public link info and public thumbnails for the gallery.

Started (Claude files UI job). Branch `worktree-agent-ac5763a033ac4a951`, base `afe8c7a`. Scope: Files mode UI (Finder-style browser, Shared, Recent, Trash, shares and public links, Quick Look viewer, Sonner upload progress, public link page, resizable sidebar edge). Small Files API additions needed by the UI: item lookup by ID for `/f/<item-id>` deep links, a people list for picking share recipients, recent files, original path in Trash listings, owner name/size on public link info and public thumbnails for the gallery.
Author
Owner

Finished (Claude files UI job). Branch worktree-agent-ac5763a033ac4a951, head d023c06 (base afe8c7a). Not pushed or merged.

Built on the real Files API:

  • Finder-style browser (/files?path=): list and grid remembered per folder, click / ⌘-click / shift-click, rubber-band, arrows + type-to-select, Enter / ⌘↓ open, ⌘↑ parent, Space Quick Look, F2 rename inline, ⌘⌫ / Delete to Trash with Undo, ⌘A, ⌘I info, ⌘⇧N new folder, ⌘⇧C copy link. Virtualized list and grid (only visible rows in the DOM). Context menu; drag to move onto folders, breadcrumbs and the sidebar tree (Undo toast); desktop drop of files and folders uploads through tus; live refresh from /api/v1/files/events.
  • Right info panel (resizable edge): metadata, shares and public links with view counts, versions (owner only: download, restore), linked notes.
  • Share dialog: people (viewer or editor) and public links with every option (custom or random name, view / download / file drop / edit / linked notes, expiry, password, download limit, presentation, hidden names), copy, edit, revoke.
  • Shared (grouped by person, write actions only with an editor grant, plus "Shared by you"), Recent, Trash (put back, empty with confirmation).
  • #35 Quick Look on OverlaySurface: zoom and pan images, pdf.js pages (lazy), native media, highlighted text and code, arrows and swipe, info and linked-notes side panel.
  • #36 upload progress: one Sonner toast in the calternal.js capsule that expands to the per-file list with pause, resume, retry, cancel; survives route changes; full-window drop target.
  • #37 public link page /s/<slug>: no chrome, password screen, file card, gallery for photo folders, list, file drop with progress.
  • #66 resizableEdge action in packages/ui, applied to the sidebar, the info panel and the Quick Look panel.
  • Touch: long-press selection mode with a bottom action bar, swipe left to copy the link, swipe right to select, action and info sheets.
  • Deep links /f/<item-id> (folder opens, file opens in Quick Look; missing items get one answer).

Small API additions in crates/plugins/files (with a test and adversarial probes): GET /files/items/{id}, /files/people, /files/recent, original_path on Trash entries, owner name / kind / size on public link info, GET /public/{slug}/thumb.

Gates: bun run check 0 errors, 0 warnings; bun run test 91 passed; bun run build done; check-generated.sh clean; cargo fmt --check, cargo clippy -p calternal-plugin-files --all-targets -D warnings, cargo test -p calternal-plugin-files 40 passed; bun e2e/files.mjs FILES E2E PASSED.

Known gaps: Markdown in Quick Look shows highlighted source (the editor package fails the web app's strict type check); linked notes have a "New note" button, not an inline editor yet; widths are stored in browser storage until settings.json lands; a 10k-entry folder is limited by the server listing (each page re-lists the directory); view-only public links cannot preview file bytes (the public download route needs the download permission); hidden file names break subfolder browsing on public links.

Finished (Claude files UI job). Branch `worktree-agent-ac5763a033ac4a951`, head `d023c06` (base `afe8c7a`). Not pushed or merged. Built on the real Files API: - Finder-style browser (`/files?path=`): list and grid remembered per folder, click / ⌘-click / shift-click, rubber-band, arrows + type-to-select, Enter / ⌘↓ open, ⌘↑ parent, Space Quick Look, F2 rename inline, ⌘⌫ / Delete to Trash with Undo, ⌘A, ⌘I info, ⌘⇧N new folder, ⌘⇧C copy link. Virtualized list and grid (only visible rows in the DOM). Context menu; drag to move onto folders, breadcrumbs and the sidebar tree (Undo toast); desktop drop of files and folders uploads through tus; live refresh from `/api/v1/files/events`. - Right info panel (resizable edge): metadata, shares and public links with view counts, versions (owner only: download, restore), linked notes. - Share dialog: people (viewer or editor) and public links with every option (custom or random name, view / download / file drop / edit / linked notes, expiry, password, download limit, presentation, hidden names), copy, edit, revoke. - Shared (grouped by person, write actions only with an editor grant, plus "Shared by you"), Recent, Trash (put back, empty with confirmation). - #35 Quick Look on OverlaySurface: zoom and pan images, pdf.js pages (lazy), native media, highlighted text and code, arrows and swipe, info and linked-notes side panel. - #36 upload progress: one Sonner toast in the calternal.js capsule that expands to the per-file list with pause, resume, retry, cancel; survives route changes; full-window drop target. - #37 public link page `/s/<slug>`: no chrome, password screen, file card, gallery for photo folders, list, file drop with progress. - #66 `resizableEdge` action in packages/ui, applied to the sidebar, the info panel and the Quick Look panel. - Touch: long-press selection mode with a bottom action bar, swipe left to copy the link, swipe right to select, action and info sheets. - Deep links `/f/<item-id>` (folder opens, file opens in Quick Look; missing items get one answer). Small API additions in crates/plugins/files (with a test and adversarial probes): `GET /files/items/{id}`, `/files/people`, `/files/recent`, `original_path` on Trash entries, owner name / kind / size on public link info, `GET /public/{slug}/thumb`. Gates: `bun run check` 0 errors, 0 warnings; `bun run test` 91 passed; `bun run build` done; `check-generated.sh` clean; `cargo fmt --check`, `cargo clippy -p calternal-plugin-files --all-targets -D warnings`, `cargo test -p calternal-plugin-files` 40 passed; `bun e2e/files.mjs` FILES E2E PASSED. Known gaps: Markdown in Quick Look shows highlighted source (the editor package fails the web app's strict type check); linked notes have a "New note" button, not an inline editor yet; widths are stored in browser storage until settings.json lands; a 10k-entry folder is limited by the server listing (each page re-lists the directory); view-only public links cannot preview file bytes (the public download route needs the download permission); hidden file names break subfolder browsing on public links.
Author
Owner

Hygiene review: the resize report says widths are still stored in browser storage, not persisted per User. Keeping #66 open for the per-User persistence requirement.

Hygiene review: the resize report says widths are still stored in browser storage, not persisted per User. Keeping #66 open for the per-User persistence requirement.
Author
Owner

Evidence from Chrome audit #862 (2026-10-02):

  • packages/ui/src/actions/resizableEdge.ts:53-71,130-134 reads and writes localStorage keys named calternal.panel.<id>, without a User ID.
  • apps/web/src/lib/components/app-sidebar.svelte:458-467, apps/web/src/lib/components/search-dialog.svelte:962-974 and apps/web/src/lib/ai/TurnPanel.svelte:129-137 use this helper.
  • apps/web/src/lib/userStorage.ts:44-48,163-167 clears unscoped legacy calternal.* keys at session end. It does not preserve calternal.panel.*.
  • The action comment at packages/ui/src/actions/resizableEdge.ts:19-23 says the per-User settings file will take these keys after a future settings-store change.

The shared helper therefore does not keep separate panel widths per User or restore them after sign-in.

Evidence from Chrome audit #862 (2026-10-02): - `packages/ui/src/actions/resizableEdge.ts:53-71,130-134` reads and writes `localStorage` keys named `calternal.panel.<id>`, without a User ID. - `apps/web/src/lib/components/app-sidebar.svelte:458-467`, `apps/web/src/lib/components/search-dialog.svelte:962-974` and `apps/web/src/lib/ai/TurnPanel.svelte:129-137` use this helper. - `apps/web/src/lib/userStorage.ts:44-48,163-167` clears unscoped legacy `calternal.*` keys at session end. It does not preserve `calternal.panel.*`. - The action comment at `packages/ui/src/actions/resizableEdge.ts:19-23` says the per-User settings file will take these keys after a future settings-store change. The shared helper therefore does not keep separate panel widths per User or restore them after sign-in.
Author
Owner

Reuse finding for this issue: the repository has two live edge-resize actions. packages/ui/src/actions/resizableEdge.ts::resizableEdge is used by the App sidebar, Search preview and AI panel; apps/web/src/lib/actions/edgeResize.ts::edgeResize is used by Settings navigation. Both implement pointer capture, animation-frame width updates, keyboard resizing and a separator role, but they use different storage, collapse and reset rules. resizableEdge writes to localStorage keys; edgeResize writes through userStorage. Search for resizableEdge and edgeResize found no more specific open issue beyond #66. Recommend one shared action for every panel, with the per-User settings store and per-panel options, so behavior and width persistence stay consistent.

Reuse finding for this issue: the repository has two live edge-resize actions. packages/ui/src/actions/resizableEdge.ts::resizableEdge is used by the App sidebar, Search preview and AI panel; apps/web/src/lib/actions/edgeResize.ts::edgeResize is used by Settings navigation. Both implement pointer capture, animation-frame width updates, keyboard resizing and a separator role, but they use different storage, collapse and reset rules. resizableEdge writes to localStorage keys; edgeResize writes through userStorage. Search for resizableEdge and edgeResize found no more specific open issue beyond #66. Recommend one shared action for every panel, with the per-User settings store and per-panel options, so behavior and width persistence stay consistent.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#66
No description provided.