Skip to content

Commit 053af9f

Browse files
committed
Stage trade startup work and harden Playwright readiness
1 parent b76b8a2 commit 053af9f

11 files changed

Lines changed: 752 additions & 251 deletions

File tree

docs/exec-plans/completed/2026-04-13-trade-startup-main-thread-staging.md

Lines changed: 294 additions & 0 deletions
Large diffs are not rendered by default.

src/hyperopen/app/startup.cljs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
[f]
3737
(startup-runtime-lib/schedule-post-render-startup! f))
3838

39+
(defn yield-to-main!
40+
[]
41+
(startup-runtime-lib/yield-to-main!))
42+
3943
(defn startup-base-deps
4044
[{:keys [runtime store api]}]
4145
(startup-collaborators/startup-base-deps
@@ -282,6 +286,7 @@
282286
effects (post-render-route-effects @startup-store path)]
283287
(when (seq effects)
284288
(nxr/dispatch startup-store nil effects))))
289+
:yield-to-main! yield-to-main!
285290
:schedule-post-render-startup! schedule-post-render-startup!
286291
:kick-render! (fn [runtime-store]
287292
(swap! runtime-store identity))}))))

src/hyperopen/startup/init.cljs

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
(mark-performance! "app:init:start")
1313
(schedule-startup-summary-log!))
1414

