SHEETS: floating inset presentation sheets (iOS 26 style) — concentric rounded corners, springy drag, detents; one shared primitive #530

Open
opened 2026-09-30 15:20:53 +00:00 by kayg · 18 comments
Owner

Owner request (2026-09-30, screenshot of a native iOS app's sheet): "can we have presentation sheets that look more like this? rather than hugging the borders everywhere? ... it should feel like a floating, springy card but is a presentation sheet"

Reference look (iOS 26 style floating sheet):

  • The sheet is inset from the screen edges on the left, right and bottom (it does not touch the bezels), with all four corners rounded. The bottom corners are concentric with the device's display corners: sheet radius = display corner radius − inset, and the inset follows env(safe-area-inset-*). The top corners use the same radius.
  • It keeps a grabber at the top, the glass material (#436 recipe; no text halo), and a soft shadow so it reads as a card floating above the page, which dims slightly behind it.
  • Springy: it presents with the #236/#291 spring (a slight overshoot); drag follows the finger 1:1 with rubber-banding past the detents; release snaps to the nearest detent with velocity; swipe down dismisses. Detents are medium and large; at the large detent the insets shrink toward the edges smoothly (as iOS does), and it never goes full-bleed rectangular. Reduced motion means no overshoot and a fade. Keyboard-triggered open and close are instant (#527).
  • Content scrolls inside the sheet; scrolling at the top hands off to the sheet drag (the iOS scroll-to-dismiss handoff).
    Scope: the ONE shared sheet primitive (find it: OverlaySurface / phone sheet / PresentationSheet; reuse it and do not create a second one) used by every phone sheet (Calendar item preview on touch, Settings on phone, Search on phone, attachments, Money quick entry, and so on). Desktop dialogs are unchanged unless they already use the phone sheet.
    Proof: screenshots on iPhone-sized viewports (390×844 with safe-area insets simulated, and 430×932) in both themes at both detents; frame strips of present, drag and dismiss; an e2e asserting the sheet's rect is inset from the viewport edges and that all corner radii are equal. Real-device check on the Mac VM's iOS Simulator if available. Web gates.
## Owner request (2026-09-30, screenshot of a native iOS app's sheet): "can we have presentation sheets that look more like this? rather than hugging the borders everywhere? ... it should feel like a floating, springy card but is a presentation sheet" Reference look (iOS 26 style floating sheet): - The sheet is **inset from the screen edges** on the left, right and bottom (it does not touch the bezels), with **all four corners rounded**. The bottom corners are concentric with the device's display corners: sheet radius = display corner radius − inset, and the inset follows `env(safe-area-inset-*)`. The top corners use the same radius. - It keeps a grabber at the top, the glass material (#436 recipe; no text halo), and a soft shadow so it reads as a card floating above the page, which dims slightly behind it. - **Springy:** it presents with the #236/#291 spring (a slight overshoot); drag follows the finger 1:1 with rubber-banding past the detents; release snaps to the nearest detent with velocity; swipe down dismisses. Detents are medium and large; at the large detent the insets shrink toward the edges smoothly (as iOS does), and it never goes full-bleed rectangular. Reduced motion means no overshoot and a fade. Keyboard-triggered open and close are instant (#527). - Content scrolls inside the sheet; scrolling at the top hands off to the sheet drag (the iOS scroll-to-dismiss handoff). **Scope:** the ONE shared sheet primitive (find it: OverlaySurface / phone sheet / PresentationSheet; reuse it and do not create a second one) used by every phone sheet (Calendar item preview on touch, Settings on phone, Search on phone, attachments, Money quick entry, and so on). Desktop dialogs are unchanged unless they already use the phone sheet. **Proof:** screenshots on iPhone-sized viewports (390×844 with safe-area insets simulated, and 430×932) in both themes at both detents; frame strips of present, drag and dismiss; an e2e asserting the sheet's rect is inset from the viewport edges and that all corner radii are equal. Real-device check on the Mac VM's iOS Simulator if available. Web gates.
Author
Owner

Starting #530 on branch job/floating-sheet from base 9ecde429a7. I found the shared primitive at packages/ui/src/components/OverlaySurface.svelte and am tracing its existing sheet consumers and motion rules before editing.

Starting #530 on branch job/floating-sheet from base 9ecde429a7e6f8b846211287ecd6b1475394db94. I found the shared primitive at packages/ui/src/components/OverlaySurface.svelte and am tracing its existing sheet consumers and motion rules before editing.
Author
Owner

Finding and decisions before implementation:

  • OverlaySurface.svelte is the shared modal sheet used by the item preview, Settings, Search, Money/composer, file/photo actions, and other phone flows. Its current .surface.sheet is full-width with square bottom corners; it has a grabber drag-to-dismiss path but no detents.
  • FloatingSurface.svelte has a second phone-sheet path used by the range picker and reminder picker. I will route that phone presentation through OverlaySurface, keeping its desktop popover unchanged.
  • The closed #236/#291 motion contract says overdamped/no overshoot; #530 asks for slight overshoot. I will use the already-shared --ease-spring token for the sheet entrance and detent snap only, and retain the overdamped interactive spring for drag return. The sheet-specific request is the newer decision.
  • The initial detent will be medium for sheetSize="content"; the existing sheetSize="three-quarter" callers will open at large so their current intent is preserved.
Finding and decisions before implementation: - `OverlaySurface.svelte` is the shared modal sheet used by the item preview, Settings, Search, Money/composer, file/photo actions, and other phone flows. Its current `.surface.sheet` is full-width with square bottom corners; it has a grabber drag-to-dismiss path but no detents. - `FloatingSurface.svelte` has a second phone-sheet path used by the range picker and reminder picker. I will route that phone presentation through `OverlaySurface`, keeping its desktop popover unchanged. - The closed #236/#291 motion contract says overdamped/no overshoot; #530 asks for slight overshoot. I will use the already-shared `--ease-spring` token for the sheet entrance and detent snap only, and retain the overdamped interactive spring for drag return. The sheet-specific request is the newer decision. - The initial detent will be medium for `sheetSize="content"`; the existing `sheetSize="three-quarter"` callers will open at large so their current intent is preserved.
Author
Owner

Finding from the first bun run check: the shared text/shape token guard rejected a border-radius: 0 literal in the new phone-sheet content wrapper. The wrapper uses the existing --radius-none token now; no user-facing behavior changed.

Finding from the first `bun run check`: the shared text/shape token guard rejected a `border-radius: 0` literal in the new phone-sheet content wrapper. The wrapper uses the existing `--radius-none` token now; no user-facing behavior changed.
Author
Owner

The caller audit found geometry and scrolling that would have bypassed the shared detents: Quick Look set its own phone-sheet height, ShareDialog and TagEditor capped phone-sheet height, and TurnPanel scrolled its transcript inside a nested body. I removed the height caps, gave tall tools an explicit initialDetent="large", and made TurnPanel use OverlaySurface's scrollport on phones. Desktop dialog sizing and its local transcript scroller stay intact.

The caller audit found geometry and scrolling that would have bypassed the shared detents: Quick Look set its own phone-sheet height, ShareDialog and TagEditor capped phone-sheet height, and TurnPanel scrolled its transcript inside a nested body. I removed the height caps, gave tall tools an explicit `initialDetent="large"`, and made TurnPanel use OverlaySurface's scrollport on phones. Desktop dialog sizing and its local transcript scroller stay intact.
Author
Owner

Finding during the shared-sheet regression run: moving phone presentation sheets into OverlaySurface removed the old .fs-scrim.sheet tint selector, so the shared scrim used the darker generic tint. I added a sheet modifier to the shared scrim, restored its existing --scrim-tint-sheet value (and solid tint for reduced transparency), and moved the theme assertion to the shared scrim stylesheet. Evidence: bun run test -- src/lib/themes.test.ts — 74 passed.

Finding during the shared-sheet regression run: moving phone presentation sheets into `OverlaySurface` removed the old `.fs-scrim.sheet` tint selector, so the shared scrim used the darker generic tint. I added a sheet modifier to the shared scrim, restored its existing `--scrim-tint-sheet` value (and solid tint for reduced transparency), and moved the theme assertion to the shared scrim stylesheet. Evidence: `bun run test -- src/lib/themes.test.ts` — 74 passed.
Author
Owner

Finding during the production browser proof: the grabber could lose pointer movement when the first upward move left its hit area. Capturing the pointer on grabber-down fixed drag tracking; content pointers still wait until the sheet recognizes a vertical drag at scroll top. Evidence: bun run test:e2e:floating-sheet-530 passed across both phone sizes, themes, detents, scroll handoff, and dismissal.

The final web suite also exposed duplicate live announcements when a caller supplied a fixed sheet title. The shared header now uses live announcements only for its changing fallback detent title; the RangeBar status remains its single live region. Evidence: bun run test -- src/lib/components/analytics/RangeBar.svelte.test.ts src/lib/calendar/TimeGrid.svelte.test.ts passed (15 tests).

Finding during the production browser proof: the grabber could lose pointer movement when the first upward move left its hit area. Capturing the pointer on grabber-down fixed drag tracking; content pointers still wait until the sheet recognizes a vertical drag at scroll top. Evidence: `bun run test:e2e:floating-sheet-530` passed across both phone sizes, themes, detents, scroll handoff, and dismissal. The final web suite also exposed duplicate live announcements when a caller supplied a fixed sheet title. The shared header now uses live announcements only for its changing fallback detent title; the RangeBar status remains its single live region. Evidence: `bun run test -- src/lib/components/analytics/RangeBar.svelte.test.ts src/lib/calendar/TimeGrid.svelte.test.ts` passed (15 tests).
Author
Owner

#530 final report

Built

OverlaySurface now provides the shared floating phone presentation sheet used by phone sheets throughout the app. It has safe-area-aware insets and equal rounded corners, medium and large detents, spring presentation, rubber-banded direct dragging, velocity settling, scroll-top handoff, swipe dismissal, reduced-motion fade, and instant keyboard-triggered open and close. Shared callers use one shell; desktop dialogs and popovers keep their existing presentation. The sheet keeps the shared glass and sheet scrim material.

The browser proof uses the production app and real account data. It checks 390×844 and 430×932 in both themes and detents, plus 820 px tablet and 1440 px desktop captures. Assertions cover inset geometry, equal radii, safe-area changes, drag, dismissal, scroll handoff, reduced motion and keyboard timing. Present, drag and dismiss frame strips are attached below.

Files changed

  • apps/web/e2e/floating-sheet-530.mjs
  • apps/web/package.json
  • apps/web/src/lib/ai/TurnPanel.svelte
  • apps/web/src/lib/calendar/TimeGrid.svelte.test.ts
  • apps/web/src/lib/capsule-motion.test.ts
  • apps/web/src/lib/components/TagEditor.svelte
  • apps/web/src/lib/components/analytics/RangeBar.svelte.test.ts
  • apps/web/src/lib/files/ShareDialog.svelte
  • apps/web/src/lib/notifications/InboxPanel.svelte
  • apps/web/src/lib/sheetSnap.test.ts
  • apps/web/src/lib/styles/scrim.css
  • apps/web/src/lib/themes.test.ts
  • apps/web/src/routes/settings/[...path]/+page.svelte
  • apps/web/src/routes/settings/account/RecoveryKeySheet.svelte
  • apps/web/src/routes/settings/appearance/UnsplashSheet.svelte
  • bench/floating-sheet-530.mjs
  • bench/record.py
  • bench/run.sh
  • docs/DESIGN.md
  • packages/ui/src/components/OverlaySurface.svelte
  • packages/ui/src/components/PopoverSurface.svelte
  • packages/ui/src/components/menu/FloatingSurface.svelte
  • packages/ui/src/components/viewer/QuickLook.svelte
  • packages/ui/src/gestures.ts
  • packages/ui/src/index.ts
  • packages/ui/src/motion.ts

Commits

  • f565bcc71 — implement the shared floating phone sheet.
  • 6c0720b94 — add the browser proof and performance profile.
  • 9fb15292b — merge origin/dev once before final gates.
  • dcf2e79d7 — capture the grabber pointer so drag tracking follows the finger.
  • 2d6598bdb — complete the browser evidence.
  • 0e238c517238471c254db172f46e523b1ad7fc0f — keep sheet announcements singular and update affected tests.

Gates

bun run check:

$ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

$ vitest run
 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/floating-sheet/apps/web
 Test Files  138 passed (138)
      Tests  906 passed (906)
   Start at  19:55:29
   Duration  149.26s (transform 49%, environment 21%, import 14%, tests 11%, setup 5%)

bun run build exited 0. The production test:e2e:floating-sheet-530 run passed its browser assertions and generated all attached screenshots. cargo build -p calternal-server succeeded to provide the local E2E server. No Rust source changed, so Rust clippy/test gates did not apply. Cleanup: cargo clean removed 7067 files, 4.4GiB; apps/web/build was removed.

Performance profile

Measured locally on calternal-dev because the perf VM was unavailable. The host load average before the run was 21.83, 28.48, 32.05. There is no floating_sheet entry in docs/perf/baseline.json, so these are initial measurements, not a regression delta.

Viewport Average p50 / p95 Average CPU / RSS Burst p50 / p95 / max Burst CPU / peak RSS
390×844 577.8 / 940.1 ms 12.35 s / 544,460,800 B 509.6 / 808.5 / 960.8 ms 27.68 s / 598,306,816 B
430×932 548.5 / 848.8 ms 15.91 s / 552,738,816 B 523.9 / 685.6 / 1,503.2 ms 29.56 s / 582,524,928 B

Known gaps

  • I could not run the iOS Simulator check: xcrun is not installed in this environment, and the shared Mac VM lock was busy.
  • The performance numbers are local under heavy host load. The baseline has no sheet profile yet.

Decisions not specified by DESIGN before this change

  • The design did not give exact detent geometry. §34 now records the medium top as 42dvh below the large top, the large top as 8 px below the top safe area, side insets of 16 px at medium and 8 px at large, and bottom insets that interpolate from half to one quarter of the safe area with 16 px and 8 px minimums.
  • CSS does not expose the device display-corner radius. §34 records a 44 px proxy (28 px sheet radius plus the 16 px medium inset), subtracts the largest live edge inset for the sheet radius, and keeps a 16 px minimum.
  • A fixed caller title is the dialog label and is not a live region; only the changing fallback detent title uses polite live announcements. This keeps dynamic sheet state announced without duplicating caller content announcements.

Review images

Frame strips: present-390-light-strip.png · drag-390-light-strip.png · dismiss-390-light-strip.png

Phone screenshots: settings-sheet-light-390-large.png · settings-sheet-light-390-medium.png · settings-sheet-dark-390-large.png · settings-sheet-dark-390-medium.png · settings-sheet-light-430-large.png · settings-sheet-light-430-medium.png · settings-sheet-dark-430-large.png · settings-sheet-dark-430-medium.png

Tablet screenshots: settings-sheet-light-820-large.png · settings-sheet-light-820-medium.png · settings-sheet-dark-820-large.png · settings-sheet-dark-820-medium.png · settings-light-820.png · settings-dark-820.png

Desktop screenshots: settings-light-1440.png · settings-dark-1440.png

## #530 final report ### Built `OverlaySurface` now provides the shared floating phone presentation sheet used by phone sheets throughout the app. It has safe-area-aware insets and equal rounded corners, medium and large detents, spring presentation, rubber-banded direct dragging, velocity settling, scroll-top handoff, swipe dismissal, reduced-motion fade, and instant keyboard-triggered open and close. Shared callers use one shell; desktop dialogs and popovers keep their existing presentation. The sheet keeps the shared glass and sheet scrim material. The browser proof uses the production app and real account data. It checks 390×844 and 430×932 in both themes and detents, plus 820 px tablet and 1440 px desktop captures. Assertions cover inset geometry, equal radii, safe-area changes, drag, dismissal, scroll handoff, reduced motion and keyboard timing. Present, drag and dismiss frame strips are attached below. ### Files changed - `apps/web/e2e/floating-sheet-530.mjs` - `apps/web/package.json` - `apps/web/src/lib/ai/TurnPanel.svelte` - `apps/web/src/lib/calendar/TimeGrid.svelte.test.ts` - `apps/web/src/lib/capsule-motion.test.ts` - `apps/web/src/lib/components/TagEditor.svelte` - `apps/web/src/lib/components/analytics/RangeBar.svelte.test.ts` - `apps/web/src/lib/files/ShareDialog.svelte` - `apps/web/src/lib/notifications/InboxPanel.svelte` - `apps/web/src/lib/sheetSnap.test.ts` - `apps/web/src/lib/styles/scrim.css` - `apps/web/src/lib/themes.test.ts` - `apps/web/src/routes/settings/[...path]/+page.svelte` - `apps/web/src/routes/settings/account/RecoveryKeySheet.svelte` - `apps/web/src/routes/settings/appearance/UnsplashSheet.svelte` - `bench/floating-sheet-530.mjs` - `bench/record.py` - `bench/run.sh` - `docs/DESIGN.md` - `packages/ui/src/components/OverlaySurface.svelte` - `packages/ui/src/components/PopoverSurface.svelte` - `packages/ui/src/components/menu/FloatingSurface.svelte` - `packages/ui/src/components/viewer/QuickLook.svelte` - `packages/ui/src/gestures.ts` - `packages/ui/src/index.ts` - `packages/ui/src/motion.ts` ### Commits - `f565bcc71` — implement the shared floating phone sheet. - `6c0720b94` — add the browser proof and performance profile. - `9fb15292b` — merge `origin/dev` once before final gates. - `dcf2e79d7` — capture the grabber pointer so drag tracking follows the finger. - `2d6598bdb` — complete the browser evidence. - `0e238c517238471c254db172f46e523b1ad7fc0f` — keep sheet announcements singular and update affected tests. ### Gates `bun run check`: ```text $ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text $ vitest run RUN v5.0.1 /home/kayg/Developer/calternal-wt/floating-sheet/apps/web Test Files 138 passed (138) Tests 906 passed (906) Start at 19:55:29 Duration 149.26s (transform 49%, environment 21%, import 14%, tests 11%, setup 5%) ``` `bun run build` exited 0. The production `test:e2e:floating-sheet-530` run passed its browser assertions and generated all attached screenshots. `cargo build -p calternal-server` succeeded to provide the local E2E server. No Rust source changed, so Rust clippy/test gates did not apply. Cleanup: `cargo clean` removed 7067 files, 4.4GiB; `apps/web/build` was removed. ### Performance profile Measured locally on `calternal-dev` because the perf VM was unavailable. The host load average before the run was `21.83, 28.48, 32.05`. There is no `floating_sheet` entry in `docs/perf/baseline.json`, so these are initial measurements, not a regression delta. | Viewport | Average p50 / p95 | Average CPU / RSS | Burst p50 / p95 / max | Burst CPU / peak RSS | | --- | --- | --- | --- | --- | | 390×844 | 577.8 / 940.1 ms | 12.35 s / 544,460,800 B | 509.6 / 808.5 / 960.8 ms | 27.68 s / 598,306,816 B | | 430×932 | 548.5 / 848.8 ms | 15.91 s / 552,738,816 B | 523.9 / 685.6 / 1,503.2 ms | 29.56 s / 582,524,928 B | ### Known gaps - I could not run the iOS Simulator check: `xcrun` is not installed in this environment, and the shared Mac VM lock was busy. - The performance numbers are local under heavy host load. The baseline has no sheet profile yet. ### Decisions not specified by DESIGN before this change - The design did not give exact detent geometry. §34 now records the medium top as 42dvh below the large top, the large top as 8 px below the top safe area, side insets of 16 px at medium and 8 px at large, and bottom insets that interpolate from half to one quarter of the safe area with 16 px and 8 px minimums. - CSS does not expose the device display-corner radius. §34 records a 44 px proxy (28 px sheet radius plus the 16 px medium inset), subtracts the largest live edge inset for the sheet radius, and keeps a 16 px minimum. - A fixed caller title is the dialog label and is not a live region; only the changing fallback detent title uses polite live announcements. This keeps dynamic sheet state announced without duplicating caller content announcements. ### Review images **Frame strips:** [present-390-light-strip.png](https://git.kayg.org/attachments/917c7cf0-4d72-4259-8f14-fc05d7097c30) · [drag-390-light-strip.png](https://git.kayg.org/attachments/685c531a-fbcc-4a87-9adf-4504f1ed3506) · [dismiss-390-light-strip.png](https://git.kayg.org/attachments/2b3316d4-43d4-4c4f-911c-3b7657764471) **Phone screenshots:** [settings-sheet-light-390-large.png](https://git.kayg.org/attachments/5ce85942-ca8c-403b-8372-96d8ab9ed4e0) · [settings-sheet-light-390-medium.png](https://git.kayg.org/attachments/68b01d71-face-4ca3-83b3-f3b8d9123445) · [settings-sheet-dark-390-large.png](https://git.kayg.org/attachments/6b009562-9607-4811-84da-11ad3b2481b8) · [settings-sheet-dark-390-medium.png](https://git.kayg.org/attachments/adecfd72-1dfe-4d26-ab51-1e99c7b99f3b) · [settings-sheet-light-430-large.png](https://git.kayg.org/attachments/060cfa5c-cabe-4b35-94e1-dcdab3d3ba6c) · [settings-sheet-light-430-medium.png](https://git.kayg.org/attachments/e63c3398-d701-42b8-b173-b886d2a1a190) · [settings-sheet-dark-430-large.png](https://git.kayg.org/attachments/4ffec41a-b457-4371-83fd-8bdb74e631bf) · [settings-sheet-dark-430-medium.png](https://git.kayg.org/attachments/2b1ed62d-621e-42f5-aaa4-fc18f0156eea) **Tablet screenshots:** [settings-sheet-light-820-large.png](https://git.kayg.org/attachments/b1038ae9-2bce-4097-a496-e7f5f6559d5d) · [settings-sheet-light-820-medium.png](https://git.kayg.org/attachments/4f449df4-2cb6-43c8-abf5-802e29defd55) · [settings-sheet-dark-820-large.png](https://git.kayg.org/attachments/4bec9254-5e8c-4903-97b5-3221f2994294) · [settings-sheet-dark-820-medium.png](https://git.kayg.org/attachments/30a456c7-c145-476f-9d29-fd6583973d43) · [settings-light-820.png](https://git.kayg.org/attachments/350e3b06-eb75-4e9f-b89d-26a1afdde7db) · [settings-dark-820.png](https://git.kayg.org/attachments/55108d94-687b-4fdb-89f4-d4b31fd90b4e) **Desktop screenshots:** [settings-light-1440.png](https://git.kayg.org/attachments/4efb0328-c795-4d56-aa37-92652c33eb4c) · [settings-dark-1440.png](https://git.kayg.org/attachments/be30abf5-4bce-4306-9548-32fb41f5aa1d)
Author
Owner

Starting #530 polish on branch job/floating-sheet. Branch base: 1e634aefb6 (origin/dev).

Starting #530 polish on branch job/floating-sheet. Branch base: 1e634aefb67d8fbb9d4bdf638e76f82f19e4b3fb (origin/dev).
Author
Owner

Finding after the origin/dev merge: apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte passed sheetSize="three-quarter", but the #530 OverlaySurface contract uses initialDetent and the type check rejected the missing prop at line 193. I reconciled the caller with the shared large detent from DESIGN §34; no second sheet size path was added.

Finding after the origin/dev merge: apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte passed sheetSize="three-quarter", but the #530 OverlaySurface contract uses initialDetent and the type check rejected the missing prop at line 193. I reconciled the caller with the shared large detent from DESIGN §34; no second sheet size path was added.
Author
Owner

Finding: source inspection confirmed that Settings rendered a body Back control in addition to the shared sheet-header Back control above 767 px, the sheet header painted a static band before scrolling, and SettingRow drew its border on the final row. The shared sheet now hides the marked body Back control at every sheet width, fades the shared ProgressiveBlur in only after scroll, and limits row borders to rows that have a following row. The production browser run is checking these rules across all User/Admin Settings sections and the 390/820 captures.

Finding: source inspection confirmed that Settings rendered a body Back control in addition to the shared sheet-header Back control above 767 px, the sheet header painted a static band before scrolling, and `SettingRow` drew its border on the final row. The shared sheet now hides the marked body Back control at every sheet width, fades the shared ProgressiveBlur in only after scroll, and limits row borders to rows that have a following row. The production browser run is checking these rules across all User/Admin Settings sections and the 390/820 captures.
Author
Owner

The first Playwright run passed the 390 px large-detent chrome checks, then caught the final “Colour fill” row still painting a 1 px divider. The selector used :last-child of outside the :nth-child() family, so the final-row exception did not match when later non-row content followed. I changed it to :not(:nth-last-child(1 of .setting-row)), which counts only sibling SettingRow elements. I am rebuilding the production web bundle before the next browser run.

The first Playwright run passed the 390 px large-detent chrome checks, then caught the final “Colour fill” row still painting a 1 px divider. The selector used `:last-child of` outside the `:nth-child()` family, so the final-row exception did not match when later non-row content followed. I changed it to `:not(:nth-last-child(1 of .setting-row))`, which counts only sibling SettingRow elements. I am rebuilding the production web bundle before the next browser run.
Author
Owner

#530 polish round complete

Branch: job/floating-sheet
Head SHA: e531cbf03a5606ff5b9658d6813eb67600ac37c7

Built

  • The shared presentation sheet hides only the marked Settings page Back control when a sheet header owns Back. The shared ProgressiveBlur is clear at the top, fades in after scrolling, and clears again at the top.
  • SettingRow now draws separators only before another SettingRow. The 390 px medium/dark check confirms the grabber is visible and the title clears its row.
  • The #530 E2E covers geometry, detents, drag/dismiss, scroll handoff, reduced motion and keyboard behavior. Its SETTINGS_BACK_AUDIT 21 sections passed output confirms one visible Back action across User/Admin Settings sections.
  • Source review of ShareDialog, TagEditor, InboxPanel and TurnPanel found Close/task actions and no body Back control to duplicate.

Files

  • packages/ui/src/components/OverlaySurface.svelte
  • packages/ui/src/components/SettingRow.svelte
  • apps/web/src/routes/settings/[...path]/+page.svelte
  • apps/web/src/lib/components/OverlaySurface.svelte.test.ts
  • apps/web/e2e/floating-sheet-530.mjs
  • apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte (updated the merged caller to the public initialDetent="large" API)
  • bench/record.py (merge conflict resolution retained the sheet and origin/dev workload reporting)

Gates (verbatim output excerpts)

bun run check:

Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

Test Files  140 passed (140)
      Tests  933 passed (933)
   Start at  03:56:58
   Duration  179.23s (transform 55%, import 16%, environment 16%, tests 9%, setup 3%)

bun run build:

✓ built in 1m 41s
  Wrote site to "build"
  ✔ done

E2E printed Version 1.63.0 and SETTINGS_BACK_AUDIT 21 sections passed, then captured the 390, 820 and 1440 px evidence below. The run completed without assertion or browser errors. Vitest also printed Could not parse CSS stylesheet and jsdom Not implemented: Window's scrollTo() method diagnostics while passing.

Cleanup output:

Removed 7470 files, 5.0GiB total

Adversarial round

One focused real-local-server round ran after the origin/dev merge. The runner ended nonzero with FINDINGS 235; most records were SLOW-only and treated as host load. It reported server alive at end: True. Non-SLOW findings were filed for owner review: #564 covers the MCP exact-tool-list mismatch (calternal_calendar was extra); #568 records tag 409/rebuild inconsistencies, Task/DAV/Reminders timeouts, DAV credential setup 403, and DAV status/ETag mismatches. Existing expectations were not changed.

Other round output included search storm: 0 failures, mkdir storm: 200 created, 0 errors, rename race: 1 succeeded, 0 errors, and calendar duplicate account storm: 20 conflicts, 0 unexpected statuses.

Performance

The local profile ran on calternal-dev; load average before measurement was [24.95, 30.93, 28.55]. docs/perf/baseline.json has no floating-sheet baseline entry.

Viewport Average toggle p50/p95 Average CPU / RSS 30-pair burst p50/p95 Burst CPU / peak RSS
390×844 568 / 831.7 ms 75% / 536 MB 573.4 / 888.9 ms 66.5% / 563 MB
430×932 613.6 / 1022.5 ms 57.7% / 544 MB 603.6 / 911.7 ms 65.7% / 557 MB

Decisions and known gaps

  • The divider selector uses :nth-last-child(1 of .setting-row) because BackgroundGroup has later non-row content; this counts only SettingRow siblings.
  • CalendarFeedsGroup uses the existing large detent because its pre-merge sheetSize="three-quarter" prop is not part of the shared OverlaySurface API.
  • The adversarial non-SLOW findings remain open in #564 and #568. The performance values are local observations under high host load, with no baseline comparison available.

Attached production evidence

Full screens: 390 light large, 390 dark large, 390 light medium, 390 dark medium, 390 dark scrolled, 820 light large, 820 dark large, 820 light medium, 820 dark medium, 1440 light, 1440 dark.

3× header crops: 390 light large, 390 light medium, 390 dark large, 390 dark medium, 390 dark scrolled, 820 light large, 820 light medium, 820 dark large, 820 dark medium.

Interaction strips: presentation, drag, dismissal.

#530 polish round complete Branch: `job/floating-sheet` Head SHA: `e531cbf03a5606ff5b9658d6813eb67600ac37c7` ## Built - The shared presentation sheet hides only the marked Settings page Back control when a sheet header owns Back. The shared ProgressiveBlur is clear at the top, fades in after scrolling, and clears again at the top. - `SettingRow` now draws separators only before another SettingRow. The 390 px medium/dark check confirms the grabber is visible and the title clears its row. - The #530 E2E covers geometry, detents, drag/dismiss, scroll handoff, reduced motion and keyboard behavior. Its `SETTINGS_BACK_AUDIT 21 sections passed` output confirms one visible Back action across User/Admin Settings sections. - Source review of ShareDialog, TagEditor, InboxPanel and TurnPanel found Close/task actions and no body Back control to duplicate. ## Files - `packages/ui/src/components/OverlaySurface.svelte` - `packages/ui/src/components/SettingRow.svelte` - `apps/web/src/routes/settings/[...path]/+page.svelte` - `apps/web/src/lib/components/OverlaySurface.svelte.test.ts` - `apps/web/e2e/floating-sheet-530.mjs` - `apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte` (updated the merged caller to the public `initialDetent="large"` API) - `bench/record.py` (merge conflict resolution retained the sheet and `origin/dev` workload reporting) ## Gates (verbatim output excerpts) `bun run check`: ```text Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 140 passed (140) Tests 933 passed (933) Start at 03:56:58 Duration 179.23s (transform 55%, import 16%, environment 16%, tests 9%, setup 3%) ``` `bun run build`: ```text ✓ built in 1m 41s Wrote site to "build" ✔ done ``` E2E printed `Version 1.63.0` and `SETTINGS_BACK_AUDIT 21 sections passed`, then captured the 390, 820 and 1440 px evidence below. The run completed without assertion or browser errors. Vitest also printed `Could not parse CSS stylesheet` and jsdom `Not implemented: Window's scrollTo() method` diagnostics while passing. Cleanup output: ```text Removed 7470 files, 5.0GiB total ``` ## Adversarial round One focused real-local-server round ran after the `origin/dev` merge. The runner ended nonzero with `FINDINGS 235`; most records were SLOW-only and treated as host load. It reported `server alive at end: True`. Non-SLOW findings were filed for owner review: [#564](https://git.kayg.org/kayg/calternal/issues/564) covers the MCP exact-tool-list mismatch (`calternal_calendar` was extra); [#568](https://git.kayg.org/kayg/calternal/issues/568) records tag 409/rebuild inconsistencies, Task/DAV/Reminders timeouts, DAV credential setup 403, and DAV status/ETag mismatches. Existing expectations were not changed. Other round output included `search storm: 0 failures`, `mkdir storm: 200 created, 0 errors`, `rename race: 1 succeeded, 0 errors`, and `calendar duplicate account storm: 20 conflicts, 0 unexpected statuses`. ## Performance The local profile ran on `calternal-dev`; load average before measurement was `[24.95, 30.93, 28.55]`. `docs/perf/baseline.json` has no floating-sheet baseline entry. | Viewport | Average toggle p50/p95 | Average CPU / RSS | 30-pair burst p50/p95 | Burst CPU / peak RSS | |---|---:|---:|---:|---:| | 390×844 | 568 / 831.7 ms | 75% / 536 MB | 573.4 / 888.9 ms | 66.5% / 563 MB | | 430×932 | 613.6 / 1022.5 ms | 57.7% / 544 MB | 603.6 / 911.7 ms | 65.7% / 557 MB | ## Decisions and known gaps - The divider selector uses `:nth-last-child(1 of .setting-row)` because `BackgroundGroup` has later non-row content; this counts only SettingRow siblings. - `CalendarFeedsGroup` uses the existing large detent because its pre-merge `sheetSize="three-quarter"` prop is not part of the shared OverlaySurface API. - The adversarial non-SLOW findings remain open in #564 and #568. The performance values are local observations under high host load, with no baseline comparison available. ## Attached production evidence Full screens: [390 light large](https://git.kayg.org/attachments/306a5c69-ed55-4c3a-8fd4-ae584162038f), [390 dark large](https://git.kayg.org/attachments/1c1c8d65-9094-45c4-9bea-e09f7c7b3124), [390 light medium](https://git.kayg.org/attachments/bad73ba1-90c3-4a29-a005-1e09d0e58ccf), [390 dark medium](https://git.kayg.org/attachments/9549ea8e-869c-4a98-bef5-07c45a63365b), [390 dark scrolled](https://git.kayg.org/attachments/067a7602-53ca-4758-9b7d-3cd1b3f19b92), [820 light large](https://git.kayg.org/attachments/51d5b041-0ca4-4015-86b8-603011a99493), [820 dark large](https://git.kayg.org/attachments/a591a569-2d5e-48a0-90b6-6be8400e66f7), [820 light medium](https://git.kayg.org/attachments/69f69edd-cd9d-48e1-a4a0-6cfc7fc10b26), [820 dark medium](https://git.kayg.org/attachments/dce206a4-09d0-45a0-bc78-037296d13422), [1440 light](https://git.kayg.org/attachments/0c9dc642-f362-47a5-a252-3c2fac8c867b), [1440 dark](https://git.kayg.org/attachments/30ce5f31-d7d9-442e-8493-c66ae212446d). 3× header crops: [390 light large](https://git.kayg.org/attachments/8795faf6-70d4-4acd-921b-43a0ebd949e8), [390 light medium](https://git.kayg.org/attachments/5725834d-aba8-458d-8a03-e7105c914e93), [390 dark large](https://git.kayg.org/attachments/096dc4ae-5e11-466d-90bb-53eee84c7eb3), [390 dark medium](https://git.kayg.org/attachments/c514f812-7f20-49e5-90ea-afaadc911901), [390 dark scrolled](https://git.kayg.org/attachments/76df2e5f-680b-4714-9e31-ec0a2b9d425c), [820 light large](https://git.kayg.org/attachments/dc3fbd18-c37f-49db-8dad-a79110428418), [820 light medium](https://git.kayg.org/attachments/4d43550a-bfd0-4570-9863-60a44fe5b354), [820 dark large](https://git.kayg.org/attachments/d74a6efb-b4f8-4cef-a67d-ac580e2e9f42), [820 dark medium](https://git.kayg.org/attachments/7715418d-3d87-4f99-ade5-2f6a0ae326d7). Interaction strips: [presentation](https://git.kayg.org/attachments/6fa37b2f-0b5d-4608-99c7-cb5d4f501226), [drag](https://git.kayg.org/attachments/c640e744-9611-46bc-8a98-e29dc7bdf756), [dismissal](https://git.kayg.org/attachments/c391bd3d-3997-45f2-8d4f-e7637523ef10).
Author
Owner

Round 3 started on job/floating-sheet.

Base SHA: cc25c441b7a974185622a1dee853cf38686d2b67 (merge base with origin/dev). Current head: e531cbf03a5606ff5b9658d6813eb67600ac37c7.

I am tracing the #530 Settings divider regression in the shared row/group primitives and will add a per-group divider-count assertion across all Settings sections before rerunning the requested production e2e and web gates.

Round 3 started on `job/floating-sheet`. Base SHA: `cc25c441b7a974185622a1dee853cf38686d2b67` (merge base with `origin/dev`). Current head: `e531cbf03a5606ff5b9658d6813eb67600ac37c7`. I am tracing the #530 Settings divider regression in the shared row/group primitives and will add a per-group divider-count assertion across all Settings sections before rerunning the requested production e2e and web gates.
Author
Owner

Finding: the divider rule compares .setting-row siblings under the same DOM parent. In AppearanceSection.svelte, Theme is a direct child of the card and Background is inside the .background-group wrapper. In FontsGroup.svelte, its rows are also wrapped before the separate Text size row. The selector cannot see either cross-wrapper pair, so those group boundaries miss a divider. The existing #530 assertion only checked whether Colour fill was terminal under its immediate parent; it did not count dividers for the Settings card. I am moving the divider decision to the shared row group and extending the e2e audit to assert rows - 1 for every Settings group on every User and Admin section route.

Finding: the divider rule compares `.setting-row` siblings under the same DOM parent. In `AppearanceSection.svelte`, `Theme` is a direct child of the card and `Background` is inside the `.background-group` wrapper. In `FontsGroup.svelte`, its rows are also wrapped before the separate `Text size` row. The selector cannot see either cross-wrapper pair, so those group boundaries miss a divider. The existing #530 assertion only checked whether `Colour fill` was terminal under its immediate parent; it did not count dividers for the Settings card. I am moving the divider decision to the shared row group and extending the e2e audit to assert `rows - 1` for every Settings group on every User and Admin section route.
Author
Owner

Round 3 e2e finding: the new layout assertion opened /settings/appearance/theme at 390 px and found 4 logical SettingRows but 0 computed dividers. The SettingsCard context did not reach rows created in the caller-owned snippet. I am switching registration to the rendered .settings-card DOM boundary so nested wrappers and conditional blocks still share one ordered group.

Round 3 e2e finding: the new layout assertion opened `/settings/appearance/theme` at 390 px and found 4 logical SettingRows but 0 computed dividers. The SettingsCard context did not reach rows created in the caller-owned snippet. I am switching registration to the rendered `.settings-card` DOM boundary so nested wrappers and conditional blocks still share one ordered group.
Author
Owner

Round 3 e2e environment finding: after all row-count assertions passed, the final console gate failed on repeated 404s. A focused Playwright trace identified GET /api/v1/preferences/mode-order. The e2e used the available server binary from before the origin/dev merge, so I am building the current server before the final e2e run.

Round 3 e2e environment finding: after all row-count assertions passed, the final console gate failed on repeated 404s. A focused Playwright trace identified `GET /api/v1/preferences/mode-order`. The e2e used the available server binary from before the origin/dev merge, so I am building the current server before the final e2e run.
Author
Owner

Round 3 e2e finding: the current-server full run twice stopped at the pre-existing 430 px medium-sheet grabber/title clearance check, while a focused reproduction measured 0 px clearance and stayed stable through 350 ms. I kept the same 1 px assertion and added the suite's two-frame settle before reading those bounds, so it checks the painted state after the scroll/blur update.

Round 3 e2e finding: the current-server full run twice stopped at the pre-existing 430 px medium-sheet grabber/title clearance check, while a focused reproduction measured 0 px clearance and stayed stable through 350 ms. I kept the same 1 px assertion and added the suite's two-frame settle before reading those bounds, so it checks the painted state after the scroll/blur update.
Author
Owner

Round 3 complete. Commit: c8516493f1e8cdc2ec7d75cc1919bd8d9315ae69 (job/floating-sheet).

Built

  • SettingsCard marks its rendered card root. Each SettingRow registers with its nearest card, and a coalesced DOM-order pass gives every row except the last exactly one divider. This works across wrapper elements and conditional blocks.
  • The #530 e2e now asserts computed divider count = rows − 1 for every Settings group on all 21 User/Admin routes.
  • The existing 1 px grabber/title clearance assertion is unchanged. The check waits for the shared two-frame settle before reading bounds.

Files: packages/ui/src/components/settingRowDividers.ts, packages/ui/src/components/SettingRow.svelte, packages/ui/src/index.ts, apps/web/src/routes/settings/parts/SettingsCard.svelte, apps/web/e2e/floating-sheet-530.mjs.

Verification (verbatim output)

E2E command: CALTERNAL_E2E_ASSET_OVERRIDE=1 CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/floating-sheet/debug/calternal-server node e2e/floating-sheet-530.mjs (exit 0).

SETTINGS_BACK_AUDIT 21 sections passed
SETTINGS_DIVIDER_AUDIT appearance 390px groups=2 rows=8 dividers=6
SETTINGS_DIVIDER_AUDIT appearance 820px groups=2 rows=8 dividers=6
SETTINGS_DIVIDER_AUDIT appearance 1440px groups=2 rows=8 dividers=6

bun run check:

User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

Test Files  148 passed (148)
      Tests  1014 passed (1014)
   Duration  75.30s (transform 47%, environment 22%, import 16%, tests 11%, setup 4%)

Local server build: Finished dev profile [unoptimized + debuginfo] target(s) in 19m 33s.

Performance

Ran the existing floating-sheet-530 profile locally. Load average before the run was 13.85, 18.83, 23.60.

Viewport Average transition p50/p95 Burst transition p50/p95 Browser RSS after / peak
390×844 595.2 / 824.1 ms 493.1 / 641.7 ms 541,822,976 / 626,532,352 bytes
430×932 474.5 / 726.3 ms 509.6 / 675.0 ms 554,823,680 / 562,593,792 bytes

docs/perf/baseline.json has no floating_sheet profile yet, so these results have no direct baseline comparison. The profile JSON is in the ignored artifacts/floating-sheet-530-profile-round3.json file.

Screenshots

The 12 requested screenshots are attached to this issue:

Section 820 light 820 dark 1440 light 1440 dark
Appearance PNG PNG PNG PNG
Account PNG PNG PNG PNG
Calendars PNG PNG PNG PNG

Known gaps and decisions

  • The Bun e2e wrapper could not load the host's sharp native module. I ran the same e2e script with Node 22.23.3 against the current production build and current worktree server; it passed.
  • DESIGN does not define how row groups cross caller-owned snippets. The implementation uses an explicit marker on the rendered SettingsCard and nearest-card DOM registration because Svelte context does not flow into those snippets. No layout observers or geometry loops are used.
  • No Rust source changed, so Rust fmt/clippy/test gates were not run. Screenshots and profile output are ignored artifacts, not committed.
Round 3 complete. Commit: `c8516493f1e8cdc2ec7d75cc1919bd8d9315ae69` (`job/floating-sheet`). ### Built - SettingsCard marks its rendered card root. Each SettingRow registers with its nearest card, and a coalesced DOM-order pass gives every row except the last exactly one divider. This works across wrapper elements and conditional blocks. - The #530 e2e now asserts computed divider count = rows − 1 for every Settings group on all 21 User/Admin routes. - The existing 1 px grabber/title clearance assertion is unchanged. The check waits for the shared two-frame settle before reading bounds. Files: `packages/ui/src/components/settingRowDividers.ts`, `packages/ui/src/components/SettingRow.svelte`, `packages/ui/src/index.ts`, `apps/web/src/routes/settings/parts/SettingsCard.svelte`, `apps/web/e2e/floating-sheet-530.mjs`. ### Verification (verbatim output) E2E command: `CALTERNAL_E2E_ASSET_OVERRIDE=1 CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/floating-sheet/debug/calternal-server node e2e/floating-sheet-530.mjs` (exit 0). ```text SETTINGS_BACK_AUDIT 21 sections passed SETTINGS_DIVIDER_AUDIT appearance 390px groups=2 rows=8 dividers=6 SETTINGS_DIVIDER_AUDIT appearance 820px groups=2 rows=8 dividers=6 SETTINGS_DIVIDER_AUDIT appearance 1440px groups=2 rows=8 dividers=6 ``` `bun run check`: ```text User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/floating-sheet/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 148 passed (148) Tests 1014 passed (1014) Duration 75.30s (transform 47%, environment 22%, import 16%, tests 11%, setup 4%) ``` Local server build: `Finished dev profile [unoptimized + debuginfo] target(s) in 19m 33s`. ### Performance Ran the existing `floating-sheet-530` profile locally. Load average before the run was `13.85, 18.83, 23.60`. | Viewport | Average transition p50/p95 | Burst transition p50/p95 | Browser RSS after / peak | |---|---:|---:|---:| | 390×844 | 595.2 / 824.1 ms | 493.1 / 641.7 ms | 541,822,976 / 626,532,352 bytes | | 430×932 | 474.5 / 726.3 ms | 509.6 / 675.0 ms | 554,823,680 / 562,593,792 bytes | `docs/perf/baseline.json` has no `floating_sheet` profile yet, so these results have no direct baseline comparison. The profile JSON is in the ignored `artifacts/floating-sheet-530-profile-round3.json` file. ### Screenshots The 12 requested screenshots are attached to this issue: | Section | 820 light | 820 dark | 1440 light | 1440 dark | |---|---|---|---|---| | Appearance | [PNG](https://git.kayg.org/attachments/b2086cc8-bfff-4583-b52b-4f07b443793d) | [PNG](https://git.kayg.org/attachments/010bb39c-47bd-4964-b051-f518a4709400) | [PNG](https://git.kayg.org/attachments/ff5e533a-82f8-4def-a001-e7be85e44136) | [PNG](https://git.kayg.org/attachments/fa7c51a6-44d4-4458-a995-1612c76e40b5) | | Account | [PNG](https://git.kayg.org/attachments/6ba32053-248e-47a8-a0d5-24b4ba4eca8e) | [PNG](https://git.kayg.org/attachments/76347a92-9863-474c-879a-99c4c4830e52) | [PNG](https://git.kayg.org/attachments/2bde255f-02ad-4d02-bdda-3ee7e0c8319e) | [PNG](https://git.kayg.org/attachments/d5f1e443-ff06-4fdb-9c35-5481196b2916) | | Calendars | [PNG](https://git.kayg.org/attachments/e0cba348-df04-4afb-a1df-fc18f90248a5) | [PNG](https://git.kayg.org/attachments/0fe9af7b-7fea-4b3c-be11-1cce6c076b23) | [PNG](https://git.kayg.org/attachments/73af69c6-7c9a-4d73-bcdd-3d4c4632de35) | [PNG](https://git.kayg.org/attachments/aa4e2fe4-1926-4e03-a029-948aa91e0bc1) | ### Known gaps and decisions - The Bun e2e wrapper could not load the host's `sharp` native module. I ran the same e2e script with Node 22.23.3 against the current production build and current worktree server; it passed. - DESIGN does not define how row groups cross caller-owned snippets. The implementation uses an explicit marker on the rendered SettingsCard and nearest-card DOM registration because Svelte context does not flow into those snippets. No layout observers or geometry loops are used. - No Rust source changed, so Rust fmt/clippy/test gates were not run. Screenshots and profile output are ignored artifacts, not committed.
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#530
No description provided.