fix(phone): one push/pop slide for every phone stack; no flash of the previous pane (#1005) #1006
Closed
kayg
wants to merge 19 commits from
t3/smooth-pane-transitions into dev
pull from: t3/smooth-pane-transitions
merge into: kayg:dev
kayg:main
kayg:feat/contrast
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:shared/restore-and-stale-918-919
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:feat/files-s1-identity
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:feat/tasks-delete-promote-duplicate
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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!1006
Loading…
Reference in a new issue
No description provided.
Delete branch "t3/smooth-pane-transitions"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Design note, round 5: rethink of the phone pane transition (decision needed; nothing built)
State. Branch head
810e05294is left as is. Four rounds of patches each fixed what the last strips showed and each exposed the next fault (z-order, start latency, an iOS-only dropped tap), and the round-4 strips still show kayg's original bug: the departed note's card is drawn back over the list for one frame, about 265 ms after the pop starts, in light and in dark; and the pop is not a slide by its curve (13, 32, 55, 74, 84, 89 % of the width in successive frames, then it sits). Evidence, the spike page and its numbers: round 5 comment.Why it keeps breaking (structure, not tuning). One pane's position is written by up to five things (the
data-placeCSS transition, theanimate()head start, the MutationObserver that starts it, the swipe module's inline styles, the settle that unmounts the screen), the note is built ahead under the finger (this is what made iOS drop the tap's click) and then swapped for a plain-note stand-in and then for the editor. I could not isolate which pair draws the flash (it does not reproduce on Mac Safari in 10 sampled pops, only on iOS), and I am not going to guess a seventh patch.Requirements (rounds 1 to 5): no flash of any earlier pane, ever; one slide for tap, back pill and swipe; tap to first motion within one frame; no frame of 33 ms or more while it moves; nothing built or changed under the finger; first paint at or under prod (JS 225.675 KB, CSS 17.984 KB); Reduce Motion crossfade.
Options (spiked on Mac Safari 26.5.2, 10 reps x 3 interleaved rounds; full table in the comment)
startViewTransition, named list/note/dock groups)animate()per layer (fill both; state committed and animation cancelled in the same task asfinished)data-route), so a flash would need the attribute to say two things. The browser reverses an interrupted slide itself.transition-delay: -16ms: 4 to 6 / 13 msAll three: the live editor mounts only after landing (one long frame right after landing with nothing moving, 0 with a free mount); nothing is built under the finger, so the dropped-tap bug cannot recur; the plain-note stand-in, the build-ahead (
lead), the touchend tap workaround, the MutationObserver head start,pane-stack.svelte.tsand the settle timers all go. The first paint should fall by about 1 to 2 KB zlib of first JS plus the lazy chunk (about 2.9 KB zlib) shrinking to the edge swipe; to be measured on the build, since brotli moves the first JS by about ±150 B on any edit.Recommendation
(c): it has the fewest parts (about 180 B of core code, no animation lifecycle) and, with a negative
transition-delay, the same first motion as the JS version (4 to 6 ms push, 13 ms pop on Mac Safari); (b) buys nothing over it except explicit control, which is the thing that failed here; (a) hands the flash to a WebKit area that still has an open flash bug, blocks taps during the slide and cannot start before the new state is drawn.What I need from kayg
dev, not on this branch.cubic-bezier(0.25, 0.8, 0.25, 1)at 300 ms (17, 34, 49, 63, 73, 80, 85, 89 % per frame; at most 17 % a frame) instead of(0.32,0.72,0,1)at 280 ms; per-frame tables for six curves are in the comment.Not established: the cause of the current flash; the real app's synchronous click work under (b) and (c) (the spike's DOM and editor cost are synthetic); iOS looks beyond a 30 fps simulator video.
Fixes #1005 (kayg, prod iPhone 11-07 14:19 IST: "a flash of the previous pane … the black middle pane still disappearing"). On dev
efb10220b.Root cause
PhoneStackbuilt the incoming screen already at its final place, so a push had nothing to slide from: the note popped in whole while the list under it was still on its 380 ms slide to −30 %. The leftover was a hard-edged block because (1) each glass screen painted its own tint, so the list drifting left showed its right edge, a tinted block cut at ~77 % of the width, through the translucent card above it; (2) the screen beneath only wentvisibility:hiddenafter the slide, with no fade; (3) a popped note was unmounted in the same task as the route change, so a pop had no slide. Contacts and Tasks had the same code pasted in.Design (one shared timeline; ruling 11-07 applied)
lib/pane-stack.svelte.ts: each screen isfront/under/ahead. A pushed screen is built atahead, style flushed, then moved tofront. Screens are removed when the screens' own slides have finished (not on a clock, not waiting for the dock's spring).styles/panes.css: onlytranslate+opacity; push 280 ms--ease-drawer, the covered screen drifts −28 % and fades; pop is the reverse; one still glass tint under the stack; Reduce Motion = 140 ms crossfade.lib/swipe-back.ts, lazy): a pointer-down on a row, or a row that keeps the keyboard focus for 80 ms, builds that note's screen off screen (one at most; cancelled when the touch scrolls / focus moves on). A screen built ahead gets 90 ms from being built before it slides (a press spends that before the tap lands); one that was not built ahead slides at once with the plain note, no pre-roll.animationend. At the editor-mount moment the only running animation is the editor's own caret blink (probe, Safari). Text that arrives mid-slide is not redrawn over the plain note.PhoneStack), Contacts, Tasks. (Mail, Files, Settings have no phone push/pop stack today.)First paint (exact head,
perf/web.sh --bundle-only --gate)98f14a3d1efb10220bstyles/grain.css, imported by the idle screens (applied right after first paint, unchanged picture); dead--chrome(4 definitions),--solid,--side-tint; duplicate strip/edge rules; pane styles golfed.swipe-backchunk. Caveat: brotli on this 800 KB file is not monotonic: neutral reorderings of the same code moved it by ±150 B (e.g. removing 41 raw bytes once made it 155 B bigger). The statement order inPhoneStackand theshownNote/activeexpressions were chosen by measuring variants; a future edit there needs a re-measure.swipe-back0.6 → 1.57 KB (it holds the press/focus code and the plain note), fromvim(38.4 → 38.0) andPalette(22 → 21.43, actual 20.7); total unchanged (the Palette donation approved).Mac VM real Safari 26.5, 15 reps each (macos-01, 390 px, ≥ 2 GB free, no swap; idle floor p95 20 ms)
Two full runs; build
3c086c311is the later (build-ahead, focus lead, dock, fade ordering; later commits change only a toast fix, byte-order and specs). Range shows run 1 → run 2.Not met, said plainly
MutationObserver+ rAF): the click handler and Svelte flush finish by +6 ms; the style change lands in the frame after; frames are 14 → 34 → 49 ms (20, 15, 17 ms: steady). A CSS transition made in the tap's own task becomes visible two rAFs later (first frame starts it at progress 0). Not the hidden list (visibility forced visible: same), not the list dock (now pre-built: same), not scripts (no rAF/timeout callback > 2 ms). Push looks faster only because its place change happens after a timeout, aligned just before a frame. Getting pop under one frame would need the first frame to start the animation part-way (WAAPI with a head start), which I did not do.3c086c311; the strips are at08b0d8384; the final head only adds a toast fix (below), byte order and specs.Bugs this round found and fixed
settled);pointercancelfinishes the slide (earlier).Specs (4 projects, 32 phone/navigation files +
pane-transition.spec.ts,--workers=1)8005cc03e(code of the final head)3c086c311: 738 passed; its failures were the toast bug and spec waits, fixed)6924438c7(final; spec-only change)journal-trash"in this session",tint"tagged, dark: on a phone › swipe tints"journal-trash"in this session"journal-trashand the tint swipe pass alone: 40/40 (--repeat-each=10) on the branch; the same 32-file subset on devefb10220b(chromium-390) is 609 passed, 0 failed;journal-trashfails with another spec's task lines in today's shared daily note (spec-order pollution, seen 3 of 6 of my subset runs). Flaky rule: not reproducible → not blocking, to be confirmed by the daily train.pane-transition.spec.ts: 30/30 in webkit-390 (--repeat-each=6), 15/15 in chromium-390 at the final head; it waits for the editor before dragging and no longer expects the note to be gone (a focused row builds it ahead again).bun run check0 errors;bun run lintok.Frame strips
Attached to the comment: quick tap and a 350 ms press, push and pop, before (dev) and after, at 2× from the iOS 27 simulator Home Screen app; a real edge swipe back (XCUITest drag from x = 23); videos and the full frame sets.
🤖 Generated with Claude Code
Round 4 (head
810e05294)Pop z-order fixed (the list's bar fades in after the card has left; never over it), pop starts with an
animate()head start (tap to first motion p50 13 ms on Mac Safari), cold-push tail measured ABAB x5 against dev with a dev-vs-dev control (the tail is the epoch), first paint JS 225.666 KB / CSS 17.820 KB (prod 225.675 / 17.984), and a tap bug that only iOS shows (the row's click was dropped while a note was built under the finger) fixed by making the tap at touchend. Numbers, strips and what is not met: see the round 4 comment.Frame evidence for #1005 (iOS simulator, 2×): before/after push and pop sheets, Home Screen videos, edge-swipe sheet and the full frame sets.
Round 2 frame evidence (iOS simulator Home Screen app, 2×): quick tap and 350 ms press, push and pop, before (dev) and after, plus the full frame sets.
Round 3 frame evidence (iOS simulator Home Screen app, 2×): quick tap and 350 ms press, push and pop, before (dev) and after; a real edge swipe back; full frame sets.
Round 4: pop z-order, pop start, cold-push tail, and a tap bug found on the way
Head
810e05294. Everything below was measured at this head unless it says otherwise.New finding: on a real iPhone a tap on a row did nothing in rounds 2 to 4 (fixed here)
Rows only opened with a mouse. In the Home Screen simulator the log (an on-screen event overlay, not committed) showed
pointerdown, touchstart, pointerup, touchendand then no mousedown, mouseup or click, while taps on the back pill, the places rows and the dev build were fine. Cause: Safari sends no click for a tap when something visible appears during the touch (WebKit's content-change check for hover menus), and the note built under the finger on press (lead) is that. Checked, each on the same build: hiding the parked screen (visibility) lets the click through;opacity: 0on it, or showing it 45 ms into the press, does not. Hiding it until the slide costs the push a first frame (first motion p50 27 ms and 3 slow slide frames per run in the same epoch), so it was reverted. The fix istapinlib/swipe-back.ts: attouchendof a touch that went down on a led row (primary finger, not moved, under 400 ms) the browser's own click is cancelled and the row gets one click of ours (detail: 1, so it is not taken for a key press). That also skips the wait between touchend and the click. Spec: every browser click is dropped (as iOS does), a touch tap must still open the note and the row must get exactly one click of ours and none of the browser's.1. Pop z-order
The list's bar is no longer drawn over the departing card: it is hidden while a note covers the list, and on a pop it fades in (170 ms, starting 183 ms after the tap) once the card has mostly left. Frame by frame in the spec: no frame has the bar above 2 % opacity unless the card is past 60 % of the width. Same in the edge swipe (the bar stays hidden for the whole drag and the release) and a cancelled swipe. On the Home Screen the dock is positioned against the stack, so its wrapper has no box (
display: contents); a wrapper withposition: relativehad lost the dock there.2. Pop start
element.animate()head start in the tap's own task (delay: -15ms, same curve and duration), then the state change carries on. Mac Safari, 15 reps each: pop tap to first moving frame p50 13 ms, max 14 (warm press), 8 / 6 ms from the keyboard. A pop that follows a swipe release keeps the finger's own motion (no head start, Reduce Motion skips it).3. Cold push, ABAB x5 at this head (Mac VM, one session, both perf locks, 15 reps each, cold press)
A = dev (4286), B = this PR, C = a second dev copy as control.
Dev and its control show the same tail (win max 118 to 405 ms, 15 to 24 frames ≥100 ms in runs 3 to 5), so the tail is the epoch, not the PR; the PR has about half the slow frames dev has and its slide is clean in 4 of 5 runs (run 4 is the epoch where dev and control spike too). The 102 ms slide frame of the earlier run 2 did not come back. Not met: cold press tap to first moving frame is 24 to 29 ms p50 (target 20); warm press is 7 ms.
Mac Safari gate (15 reps each)
These are
requestAnimationFramegaps (main thread); the slide istranslate/opacityonly. I did not get the pop's two main-thread frames under 33 ms; I did not chase the landing one further.First paint (
perf/web.sh --bundle-only --gate, this head)First JS 225.666 KB (prod
98f14a3d1: 225.675), first CSS 17.820 KB (prod: 17.984). All budgets ok. Budget moves only, nothing raised:swipe-back1.85 to 2.0 KB (the tap),sveltechunk 9.75 to 9.6 KB, total unchanged. The first JS moves by about ±150 B with any neutral edit (brotli on 800 KB, chunk names feed into it), so the dock delay (183 ms) was picked among 30 neutral variants measured in parallel worktrees; any later edit to swipe-back, PhoneStack's style or the stack code needs a re-measure.Specs and checks (this head)
journal-trash.spec.ts:25"trash today, open Journal in this session…" in webkit-390 (it reads another note's text). It passes 8 of 8 when run alone at the same head, and it has failed in some earlier full runs of this branch too, so I take it for a load flake of that spec, not for this change; I have not run it on dev under the same load.svelte-check: 0 errors (20 warnings),lint: all pass.Strips at 2× (iOS simulator, Home Screen app, iPhone 18 Pro)
Each image: tap pop, edge swipe (held at 83 % and released), cancelled swipe (released at 17 %), every second recorded frame, labelled with the source frame number (about 60 per second; windows picked by eye).
beforeis the dev build,afteris this head. The note slides away over the list with no bar over it; the bar fades in after the card has gone; dev vanishes in one frame with a dark gap.after, light

after, dark

before (dev), light

before (dev), dark

The before windows for the edge swipe and cancelled swipe are approximate (dev has no 1:1 slide of the list under the finger).
🤖 Generated with Claude Code
Round 5: evidence for the rethink (design note is at the top of the PR description)
Branch left as is (head
810e05294). The spike is a separate throwaway page, not on this branch: pane-spike.html.txt (three variants behind?v=a|b|c, rows, card, glass bars and backdrop at about the weight of the real screens, the live editor stood in for by a 90 ms main-thread task after landing;&head=16is the 16 ms head start).What I could and could not establish about the current flash
animate()head start (280 ms minus its 15 ms), just before the 340 ms settle. I read frame 192 in the last round as the next push; it is not (the next tap is 2 s later). My mistake.data-placechange, theanimate()head start on the sametranslate, the MutationObserver that starts it, the swipe module's inline styles, and the settle that unmounts the screen, while a focused row can mount the note ahead again.getBoundingClientRectof each screen), no reversal, no card back; so it is iOS (compositor) only, and my iOS A/B with the head start switched off did not run cleanly (Home Screen apps restore their last route, so the tap script drifted). Treat the cause as "handoff between more than one animation system, or a screen mounted again", unproven.cubic-bezier(0.32,0.72,0,1)over 280 ms with the 15 ms head start moves the card 13, 32, 55, 74, 84, 89 % of the width in successive frames (steps of 19 to 23 %, 90 % after 6 frames) and then creeps 10 % over 12 more. Candidate:cubic-bezier(0.25,0.8,0.25,1), 300 ms: 17, 34, 49, 63, 73, 80, 85, 89 % (max step 17 %). Per-frame tables for six curves are in the spike notes below.Spike on Mac Safari 26.5.2 (real Safari, safaridriver, 390 px, 10 reps x 3 interleaved rounds, perf lock held)
Tap = the page's
clickevent; "first motion" = first frame the card is off its rest place (for VT: first frame afterready, where the animation starts). Frames arerequestAnimationFramegaps over the 330 ms of the slide.animate()per layer, state committed on finish, 16 ms head starttransition-delay: -16msready(old state captured, new state rendered, animation about to start) comes 12 to 15 ms after the tap in (a) with a callback that only changes one attribute; with the real app's update (a list or note render inside the callback) that is added to every start, because motion cannot begin before the new state is drawn.finished.View Transitions in WebKit today (web search)
::view-transitionis nowposition: absolute;document.activeViewTransitionadded. Before 26.2 those bugs exist on the phones that are not updated.ViewTransition.types(Shopify/horizon #104).document.getAnimations()lists the::view-transition-*ones), so pausing and scrubbingcurrentTimeis possible, but the DOM has already switched when the transition starts, so a cancelled swipe is a second transition; not attempted.First-paint bytes
Not measured on a build (nothing is built in this round). Core code of each option, minified, zlib (brotli will be about 10 % smaller): a 415 B (JS plus the
::view-transitionCSS), b 313 B, c 179 B. The current branch's first-paint machinery that a rethink deletes:pane-stack.svelte.tsabout 730 B zlib, plus the led/plain/fading/stable logic in PhoneStack (about 1.6 KB zlib added over dev); the lazyswipe-backchunk (about 2.9 KB zlib) shrinks to the edge swipe. Any of a, b, c should land below the branch's first JS 225.666 KB and prod's 225.675 KB by a margin, but brotli moves the first JS by about ±150 B on any edit, so that is to be measured on the build, not assumed.Per-frame table (percent of the width, 60 fps, head start where stated)
Limits of the spike: synthetic DOM and editor cost, one phone width, Mac Safari for numbers and a coarse simulator video for looks; the real click handler's synchronous work (route, state, DOM) is not included and must be re-measured on the build (the earlier real-app cold path, which also built a plain note, was 25 ms p50).
🤖 Generated with Claude Code
Superseded by #1014 (the option (c) rebuild from
dev, as decided). This PR is left open for you to close.🤖 Generated with Claude Code
kayg referenced this pull request2026-11-08 05:23:21 +00:00
kayg referenced this pull request2026-11-08 07:49:49 +00:00
Closing: superseded by #1014 (rebuilt from dev with the simpler always-mounted design after the rethink rule; the branch stays for reference).
kayg referenced this pull request2026-11-08 11:28:01 +00:00
Pull request closed