15-
(defn restore-persisted-ui-state!
15+
(defn restore-critical-ui-state!
1616
[{:keys [store
1717
restore-ui-font-preference!
1818
restore-ui-locale-preference!
19-
restore-asset-selector-sort-settings!
2019
restore-chart-options!
2120
restore-orderbook-ui!
22-
restore-portfolio-summary-time-range!
2321
restore-vaults-snapshot-range!
2422
restore-agent-storage-mode!
2523
restore-agent-passkey-capability!
@@ -28,24 +26,15 @@
2826
restore-spectate-mode-url!
2927
restore-trade-route-tab!
3028
restore-active-asset!
31-
restore-asset-selector-markets-cache!
32-
restore-leaderboard-preferences!
33-
restore-open-orders-sort-settings!
34-
restore-funding-history-pagination-settings!
35-
restore-trade-history-pagination-settings!
36-
restore-order-history-pagination-settings!]}]
29+
restore-asset-selector-markets-cache!]}]
3730
;; Restore root typography preference (system default, optional Inter override).
3831
(restore-ui-font-preference!)
3932
;; Restore UI locale preference for number/date formatting ownership.
4033
(restore-ui-locale-preference! store)
41-
;; Restore asset selector sort settings from localStorage.
42-
(restore-asset-selector-sort-settings! store)
4334
;; Restore chart options from localStorage.
4435
(restore-chart-options! store)
4536
;; Restore orderbook UI options from localStorage.
4637
(restore-orderbook-ui! store)
47-
;; Restore portfolio summary range selector from localStorage.
48-
(restore-portfolio-summary-time-range! store)
4938
;; Restore vault snapshot range selector from localStorage.
5039
(restore-vaults-snapshot-range! store)
5140
;; Restore agent storage preference from localStorage.
@@ -65,7 +54,21 @@
6554
;; Restore selected asset from localStorage (default to BTC).
6655
(restore-active-asset! store)
6756
;; Restore cached selector market symbols for immediate dropdown population.
68-
(restore-asset-selector-markets-cache! store)
57+
(restore-asset-selector-markets-cache! store))
58+
59+
(defn restore-deferred-ui-state!
60+
[{:keys [store
61+
restore-asset-selector-sort-settings!
62+
restore-portfolio-summary-time-range!
63+
restore-leaderboard-preferences!
64+
restore-open-orders-sort-settings!
65+
restore-funding-history-pagination-settings!
66+
restore-trade-history-pagination-settings!
67+
restore-order-history-pagination-settings!]}]
68+
;; Restore asset selector sort settings from localStorage.
69+
(restore-asset-selector-sort-settings! store)
70+
;; Restore portfolio summary range selector from localStorage.
71+
(restore-portfolio-summary-time-range! store)
6972
;; Restore leaderboard timeframe, sort, and page-size preferences from IndexedDB.
7073
(when (fn? restore-leaderboard-preferences!)
7174
(restore-leaderboard-preferences! store))
@@ -91,6 +94,8 @@
9194
mark-post-render-trade-secondary-panels-ready!
9295
initialize-remote-data-streams!
9396
load-post-render-route-effects!
97+
restore-deferred-ui-state!
98+
yield-to-main!
9499
kick-render!
95100
schedule-post-render-startup!]}]
96101
(set-on-connected-handler! handle-wallet-connected)
@@ -105,22 +110,32 @@
105110
(kick-render! store)
106111
(let [post-render-startup!
107112
(fn []
108-
;; Install global keyboard shortcuts that should work regardless of focus target.
109-
(when (fn? install-asset-selector-shortcuts!)
110-
(install-asset-selector-shortcuts!))
111-
;; Install click-away behavior for positioned overlays (TP/SL, Reduce, Margin, Spectate Mode).
112-
(when (fn? install-position-tpsl-clickaway!)
113-
(install-position-tpsl-clickaway!))
114-
;; Register icon cache service worker for cross-reload symbol icon caching.
115-
(register-icon-service-worker!)
116-
;; Reveal lower desktop trade surfaces after the first paint has landed.
117-
(when (fn? mark-post-render-trade-secondary-panels-ready!)
118-
(mark-post-render-trade-secondary-panels-ready! store))
119-
;; Defer route-specific heavyweight work until after the initial shell paint.
120-
(when (fn? load-post-render-route-effects!)
121-
(load-post-render-route-effects! store))
122-
;; Initialize remote data streams.
123-
(initialize-remote-data-streams!))]
113+
(-> (js/Promise.resolve nil)
114+
(.then (fn [_]
115+
;; Install global keyboard shortcuts that should work regardless of focus target.
116+
(when (fn? install-asset-selector-shortcuts!)
117+
(install-asset-selector-shortcuts!))
118+
;; Install click-away behavior for positioned overlays (TP/SL, Reduce, Margin, Spectate Mode).
119+
(when (fn? install-position-tpsl-clickaway!)
120+
(install-position-tpsl-clickaway!))
121+
;; Reveal lower desktop trade surfaces after the first paint has landed.
122+
(when (fn? mark-post-render-trade-secondary-panels-ready!)
123+
(mark-post-render-trade-secondary-panels-ready! store))
124+
;; Defer route-specific heavyweight work until after the initial shell paint.
125+
(when (fn? load-post-render-route-effects!)
126+
(load-post-render-route-effects! store))
127+
;; Initialize remote data streams.
128+
(initialize-remote-data-streams!)))
129+
(.then (fn [_]
130+
(if (fn? yield-to-main!)
131+
(yield-to-main!)
132+
(js/Promise.resolve nil))))
133+
(.then (fn [_]
134+
;; Restore clearly non-visible UI state after first paint has settled.
135+
(when (fn? restore-deferred-ui-state!)
136+
(restore-deferred-ui-state!))
137+
;; Register icon cache service worker for cross-reload symbol icon caching.
138+
(register-icon-service-worker!)))))]
124139
;; Ensure first render is enqueued before expensive subscriptions/fetch startup work.
125140
(if (fn? schedule-post-render-startup!)
126141
(schedule-post-render-startup! post-render-startup!)
@@ -130,5 +145,9 @@
130145
[{:keys [log-fn] :as deps}]
131146
(log-fn "Initializing Hyperopen...")
132147
(reset-startup-state! deps)
133-
(restore-persisted-ui-state! deps)
134-
(initialize-systems! deps))
148+
(restore-critical-ui-state! deps)
149+
(initialize-systems!
150+
(assoc deps
151+
:restore-deferred-ui-state!
152+
(fn []
153+
(restore-deferred-ui-state! deps)))))
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
(ns hyperopen.startup.route-refresh
2+
(:require [hyperopen.api-wallets.actions :as api-wallets-actions]
3+
[hyperopen.funding-comparison.actions :as funding-comparison-actions]
4+
[hyperopen.leaderboard.actions :as leaderboard-actions]
5+
[hyperopen.portfolio.routes :as portfolio-routes]
6+
[hyperopen.router :as router]
7+
[hyperopen.staking.actions :as staking-actions]
8+
[hyperopen.vaults.infrastructure.routes :as vault-routes]))
9+
10+
(defn current-route-path
11+
[state]
12+
(router/normalize-path (or (get-in state [:router :path])
13+
"/trade")))
14+
15+
(defn current-route-refresh-effects
16+
[state new-address]
17+
(let [route (current-route-path state)]
18+
(cond-> (cond
19+
(leaderboard-actions/leaderboard-route? route)
20+
[[:actions/load-leaderboard-route route]]
21+
22+
(vault-routes/vault-route? route)
23+
[[:actions/load-vault-route route]]
24+
25+
(funding-comparison-actions/funding-comparison-route? route)
26+
[[:actions/load-funding-comparison-route route]]
27+
28+
(staking-actions/staking-route? route)
29+
[[:actions/load-staking-route route]]
30+
31+
(api-wallets-actions/api-wallet-route? route)
32+
[[:actions/load-api-wallet-route route]]
33+
34+
:else [])
35+
(and new-address
36+
(portfolio-routes/portfolio-route? route))
37+
(conj [:actions/select-portfolio-chart-tab
38+
(get-in state [:portfolio-ui :chart-tab])]))))

