PERF: tab switch to fully painted on a hard disk with heavy data (Calendar much slower than Photos); stress, measure, fix #549
Open
opened 2026-09-30 18:01:25 +00:00 by kayg
·
65 comments
No Branch/Tag specified
dev
wip/draw3-1101
wip/draw2-1101
wip/draw-1101
wip/directory-1199-r
wip/directory-1199
wip/delete-1119
wip/collabrev-1197
wip/collabloss-1197
wip/cards2-1083
wip/cards-1083
wip/canvas-visual
wip/canvasvis2-976
wip/calhdr-1112
wip/calcards-1115
wip/browserfix
wip/blocks-1125
wip/allday-1107
wip/agenda-decks
wip/agenda-1086
wip/adv7c-1105
wip/txentry-1198
wip/trayicons2-1095
wip/trayicons-1095
wip/tagperf-1186
wip/sidebar3-1094
wip/rev2-webperf
wip/rev2-money-ident
job/adv-1202
wip/restyle-notes
wip/previewcard-1098
wip/palette2-1123
wip/palette-1093
wip/onboard2-1141
wip/onboard-1141.aborted-early
wip/onboard-1141
wip/nlpchip-1127
wip/morph-1104
wip/merge-round-7c5
wip/merge-round-7c4
job/notifloop-1194
wip/merge-round-7c3
wip/merge-round-7c2
wip/merge-round-7c
wip/mchrome-1084
wip/mailghost2-1094
wip/mailghost-1094
wip/kbpreview2-1118
wip/kbpreview-1118
wip/kanban-1092
wip/importhang-1121
wip/hiderev-1153
wip/hide4-1153
wip/hide3-1153
wip/hide2-1153
wip/hide-1153
wip/editreg-1132
job/collabloss-1197
wip/editorrail3-1113
wip/editorrail2-1113
wip/editorrail-1113
wip/e2e-b2-1071
wip/e2e-b-1071
wip/draw4-1101
job/onboard-1141
job/hide-1153
job/perf-1124
job/perf2-1124
job/tocrail-1191
job/restyle-settings
wip/restyle-settings
job/segmented-1200
wip/notifloop-1194
job/tagperf-1186
wip/segmented-1200
job/restyle-files
job/tagdnd-1187
job/merge30
job/cards-1179
wip/cards2-1179
wip/cards-1179
wip/tocrail-1191
wip/tagdnd-1187
wip/restyle-files
wip/perf-1124
wip/merge30j
job/restyle-notes
job/wizchoices-1140
wip/wizchoices-1140
wip/restyle-1190
job/moneyfmt-1180
job/txentry-1198
wip/moneyfmt2-1180
wip/moneyfmt-1180-r
wip/moneyfmt-1180
job/pillglass-1189
job/flags-1181
wip/flags-1181
job/restyle-1190
job/restyle-mailmoney
job/restyle-search
job/settingsreg-1195
job/wizard-1140
site/website
wip/wizardrev2-1140
wip/wizardrev-1140
wip/wizard5-1140
wip/wizard4-1140
wip/wizard3-1140
wip/wizard2-1140
wip/wizard-1140
wip/pillglass-1189
wip/settingsreg-1195
job/merge29
job/fu-1171
wip/merge29j
wip/fu-1171
job/fu-1166
job/directory-1199
job/proflog-1204
job/txresearch-1188
wip/fu-1166
job/merge28
job/search-1066
wip/search-1066
wip/merge28j
job/gateslot-1182
job/bulkimport-1157
job/mailnet-1160
wip/mailnetrev-1160
wip/mailnet-1160
wip/bulkrev-1157
wip/bulkimport-1157
job/startup-1161
wip/startup-1161
job/merge27
job/linkcards-1151
wip/linkcards3-1151
wip/linkcards2-1151
wip/linkcards-1151
job/traydate-1144
wip/traydate3-1144
wip/traydate2-1144
wip/traydate-1144
job/draw-1101
wip/merge27j
job/blockpill-1152
wip/blockpill3-1152
wip/blockpill2-1152
wip/blockpill-1152
job/minihover-1149
wip/minihover2-1149
wip/minihover-1149
job/merge25
wip/merge25-r
wip/merge25b
wip/merge25
job/inspector-1129
job/tags-1110
wip/inspector3-1129
wip/inspector2-1129
wip/inspector-1129
wip/tagsrev-1110
wip/tags2-1110
wip/tags-1110
job/dates-1148
wip/datesrev-1148
wip/dates2-1148
wip/dates-1148
job/licence-1145
wip/licence2-1145
wip/licence-1145
job/selfhost-1156
job/merge23
wip/merge23
job/tagfilter-1109
wip/tagfilter2-1109
wip/tagfilter-1109
job/kbd-1134
wip/kbd2-1134
wip/kbd-1134
job/palfoot-1137
wip/selfhost-1156
wip/palfoot2-1137
wip/palfoot-1137
job/toggle-1158
wip/toggle-1158
job/kbpreview-1118
job/docratchet-1155
job/perflint-1133
job/devtests-1159
wip/docratchet-1155
wip/devtests-1159
job/segv-1136
wip/toast-1142
wip/segv-1136
job/toast-1142
job/blockreload-1147
wip/blockreload-1147
job/font-1150
wip/font-1150
job/importui-1120
job/minimonth-1149
wip/importui-1120
wip/minimonth-1149
job/depcheck-1146
wip/perflint-1133
wip/depcheck-1146
job/calcards-1115
job/blocks-1125
job/plus-1128
job/shift-1138
wip/plus2-1128
wip/plus-1128
wip/shift-1138
job/moneyfid-1130
job/editorrail-1113
wip/moneyrev-1130
wip/moneyfid-1130
job/noext-851
wip/noext-851
wip/noext3-851
wip/noext2-851
job/week-1135
wip/week-1135
job/editreg-1132
job/smoke-1122
wip/smoke-1122
job/docs-1143
job/palette2-1123
job/calhdr-1112
job/nlpchip-1127
job/mailghost-1094
job/reconnect-1131
wip/reconnect-1131
job/trayicons-1095
job/delete-1119
job/importhang-1121
job/cards-1083
job/palette-1093
job/mchrome-1084
job/e2e-a-1071
job/canvas-visual
job/previewcard-1098
job/allday-1107
wip/e2e-a2-1071
wip/e2e-a-1071
job/e2e-b-1071
job/adv7c-1105
job/kanban-1092
job/agenda-1086
job/merge-round-7c
job/morph-1104
wip/surfaces-p2
job/merge-round-9
wip/merge-round-9
job/7cfix-small
wip/7cfix-small
job/mailui-1078
job/merge-round-8
wip/merge-round-8
wip/mailui-1078
job/mailround-1038
job/applemail-accept
wip/settitle-1068
wip/mailround2-1038
wip/mailround-1038
wip/e2e-7b
job/crash-1069
wip/crash-1069
job/searchlost-1066
wip/searchlost-1066
job/7b-reconcile
job/flake-1065
wip/flake-1065
wip/merge-round-7b7
wip/merge-round-7b6
wip/merge-round-7b5
wip/merge-round-7b4
wip/7b-reconcile
job/appupdate-1059
job/nfd-1044
wip/appupdate-1059
job/e2e-7b
job/loop-1062
wip/loop-1062
job/pdfprev-1045
job/invtoggle-1053
wip/pdfprev-1045
wip/nfd-1044
wip/invtoggle-1053
job/7bfix-e2e
job/mailstress-b
wip/7bfix-e2e
wip/mailstress-b
job/7bfix-adv
wip/7bfix-adv
job/mailstress-a
job/stack-1054
wip/stack-1054
wip/mailstress-a
job/mailstress-1038
wip/mailstress-1038
job/upload500-1051
wip/upload500-1051
job/share-1034
wip/share-1034
job/syncerr-1037
job/7bfix-photos
wip/7bfix-photos
job/paste-1036
job/setside-1039
wip/setside-1039
wip/paste-1036
job/lease-1042
wip/syncerr-1037
wip/lease-1042
job/7bfix-data
job/passkeybind-1043
wip/apprevoke-1041
job/invite-1035
wip/invite-1035
job/merge-round-7b2
wip/merge-round-7b2
job/mailproxy-486
job/apprevoke-1041
job/rebuild-1033
job/pillborder-1029
wip/pillborder-1029
wip/mailproxy-486
wip/applemail-486
job/headless-998
wip/headless-998
job/groups-1028
wip/groups-1028
job/rebuildwarn-1016
wip/rebuildwarn-1016
job/startup-1011
wip/startup-1011
job/monthpill-1009
job/bgthumb-1025
job/sharetitle-1012
wip/monthpill-1009
wip/bgthumb-1025
wip/sharetitle-1012
job/canvas-cards-977
wip/canvas-cards-977
job/canvas-pencil-978
job/canvas-sketch-990
wip/canvas-sketch-990
wip/canvas-pencil-978
job/canvas-files-989
wip/canvas-files-989
job/canvas-collab-991
wip/canvas-collab-991
job/weekscroll-1018
wip/weekscroll-1018
wip/canvas-core-976
job/canvas-core-976
job/round-drag
wip/round-drag
job/round-settings
job/browserfix
wip/oapi-974
job/oapi-974
job/hist2-integrate
job/mailhtml-726
wip/mailhtml-726
wip/hist2-integrate
job/moneyfu-984
job/drag-1015
wip/drag-1015
job/rename-1017
wip/rename-1017
job/hist2-api
wip/hist2-api
job/oneacct-1014
wip/oneacct-1014
wip/moneyfu-984
job/hist2-bench
job/hist2-restore
wip/hist2-bench
job/hist2-write
job/hotfix-724
wip/hotfix-724
wip/hist2-write
wip/hist2-restore
job/hist2-store
job/hist2-ui
wip/hist2-ui
wip/hist2-store
job/searchstarve-965
job/shutdown-963
wip/shutdown-963
wip/pubedit-981
job/pubedit-981
job/analytics-973
wip/searchstarve-965
job/authflash-850
job/weeklane-969
job/pvtitle-1004
job/hist-975
wip/authflash-850
job/voicepill-617
wip/pvtitle-1004
job/headring-1003
wip/weeklane-969
wip/voicepill-617
wip/headring-1003
wip/analytics-973
job/agentscope-980
wip/thumbsandbox-988
job/thumbsandbox-988
wip/hist-975
job/links-856
wip/links-856
job/davetag-966
wip/davetag-966
job/filesstorm-1000
job/hoverpad-725
wip/filesstorm-1000
job/ffmpegblas-993
job/merge-round-7a
wip/hoverpad-725
wip/ffmpegblas-993
job/nowdot-1002
wip/verify-7a
job/noteid-857
wip/nowdot-1002
wip/noteid-857
wip/merge-round-7a
wip/agentscope-980
job/imapedge
job/a11yfix2
wip/imapedge-941
wip/imapedge
wip/a11yfix2
job/notetask-986
job/logheading
wip/logheading-998
job/textthumb-652
job/photolive-987
wip/photolive-987
job/davactive-983
job/savefix-985
job/tabicons-607
wip/davactive-983
wip/tabicons-607
wip/notetask-986
wip/savefix-985
job/dirid-627
job/buildspeed-1007
wip/dirid-627
job/agenda-decks
job/perfguards-impl
job/undo-a11y
wip/undo-a11y
job/mailperf
job/wal-824
wip/settings-50
job/settings-50
job/notesfilter-606
wip/notesfilter-606
job/surfaces-p2
wip/wal-824
job/maillayouts
wip/mailperf
wip/maillayouts
job/taskmeta-659
job/money-ident
wip/money-ident
wip/taskmeta-659
job/errstates
wip/perfguards-impl
job/headings-881
wip/headings-881
wip/errstates
job/voice-619
job/gaps-827
job/notesperf
wip/notesperf
wip/voice-619
job/hddsql-549
job/perf-stream-668
wip/perf-stream-668
wip/deeplinks-fix
job/deeplinks-fix
job/authfix
job/docsfix-rust
wip/docsfix-rust
job/webperf
job/docsfix-web
job/datafix2
job/webdav-lock-476
job/copyfix
wip/copyfix
wip/webperf
job/focus-658
wip/protofix
job/mediafix
job/protofix
wip/mediafix
job/agentfix
job/hhmm-724
wip/agentfix
job/undo-722
job/reuse
wip/webdav-lock-476
wip/reuse
job/scopefix
job/datafix
wip/hhmm-724
wip/undo-722
job/surfaces-p1
wip/hddsql-549
job/voicememos-618
wip/datafix2
wip/surfaces-p1
job/fix-940
wip/fix-940
job/blaze-surfaces
wip/datafix
wip/blaze-surfaces
job/taskday-655
job/linknav-639
wip/linknav-639
wip/gaps-827
job/isolation-707
job/audiophotos-720
wip/audiophotos-720
job/advfind-664
wip/voicememos-618
wip/taskday-655
wip/isolation-707
wip/advfind-664
wip/scopefix
wip/focus-658
job/testgaps
wip/testgaps
job/overscroll-718
wip/authfix
job/deps
wip/overscroll-718
job/rev2-agentfix
job/rev2-money-ident
job/rev2-mailperf
wip/deps
job/hardening-728
wip/hardening-728
job/searchgen-832
wip/searchgen-832
job/photopw-849
job/mailsql-825
wip/photopw-849
job/sharefix
wip/sharefix
job/rev2-mailhtml-726
job/rev2-perfguards
job/copyval-723
job/lightglass-r2
wip/lightglass-r2
wip/docsfix-web
job/copy-audit
job/macinterop-staging-r2
job/design-sync
job/rev2-taskmeta-659
job/rev2-webperf
job/docs-audit
job/rev2-advfind-664
job/rev2-mailproxy-486
job/states-audit
job/rev2-datafix
job/design-drift
job/test-gaps
job/rev2-voicememos-618
job/rev2-mediafix
job/rev2-deps
job/rev2-datafix2
job/licence-audit
job/issue-hygiene
job/rev2-protofix
job/rev2-voice-619
job/rev2-isolation-707
job/rev2-surfaces-p1
job/deeplink-audit2
job/rev2-audiophotos-720
wip/test-gaps
job/rev2-overscroll-718
job/rev2-undo-722
wip/states-audit
job/rev2-dropmd-719
job/rev2-linknav-639
job/merge-7b-plan
wip/merge-7b-plan
job/rev2-taskday-655
wip/mailsql-825
job/rev2-webdav-lock-476
job/rev2-browserfix
wip/design-drift
job/rev2-hddsql-549
wip/deeplink-audit2
job/rev2-scopefix
job/rev2-authfix
job/rev2-hardening-728
job/rev2-wal-824
job/rev2-sharefix
job/calsidebar-638
job/chrome-audit
job/ioperf
wip/ioperf
wip/chrome-audit
wip/calsidebar-638
job/dropmd-719
wip/dropmd-719
job/ocr-build
wip/ocr-build
job/blaze-settings
wip/copyval-723
job/toastring-721
wip/toastring-721
job/deployfix-732
wip/deployfix-732
wip/blaze-settings
job/money-import-recheck
job/rev-a11y
job/perf-arch-db
job/rev-7b-data
wip/textthumb-652
wip/perf-arch-db
job/sec-protocols
job/sidehdr-660
job/rev-7b-security
job/research-surfaces
job/rev-design-gaps
job/rev-mcp-api
wip/sidehdr-660
job/perf-arch-memory
wip/sec-protocols
job/perf-arch-bundle
job/snapedge-714
wip/rev-mcp-api
job/sec-supplychain
wip/research-surfaces
job/perf-arch-sync
job/rev-consistency
job/perf-arch-server
wip/perf-arch-server
wip/perf-arch-memory
job/perf-arch-io
job/perf-arch-client
job/sec-fs
job/sec-mcp-scopes
job/sec-sharing
job/perf-guards
job/sec-browser
job/sec-admin-deploy
job/sec-auth
wip/snapedge-714
job/bgpicker-717
wip/perf-arch-bundle
wip/money-import-recheck
job/advsetup-654
wip/bgpicker-717
wip/advsetup-654
job/burst-709
job/kbdcaps-710
job/app-pw-chooser
wip/burst-709
wip/app-pw-chooser
job/imaptest-625
wip/kbdcaps-710
job/fix-499
wip/fix-499
job/perf-mut-667
job/calimg-589
job/perf-snap-666
wip/calimg-589
wip/perf-snap-666
wip/perf-mut-667
job/perf-cache-665
wip/perf-cache-665
job/voicefiles-620
wip/voicefiles-620
job/admin-burst-705
wip/admin-burst-705
job/voicememos-review
wip/voicememos-review
wip/ryw-653
job/ryw-653
job/writeonopen-661
job/instant-663
wip/writeonopen-661
job/money-import-review
wip/money-import-review
wip/importjs-610
review/integrations-407-round6
wip/integrations-review
job/dragghost-612
wip/dragghost-612
job/integrations
wip/integrations
job/decider-656
job/merge-round-6
job/perf-rerun
wip/merge-round-6
job/integrations-review-round5
job/selalign-576
wip/selalign-576
job/mcp-events-491
job/files-631
job/cal-e2e-569
wip/cal-e2e-569
job/reload-423
wip/reload-423
wip/mcp-events-491
wip/files-631
job/notesbridge-644
wip/notesbridge-644
job/editor-series
job/calcard-series
wip/calcard-series
job/mcp-events-review-491
wip/mcp-events-review
wip/editor-series
job/quirks-546
job/integrations-recheck
job/tocrail-636
wip/tocrail-636
wip/quirks-546
wip/reminders-643
job/reminders-643
wip/davscale-573
job/davscale-573
job/integrations-review
wip/ocr-eval-584
job/ocr-eval-584
job/esc-537
wip/esc-537
job/toastname-586
wip/toastname-586
job/submenu-579
wip/submenu-579
job/tasks-mode
wip/tasks-mode
job/agentdocs-630
job/dupwrite-634
wip/agentdocs-630
wip/dupwrite-634
job/lightglass-588
wip/lightglass-588
job/tabswitch-549
job/ghosttask-623
wip/ghosttask-623
job/toaststack-616
job/weekstate-609
job/mailsync-613
wip/mailsync-613
wip/weekstate-609
job/maildup-626
wip/tabswitch-549
wip/maildup-626
wip/toaststack-616
job/motion-611
wip/motion-611
job/tlstest-601
wip/tlstest-601
job/perf-495
job/floating-sheet
wip/floating-sheet
job/remdup-585
wip/remdup-585
job/fix-502
wip/fix-502
job/attachplay-622
job/perf-batch
wip/perf-batch-563
wip/perf-495
hotfix/mail-sync-diag
job/mail-m3
wip/mail-m3
job/attach-poof-603
job/calhover-608
job/editorbar-604
job/mentions-605
job/merge-round-4
job/allday-514
wip/merge-round-4
wip/allday-514
job/merge-round-4a
wip/merge-round-4a
job/sharestack-580
job/fix-501
wip/sharestack-580
wip/fix-501
job/perf-batch-563
job/apw-cache-review
wip/apw-cache-review
job/probe-520
wip/probe-520
job/mac-393
wip/mac-393
job/header-571
job/flake-513
wip/flake-513
job/docs-thumb-547
wip/header-571
job/webcal-572
wip/webcal-572
wip/shortcuts-542
job/shortcuts-542
wip/docs-thumb-547
job/caldav-stress
wip/caldav-stress
wip/sweep-478
job/apw-cache-512
wip/apw-cache-512
job/money-empty-540
wip/restart-505
wip/money-empty-540
wip/fix-510
job/restart-505
job/fix-503
job/perf-496
wip/perf-496
job/fix-498
wip/fix-498
job/info-inspector-465
wip/info-inspector-465
job/fix-510
job/fix-507
wip/fix-507
wip/fix-503
job/fix-493
job/money-kinds
wip/money-kinds
job/hygiene-548
job/merge-round-3
wip/fix-493
job/drag-snap-536
wip/merge-round-3
wip/merge-round-0930
wip/drag-snap-536
job/align-538
wip/align-538
job/bg-flash
wip/bg-flash
job/money-import
job/search-count-544
wip/search-count-544
wip/money-import
job/settings-key-541
wip/settings-key-541
job/toast-539
job/preview-421
wip/preview-421
wip/toast-539
job/tasks-500-531
job/title-plain-526
wip/title-plain-526
wip/tasks-500-531
job/notes-bridge
wip/parity-484
job/parity-484
job/files-slow
job/crash-525
wip/notes-bridge
wip/files-slow
wip/crash-525
job/kbd-motion-527
wip/bg-422
job/analytics-504
wip/analytics-504
wip/kbd-motion-527
job/upload-pill-523
wip/upload-pill-523
wip/tray-order
job/tray-order
wip/overflow-mid
wip/merge-round-2
job/perf-494
wip/perf-494
wip/mcp-fast-492
wip/motion-477
wip/asr-ab-489
wip/theme-variants-506
wip/overflow-511
wip/week-header-508
wip/attach-427
job/dav-delete-471
job/iso-435
wip/iso-435
wip/files-sel-keys
wip/dav-delete-471
job/align-253
job/siwc-490
wip/siwc-490
job/money-kinds-review
wip/align-253
wip/money-kinds-review
job/small-bugs-3
wip/overlay-title-487
wip/multiget-500
wip/hidden-420
wip/webcal-ui
wip/webcal-431
job/perf-367
job/location
wip/small-bugs-3
wip/location
wip/perf-367
wip/admin-deny-483
job/tag-unicode-473
wip/tag-unicode-473
job/blur-436
wip/photos-470
wip/blur-436
wip/small-bugs-4
wip/hunt-20260930
wip/settings-hdr-482
wip/chips-416
job/dedup-375
wip/dedup-375
job/doc-stack
wip/doc-stack
job/tokens-literals
wip/tokens-literals
job/jobs-leftovers
wip/send-fast
wip/paste-467
wip/money-numbers
job/money-plugin
wip/money-plugin
job/break-dav
wip/merge-batch
wip/crossday-469
wip/mac-verify
wip/mail-m2
wip/break-dav
wip/money-review2
job/money-md
job/modes-424
wip/money-md
wip/jobs-leftovers
job/agenda-413
wip/agenda-413
wip/modes-424
job/recog-417
wip/recog-417
wip/bounce-425
wip/ab-384-luna
job/webdav-perf
wip/webdav-perf
job/toast-ring
wip/toast-ring
job/money-review
wip/money-review
wip/micro-motion
wip/settings-card
wip/minical
job/notes-imap-428
job/least-priv
wip/ui-small-2
wip/flaky-426
wip/drag-end-418
job/jank
wip/jank
wip/least-priv
wip/docs-site
job/agenda
job/sec-batch
wip/sec-batch
wip/per-user-index
job/area-calendars
wip/area-calendars
job/parity
wip/parity
job/documents-research
wip/documents-research
job/test-infra
job/reminders-sync
wip/small-bugs-2
wip/reminders-sync
wip/gestures
job/google-oauth
wip/tags-merge
wip/tags
job/e2e-theme
wip/e2e-theme
job/icon-align
wip/test-infra
wip/select-align
wip/editor-385
job/voice
wip/webdav
job/webdav
job/app-pw-ui
job/editor-integrity
wip/editor-integrity
wip/voice
wip/quota
wip/cal-followups
wip/icon-align
job/composer-scale
wip/composer-scale
job/jobs-page
wip/jobs-page
job/hig-type
wip/hig-type
wip/app-pw-ui
job/motion-spring
job/mcp
wip/motion-spring
wip/mcp
job/small-bugs
wip/push-hosts
job/profile-sign
wip/touch-369
wip/profile-sign
job/mobile-focus
wip/mobile-focus
wip/ui-polish-354
wip/small-bugs
wip/dup-task
job/toast-polish
job/app-pw-scopes
wip/toast-polish
wip/app-pw-scopes
wip/cli-agent
wip/selection-pills
job/preview-attach
wip/preview-attach
job/dav-proppatch
wip/dav-proppatch
wip/cal-switcher
job/atomic-race
wip/atomic-race
job/photos-shared
wip/photos-shared
wip/cal-grid
wip/note-rewrite
wip/search-rebuild
job/mail-m1
job/paperless-import
wip/paperless-import
wip/mail-m1
wip/hidden-activity
wip/search-d
wip/pricing-research
wip/cursors
wip/auto-scheme
job/single-pills
wip/single-pills
wip/xuser-matrix
wip/money-format
wip/app-pw-setup
wip/purge-dos
wip/vault-health
wip/caldav-apple
wip/xuser-audit
wip/e2e-green
wip/tabbar
wip/adv-harness
wip/maple-mono
job/search-fix
wip/search-fix
wip/search-perf-c
job/adv-harness
wip/sidebar-headers
job/glass
wip/temp-index
job/polish
wip/polish
wip/file-protocols
wip/money-research
wip/glass
wip/voice-models
wip/collab-redo
job/voice-research
wip/hunt-20260928
wip/notes-actions-research
wip/search-pad
wip/search-perf
wip/search-sticky
wip/editor-undo
wip/chrome-rules
wip/motion
wip/appearance-research
wip/appearance
wip/audit-bugs
wip/cal-glass
wip/block-actions
wip/authz-order
wip/event-stripes
wip/chrome-sidebar
wip/auth-flaky
wip/robust-2
wip/gate-fix
wip/menu-blur
wip/import-calternaljs
wip/tray-fix
job/import-calternaljs
wip/index-order
wip/audit-fixes
wip/search-chevrons
research/mail
wip/phone-chrome
wip/dedup-break
wip/csp
wip/ui-audit
wip/select-toast
wip/perf
wip/flat-layout
wip/fonts
wip/event-tint
wip/sync-converge
wip/data-split
wip/glass-audit
wip/robustness
wip/sync-chaos
wip/search-thumbs
wip/fuzz
wip/menu-icons
wip/search-pill
wip/sync-changing
wip/heading-links
wip/date-formats
wip/a11y
wip/break-editor
wip/e2e-fix
wip/settings-sections
wip/sync-root-guard
wip/search-palette
wip/share-edit
job/toasts
wip/toasts
wip/cont-analytics
wip/authz-review
wip/popovers
wip/overlay-glass
wip/change-feed
wip/editor-modes
wip/composer-align
wip/cont-agenda
wip/agenda-merge
job/agent-conventions
wip/agent-conventions
wip/backend-misc
job/route-audit
wip/route-audit
wip/ui-batch
wip/heif-hardening
wip/grid-resize
wip/ask-page
wip/webmcp
job/deeplink-audit
wip/deeplinks
wip/shortcuts
wip/cont-tz-days
main
No results found.
Labels
Clear labels
No items
No 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#549
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Owner request (2026-09-30, night)
While recording a demo, the owner saw that opening the Calendar tab takes much longer than Photos. Photos had 1 photo; Calendar had 2–3 days of events. "It's very important that they load very, very, very fast", and it must be tested on a hard disk, not on fast flash storage: HDD latency and IOPS are the constraint, and if it is fast there, it is lightning fast on SSD. Stress test it: switch between every tab with lots of items loaded into each tab, measure the time until the tab is fully painted, and minimise that time. Fix layout bugs that add to the load time.
1. Environment (prove it is a hard disk)
root@10.69.69.63(flock /root/perf.lockfor every run; 4 vCPU, 7 GB). Its disks are QEMU virtual disks, so the ROTA flag is not proof.fio4 KiB random reads with--direct=1on the candidate disk. Report IOPS and p50/p99 latency. An HDD shows about 100–250 IOPS and 4–15 ms. If no disk on the perf VM behaves like an HDD, stop and report; do not measure on flash./mnt/hdd/targets/release-shared, web build from the same commit); no local builds on the perf VM.sync; echo 3 > /proc/sys/vm/drop_cachesbefore each cold run, and restart the server for a cold server cache. Warm runs: no drop.2. Data (real-shaped fixtures, tests only)
Load each tab heavily and reproducibly (a seeded generator committed under
bench/):3. Measure
Playwright on a production build, Chromium and WebKit, at 1440 and 390 px. For every ordered pair of tabs (A→B), cold and warm, record 11 interleaved runs:
layout-shiftentries, long tasks);Server-Timing);/proc/<pid>/ioread_bytes delta;iostatawait);Also the first load of each tab after sign-in. Report p50/p95 per tab.
Budgets (decision rule, fixed up front):
4. Fix
Find the causes and fix them, one commit per cause, with before and after numbers:
Server changes: per-crate Rust gates. Web:
bun run check,bun run test.5. Keep it measured
bench/tab-switch.mjs+ abench/run.shprofile, and add the results todocs/perf/as the new baseline, so the weekly perf timer tracks tab switching from now on.Rules: aggregates only; no owner data; no pushes or deploys. If a fix needs a design decision (e.g. prefetching all tabs in the background), list it under "Owner decisions" with numbers; do not build it.
Starting #549 on branch job/tabswitch-549. Current HEAD/base:
aa372eef6c. I am reading the required design sections and will qualify the perf VM disk before collecting timings.Perf VM qualification finding: /mnt/hdd is a 100 GiB ext4 mount on /dev/sda (ROTA=1), but that flag is not sufficient proof. The required fio probe did not run because failed; I have collected no performance timings. I am making fio available, then I will run the 4 KiB direct random-read qualification under /root/perf.lock.
Stopped at the HDD qualification gate in #549. The job requires an HDD-like disk and explicitly says to stop if no perf VM disk behaves like one.
Both disks were tested read-only with fio 3.39, 4 KiB random reads, direct I/O, libaio, queue depth 1, 60 seconds, while holding
flock /root/perf.lock. Load average was captured inside each lock.The requested HDD behavior is about 100–250 IOPS and 4–15 ms. Neither device qualifies. ROTA was not used as proof; the direct-I/O results establish that both candidates are much faster than the required storage.
I did not place Home or SQLite data on these disks, run the tab-switch workload, create the benchmark harness, modify source files, or run build/test gates. No before/after timing exists.
Head SHA:
aa372eef6c(unchanged).Gate output: not run; the explicit HDD qualification condition stopped the job before implementation.
Known gap: tab-switch root causes, fixes, harness, baseline, visual evidence, and gates remain unaddressed until an HDD-like perf disk is available.
Owner decisions: none made.
Starting Round 2 on
job/tabswitch-549. Worktree HEAD:aa372eef6c9312403f59a65d5b90cb3648ff52ed; currentorigin/dev:15e17aeafc8ea160c109e62fba57f6961c39d21f. I will first capture the flash-like host result under/root/perf.lock, then build and qualify the file-backed delayed HDD device before moving Home and SQLite data or measuring tab switches.HDD-emulation qualification (all runs held
/root/perf.lock): the real/mnt/hddbacking device (/dev/sda, ext4) is flash-like: 4 KiB direct random read at QD1 measured 36,164 IOPS, p50 21.4 µs, p99 91.6 µs.dm-delayloaded successfully. I created a 40 GiB file-backed loop device with direct I/O, device-mapper adds 8 ms read and write delay, and ext4 is mounted at/srv/hdd-emu. Thesystemd-runscope reportsio.maxfor the loop backing device asrbps=157286400 wbps=157286400 riops=200 wiops=200(load average 0.16 before qualification).4 KiB direct random read fio, in that scope: QD1 = 125.0 IOPS, 8.03 ms p50 / 8.16 ms p99; QD16 = 200.9 IOPS, 100.14 ms p50 / 104.33 ms p99. QD16 saturates the required 200 IOPS ceiling and shows queueing latency. I will run the server and workload together in the same limited scope for the app measurements.
Smoke found two harness defects. First, CLI parsing read
querybefore itsconstinitialization and exited with a temporal-dead-zone error; fixed in commitd1f3d3da6. Second, the first real same-cgroup QD1 fio result had 7.963 ms p50 with the configured 8 ms delay, so the strict lower bound rejected a valid emulated-HDD result. The script accepted its 100–250 IOPS check, then failed only on p50. Commit4a092f093allows a 7.5–10.5 ms qualification band and ensures cleanup runs after setup or qualification failures. The production smoke is running again.The next production smoke reached server startup and failed with
AddrInUsebecause the shared VM already had a listener on127.0.0.1:8080. The harness used the same fixed port for every job. Commite4cdb7186now probes a free app/CalDAV port pair while holding/root/perf.lock, then uses that pair for the server and both SSH forwards. Cleanup also tears down the local tunnel after this startup failure. I am rerunning the smoke with the isolated ports.The first production smoke confirms the Calendar/Photos difference with fixture data on the emulated HDD. At Chromium 1440 px, light theme, one direct post-sign-in visit: Calendar fully painted in 19,472.7 ms and Photos in 5,076.7 ms. These are one-run diagnostic values, not p95 results. The same smoke's fio probe in the server cgroup measured QD1 125 IOPS (p50 8.028 ms, p99 8.159 ms) and QD16 200.919 IOPS (p50 100.139 ms, p99 104.333 ms). All seven requested tabs are enabled, and the interleaved switch matrix is still running.
The smoke fixture generator reported 366 daily notes and 10,980 log entries for the 2026-09-30 anchor, but the JSON report initially overwrote those counts with an assumed 365/10,950 because this range includes an extra day. Commit
584c39ef2now preserves counts from the generated fixture, and it checkpoints after every five switch samples so a time-boxed run retains partial aggregates. The active smoke was launched before that commit; I will correct its aggregate fixture metadata before using it as evidence.The seven-Tab smoke recorded first-load aggregates and the same-cgroup fio qualification, but it did not record a switch sample. Its next
/root/perf.lockacquisition waited 15 minutes and exited withError: perf lock exited early:while another perf run held the single-tenant VM. I stopped this run and confirmed the benchmark server PID had exited. No switch timing is reported from that attempt. The output still records the qualified device and first-load timings.Source inspection found a Calendar HDD hot path: Today loads a 14-day Agenda page, then
loadAgendacallsreadDay()once for every day with Logs to fetch repair details. EachreadDay()is a separate/api/v1/notes/journal/{date}request and reads that Daily note.AgendaListrenders those repair details only when the matching day is shown. I will move those reads to the existing viewport boundary so the initial Calendar view reads only nearby days and loads later repair details as the User scrolls. This is a code-path finding; the perf lock timeout prevented a switch-request count.Post-change smoke finding: the harness currently treats a 120 s page-wide network-idle timeout as a completed paint. The 390 px first visits for Calendar and Photos hit that exact cap (120,801 ms and 120,601 ms), while other widths completed much sooner. These rows are timeouts, not paint timings. I am preserving them as invalid diagnostics and will correct the paint boundary to use visible tab content, busy markers, decoded viewport images and paint frames, then record any rerun separately. The 1440 px dark profile is still in progress; its cold server restart is running under the HDD cgroup.
The first after-change smoke stopped during the 1440 px dark profile: a cold server restart did not reach
/readyzbefore the harness start deadline, so that profile has no switch rows. The server log showed SQLx pool acquisition delays up to 14.56 s and a job update taking 2.81 s. The preceding 1440 px dark first-load samples recorded 1-minute load averages of 5.84 and 6.09. I stopped the attempt and verified that its PID file did not refer to a live tabswitch server. This is a host-load-limited smoke result; I have not treated it as a product crash.I corrected the #549 harness and committed it as
c9868f93b: the full-paint boundary now waits for route content, visible busy markers to clear, viewport images to decode and two paint frames. It no longer waits for page-wide network idle or silently accepts the 120 s deadline as a completed paint. The corrected Chromium smoke is now running with the same HDD image and cgroup.The 424 came from a race in the benchmark fixture:
startReverseCalDavTunnelreturned after a fixed 250 ms delay without checking that the VM loopback listener could reach the local provider. I changed it to poll a TCP connection through the reverse tunnel before posting the account, committed ase2d3eea51, and passednode --check bench/tab-switch.mjs. The failed setup did not modify application code; its smoke run saved no measurement rows.The targeted paint pass timed out after 120 s at the helper's busy-marker wait. The selector included
#route-contentitself, which is always visible, so the predicate could never succeed. I removed the root andaria-busyalone from the visual loading test; cache-first content may stay aria-busy while it revalidates. The correction is82bcc9ddd, andnode --check bench/tab-switch.mjspasses. No app loading state caused this timeout.Completed the #549 job on
job/tabswitch-549.Head:
234f046864017ac60364183cd6d8cb8fbfb90e77Built
dm-delayat 8 ms read/write, ext4, and a systemd scope limited to 200 read IOPS, 200 write IOPS, and 150 MiB/s in each direction. Setup and teardown live inbench/hdd-emu.sh.bench/run.sh --measure-only --tab-switchprofile.HDD qualification
Queue depth 1 confirms the configured delay. Queue depth 16 reached the 200 IOPS limit and queued requests.
Corrected smoke results
This is one Chromium run per pair, on the seeded smoke fixture, in light mode. Since
n=1, p50 and p95 are equal and are not percentile estimates. “Before” switch values were not measured.None of these rows met the fixed budgets: warm full paint was over 100 ms, and cold first paint was over 400 ms. The deepest critical path was 12 requests. The longest observed main-thread task was 5,907 ms, and click INP reached 2,304 ms.
Server-Timingheaders were absent;iostatawait values were null. The 390 px first visits painted Calendar in 3,731.9 ms and Photos in 9,118.8 ms. At 1440 px, Calendar was 17,262.9 ms and Photos was 5,498.6 ms.The earlier pre-change first-visit smoke reported 19,472.7 ms for Calendar and 5,076.7 ms for Photos at 1440 px, but used the old page-wide network-idle boundary. It is not a valid before/after comparison. The shared baseline remains unchanged.
Known gaps and decisions
Gates
bun run checkoutput:bun run testsummary output:node --check bench/tab-switch.mjs,bash -n bench/hdd-emu.sh,git diff --check, and JSON validation passed.cargo build --release -p calternal-serverpassed:Finished release profile [optimized] target(s) in 2m 07s. No Rust source changed, so Rust clippy/test gates were not run.cargo cleanremoved 7,653 files (2.5 GiB), and web build output was removed.No push, merge or deploy was done.
Starting Round 3 on
job/tabswitch-549, base/head234f046864017ac60364183cd6d8cb8fbfb90e77. Read CLAUDE.md, CONTEXT.md and Calendar/design requirements. I will capture a Chromium 390 px warm Photos → Calendar CDP timeline + CPU profile + network log on the existing qualified HDD emulator before product changes, then post top self-time functions and the request waterfall. No push or deploy.Trace attempt 1 reached the seeded fixture and Calendar screenshot, then failed before any switch:
goto: Timeout 120000ms exceeded, navigating to/photos, waiting untilload. No trace or switch timing is claimed. The browser runs on the shared build host (load average 29.53 / 30.81 / 29.47 at diagnosis). Commit06cecc230usesdomcontentloadedfollowed by the explicit existing viewport paint boundary; off-screen image loads must not define fully painted. It also adds the CPU trace summarizer and a one-pair diagnostic option. Retrying the requested 390 px warm trace; no product code has changed.The second trace setup also timed out before a switch (
/photos, waiting fordomcontentloaded). Its Calendar screenshot contained only “Loading earlier days…”. This showed that the oldfullyPaintedprerequisite could accept an empty Agenda before its data arrived. Commit778b37db6waits while.agenda[aria-busy=true]has no.day, while still allowing cached days during revalidation. Diagnostic priming uses the Tab Bar instead of a second full-document navigation. Retrying the same seeded 390 px warm trace. Product code is unchanged.Round 3 trace completed before product changes, using Chromium at 390 px, light, the Round 2 smoke fixture and the existing qualified HDD cgroup. Warm Photos → Calendar: 1,781.8 ms first / 1,816.8 ms full, largest switch-time task 1,070.15 ms, CLS 0, disk-read delta 0. This is one diagnostic run, not p95. The earlier 5,907 ms task was not reproduced and cannot be attributed from this trace.
The leading app function
Jrmaps topackages/ui/src/time.ts:303(systemTimeZone), called byYr(formatClockTime) while mounting the Calendar Agenda. It constructs a freshIntl.DateTimeFormatand callsresolvedOptions()for each row clock, even though clock label formatters are already cached. The remaining cost is Svelte DOM/style work, icon construction and GC while mounting all 14 days.wamaps to Sveltedom/elements/style.js:31;Tto LucidebuildLucideIconNode.js:19;Arto Svelteruntime.js:225.Top 10 sampled self-time functions below use only samples after the click. The trace setup task (1,203 ms before the click) is excluded. Raw traces stay in ignored artifacts.
Longest RunTask ms: [1070.15, 313.88, 97.92, 59.48, 50.8]
The Calendar range was already cached after correct priming; no range or Tasks request was on this warm switch's data path. The four logged requests were still pending at full paint and did not block it. The trace does not reproduce the Round 2 depth-12 path. Step 2 will add server phase timing and replay request disk counters, followed by separate fixes with measurements.
Round 3 finding: the old release server's serial startup probe returned Calendar range in 67.10 ms (258,231 bytes), Tasks/day in 116.70 ms, Files entries in 120.30 ms, but Journal/2026-10-01 took 387,005.80 ms, consumed 33,780 ms server CPU, and RSS rose from 258,207,744 to 507,092,992 bytes. All four physical read-byte deltas were zero. These are process-wide counters, including background work.
The Notes startup reconciler holds the same per-User lock that Journal GET acquires. Photos indexing readiness does not prove Notes reconciliation readiness. I am instrumenting the routes before attributing this delay to either reconciliation lock wait or Journal work. The probe now has a 30-second bound; SLOW-only startup probes are reported without stopping the Tab matrix. No Calendar cause is inferred from this isolated Journal result.
Round 3 application finding: the interrupted warm sample rendered Calendar but kept Photos selected in the Tab Bar. Evidence:
artifacts/round3/local-before/chromium-390-light-failure.png.apps/web/src/routes/+layout.sveltecaptures the requested pathname inpendingHeaderNavigation. ItsafterNavigatecallback returns unless that pathname exactly equals the completed pathname. Calendar enters at/today, which redirects to/calendar/today/<date>. A warm navigation snapshots the Photos header; the redirect then leaves that snapshot in place. Cold navigation can hide the error because its pending header already says Calendar. This explains the selected-Tab timeout; it is an application error, not a slow server probe.The fix accepts a completed redirect within the requested Tab, then clears the navigation snapshot using the existing navigation ID guard. I will verify repeated warm Photos → Calendar returns in a production browser. The separate main-thread clock and Agenda mounting findings remain relevant to actual paint time.
Round 3 application finding: the interrupted warm sample rendered Calendar but kept Photos selected in the Tab Bar. Evidence:
artifacts/round3/local-before/chromium-390-light-failure.png.apps/web/src/routes/+layout.sveltecaptures the requested pathname inpendingHeaderNavigation. ItsafterNavigatecallback returns unless that pathname exactly equals the completed pathname. Calendar enters at/today, which redirects to/calendar/today/<date>. A warm navigation snapshots the Photos header; the redirect then leaves that snapshot in place. Cold navigation can hide the error because its pending header already says Calendar. This explains the selected-Tab timeout; it is an application error, not a slow server probe.The fix accepts a completed redirect within the requested Tab, then clears the navigation snapshot using the existing navigation ID guard. I will verify repeated warm Photos → Calendar returns in a production browser. The separate main-thread clock and Agenda mounting findings remain relevant to actual paint time.
The full web suite passed before the redirect fix:
Test Files 140 passed (140)andTests 918 passed (918). The redirect fix passes web checking with zero errors and warnings. The production Calendar e2e now checks three repeated Photos → Calendar returns at 390, 820 and 1440 px in light and dark, and records Agenda screenshots.Round 3 cache finding: the saved local warm Photos → Calendar sample after interleaved process restarts transferred 267,688 bytes in four requests, took 8,668 ms, and had a 3,534 ms main-thread task. The process-wide server counter also included 4,150 ms CPU and 180,269,056 physical read bytes from startup background work.
subscribeCalendarFileChangeserased Calendar navigation caches on each Files notification. Its coalesced page refresh erased them again and emptied the Agenda before awaiting replacement rows. This can turn a previously visited Calendar into a data-cold return during startup reconciliation.Calendar now opts into retaining bounded stale snapshots, rejects pre-change pending cache fills, paints a cached Agenda synchronously, and revalidates the visible page. Quiet Agenda refresh keeps its rows until replacement data arrives. Auth changes still erase cached data. Legacy callers retain the old clear behavior; no existing test expectation was changed. Two new tests prove stale paint during a delayed refresh and rejection of an older pending cache fill. The targeted 15 tests passed and web checking reported zero errors and warnings.
Round 3 update: the instrumented local cold trace is saved at
artifacts/round3/local-cold-trace/. This is a local fallback with a frozen production SPA; it is not an HDD measurement. The HDD attempt exited before lock acquisition. The cold trace used the clock, deferred-day and redirect fixes, before the two-day range change. One sample: first marker 631.8 ms, full paint 709.7 ms; longest traced task 235.74 ms. The old automation marker can include a loading shell, so it does not verify the cold first-viewport budget. The harness now records a separate input-to-Calendar-content marker inside the browser, while retaining the old metric for comparison.Selected server phase probe, same fixture (366 Daily notes, 10,980 Logs): Calendar 15-day response: 361.03 ms wall,
total;dur=333.439, db;dur=14.100, parse;dur=2.587, 257,397 bytes, 0 physical read bytes. Tasks: 12.39 ms wall,total;dur=3.426, db;dur=3.364, parse;dur=0.000, 0 reads. Files: 33.97 ms wall,total;dur=15.195, db;dur=0.382, parse;dur=0.000, 0 reads. Journal exceeded the bounded 30 s timeout; process read delta 20,480 bytes, CPU delta 9,150 ms, RSS 495,828,992 bytes. Process counters include startup background work. These are not exclusive request costs. Timing phases cover selected operations; total includes uninstrumented work.Decision: Today starts with two days, matching the immediate Agenda mount. Older pages remain fourteen days. The preloader uses the same initial range key. A write refresh retains all loaded days. The real production e2e checks that the initial API request covers two days plus the one-day Log carry-in. No new endpoint is needed: the range endpoint already exists.
The required origin/dev merge is being resolved with both sides retained. Web check:
svelte-check found 0 errors and 0 warnings. Web tests:Test Files 140 passed (140)andTests 937 passed (937). Final Rust gates are running.Longest RunTask ms: [235.74, 50.79]
The local 11-run Photos → Calendar matrix completed (88 samples): Chromium and WebKit, warm and cold, 390 and 1440 px, light mode. This matrix used a frozen production SPA at the shared-window stage, before the later request-zone and paging fixes. The server was frozen as well. It is a local fallback, not HDD evidence. The unmodified old build stopped after one sample on the selected-header bug; the intermediate three-fix build stopped after four samples on a cold missing-Tab failure. The old build does not provide eleven valid before samples.
WebKit does not expose the layout-shift observer in this run: its zero shift counts do not prove zero CLS. The historic full marker did not require TimeGrid placement and date chunks; the next harness revision does, so cold desktop values are diagnostic rather than verified first-viewport times. No budget has passed.
New evidence: several phone shifts identify
BUTTON.status.more-daysat y=511, then outside the viewport. The shared sentinel observer immediately requested another fourteen days when the initial two-day page was short. This defeated the range bound and moved the visible footer. The fix requires the end to approach from outside the viewport before automatic paging; the manual action remains. The paging control also keeps one node and reserves both labels while loading. New tests preserve paging after a real approach and verify the retained node.The Calendar adapter also resolved the device zone per Event and converted two midnight bounds per Event. It now captures the query zone for async projection, computes bounds once per day and reuses the existing formatters. The new 120-Event test verifies two zone lookups and the query zone after preferences change in flight. No existing test expectation was changed.
The full Calendar e2e exposed a setup bug: its N helper was called while the Composer field was still open, typing a literal n into the frozen snapshot. Closing the verified empty Composer before the next flow preserves all old assertions. A later original pointer click was blocked by the toast list rectangle, outside the visible toast. The fix lets empty list space pass input through while visible toasts keep pointer handling.
All five Rust crate gates passed after origin/dev merge; the latest extra timing slice also passed Files, Calendar and Server gates. The updated full web gate and Calendar e2e are running.
Round 3 HDD measurement is running with the lock. Qualification: QD1 125 IOPS, p50 8.028 ms; QD16 200.946 IOPS, p50 100.139 ms. The current 11-run matrix freezes server/frontend at
67db40161.Per-request process-I/O probe: Calendar range 200, wall 2056.453 ms,
total;dur=11.073, db;dur=1.666, fs;dur=0.041, parse;dur=5.963, physical read_bytes 0; Files entries 200, wall1198.118 ms,total;dur=1034.562, db;dur=1033.788, parse;dur=0.000, read_bytes0; Journal exceeded the 30s probe limit during reconciliation, read_bytes0. Process counters include background work. Wall includes the browser/build-host transport and is not Server-Timing total.The new warm390 trace has longest RunTask626.011ms. Its largest sampled self-time is getPropertyValue227.49ms, called by chunk CDN-Za5M.js function e. That chunk is
apps/web/src/lib/ui/uiScale.svelte.ts: its root MutationObserver calls getComputedStyle for every root style/class mutation, even when --ui-scale does not change. Current tokens only set scale on :root, the coarse-pointer media query, and explicit inline scale overrides; theme classes do not set it. I will filter observer work to actual inline --ui-scale changes and retain pointer-change reads. This preserves CSS as the token source.Calendar e2e also exposed a real same-Log refresh race: ItemPreview resets editing on any item object change; LogEntryEditor then resets text from its refreshed log prop. A stable Log identity plus an edit-session snapshot now has a passing regression test (14 Agenda tests). Existing e2e assertions are unchanged.
Round 3 progress: atomic commits
eeac28920preserve same-Log drafts,51032dfe1rejects unplaced/loading TimeGrid shells in the full-paint marker,9a4bf94e8filters UI-scale computed-style reads, andb8ee4e580keeps independent Composer e2e flows closed and adds production Log-editor screenshots. The existing e2e assertions are unchanged.Web gates:
svelte-check found 0 errors and 0 warnings;Test Files 141 passed (141);Tests 942 passed (942). Production build passed. Rust per-crate gates passed for calternal-plugin, Calendar, Notes, Files and calternal-server; no Rust changes since those gates.The locked HDD matrix freezes
67db40161, before the UI-scale and draft fixes. Its completed Chromium390 group has 11 warm and 11 cold samples. Warm browser DOM marker p50/p95 1052.0/1366.6ms; historic automation-inclusive first1428.8/2985.2ms, full1515.2/3151.8ms. Cold historic first787.8/2330.6ms, full1336.0/2677.3ms. Only4 cold samples saved a DOM marker before the full marker, so no 11-sample cold DOM percentile is claimed. Warm100ms and cold400ms budgets are not met. Warm3/11 and cold4/11 recorded shifts after the historic first marker; that marker can include loading chrome. The zero-CLS budget is unverified.The matrix is now measuring1440px. I will run the later scale fix separately with the same harness and record its exact variant. Full Calendar e2e and the single adversarial round wait for the benchmark browser, to keep one browser active at a time. I will finish the report by the5.5h cutoff even if measurements remain incomplete.
CDP traces and full per-crate Rust gate logs are attached: tabswitch-549-traces-and-rust-gates.zip.
trace3-beforeis the required pre-code warm390 trace.hdd-matrix-latestis the later trace at67db40161 before the UI-scale fix.Chromium1440 completed11warm+11cold samples: at1440 warm first2504.1/4406.2ms and full2700.8/6392.0ms; cold first840.6/1289.2ms and full1074.4/1811.5ms. Only2 cold1440 samples saved the browser DOM marker, so no11-sample cold DOM percentile is claimed. Budgets remain unmet/unverified. WebKit390 is running now.
Latest full Calendar e2e at the production UI after9a4bf94e8 passes the earlier Turn-into-note step, but fails at the Daily note repair fixture (
apps/web/e2e/calendar.mjs, expected201 retained): Files tus replace returns{status:412, body:{error:{code:"conflict",message:"upload destination changed"}}}. The fixture now targets the sameNotes/<date>-dailynote.mdthat it reads, instead of creating a second file inNotes/Journal.The412 comes from a destination guard in Files uploads (parent identity, existence or item/hash snapshot), not from a missing If-Match header: absent If-Match is allowed by the existing API. A trial Notes reconcile before reading the fixture did not resolve it and was reverted. No expectation was weakened. The exact changed fingerprint/identity has not been isolated. The full Calendar gate is failing; the header/review path is running separately for visual evidence. This is tracked here as an unresolved finding.
The complete88-sample HDD matrix is committed atb9fa4a0f6. With the remaining time, the later UI-scale comparison will prioritize11interleaved warm/cold Chromium samples at390px. Latest-code1440px andWebKit remeasurement may remain incomplete. Budgets are not met.
Round3 result: Calendar is still above budget. This job has substantive fixes, code and atomic commits, but it is not ready to claim the performance target or a passing full Calendar gate.
Built: cheap fixed-name Server-Timing middleware with selected DB/FS/parse/lock intervals; shared ICU formatter metadata and UTC wall-clock labels; captured request-zone/day bounds; bounded stale Calendar snapshots with auth clearing and stale-fill rejection; a two-day initial Agenda window, near-viewport day/repair mounting, deferred keyboard focus and stable paging control; redirected Tab header completion; same-Log draft preservation; UI-scale reads limited to actual scale changes; toast blank-space pointer pass-through; CDP/network/disk-read measurement and frozen HTTPS browser profiles. No new dependencies.
Trace: the pre-code warm390 trace did not reproduce the original5907 ms task. It captured1070.15 ms; top self-time232.56 ms was systemTimeZone (Jr), reached from Agenda Event clock labels. A later warm390 trace captured626.01 ms and227.49 ms in the root uiScale computed-style callback. Top10 functions and waterfalls were posted before product edits and attached. Recurrence was already server-side; the15-day range probe spent5.963 ms in selected parse work.
Measurements: complete88-sample locked HDD matrix at67db40161, before the later scale/draft fixes;11 warm+11 cold each forChromium/WebKit at390/1440 px, light, one-year10,980-Log fixture. Warm Chromium390 DOM p50/p951052.0/1366.6ms. Historic full-paint p95: Chromium390 3151.8ms, Chromium1440 6392.0ms, WebKit390 7402ms, WebKit1440 9666ms. These do not meet the warm100 ms target. Cold DOM markers are incomplete; no valid11-sample cold DOM p95 is claimed. Cold400 ms, zeroCLS and causal depth2 are not proved. Original-build11-run baseline stops at its warm header bug.
Production evidence:36screenshots coverAgenda/Day/Week/Log editor at390/820/1440 px, light/dark. Geometry checks passed; the visual reviewer remains the orchestrator. Screenshots and web/e2e/probe logs. Original/later CDP traces and full Rust logs.
Known gaps: fullCalendar e2e fails its unchanged201 assertion when the same-path Daily-note tus fixture replace returns412
upload destination changed; exact changed parent/file identity has not been isolated. Notes reconciliation still scans twice and Journal hit the30s probe limit. Plugin total excludes earlier session/access middleware and body streaming; physical I/O deltas include background work and all process file reads. WebKit observer absence is not zeroCLS/long-task evidence. Day deferral is not full row virtualization. Latest-build warm390 now measured; cold/1440/WebKit matrix, largest fixture, burst, keyboard latency and all-seven-Tab matrix are incomplete. Browser shared-host load limits wall-clock attribution. No formal baseline regression ratio is claimed.Decisions: two initial Agenda days, fourteen-day later pages,180px deferred-day minimum from the existing intrinsic size, manual paging until the sentinel approaches from outside, a separatelock timing phase, per-request projection zone, and an edit-session Log snapshot. Theme classes do not set ui-scale; observe pointer and inline scale changes. DESIGN specifies the principles, not these bounds and field names.
Gates (verbatim excerpts; complete logs attached):
Full Calendar gate failure (verbatim):
cargo fmt --checkproduced no output and exited0. Web production build passed. Rust gates were per crate. The single adversarial round passed after correcting a new test setup assumption: its fixture has Events but no Daily note, so Notes success is tested through Tasks/day and missing Journal retains404. All prior expectations are unchanged.Files:
HEAD:
2c10cc92f7. Required origin/dev fetch/merge done once (merge7bfd3676a). No push, deploy or merge into dev/main. Cleanup: cargo clean completed; apps/web/build and apps/web/.svelte-kit removed; no own perf servers remain; HDD emulation stays configured. Latest-build comparison: 11 warm Chromium390 px HDD samples completed on embedded build7f7b86528. DOM p50/p95623.7/768.0 ms; full933.8/1241.6 ms;7 samples with shifts after the historic marker. Latest single trace longest task152.88 ms; getAnimations48.23 ms, getBoundingClientRect31.04 ms, GC22.36 ms; root computed-style callback absent from top10. Earlier run interleaved cold switches; later run was warm-only, so this is diagnostic. The short-readiness warm/cold attempt took no samples. Final measurements and trace.Round 4 starts on
job/tabswitch-549, base/head2c10cc92f7ce1bf001cc4dcdad8a3c549c2e8931. Scope: separate Journal projection reads from Notes reconciliation, render the cached Calendar snapshot before revalidation, investigate the fixture replacement 412, and repeat cold/warm browser measurements. No push or deploy.Round 4 evidence: Journal GET acquires the Notes User guard and calls
ensure_idsover every pending Daily note before reading one day (crates/plugins/notes/src/lib.rs). Startup/watch reconciliation holds that same guard for both Tasks and Notes scans. The new read projection commits the source with Journal resource ETags and reads through the WAL reader pool; its regression test holds both the guard and an uncommitted SQLite writer and requires the old committed day within 100 ms. Gates are running; no passing-gate claim yet.The local production pre-change desktop trace completed: cached Photos → Calendar, full paint 3309.2 ms, one sample, largest task 429.49 ms. No Calendar range/Journal read on the switch critical path. Top sampled self cost:
getBoundingClientRect328.52 ms; TimeGrid scroll callbackRe291.60 ms;scrollTo103.12 ms; DOMbefore96.88 ms; GC 72.10 ms;getAnimations61.86 ms. This is a shared-host local diagnostic, not an HDD percentile. The 390 px trace is running.Two client findings are fixed in code: Tab selection waited for
preloadMode(including API reads) and the Agenda first render was empty until its loading effect. Navigation now starts at selection, and initial Agenda state comes from the retained range before effects/awaits. Journal PATCH/DELETE also reused indefinitely cached ETags. They now read the current stable block immediately before If-Match; three new tests pass. The Daily-note upload 412 remains a separate Files destination identity/hash guard, not a Journal ETag header or DAV sync-token epoch; exact failed guard still under investigation.Round 4 finding: the Daily note replacement 412 is a Files tus parent check, separate from cached Log ETags and the Notes DAV epoch. A focused real Root/tus test creates
Notes/day.md, opens a replacement upload, then writes an unrelated sibling through Root before PATCH. Before the fix it returns 412 (expected 204). The request has no If-Match. The directory inode and target bytes did not change; its mtime did.The fix uses the captured parent item ID plus device/inode for this directory destination check. It keeps pending-namespace denial, full grant checks, and target ID/hash checks. The same regression also replaces the directory inode and requires 412 with both original and replacement paths unchanged. No existing test expectation changed. This is a small Files change required by #549's Calendar fixture diagnosis.
Client commits:
d91b507cereads fresh ETags immediately before Log PATCH/DELETE;ee28ca343mounts the cached Agenda before preload awaits;8de003cbftracks label transitions without getAnimations;f3b1c8f87releases a Tab entry header in the navigation update. Notes projection commit:799ab19ce. The legacy-import compatibility test now passes without changing its expectation. Full crate gates are in progress.The single requested fetch/merge is complete: merge commit
307d8221a, merging4dc9ca394from origin/dev. The shared remote-tracking ref then advanced while other jobs merged. The required final migration check against origin/dev (1af8ead26) found Notes migrations 21, 22 and 23 in use. Commit8bf8c3729renumbers the rebuildable Journal snapshot migration to 24; no deployed number is reused. I am keeping the owner's one-merge rule and am not repeatedly merging the moving ref.Final web gates:
svelte-check found 0 errors and 0 warnings;Test Files 142 passed (142);Tests 946 passed (946). Rust gates and the current release build are running with four jobs, no incremental output, and line-table debug info. The latest local trace attempt did not produce samples: the fixture restart did not reach readyz within 300 seconds under host load 36.48 and I/O pressure (full avg10 50.47%). This is recorded as failed setup, not as a latency sample. The perf VM lock was busy on the one availability check.Production UI evidence for Round 4: round4-production-review.zip (9369225 bytes). This bundle has Agenda, Day, Week and Log editor screenshots at 390, 820 and 1440 px, light and dark, plus 3x header crops and the passing geometry/redirect review log. It also includes the web gate logs and the tus regression's before/after output. Screenshots remain ignored and were not committed. The UI is the current production build; this focused visual run used the frozen existing server binary while the new server release build is in progress. The final Calendar fixture run will use the new server.
Owner (2026-10-01): "Calendar events still take a long time to appear, by long I mean close to a second - it should be instant! It is not like they are animating either, they simply take time to load." Confirms round 4 of this issue (warm switch target ≤ 100 ms; the cached snapshot must render synchronously on tab selection).
Round 4 desktop finding:
loadChunkin the Calendar route waits forPromise.all([loadRange(...), loadItems(...)])before it fillsdays. The retained range alone does not contain an explicitly completed standalone rail. A warm Week/Day remount therefore still starts empty and awaits the chunk read.The view now retains a completed chunk with its filtered rail in the existing bounded, generation-aware Calendar cache. Initial grid state reads this snapshot synchronously. The publisher captures the request's zone, photo preference and cache generation before awaiting; a completed old request cannot refill a changed or signed-out User's cache. An items error does not replace a good completed snapshot. Three new tests cover complete rail retention, photo preference separation, auth reset and invalidation races. The data and Journal targeted tests pass (24 tests). Final web gates are running for this last client slice.
Notes final gates passed:
The test command was
cargo test -p calternal-plugin-notes -- --test-threads=1because independent test databases share the fixed fixture User ID and process-wide writer registry. The held-reconcile read test still uses its unchanged 100 ms budget and passed. Files clippy passed; full Files tests and the release server build remain in progress.Round 4 finding: the full Calendar e2e reaches calendar.mjs:855 and fails
the new log entry is in the Daily note(actual undefined). This is from this job: Composer batch ACKs queue the full projection, but the new committed Journal reader can still see the previous snapshot. The fix publishes the ready source snapshot before the batch ACK; Search, Calendar and DAV indexing remain queued. It does not wait for indexing on GET.Regression:
composer_batch_publishes_journal_before_queued_indexingholds the Notes guard after the batch ACK and requires the new Log through Journal GET within 100 ms. Output:The final Notes/Files/server gates are running. Files' first full test run had one unchanged five-minute storm timeout on the loaded host; I am running the crate serially, with the same assertions and timeout. The web gates pass with a 15-second runner budget: 142 files, 951 tests; no test expectations changed.
Decision: publish only the minimal ready Daily note source before the batch ACK. Keep the heavier queued projections. The benchmark now covers single-Log ACKs, a bounded 1,000-Log batch and 20 reads of that large day. It records browser-host load separately from perf-VM load.
Round 4 client finding (local, not an HDD comparison): the latest current production UI restores cached Agenda and grid snapshots synchronously, enters the Tab route without awaiting preload, releases registered entry-route headers in the navigation update, and tracks label transitions through events instead of getAnimations. A restored Agenda also skips arrival motion for its initial rows; new rows and fresh views keep the shared motion.
Host load and background work differ, so this is diagnostic evidence, not a controlled regression ratio. The 100 ms target remains unmet. The first trace has a 422.7 ms callback, a 92.5 ms layout of 3,924 objects and a 46.9 ms style pass. The later trace's longest task is 257.97 ms. Cached rows still cost DOM construction and layout; full row virtualization is not in this change. Web gates after the motion change:
svelte-check found 0 errors and 0 warnings,Test Files 142 passed (142),Tests 951 passed (951).The original Calendar tus fixture stop is diagnosed and filed as #627. It returns
upload destination changed, with no If-Match sent. A bounded test reproduces 412 after re-indexing the same parent directory following an unrelated child write. The full flow failed before Round 4 too. The early parent-mtime exception did not fix that flow and was reverted; Files retains its full destination checks. This is separate from the Notes epoch triage in #427.The rebuilt final embedded UI/server is compiling. The HDD warm/cold matrix is next, followed by the seven-Tab matrix as time permits.
The first qualified HDD matrix is still above the target. It uses build
b126ffe335, before the latest row and cache-window changes. Do not count it as evidence for the final client.Latest client change,
44cc2e98d: extend the existing Agenda deferral to rows. The first window uses viewport height and the existing 40 px minimum row, with two spare rows. One shared row observer has a 200 px lead; existing day and repair observation keeps a 600 px lead. Rows stay mounted after visibility, selection or keyboard use, including after an insertion shifts their position. Each mounted list item has its full data position and set size for assistive input. Keyboard and block links mount their target before moving focus. No text, style or per-row geometry measurement was added. This is deferred mounting, not unmounting/recycling row virtualization.Latest cache change: retain six completed grid chunks. Week can rest on any date (DESIGN §39), so its 35-day rendered/prefetched window can span six week chunks. Four could evict the visible week by completion order. The regression retains the visible revision after all six complete and verifies bounded eviction after a seventh.
Final web output:
The focused cache tests also pass:
The embedded final client is rebuilding. I will repeat the cold/warm engine/width matrix for it with eleven warm and three cold samples per profile, then measure the other Tabs as time allows. Decisions: conservative viewport arithmetic, one observer per visibility purpose, retain mounted heights, and a six-chunk grid LRU. These extend the existing components and cache writer.
Round 4 measurement update (head
66a14a005; final client source814258d16):The pre-row-deferral HDD matrix recorded all 88 requested switches (Chromium/WebKit, 390/1440, eleven warm and eleven cold samples each). Its optional Journal write profile then timed out on the first single-Log POST at 60 seconds, so the checkpoint JSON remains
in_progress. This is an incomplete write measurement, not a successful latency result. Journal reads all returned 200. The final row-deferral/cache-window client matrix is running with eleven warm and three actual cache-drop/server-restart cold samples per engine/width.The first matrix's warm Calendar DOM p95 values were 4505.3 ms (Chromium390), 1604 ms (Chromium1440), 5433 ms (WebKit390), and 9118 ms (WebKit1440). Browser-host load ranged 5.68–34.32; no outliers are discarded, and this is not a controlled comparison to Round 3. These values do not meet the 100 ms target.
The serial Journal read took 42.884 ms end to end, with 1.519 ms plugin Server-Timing. The 20-read burst had p50 224.664 ms/p95 237.130 ms, 150 ms process CPU, 394055680 bytes RSS and no physical reads. This separates the remaining HTTP/session/pool cost from Notes reconciliation. The held-writer regression and the real-server reconciliation probe still pass.
A failed Playwright request prints credential headers unless they are redacted. The bench now redacts those lines as well as setup URLs, with an ANSI-header regression test (1 pass, 0 fail). Diagnostic logs were checked before attachment.
Final client HDD matrix recorded at
71597bfb6(production client source814258d160). All 56 samples completed. The 100 ms warm target remains unmet.Final Round 4 client HDD matrix
The final production client source is
814258d16. The result isruns/tab-switch-2026-10-01-549-round4-final-hdd.json.It has 56 completed samples: eleven warm and three cold per browser and width.
Cold samples stop the server, drop Linux page caches and start the same binary.
Each measured run holds
/root/perf.lock. The browser runs on the shared buildhost. Light mode and the smoke Home are used; no outlier is removed.
HDD qualification again gives QD1 125.008 IOPS, p50 8.028 ms, p99 8.159 ms.
QD16 gives 200.906 IOPS, p50 100.139 ms, p99 104.333 ms. The VM load during
qualification is 1.17/1.89/2.19 and 1.40/1.91/2.19.
Browser-host one-minute load during switches ranges from 8.52 to 21.72.
Three cold samples are a bounded confirmation, not a strong percentile
estimate. The historic First marker can precede the real Calendar DOM marker.
Use the DOM marker for real Calendar content; Full also waits for visible
loading UI, fonts and image decoding. WebKit CLS and long tasks are unavailable.
Standard CLS excludes recent-input shifts. Raw shift sources are in the JSON.
Final production header e2e:
calendar header e2e: all geometry checks passed. There are 36 screenshots across 390/820/1440 px, light/dark, Agenda/Day/Week/Log editing. Claude reviews visual quality.Final-server adversarial output:
The remaining all-Tab run measures first visits for every enabled Tab at all six width/theme profiles, plus one warm return to Calendar from each other Tab. These single-sample diagnostics do not replace the eleven-sample matrix.
Paint-boundary finding (
738405d2f): pending Tab selection changes before route commit. The old benchmark checked only a non-empty route root, so a cold Calendar Full result could still measure Photos. Calendar DOM marks check Calendar content independently; their warm p95 values remain useful. The old First/Full and request-depth figures are now marked invalid in the report, and their CLS window can be shorter than one second after target paint.The harness now waits for the target pathname and Calendar root before First/Full. Regression output:
I stopped the all-Tab run during setup (no samples) to prioritize a corrected HDD warm/cold smoke: one sample per condition, Chromium/WebKit and 390/1440. Eleven warm DOM samples per profile remain in the earlier final-client matrix; the corrected smoke verifies the target Full boundary, rather than fabricating a p95 from one sample. Final screenshots and the real-server adversarial probe already pass. The 100 ms warm target remains unmet.
Round 4 final report. Head:
c19f56116032da92c1f432c2b60358e5d94657e3onjob/tabswitch-549. Initial base:2c10cc92f7ce1bf001cc4dcdad8a3c549c2e8931. One authorized merge oforigin/dev:307d8221a1f34d59944c67c355a67baa46adb8fc(origin/dev parent4dc9ca394). No push or deploy. Working tree is clean.Result: the warm 100 ms p95 goal remains unmet. Final Chromium390 Calendar DOM p50/p95 is 127.1/229.5 ms across eleven samples. The final trace's longest post-pointerdown task is 65.58 ms; the eleven-sample longest-task p95 is 130 ms. Round 3 optimizations remain. The prior qualified DOM p95 was 768 ms, but host loads differ, so no controlled ratio is claimed.
Built
412 diagnosis
The final full Calendar flow still reaches calendar.mjs:1117 and receives 412
upload destination changedinstead of 201. It sends no If-Match. Parent reindex after a sibling write can change the Files directory item ID. #627 has the reproducer. This predates Round 4 and is separate from #427 Notes epoch triage. The attempted parent-mtime exception was reverted because it did not fix the real flow; final Files behavior is unchanged. No existing e2e expectation or fixture was weakened. Final full-flow proof.Files
Measurements
The final production source is
814258d16089ac289f458f8a37edf907857b9d9a. The 56-sample HDD matrix has eleven warm and three actual server-restart/Linux-cache-drop cold samples per engine/width. It uses light mode and the smoke Home (366 Daily notes, 10980 Logs, 30 daily recurring Events, 100 Photos). Every measured phase holds/root/perf.lock; load is recorded inside it. QD1: 125.008 IOPS, p50 8.028 ms, p99 8.159 ms. QD16: 200.906 IOPS, p50 100.139 ms, p99 104.333 ms. Browser-host switch load ranges 8.52–21.72. No outlier is removed.The benchmark review found that pending Tab selection could pass the old non-empty-root check on Photos. The older First/Full and request-depth counters are invalid target measurements; their CLS window can be shorter than one second after target paint. The Calendar DOM marks check Calendar content independently. The saved report qualifies these old counters. The corrected smoke below requires target pathname and Calendar root before First/Full.
Corrected HDD smoke: eight completed samples, one per row. These are diagnostic values, not p95 estimates. Cold includes Linux cache drop plus server restart. CLS observes one second after target Full. Request depth is a chronological non-overlap upper bound, not a causal graph.
Corrected serial Journal read: 41.282 ms end to end, 0.837 ms plugin phases. Twenty-read burst: p50 126.520 ms/p95 156.732 ms, CPU 280 ms, RSS 391319552 bytes, no physical reads. Calendar range: 74.762 ms end to end, 12.207 ms phases, 1.953 ms database. Snapshot reads have no Notes lock phase. Earlier loaded probes and process CPU/RSS tables remain in the dated report.
baseline.jsonhas no matching snapshot/batch/Tab profile; its Calendar range p95 is 4.0 ms and Notes Daily p95 4.8 ms, with different workloads. No baseline is promoted.The warm trace starts its large Svelte queue callback 62.09 ms after pointerdown; it takes 57.66 ms. Layout starts at 133.34 ms and takes 7.50 ms over 1248 objects (earlier trace: 3924). Navigation and component construction remain on the path to paint.
Gates (verbatim result excerpts; full logs attached)
cargo fmt --check: exit 0, no output.cargo clippy -p <crate> --all-targets -- -D warningsandcargo test -p <crate>passed for calternal-plugin-notes, restored calternal-plugin-files, and calternal-server. Notes and Files tests were serial because fixed process-global fixtures contend. Web commands:bun run --cwd apps/web check,bun run --cwd apps/web test --testTimeout 15000, production build. The CLI budget handles shared-host SLOW; assertions are unchanged. Benchmark command:bun test bench/tab-switch.test.mjs.Final production header output:
Final real-server adversarial output:
Final review bundle: 36 production Calendar screenshots for Agenda/Day/Week/Log editing at 390/820/1440 px in both schemes, including 3x header crops; dense HDD screenshots and trace; gates; raw measurements. Claude reviews visual quality. The bundle's earlier full-calendar-e2e log records the now-fixed line855 failure; the separate final proof above records the remaining line1117 412.
Known gaps
Decisions not specified by DESIGN
Use complete Journal source snapshots and minimal ready batch publication; preserve idle-only legacy repair for compatibility. Keep six completed weekly chunks for the 35-day rendered/prefetched window. Mount max(12, ceil(viewport height/40)+2) Agenda rows with a shared 200 px observer lead and retain mounted rows. Use eleven warm/three cold samples, then a corrected one-sample boundary confirmation within the time limit. Observe CLS for one second after Full; report request-depth limits explicitly. Keep the shared baseline unchanged.
All touched module and non-obvious function comments were re-read. Build cleanup completed:
Web build output and this worktree's temporary test/build files were removed. Review artifacts remain ignored. Issues stay open.
Starting measurement-only rerun for #549 on branch
job/perf-rerun.Base and comparison baseline:
origin/devatc4a61e8cf090170f35b1bed3350d9de20c83ecd5(merge round 6). I will use the shared release artifacts, qualify HDD emulation with fio, hold/root/perf.lockfor every measured run, and record uptime/load with interleaved A/B samples. Scope is the six requested profiles (#632, #573, #476, #651, #549, #641); no product code changes.HDD qualification completed under
flock -w 14400 /root/perf.lockon the fresh VM boot.bench/hdd-emu.sh upmounted/srv/hdd-emufrom/dev/mapper/calternal-hdd-emu(direct-I/O loop, 8 ms read/write delay). Command:fio --name=hdd-qualification --filename=/srv/hdd-emu/qualification.bin --size=128m --rw=randread --bs=4k --ioengine=libaio --iodepth=1 --numjobs=1 --direct=1 --time_based=1 --runtime=30 --ramp_time=3 --group_reporting=1 --output-format=json. Result: 117.01 IOPS, p50 7.96 ms, p99 16.58 ms. Uptime at start: 3:35; load average: 0.04, 0.02, 0.07. The IOPS and median latency match the expected HDD profile.Results
The only log evidence was slow SQLite setup work during startup:
VACUUM INTO5.85 s,journal_mode = WAL7.16 s, andCREATE TABLE semantic_meta2.04 s. The runner hit its readiness deadline. It did not report a server crash or request result, so this is not a measured product regression. JSON output was not created. VM load was 0.04/0.88/1.43 at start and 3.81/2.77/2.11 at stop.Attempted command
bun bench/tab-switch.mjs --local --engines chromium,webkit --viewports 390,1440 --themes light --conditions warm,cold --runs 3 --cold-runs 3 --json /root/perf-rerun/output/tab-switch-dev.json --production-build /root/perf-rerun/base/apps/web/build, withTAB_SWITCH_BUILD_BIN=/root/perf-rerun/bin/dev-calternal-server,CALTERNAL_E2E_ASSET_OVERRIDE=1, and Playwright browsers at/opt/ms-playwright. It was wrapped in/root/hdd-emu.sh run-limitedunderflock -w 14400 /root/perf.lock. I used the local cold-cache adapter and served the saved c4 production bundle. The matrix did not begin.Decision: I used the light theme only because this request specified Tabs, engines, widths and warm/cold conditions, but no theme matrix. No product files changed.
Final report
This was a measurement-only run. No tracked files changed and no commit was created. Branch
job/perf-rerunis atc4a61e8cf090170f35b1bed3350d9de20c83ecd5, matchingorigin/devafter the required fetch/merge.The #549, #632, #573 and #651 reports include the measurement commands, build SHAs, run counts and load records. The HDD emulation fio qualification and the failed #573 B2 setup are also recorded on this issue.
Files: none tracked. Temporary release binaries and web production builds were used for measurements and removed from the worktree. Head SHA:
c4a61e8cf090170f35b1bed3350d9de20c83ecd5.Gates: not run; there were no source changes to format, lint or test. Cleanup output:
Removed 7833 files, 3.1GiB total.Known gaps: no #549 timing samples; #476 and #641 remain unmeasured. The #573 benchmark reports peak sampled CPU rather than average CPU-seconds; #712 tracks that gap.
Decisions: I ran the #549 attempt in light theme because its requested matrix named Tabs, engines, widths and warm/cold states but no theme. The attempt failed before the matrix started. No product or design decisions were made.
UX gaps closed/left: not applicable; no UI changed.
Starting #549 on
job/hddsql-549, based onc4a61e8cf090170f35b1bed3350d9de20c83ecd5(origin/devat worktree start). I will reproduce startup on the locked HDD-emulation perf VM, trace SQLite statements over 50 ms, and record fixes plus the corrected tab-switch matrix.Full-fixture HDD reproduction finding (branch
job/hddsql-549, headb7c9e36f9): the emulated filesystem qualifies at QD1 125.008 IOPS, p50 7.963 ms, p99 8.225 ms. The seeded-Home restart missed the 300 s readiness deadline. WithCALTERNAL_SQLITE_SLOW_STATEMENT_MS=50, the initial fresh start logged WAL setup at 156–160 ms,VACUUM INTOat 161 ms and first semantic schema creation at 83 ms. A second run against the full seeded Home loggedVACUUM INTOat 737 ms, semantic LSH inserts at 87–131 ms, and a semantic document insert at 51 ms. None explain a 300 s delay. That run also logged repeated Tantivy commits and SQLx pool acquisitions over 2 s, while/readyzstayed unavailable; the exact pre-listener phase is still unknown. I am adding opt-in startup phase timing before changing behavior.perf-arch-io (#663) reviewed the startup code at origin/dev
c4a61e8cfand queued merge-round-7a2f4482ded. It found three relevant startup phases, without running a new measurement:Post-bind contention is separate: Files full reconcile holds Root's shared mutation lock for an entire Home, including changed-file hashing (#750). Pure WAL reads can work while writes wait. Notes duplicate scans and Photos whole-library writer publication have detailed evidence on existing #704/#683; repeated unchanged PDF/text work is on #695.
No new number or exact cause of the readiness timeout is claimed. No product change, push or deploy. Please reuse these source checks with your opt-in startup phase timing; this audit does not duplicate that instrumentation.
Perf VM finding (#549), captured while holding
/root/perf.lock; VM load average inside the lock was8.94 7.83 4.07.The retained full-fixture startup log has one SQLite statement above 50 ms:
VACUUM INTO ?at 737 ms. It also records repeated SQLx pool-acquire waits from 2.0 to 3.59 seconds during startup. Those waits are not statements, so statement-only diagnostics did not explain the readiness miss. The phase-timing build is committed as0e1bd9235; the next locked boot will attribute the wait to its pre-listener phase.Checkpoint at the owner’s ~4-hour limit. Issue #549 remains open; the startup fix and performance matrix are not complete.
Built and committed
5f160df8dadds opt-in SQLx slow-statement logging viaCALTERNAL_SQLITE_SLOW_STATEMENT_MS, including the DB and semantic SQLite pools.b7c9e36f9makes the benchmark retain complete redacted startup logs on readiness failure.0e1bd9235adds opt-in timings for awaited pre-listener phases viaCALTERNAL_STARTUP_PHASE_TIMING=1.Files:
Cargo.lock,bench/tab-switch.mjs,crates/calternal-db/{Cargo.toml,src/db.rs,src/lib.rs,src/sqlite.rs},crates/calternal-embed/{Cargo.toml,src/store.rs,src/user_store.rs}, andcrates/calternal-server/src/wire.rs.Perf evidence
While holding
/root/perf.lock, the retained full-fixture log showed VM load8.94 7.83 4.07, one SQL statement over 50 ms (VACUUM INTO ?, 737 ms), and repeated SQLx pool-acquire waits from 2.0 to 3.59 seconds. These waits do not identify the slow pre-listener phase. This was a log review, not a new boot or tab-switch measurement.Gate output
cargo fmt --check: exit 0, no output.cargo clippy -p calternal-db --all-targets -- -D warnings: passed;Finished dev profile ... in 23.54s.cargo test -p calternal-db: 11 unit and 16 integration tests passed; 1 ignored; doc tests passed.cargo clippy -p calternal-embed --all-targets -- -D warnings: passed;Finished dev profile ... in 4m 01s.cargo test -p calternal-embed: 31 passed, 4 ignored; doc tests passed.node --check bench/tab-switch.mjs: exit 0, no output.bun test bench/tab-switch.test.mjs:cargo clippy -p calternal-server --all-targets -- -D warnings(retry after a sharedsccachedisconnect):cargo test -p calternal-serverwas stopped with exit 143 after 65 minutes; the last output wasCompiling foreign-types v0.3.2. No test results were produced.cargo build --release -p calternal-serverwas stopped with exit 143 during server linking under high shared-host load. No updated release binary was produced.cargo clean:Removed 19118 files, 6.9GiB total. The generatedapps/web/buildoutput was removed.Known gaps
No startup behavior was changed. The 737 ms statement and pool-acquire waits are not yet attributed to a phase. The instrumented release boot, root-cause fix, warm/cold tab-switch matrix, and latency-threshold check remain. I did not fetch/merge
origin/devor run an adversarial round; no API or route changed.Decision
The design does not specify startup diagnostics. I kept both diagnostic flags opt-in, and the benchmark stores redacted logs under ignored
target/tmp; SQL bind values are not logged.Head:
0e1bd923528a8fa3ba1b8012c19e99ace46ef1ee.perfguards-impl / #798 verification finding (2026-10-02)
One local run reused
bench/tab-switch.mjsand the shared release binary. The perf VM lock was occupied. The command requested Chromium, 1440 px, light, five warm Calendar → Files samples, macOS platform emulation, and the existing large Home fixture.Setup failed before any route sample:
The build host load average was above 100 during the run. This is not a measured route regression and is not a PASS. I did not rerun. The initial legacy harness did not save a report for a setup failure. #798 now fixes that harness gap: setup and later failures save an incomplete report, preserve collected samples, redact credentials, and exit 2. Four harness tests pass, including this case. The populated profile setup remains unverified. Please investigate the shared-build/Home/projection compatibility in the existing #549 profile before accepting timing numbers.
Independent read-only review of
git diff origin/dev...0e1bd9235on branchjob/hddsql-549.Findings
crates/calternal-server/src/wire.rs:1115runsRoot::recover()before the first timed phase.wire.rs:1267-1269andwire.rs:1280also await database operations withoutstartup_phase. A pool-acquire wait in these operations has no phase name or elapsed time. Time all pre-listener work, including synchronous Root setup and each awaited database operation, and add a regression check for the phase logs.bench/tab-switch.mjs:342-344saves the output as soon aschild.exitCodeis set. Child stdio can remain open until the latercloseevent, so final startup diagnostics can arrive after the snapshot. Wait forclosewith a bounded fallback before saving and add a harness regression for a final line written immediately before exit.Both findings concern #549. Search for
CALTERNAL_STARTUP_PHASE_TIMINGreturned this issue; no separate issue was filed.No build, test, server, or browser command ran, as required by the read-only job. The report is committed at
85691d7781b16575a7b58a423865c2f87a677272inreview-hddsql-549.md; evidence is inaudit-findings.md.Starting follow-up on
job/hddsql-549at0e1bd923528a8fa3ba1b8012c19e99ace46ef1ee, based onorigin/devc4a61e8cf090170f35b1bed3350d9de20c83ecd5. I will close the two independent-review findings, then trace the pool-acquire waits and move the startup VACUUM INTO work to idle background work. I will merge origin/dev once before final gates and report the measured run and gate output.Finding: Node can set
exitCodebefore child stdout/stderr close, so the harness could save a startup log without its final diagnostic. Commit6e84a3bbfwaits for stdio close with a 1 s bound in early-exit and already-stopped paths. A regression simulates buffered output arriving after the exit state and verifies the saved text includes it.node --check bench/tab-switch.mjsandbun test bench/tab-switch.test.mjspass (3 tests).Source audit finding (not yet confirmed by the perf boot):
Db::connectsets the Index writer pool to one connection and its reader pool to eight.Worker::runenforces concurrency per job kind but has no total in-flight limit;start_backgroundregisters the system handlers plus every enabled Plugin kind and starts that Worker beforebuild_live_appreturns, so due jobs can contend for those shared pools before the listener binds. I am adding a server-specific global background-job bound and deferring Worker startup until after listener bind. The build host is currently saturated with concurrent server compiles; I will still run the requested perf profile under/root/perf.lockand label any load-limited results.Snapshot lead:
Db::apply_migration_sets_with_snapshotnow checks pending migration versions and checksums before creating a pre-upgrade image. Unchanged restarts skipVACUUM INTO; pending migrations keep the recovery snapshot required by DESIGN §27. Checksum drift and the no-snapshot restart path have regression coverage. Commit:dc5f9cdfaaf7fc5c4419e2b1aa2504dafba2229f.Gate evidence for
calternal-db:cargo clippy -p calternal-db --all-targets -- -D warnings:Finisheddevprofile [unoptimized + debuginfo] target(s) in 2m 44scargo test -p calternal-db:test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 24.80s; queue integration tests:test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 2.15s; doc-tests:0 passed; 0 failed.Next I’m validating the bounded worker and first-API idle gate. No performance numbers are claimed yet.
Startup pool contention implementation is committed as
fbb35d35b0bbaae6c9699996fdaa9d8d16f04835. The server now waits until initial/api/traffic completes and stays idle for two seconds (15-second post-bind fallback), then starts reconciliation and background work. The server Worker admits one handler across all registered kinds. Pending-migration snapshots remain before schema writes; unchanged restarts skipVACUUM INTO.Verification:
cargo fmt --checkexited 0 with no output. Thecalternal-dbclippy and test gates passed (reported in the prior comment). The first server clippy compile found a watch sender/receiver ordering error; that was corrected. The corrected clippy retry then sat infolio_wait_bit_commonfor over four minutes, andcargo test -p calternal-serverentered the same I/O wait within 34 seconds. Both were stopped. At the time, 13 host processes were in uninterruptible I/O, with multiple other server clippy/test builds active. I am not claiming the server gates passed.I have not run the HDD profile yet. No UI or route contract changed.
Completed
Head:
fbb35d35b0bbaae6c9699996fdaa9d8d16f04835.VACUUM INTO; a pending migration still gets its required pre-migration snapshot./api/traffic is complete and idle for two seconds. A 15-second post-bind fallback keeps scheduled work running when no browser opens the app.Files:
bench/tab-switch.mjs,bench/tab-switch.test.mjs,crates/calternal-db/src/migrations.rs,crates/calternal-db/src/worker.rs,crates/calternal-server/src/main.rs,crates/calternal-server/src/wire.rs.Commits:
6e84a3bbf,49706398c,dc5f9cdfa,fbb35d35b. No route contract or UI changed. UX gaps: not applicable.Gates
cargo fmt --checkexited 0 and printed no output.cargo clippy -p calternal-db --all-targets -- -D warnings:cargo test -p calternal-db:The first server clippy attempt caught a watch sender/receiver ordering mistake; it is fixed. The corrected
cargo clippy -p calternal-server --all-targets -- -D warningschild then stayed infolio_wait_bit_commonfor over four minutes with no output and was stopped.cargo test -p calternal-serverentered the same uninterruptible I/O wait within 34 seconds and was stopped. At the time, 13 host processes were in uninterruptible I/O and several other server builds were active. These server gates are incomplete, not passing.Cleanup output:
Performance and remaining work
I did not build the release binary or run the HDD profile because the build host remained under heavy I/O load. No before/after measurements are claimed. For the merge round, rerun the server gates and build once:
Then alternate one cold run per binary three times.
bench/tab-switch.mjsrecords startup first paint and tab-switch timings, server CPU/RSS, and load average; it acquires the perf VM lock for its phases. SetTAB_SWITCH_VM_LOCK_WAIT_SECONDS=14400on every invocation:Decisions and gaps
VACUUM INTO.Crate gates on
job/hddsql-549(server, db, embed), run by Claude:cargo fmt --check: exit 0-D warnings: calternal-server, calternal-db, calternal-embed all exit 0cargo test -p calternal-db:test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.52s/test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.17scargo test -p calternal-embed:test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.29scargo test -p calternal-server, first run (real, fails alone 3/3): Two causes found:build_live_appfuture deeper). They pass with 4 MiB. Production polls it on the 8 MiB main thread, so this is test-only.full_app_setup_session_config_and_backuppanicked at wire.rs:7858 (dedup scrub poll timed out after 60 s). The test droppedLiveApp, which closed the new startup gate, sostart_backgroundnever ran.5dd850804test(server): run live-app children with the server stack and release startup work (#549)— child processes getRUST_MIN_STACK=SERVER_WORKER_STACK_SIZE; the test releasesstartup_workasserver_maindoes. No assertion changed.Ready for the merge round (crate gates). Note for review: any other code that builds a
LiveAppmust releasestartup_work, or background jobs never start.Performance review results from #1124 are recorded in
docs/perf/2026-10-06-1124.md.UpdateLayoutTree, 530.1 msLayout, and 42 long tasks (236 ms max). The per-frame--hourupdate likely drives grid layout; this is not isolated to one source function.685b1105f.Files and Calendar measurements used the native
/dev/sda1filesystem and are diagnostic, not HDD-qualified. Photos A/B also ran on native storage. The post-change Photos HDD run could not start because another job held/root/perf.lock; no HDD screenshot set was produced. The likely Files derived-view and Calendar layout costs remain follow-ups.