Skip to content
File

Blob: src/client/lib/workspace-route-model.ts

typescript62 lines
1import type { WorkspaceAccessMode } from "@/client/stores/workspace-replica";
2 
3/**
4 * Resolution state for the `/$workspaceSlug` route. Consumers branch on
5 * `phase` and read `workspaceId` / `accessMode` only where the shape below
6 * guarantees them.
7 *
8 * Route state carries identity KEYS only (`workspaceId`, slug). Mutable
9 * workspace payload lives in the replica projection; consumers read it via
10 * `useWorkspaceReplica(workspaceId)` (and siblings). Cache-vs-live is a
11 * provider-internal concern used to gate revalidation, not part of the
12 * public state.
13 */
14export type WorkspaceRouteState =
15 /**
16 * Resolving. `workspaceId` is set when a cached snapshot seeded the initial
17 * render and is null on a cold start until the first response lands.
18 */
19 | { phase: "loading"; workspaceId: string | null }
20 /** Workspace resolved and usable. */
21 | {
22 phase: "ready";
23 workspaceId: string;
24 accessMode: WorkspaceAccessMode;
25 }
26 /**
27 * Cached workspace identity exists but live access could not be confirmed
28 * (e.g. shared-downgrade probe failed). Page routes can still render from
29 * cache via the active-page boundary; member-only routes (index, settings) are
30 * blocked.
31 */
32 | {
33 phase: "degraded";
34 workspaceId: string | null;
35 workspaceSlug: string;
36 reason: "stale-shared";
37 }
38 /** Terminal failure; the workspace cannot be rendered. */
39 | { phase: "error"; errorKind: "not-found" | "network"; message: string };
40 
41type ReadyState = Extract<WorkspaceRouteState, { phase: "ready" }>;
42type WithWorkspaceId = Exclude<WorkspaceRouteState, { phase: "error" }> & { workspaceId: string };
43 
44export function isWorkspaceReady(state: WorkspaceRouteState): state is ReadyState {
45 return state.phase === "ready";
46}
47 
48export function hasWorkspaceIdentity(state: WorkspaceRouteState): state is WithWorkspaceId {
49 return state.phase !== "error" && state.workspaceId !== null;
50}
51 
52/**
53 * Shell chrome is still resolving identity — render a skeleton sidebar.
54 * True for `loading` (no workspace yet) and `degraded` (stale-shared probe
55 * produced partial identity). Consumers outside this module should use this
56 * helper instead of checking `phase === "degraded"` directly so that route
57 * shape changes stay local.
58 */
59export function isResolvingWorkspace(state: WorkspaceRouteState): boolean {
60 return state.phase === "loading" || state.phase === "degraded";
61}