Content jumps when the sidebar opens although it fits in the left margin #1138

Open
opened 2026-10-05 11:44:44 +00:00 by kayg · 6 comments
Owner

Owner report (2026-10-05, production, desktop)

"Since there is already a lot of space at the left, why does the content move when the sidebar is opened?"

On /files at a wide desktop window, the content column (title "Files", the file list) jumps right when the floating sidebar opens, although the sidebar fits fully in the empty left margin. Cause (expected): the content is centred in the space beside the sidebar when open and in the full width when closed.

Expected

  • The content column stays exactly where it is when the sidebar opens or closes, as long as the sidebar (its right edge plus the standard gap token) does not overlap the content column.
  • When it would overlap, the content moves right only by the overlap amount (min shift), never by re-centring. At narrow widths the existing behaviour stays.
  • Same rule on every Tab (Calendar, Tasks, Notes, Files, Photos, Mail, Money, Settings) and for sidebar width changes while dragging its edge: no jump, the content moves only when the edge actually pushes it.
  • Motion: when a shift is needed, it uses the shared panel spring, same duration for keyboard (Cmd/Ctrl+) and pointer. Reduced motion: no animation.
  • Use tokens/CSS (e.g. padding-inline-start = max(0, sidebar-right + gap − centred-left)) without per-frame JS layout reads.

