Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/web/src/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
// downloading and evaluating. The OAuth return still leaves the URL before anything reads it.
import "./platform/oauth-return-boot"
import { startClerkBoot } from "./platform/clerk-boot"
// Static on purpose: Chrome holds a dynamic import of a module the app's own graph also imports
// until that whole graph has loaded, which would start the page code no earlier than `init`.
import { startRoutePageCode } from "./page/code"

startClerkBoot()
startRoutePageCode(location.pathname)
20 changes: 20 additions & 0 deletions apps/web/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Command } from "foldkit"
import { load, pushUrl, replaceUrl } from "foldkit/navigation"
import { Message } from "./message"
import { PRELOAD_HOLD_MS } from "./page/preload"
import { loadPageCode } from "./page/registry"
import { clearSessionToken, MapleAuthApi, readSessionToken, saveSessionToken } from "./platform/auth-api"
import { saveSelectedPlanKnown } from "./page/onboarding/plan-gate"
import { loadClerk, signOutOfClerk } from "./platform/clerk"
import { BillingCustomerCache } from "./data/billing-customer"
import { IntegrationStatuses } from "./data/integration-statuses"
import { toLoadFailure } from "./data/load-failure"
import { ReadCache } from "./data/read-cache"
import { OrgCollections } from "./data/collections/org-collections"
import { readSessionTimeRange, writeSessionTimeRange } from "./platform/session-time-range"
Expand Down Expand Up @@ -100,6 +102,24 @@ export const LoadClerk = Command.define("LoadClerk", {
),
})

/** A page's code (`page/registry.ts`), for the route about to enter it or a link's preload. */
export const LoadPageCode = Command.define("LoadPageCode", {
args: { pageId: Schema.String, preloadHref: Schema.NullOr(Schema.String) },
messages: [Message.CompletedLoadPageCode, Message.FailedLoadPageCode],
execute: ({ pageId, preloadHref }) =>
Effect.annotateCurrentSpan({
"maple.page.id": pageId,
"maple.page.code_for_preload": preloadHref !== null,
}).pipe(
Effect.andThen(loadPageCode(pageId)),
Effect.andThen(Clock.currentTimeMillis),
Effect.map((nowMs) => Message.CompletedLoadPageCode({ pageId, preloadHref, nowMs })),
Effect.catchTag("@maple/web/PageLoadError", (error) =>
Effect.succeed(Message.FailedLoadPageCode({ pageId, failure: toLoadFailure(error) })),
),
),
})