src/hyperopen/startup/runtime.cljs

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
(ns hyperopen.startup.runtime
22
(:require [clojure.string :as str]
3+
[goog.object :as gobj]
34
[hyperopen.account.surface-service :as account-surface-service]
45
[hyperopen.account.context :as account-context]
56
[hyperopen.api.info-client :as info-client]
67
[hyperopen.platform :as platform]
7-
[hyperopen.portfolio.routes :as portfolio-routes]
8+
[hyperopen.startup.route-refresh :as route-refresh]
89
[hyperopen.wallet.address-watcher :as address-watcher]))
9-
1010
(defn default-startup-runtime-state
1111
[]
1212
{:deferred-scheduled? false
1313
:bootstrapped-address nil
1414
:summary-logged? false})
15-
1615
(def default-address-handler-name
1716
"startup-account-bootstrap-handler")
18-
1917
(def ^:private default-user-handler-name
2018
"user-ws-subscription-handler")
21-
2219
(def ^:private default-webdata2-handler-name
2320
"webdata2-subscription-handler")
2421

@@ -46,6 +43,25 @@
4643
;; Yield one macrotask so browser can paint before startup fetch/subscription work.
4744
(platform/set-timeout! f 0))
4845

46+
(defn yield-to-main!
47+
[]
48+
(let [scheduler-object (some-> (when (exists? js/globalThis) js/globalThis)
49+
(.-scheduler))
50+
yield-fn (some-> scheduler-object (gobj/get "yield"))
51+
timeout-fallback (fn []
52+
(js/Promise.
53+
(fn [resolve _reject]
54+
(platform/set-timeout!
55+
(fn []
56+
(resolve nil))
57+
0))))]
58+
(if (fn? yield-fn)
59+
(try
60+
(.call yield-fn scheduler-object)
61+
(catch :default _
62+
(timeout-fallback)))
63+
(timeout-fallback))))
64+
4965
(defn reify-address-handler
5066
[on-address-changed-fn handler-name]
5167
(reify address-watcher/IAddressChangeHandler
@@ -394,6 +410,14 @@
394410
:resolve-current-address account-context/effective-account-address
395411
:log-fn log-fn})))))
396412