Also verify (#1108)

The vertical divider at the sidebar edge and the hairline under the header were very likely the removed boot skeleton (54c658489). Confirm in screenshots that no line is drawn at the sidebar/content boundary at rest, on edge hover and mid-drag; fix any remaining one at its source.

Evidence

e2e: at 1920, 1440 and 1024 widths measure the content column's left edge with sidebar open vs closed; assert equal at 1920/1440 (when it fits) and equal-to-overlap at 1024. Screenshots open/closed at each width, light + dark.

## Owner report (2026-10-05, production, desktop) "Since there is already a lot of space at the left, why does the content move when the sidebar is opened?" On /files at a wide desktop window, the content column (title "Files", the file list) jumps right when the floating sidebar opens, although the sidebar fits fully in the empty left margin. Cause (expected): the content is centred in the space beside the sidebar when open and in the full width when closed. ## Expected - The content column stays exactly where it is when the sidebar opens or closes, as long as the sidebar (its right edge plus the standard gap token) does not overlap the content column. - When it would overlap, the content moves right only by the overlap amount (min shift), never by re-centring. At narrow widths the existing behaviour stays. - Same rule on every Tab (Calendar, Tasks, Notes, Files, Photos, Mail, Money, Settings) and for sidebar width changes while dragging its edge: no jump, the content moves only when the edge actually pushes it. - Motion: when a shift is needed, it uses the shared panel spring, same duration for keyboard (Cmd/Ctrl+\) and pointer. Reduced motion: no animation. - Use tokens/CSS (e.g. padding-inline-start = max(0, sidebar-right + gap − centred-left)) without per-frame JS layout reads. ## Also verify (#1108) The vertical divider at the sidebar edge and the hairline under the header were very likely the removed boot skeleton (54c658489). Confirm in screenshots that no line is drawn at the sidebar/content boundary at rest, on edge hover and mid-drag; fix any remaining one at its source. ## Evidence e2e: at 1920, 1440 and 1024 widths measure the content column's left edge with sidebar open vs closed; assert equal at 1920/1440 (when it fits) and equal-to-overlap at 1024. Screenshots open/closed at each width, light + dark.
Author
Owner

Also seen in the sidebar job's Files screenshots (fix while you are in this layout)

  • Files loading state shows "Loading 0 items…" and "0 items" at the same time. While loading, show no count (or the cached count); "0 items" only once the folder really is empty.
  • With the sidebar collapsed at 1440, the Files "Name" column header and folder rows start under the sidebar toggle/title column (x≈155–180) while the header toolbar is right-aligned. The list's leading edge must match the page title's leading edge, collapsed and open.
## Also seen in the sidebar job's Files screenshots (fix while you are in this layout) - Files loading state shows "Loading 0 items…" and "0 items" at the same time. While loading, show no count (or the cached count); "0 items" only once the folder really is empty. - With the sidebar collapsed at 1440, the Files "Name" column header and folder rows start under the sidebar toggle/title column (x≈155–180) while the header toolbar is right-aligned. The list's leading edge must match the page title's leading edge, collapsed and open.
Author
Owner

Started work on job/shift-1138, based on origin/dev at 04afb0043c559c59084e4ec12609bb9fe679cbf6 (worktree HEAD a0311a31c50fbf0332d41cff52e45a7b6f9b2e1b). I am checking the shared shell layout, Files loading/count rendering, and sidebar edge/header rules for #1138 and #1108.

Started work on `job/shift-1138`, based on `origin/dev` at `04afb0043c559c59084e4ec12609bb9fe679cbf6` (worktree HEAD `a0311a31c50fbf0332d41cff52e45a7b6f9b2e1b`). I am checking the shared shell layout, Files loading/count rendering, and sidebar edge/header rules for #1138 and #1108.
Author
Owner

Production browser evidence for #1138: the first layout sweep showed that the route-content padding shorthand later in layout.css overrode the new overlap inset. The inset now lives in the shared padding calculation. The rerun passes all eight Tabs at 1440 px (Files closed/open content edge: 137.60/286.80 px) and checks the exact sidebar-plus-gap overlap during resize at 1024 px. The #1108 checks find no content-facing sidebar border at rest, hover or mid-drag, and no header bottom border or shadow. The screenshot matrix is in artifacts/sidebar-content-shift-1138/; screenshots use the production build and macOS platform emulation.

Production browser evidence for #1138: the first layout sweep showed that the route-content padding shorthand later in layout.css overrode the new overlap inset. The inset now lives in the shared padding calculation. The rerun passes all eight Tabs at 1440 px (Files closed/open content edge: 137.60/286.80 px) and checks the exact sidebar-plus-gap overlap during resize at 1024 px. The #1108 checks find no content-facing sidebar border at rest, hover or mid-drag, and no header bottom border or shadow. The screenshot matrix is in artifacts/sidebar-content-shift-1138/; screenshots use the production build and macOS platform emulation.
Author
Owner

Post-merge gate findings at b98dedd917:

  • bun run check stops in perf-lint. The merged origin/dev exception ledger has 21,977 entries while its ratchet total is 21,964. The error lists 13 over-limit exception categories. I kept the origin/dev ledger side of the merge because this branch made no performance-ledger edits.
  • The next check, scripts/check-user-storage.mjs, reports four direct sessionStorage accesses in apps/web/src/lib/navigation/clientRouteStatus.svelte.ts, which is unchanged from origin/dev. I left both findings outside this Files/sidebar fix.

The remaining token checks pass, direct Svelte diagnostics report 0 errors and 4 warnings, and the focused Vitest and production e2e pass.

Post-merge gate findings at b98dedd9174c631d3662b8eafa75ba08f42ea482: - `bun run check` stops in perf-lint. The merged `origin/dev` exception ledger has 21,977 entries while its ratchet total is 21,964. The error lists 13 over-limit exception categories. I kept the origin/dev ledger side of the merge because this branch made no performance-ledger edits. - The next check, `scripts/check-user-storage.mjs`, reports four direct `sessionStorage` accesses in `apps/web/src/lib/navigation/clientRouteStatus.svelte.ts`, which is unchanged from `origin/dev`. I left both findings outside this Files/sidebar fix. The remaining token checks pass, direct Svelte diagnostics report 0 errors and 4 warnings, and the focused Vitest and production e2e pass.
Author
Owner

#1138 finished

Implemented and committed the sidebar/content overlap fix and Files loading-count fix. Head: b98dedd9174c631d3662b8eafa75ba08f42ea482.

What changed

  • Made the app main area span the shell columns and kept route content viewport-centered. CSS tokens now add only the amount the open sidebar edge plus gap overlaps that centered content. Drag resizing uses the same calculation; no per-frame JS layout reads were added.
  • Kept the shared spring for pointer and keyboard changes and disabled it for reduced motion and active resize.
  • Removed the sidebar-edge divider and header bottom hairline at their sources.
  • Hid Files count and speculative accessibility count while the listing is loading. The count appears only when the API returns the listing.
  • Aligned the Files list leading edge with the title in the expanded and collapsed sidebar layouts.
  • Added the production Playwright regression matrix and removed a duplicate package script entry.

Files

  • apps/web/src/routes/layout.css
  • packages/ui/src/components/ModeHeader.svelte
  • apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/files/FilesBrowser.svelte
  • apps/web/e2e/sidebar-content-shift-1138.mjs
  • apps/web/package.json

Commits: 6d04d8000a02029b9e9ff120cfc0860b7e145bce (layout), 489d18c7c (Files loading state). The required merge from origin/dev is included in the reported head.

Evidence

The production e2e used macOS platform emulation. All eight Tabs passed the same layout assertions. At 1920 px the Files left edge stayed at 380.00 px open and closed. At 1440 px the actual sidebar overlap was 149.20 px (137.60 → 286.80); at 1024 px it was 245.84 px (40.96 → 286.80). At 820 px it was 254.00 px. The 390 px view retained the existing sheet behavior. Pointer and keyboard motion matched; resizing tracked the sidebar edge; reduced motion disabled the transition. At rest, edge hover, mid-drag and after drag, the sidebar boundary stayed transparent; the header had a 0 px bottom border and no shadow.

The delayed real empty-folder response showed Loading… with no count in visible text or the list accessible name, then showed 0 items after the API confirmed the empty listing. Files list and title leading edges matched in open and collapsed states.

Gates and focused checks

  • cargo fmt --check: exit 0; stdout and stderr were empty.
  • bun run check: exit 2. Its output was:
perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964

The merged origin/dev ledger has 21,977 exceptions against its 21,964 ratchet. This change did not touch the ledger.

  • The next check-user-storage stage was run directly and reported four direct sessionStorage accesses in unchanged apps/web/src/lib/navigation/clientRouteStatus.svelte.ts:
lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access
lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access
lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access
lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access
  • Direct Svelte check: svelte-check found 0 errors and 4 warnings in 3 files (warnings are existing empty CSS rulesets and unused Notes selectors).
  • Focused Vitest output:
 Test Files  2 passed (2)
      Tests  11 passed (11)
   Start at  17:19:41
   Duration  254ms (transform 59%, import 21%, tests 10%, worker 9%)
  • Focused production e2e ended with: PASS sidebar content shift, resize and Mac production screenshot matrix.
  • Post-merge bun run build exited 0: Compressed 885 static variants; saved 20892793 bytes.
  • Post-merge cargo build -p calternal-server exited 0: Finished \dev` profile [unoptimized + debuginfo] target(s) in 2m 19s`.
  • cargo clean exited 0: Removed 8744 files, 8.2GiB total. Web build output was deleted.

UX gaps closed

Loading no longer presents “Loading 0 items…” alongside an empty count. Files header and rows now align with the page title. Sidebar opening and edge resizing no longer recenter route content. Keyboard, pointer, resize, reduced-motion, and narrow sheet behavior were covered. No boundary line remains in the tested interaction states.

UX gaps left

No known #1138 UI gap remains. The repository bun run check remains blocked by the merged upstream exception-ratchet mismatch and the four existing sessionStorage lint findings above. The direct Svelte check still reports four existing warnings.

Decisions

The layout keeps the existing centered content width, spacing tokens, and 768 px sheet breakpoint. The content moves only by the calculated overlap at each viewport; at 1440 px the current content bounds still overlap the open sidebar, so the required shift is 149.20 px. No design-doc decision was needed beyond the issue’s CSS-only overlap rule.

Production screenshots

Width Light closed Light open Dark closed Dark open
390 closed open closed open
820 closed open closed open
1024 closed open closed open
1440 closed open closed open
1920 closed open closed open

Additional interactions: sidebar edge hover, mid-drag, after drag, Files loading.

## #1138 finished Implemented and committed the sidebar/content overlap fix and Files loading-count fix. Head: `b98dedd9174c631d3662b8eafa75ba08f42ea482`. ### What changed - Made the app main area span the shell columns and kept route content viewport-centered. CSS tokens now add only the amount the open sidebar edge plus gap overlaps that centered content. Drag resizing uses the same calculation; no per-frame JS layout reads were added. - Kept the shared spring for pointer and keyboard changes and disabled it for reduced motion and active resize. - Removed the sidebar-edge divider and header bottom hairline at their sources. - Hid Files count and speculative accessibility count while the listing is loading. The count appears only when the API returns the listing. - Aligned the Files list leading edge with the title in the expanded and collapsed sidebar layouts. - Added the production Playwright regression matrix and removed a duplicate package script entry. ### Files - `apps/web/src/routes/layout.css` - `packages/ui/src/components/ModeHeader.svelte` - `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/files/FilesBrowser.svelte` - `apps/web/e2e/sidebar-content-shift-1138.mjs` - `apps/web/package.json` Commits: `6d04d8000a02029b9e9ff120cfc0860b7e145bce` (layout), `489d18c7c` (Files loading state). The required merge from `origin/dev` is included in the reported head. ### Evidence The production e2e used macOS platform emulation. All eight Tabs passed the same layout assertions. At 1920 px the Files left edge stayed at 380.00 px open and closed. At 1440 px the actual sidebar overlap was 149.20 px (137.60 → 286.80); at 1024 px it was 245.84 px (40.96 → 286.80). At 820 px it was 254.00 px. The 390 px view retained the existing sheet behavior. Pointer and keyboard motion matched; resizing tracked the sidebar edge; reduced motion disabled the transition. At rest, edge hover, mid-drag and after drag, the sidebar boundary stayed transparent; the header had a 0 px bottom border and no shadow. The delayed real empty-folder response showed `Loading…` with no count in visible text or the list accessible name, then showed `0 items` after the API confirmed the empty listing. Files list and title leading edges matched in open and collapsed states. ### Gates and focused checks - `cargo fmt --check`: exit 0; stdout and stderr were empty. - `bun run check`: exit 2. Its output was: ```text perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964 ``` The merged `origin/dev` ledger has 21,977 exceptions against its 21,964 ratchet. This change did not touch the ledger. - The next `check-user-storage` stage was run directly and reported four direct `sessionStorage` accesses in unchanged `apps/web/src/lib/navigation/clientRouteStatus.svelte.ts`: ```text lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access lib/navigation/clientRouteStatus.svelte.ts: direct sessionStorage access ``` - Direct Svelte check: `svelte-check found 0 errors and 4 warnings in 3 files` (warnings are existing empty CSS rulesets and unused Notes selectors). - Focused Vitest output: ```text Test Files 2 passed (2) Tests 11 passed (11) Start at 17:19:41 Duration 254ms (transform 59%, import 21%, tests 10%, worker 9%) ``` - Focused production e2e ended with: `PASS sidebar content shift, resize and Mac production screenshot matrix`. - Post-merge `bun run build` exited 0: `Compressed 885 static variants; saved 20892793 bytes.` - Post-merge `cargo build -p calternal-server` exited 0: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 2m 19s`. - `cargo clean` exited 0: `Removed 8744 files, 8.2GiB total`. Web build output was deleted. ### UX gaps closed Loading no longer presents “Loading 0 items…” alongside an empty count. Files header and rows now align with the page title. Sidebar opening and edge resizing no longer recenter route content. Keyboard, pointer, resize, reduced-motion, and narrow sheet behavior were covered. No boundary line remains in the tested interaction states. ### UX gaps left No known #1138 UI gap remains. The repository `bun run check` remains blocked by the merged upstream exception-ratchet mismatch and the four existing `sessionStorage` lint findings above. The direct Svelte check still reports four existing warnings. ### Decisions The layout keeps the existing centered content width, spacing tokens, and 768 px sheet breakpoint. The content moves only by the calculated overlap at each viewport; at 1440 px the current content bounds still overlap the open sidebar, so the required shift is 149.20 px. No design-doc decision was needed beyond the issue’s CSS-only overlap rule. ### Production screenshots | Width | Light closed | Light open | Dark closed | Dark open | |---|---|---|---|---| | 390 | [closed](https://git.kayg.org/attachments/68a55480-bfc1-46cb-8142-846ed674b2f4) | [open](https://git.kayg.org/attachments/b5d39557-0d26-440c-9f17-394d205c525b) | [closed](https://git.kayg.org/attachments/c3512f68-a73f-4453-bdaf-08b78695e81b) | [open](https://git.kayg.org/attachments/ed0bdfe2-2b23-4ea2-8fbe-b6cf0ee5fcd0) | | 820 | [closed](https://git.kayg.org/attachments/12c22c27-4752-4e56-85de-8433db234160) | [open](https://git.kayg.org/attachments/d99fc141-d265-4b4b-a676-fcdfa1ce7183) | [closed](https://git.kayg.org/attachments/7137cbed-de42-4841-a0d7-8511bb56dc39) | [open](https://git.kayg.org/attachments/8a155e09-974e-4445-827b-773bbea744ee) | | 1024 | [closed](https://git.kayg.org/attachments/429ae912-c5e4-4d46-b7fe-4c6100593aa6) | [open](https://git.kayg.org/attachments/38e26107-1c69-40e3-b1cf-0d385ac9beff) | [closed](https://git.kayg.org/attachments/c28838ec-278f-467d-8744-bd25932fa5ad) | [open](https://git.kayg.org/attachments/a47fa993-3fae-4c98-8071-b816c2a699dc) | | 1440 | [closed](https://git.kayg.org/attachments/3f880220-f9d7-46ce-a7ff-588015b7afc8) | [open](https://git.kayg.org/attachments/4ef86378-d796-4af5-8838-c6a3507d9509) | [closed](https://git.kayg.org/attachments/26cb631d-f409-4b3e-b566-54c95acef885) | [open](https://git.kayg.org/attachments/3afaeb0d-fd02-4ef2-b856-b52fe707f66a) | | 1920 | [closed](https://git.kayg.org/attachments/6fc39169-79fa-4fec-b962-b7b144dad406) | [open](https://git.kayg.org/attachments/2c57cb7a-9652-4dc5-a482-02eaeca65f73) | [closed](https://git.kayg.org/attachments/40316ea6-f094-41a8-8927-8ad7b3ab29bc) | [open](https://git.kayg.org/attachments/74d8da9b-3f61-435d-b765-659a21a72db9) | Additional interactions: [sidebar edge hover](https://git.kayg.org/attachments/f5cb2d36-a1a0-4dd8-b5be-197acccb126d), [mid-drag](https://git.kayg.org/attachments/61841399-28bb-45a2-9b06-0f326b468340), [after drag](https://git.kayg.org/attachments/9a58ee16-216b-43c2-93cd-01147aa40d57), [Files loading](https://git.kayg.org/attachments/a76fea75-a8ce-424a-8bfb-f83b1d802b98).
Author
Owner

Gate scope note: this issue changed web CSS/Svelte and Playwright only; it changed no Rust crate, so per-crate Rust clippy/test gates did not apply. cargo build -p calternal-server was used only to run the production e2e. The full web test suite, full e2e suite and adversarial matrices remain for the merge round under the 2026-10-02 verification policy; this job ran the focused Vitest and issue-specific production e2e.

Gate scope note: this issue changed web CSS/Svelte and Playwright only; it changed no Rust crate, so per-crate Rust clippy/test gates did not apply. `cargo build -p calternal-server` was used only to run the production e2e. The full web test suite, full e2e suite and adversarial matrices remain for the merge round under the 2026-10-02 verification policy; this job ran the focused Vitest and issue-specific production e2e.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1138
No description provided.