export const WaitForAuthSettle = Command.define("WaitForAuthSettle", {
messages: [Message.CompletedWaitForAuthSettle],
execute: Effect.sleep(AUTH_SETTLE_TIMEOUT).pipe(Effect.as(Message.CompletedWaitForAuthSettle())),
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import "./platform/oauth-return-boot"
import "./styles.css"

import { Effect } from "effect"
import { Runtime } from "foldkit"
import type { UrlRequest } from "foldkit/navigation"
import type { Url } from "foldkit/url"
import { trackAvatarImageLoads } from "./ui/avatar-image"
import { labEnabled, labPathOf, startLab } from "./lab/boot"
import { Flags, flags, init, Message, Model, subscriptions, update, view } from "./main"
import { loadEveryPage } from "./page/registry"
import { ResourcesLive } from "./platform/resources"
import { instrument } from "./platform/telemetry"

Expand Down Expand Up @@ -40,5 +42,8 @@ const startApplication = () =>

// `/lab/<path>` boots that lab page's own program instead of the app (scenarios, benches).
const labPath = labEnabled ? labPathOf(location.pathname) : undefined
if (labPath === undefined) startApplication()
else void startLab(labPath, container)
if (labPath !== undefined) void startLab(labPath, container)
// A dev reload can restore a Model holding a page: that skips `init`, which loads the page's code.
else if (import.meta.hot)
Effect.runFork(Effect.andThen(Effect.ignore(loadEveryPage), Effect.sync(startApplication)))
else startApplication()
34 changes: 23 additions & 11 deletions apps/web/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { isClerkAuthEnabled } from "./lib/services/common/auth-mode"
import { Effect, Option, Schema } from "effect"
import type { Runtime } from "foldkit"
import type { Command, Runtime } from "foldkit"
import type { Url } from "foldkit/url"
import { CheckSession, LoadClerk } from "./command"
import { CheckSession, LoadClerk, LoadPageCode } from "./command"
import type { Message } from "./message"
import { ClerkPhase, type Model, Session } from "./model"
import { ClerkPhase, type Model, PageCode, Session } from "./model"
import { initCustomer, readKnownToRenderApp } from "./page/onboarding/plan-gate"
import * as Preload from "./page/preload"
import { unloadedPageOf } from "./page/registry"
import * as SignIn from "./page/sign-in"
import type { Resources } from "./platform/resources"
import { readSessionToken } from "./platform/auth-api"
Expand Down Expand Up @@ -51,6 +52,16 @@ export const flags: Effect.Effect<Flags> = Effect.all({

// INIT

/** The route's page code loads alongside the session, so the page can enter once it resolves. */
const withRoutePageCode = (model: Model, sessionCommand: Command.Command<Message, never, Resources>) =>
Option.match(unloadedPageOf(model.route), {
onNone: () => ({ model, commands: [sessionCommand] }),
onSome: (pageId) => ({
model: { ...model, pageCode: PageCode.Loading({ pageId }) },
commands: [sessionCommand, LoadPageCode({ pageId, preloadHref: null })],
}),
})

export const init: Runtime.RoutingApplicationInit<Model, Message, Flags, Resources> = (flags, url: Url) => {
const route = urlToAppRoute(url)
const base = {
Expand All @@ -61,6 +72,7 @@ export const init: Runtime.RoutingApplicationInit<Model, Message, Flags, Resourc
toastActions: {},
toastRootActions: {},
page: null,
pageCode: PageCode.Ready(),
pageInstances: 0,
parkedPages: [],
onboarding: null,
Expand All @@ -73,17 +85,17 @@ export const init: Runtime.RoutingApplicationInit<Model, Message, Flags, Resourc
}

if (isClerkAuthEnabled) {
return {
model: { ...base, session: Session.Resolving(), clerk: ClerkPhase.Loading() },
commands: [LoadClerk()],
}
return withRoutePageCode(
{ ...base, session: Session.Resolving(), clerk: ClerkPhase.Loading() },
LoadClerk(),
)
}

if (flags.hasStoredSession) {
return {
model: { ...base, session: Session.Resolving(), clerk: ClerkPhase.Unused() },
commands: [CheckSession()],
}
return withRoutePageCode(
{ ...base, session: Session.Resolving(), clerk: ClerkPhase.Unused() },
CheckSession(),
)
}

return settle({ ...base, session: Session.SignedOut(), clerk: ClerkPhase.Unused() })
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Schema } from "effect"
import { defineMessageUnion } from "foldkit/message"
import { UrlRequest } from "foldkit/navigation"
import { Url } from "foldkit/url"
import { LoadFailure } from "./data/load-failure"
import * as Onboarding from "./page/onboarding"
import { PageMessage } from "./page/registry"
import * as SignIn from "./page/sign-in"
Expand All @@ -28,6 +29,15 @@ export const Message = defineMessageUnion({
CompletedSaveSessionToken: {},
CompletedLoadClerk: { snapshot: ClerkSnapshot },
FailedLoadClerk: { message: Schema.String },
/** A page's code loaded; `preloadHref` is the link whose intent asked, its preload still to run. */
CompletedLoadPageCode: {
pageId: Schema.String,
preloadHref: Schema.NullOr(Schema.String),
nowMs: Schema.Number,
},
FailedLoadPageCode: { pageId: Schema.String, failure: LoadFailure },
/** The failed page's Reload: a full load of the route, which also picks up a newer deploy. */
ClickedReloadPage: {},
ChangedClerkSession: { snapshot: ClerkSnapshot },
CompletedWaitForAuthSettle: {},
SucceededLoadCustomer: { orgId: Schema.String, customer: BillingCustomer },
Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { RootToastAction } from "./page/contract"
import { PageMessage, PageSlot } from "./page/registry"
import * as SignIn from "./page/sign-in"
import * as Toast from "./ui/toast"
import { LoadFailure } from "./data/load-failure"
import { AppRoute } from "./route"
import { SessionTimeRange } from "./route/session-time-range"
import * as Shell from "./shell"
Expand All @@ -30,6 +31,14 @@ export const ClerkPhase = defineTaggedUnion({
})
export type ClerkPhase = typeof ClerkPhase.Type

/** The routed page's code (`page/registry.ts`): loading before its first entry, or failed to load. */
export const PageCode = defineTaggedUnion({
Ready: {},
Loading: { pageId: Schema.String },
Failed: { pageId: Schema.String, failure: LoadFailure },
})
export type PageCode = typeof PageCode.Type

export const Model = Schema.Struct({
route: AppRoute,
session: Session,
Expand All @@ -45,6 +54,8 @@ export const Model = Schema.Struct({
toastRootActions: Schema.Record(Schema.String, RootToastAction),
/** The routed page (`page/registry.ts`): built on route entry, dropped on leave. */
page: Schema.NullOr(PageSlot),
/** The route's page while its code loads (the page stays `null` until then), or why it did not. */
pageCode: PageCode,
/** Page instances built so far; the next build is stamped `pageInstances + 1`. */
pageInstances: Schema.Number,
/** Pages left recently, newest first: going back revives one with its last data (`parkPages`). */
Expand Down
Loading
Loading