413+
(defn- refresh-current-route!
414+
[store dispatch! new-address]
415+
(when (fn? dispatch!)
416+
(when-let [effects (seq (route-refresh/current-route-refresh-effects
417+
@store
418+
new-address))]
419+
(dispatch! store nil effects))))
420+
397421
(defn install-address-handlers!
398422
[{:keys [store
399423
bootstrap-account-data!
@@ -431,19 +455,7 @@
431455
(if new-address
432456
(bootstrap-account-data! new-address)
433457
(clear-disconnected-account-state! deps))
434-
(when (fn? dispatch!)
435-
(let [route (or (get-in @store [:router :path])
436-
"/trade")]
437-
(dispatch! store nil [[:actions/load-leaderboard-route route]])
438-
(dispatch! store nil [[:actions/load-vault-route route]])
439-
(dispatch! store nil [[:actions/load-funding-comparison-route route]])
440-
(dispatch! store nil [[:actions/load-staking-route route]])
441-
(dispatch! store nil [[:actions/load-api-wallet-route route]])
442-
(when (and new-address
443-
(portfolio-routes/portfolio-route? route))
444-
;; Ensure returns benchmark candles load on initial portfolio view entry.
445-
(dispatch! store nil [[:actions/select-portfolio-chart-tab
446-
(get-in @store [:portfolio-ui :chart-tab])]])))))
458+
(refresh-current-route! store dispatch! new-address))
447459
address-handler-name))
448460
;; Ensure already-connected wallets are handled after handlers are in place.
449461
(when sync-current-address-on-install?
@@ -536,21 +548,8 @@
536548
;; Ensure active-asset market streams are requested on startup.
537549
(when-let [asset (:active-asset @store)]
538550
(dispatch! store nil [[:actions/subscribe-to-asset asset]]))
539-
(dispatch! store nil [[:actions/load-leaderboard-route
540-
(or (get-in @store [:router :path])
541-
"/trade")]])
542-
(dispatch! store nil [[:actions/load-vault-route
543-
(or (get-in @store [:router :path])
544-
"/trade")]])
545-
(dispatch! store nil [[:actions/load-funding-comparison-route
546-
(or (get-in @store [:router :path])
547-
"/trade")]])
548-
(dispatch! store nil [[:actions/load-staking-route
549-
(or (get-in @store [:router :path])
550-
"/trade")]])
551-
(dispatch! store nil [[:actions/load-api-wallet-route
552-
(or (get-in @store [:router :path])
553-
"/trade")]])
551+
;; Keep startup route refreshes scoped to the actual current route only.
552+
(refresh-current-route! store dispatch! nil)
554553
(install-address-handlers!)
555554
;; Keep startup scoped to the active trade route. Full selector-market expansion
556555
;; off the critical path, but restore the prefetch as deferred idle work so the

test/hyperopen/app/startup_test.cljs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
(let [store (atom {:active-asset "BTC"})
1717
runtime (atom {:startup {}})
1818
captured-init-deps (atom nil)
19-
summary-calls (atom [])]
19+
summary-calls (atom [])
20+
yield-calls (atom 0)]
2021
(with-redefs [startup-collaborators/startup-base-deps
2122
(fn [deps]
2223
(merge
@@ -30,15 +31,22 @@
3031
(reset! captured-init-deps deps))
3132
startup-runtime/schedule-startup-summary-log!
3233
(fn [deps]
33-
(swap! summary-calls conj deps))]
34+
(swap! summary-calls conj deps))
35+
startup-runtime/yield-to-main!
36+
(fn []
37+
(swap! yield-calls inc)
38+
:yielded)]
3439
(app-startup/init! {:runtime runtime
3540
:store store})
3641
(is (map? @captured-init-deps))
3742
(is (identical? startup-runtime/default-startup-runtime-state
3843
(:default-startup-runtime-state @captured-init-deps)))
3944
((:schedule-startup-summary-log! @captured-init-deps))
4045
(is (= [runtime-state/startup-summary-delay-ms]
41-
(map :delay-ms @summary-calls))))))
46+
(map :delay-ms @summary-calls)))
47+
(is (= :yielded
48+
((:yield-to-main! @captured-init-deps))))
49+
(is (= 1 @yield-calls)))))
4250

4351
(deftest init-passes-trading-settings-restore-hook-into-startup-init-test
4452
(let [store (atom {})

0 commit comments

Comments
 (0)