2026-07-21 20:52:20 +03:00
|
|
|
const STORAGE_KEY = "openchamber_session_load_perf"
|
|
|
|
|
const MAX_EVENTS = 1_000
|
2026-07-31 12:51:15 +03:00
|
|
|
const ALLOWED_OPERATIONS = new Set([
|
|
|
|
|
"bootstrap.directory",
|
|
|
|
|
"bootstrap.sessions.all",
|
|
|
|
|
"bootstrap.sessions.archived",
|
|
|
|
|
"bootstrap.sessions.roots",
|
|
|
|
|
"global-sessions.active",
|
|
|
|
|
"global-sessions.archived",
|
|
|
|
|
"session-messages.initial",
|
|
|
|
|
"session-messages.older",
|
|
|
|
|
"session-messages.page",
|
|
|
|
|
"session-messages.refresh",
|
|
|
|
|
"session-messages.visible",
|
|
|
|
|
"session-prefetch",
|
|
|
|
|
])
|
|
|
|
|
const ALLOWED_CALLERS = new Set([
|
|
|
|
|
"action-demand",
|
|
|
|
|
"current-directory",
|
|
|
|
|
"initial",
|
|
|
|
|
"initial-page",
|
|
|
|
|
"known-project",
|
|
|
|
|
"known-worktree",
|
|
|
|
|
"older",
|
|
|
|
|
"pagination",
|
|
|
|
|
"prefetch",
|
|
|
|
|
"project-expanded",
|
|
|
|
|
"refresh",
|
|
|
|
|
"selected-session",
|
|
|
|
|
"server-connected",
|
|
|
|
|
"worktree-expanded",
|
|
|
|
|
])
|
|
|
|
|
const ALLOWED_OUTCOMES = new Set<SessionLoadPerformanceOutcome>([
|
|
|
|
|
"complete",
|
|
|
|
|
"error",
|
|
|
|
|
"stale",
|
|
|
|
|
"deduplicated",
|
|
|
|
|
"canceled",
|
|
|
|
|
])
|
2026-07-21 20:52:20 +03:00
|
|
|
|
|
|
|
|
type SessionLoadPerformanceOutcome = "complete" | "error" | "stale" | "deduplicated" | "canceled"
|
|
|
|
|
|
|
|
|
|
type SessionLoadPerformanceEvent = {
|
|
|
|
|
operation: string
|
|
|
|
|
caller?: string
|
|
|
|
|
queuedMs?: number
|
2026-07-31 12:51:15 +03:00
|
|
|
requestLimit?: number
|
|
|
|
|
cursorPresent?: boolean
|
2026-07-21 20:52:20 +03:00
|
|
|
durationMs: number
|
|
|
|
|
outcome: SessionLoadPerformanceOutcome
|
|
|
|
|
retryCount?: number
|
|
|
|
|
recordCount?: number
|
|
|
|
|
at: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SessionLoadPerformanceState = {
|
|
|
|
|
events: SessionLoadPerformanceEvent[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
|
|
|
|
__openchamberSessionLoadPerformance?: SessionLoadPerformanceState
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 12:51:15 +03:00
|
|
|
const isSessionLoadPerformanceEnabled = (): boolean => {
|
2026-07-21 20:52:20 +03:00
|
|
|
if (typeof window === "undefined") return false
|
|
|
|
|
try {
|
|
|
|
|
return window.localStorage.getItem(STORAGE_KEY) === "1"
|
|
|
|
|
} catch {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const now = (): number => typeof performance !== "undefined" && typeof performance.now === "function"
|
|
|
|
|
? performance.now()
|
|
|
|
|
: Date.now()
|
|
|
|
|
|
2026-07-31 12:51:15 +03:00
|
|
|
const nonNegativeNumber = (value: unknown): number | undefined => (
|
|
|
|
|
typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined
|
|
|
|
|
)
|
|
|
|
|
const nonNegativeInteger = (value: unknown): number | undefined => (
|
|
|
|
|
Number.isInteger(value) && Number(value) >= 0 ? Number(value) : undefined
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-21 20:52:20 +03:00
|
|
|
export function startSessionLoadPerformanceEvent(input: Omit<SessionLoadPerformanceEvent, "at" | "durationMs" | "outcome">) {
|
2026-07-31 12:51:15 +03:00
|
|
|
if (
|
|
|
|
|
!isSessionLoadPerformanceEnabled()
|
|
|
|
|
|| !ALLOWED_OPERATIONS.has(input.operation)
|
|
|
|
|
|| (input.caller !== undefined && !ALLOWED_CALLERS.has(input.caller))
|
|
|
|
|
) return () => undefined
|
2026-07-21 20:52:20 +03:00
|
|
|
const startedAt = now()
|
|
|
|
|
return (
|
|
|
|
|
outcome: SessionLoadPerformanceOutcome,
|
|
|
|
|
details?: Partial<Pick<SessionLoadPerformanceEvent, "retryCount" | "recordCount">>,
|
|
|
|
|
) => {
|
2026-07-31 12:51:15 +03:00
|
|
|
if (typeof window === "undefined" || !ALLOWED_OUTCOMES.has(outcome)) return
|
2026-07-21 20:52:20 +03:00
|
|
|
const state = window.__openchamberSessionLoadPerformance ?? { events: [] }
|
2026-07-31 12:51:15 +03:00
|
|
|
const queuedMs = nonNegativeNumber(input.queuedMs)
|
|
|
|
|
const requestLimit = nonNegativeInteger(input.requestLimit)
|
|
|
|
|
const retryCount = nonNegativeInteger(details?.retryCount ?? input.retryCount)
|
|
|
|
|
const recordCount = nonNegativeInteger(details?.recordCount ?? input.recordCount)
|
2026-07-21 20:52:20 +03:00
|
|
|
state.events.push({
|
2026-07-31 12:51:15 +03:00
|
|
|
operation: input.operation,
|
|
|
|
|
...(input.caller !== undefined ? { caller: input.caller } : {}),
|
|
|
|
|
...(queuedMs !== undefined ? { queuedMs } : {}),
|
|
|
|
|
...(requestLimit !== undefined ? { requestLimit } : {}),
|
|
|
|
|
...(typeof input.cursorPresent === "boolean" ? { cursorPresent: input.cursorPresent } : {}),
|
2026-07-21 20:52:20 +03:00
|
|
|
outcome,
|
|
|
|
|
durationMs: Math.max(0, now() - startedAt),
|
2026-07-31 12:51:15 +03:00
|
|
|
...(retryCount !== undefined ? { retryCount } : {}),
|
|
|
|
|
...(recordCount !== undefined ? { recordCount } : {}),
|
2026-07-21 20:52:20 +03:00
|
|
|
at: Date.now(),
|
|
|
|
|
})
|
|
|
|
|
if (state.events.length > MAX_EVENTS) state.events.splice(0, state.events.length - MAX_EVENTS)
|
|
|
|
|
window.__openchamberSessionLoadPerformance = state
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-31 12:51:15 +03:00
|
|
|
|
|
|
|
|
type FirstVisibleSessionPerformanceDependencies = {
|
|
|
|
|
enabled: () => boolean
|
|
|
|
|
requestFrame: (callback: FrameRequestCallback) => number
|
|
|
|
|
cancelFrame: (frame: number) => void
|
|
|
|
|
markVisible: () => void
|
|
|
|
|
startEvent: typeof startSessionLoadPerformanceEvent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FIRST_VISIBLE_MARK = "openchamber.chat.first_message_visible"
|
|
|
|
|
|
|
|
|
|
export function createFirstVisibleSessionPerformanceTracker(
|
|
|
|
|
dependencies?: Partial<FirstVisibleSessionPerformanceDependencies>,
|
|
|
|
|
) {
|
|
|
|
|
const enabled = dependencies?.enabled ?? isSessionLoadPerformanceEnabled
|
|
|
|
|
const requestFrame = dependencies?.requestFrame ?? ((callback) => window.requestAnimationFrame(callback))
|
|
|
|
|
const cancelFrame = dependencies?.cancelFrame ?? ((frame) => window.cancelAnimationFrame(frame))
|
|
|
|
|
const markVisible = dependencies?.markVisible ?? (() => {
|
|
|
|
|
performance.mark(FIRST_VISIBLE_MARK)
|
|
|
|
|
performance.clearMarks(FIRST_VISIBLE_MARK)
|
|
|
|
|
})
|
|
|
|
|
const startEvent = dependencies?.startEvent ?? startSessionLoadPerformanceEvent
|
|
|
|
|
const measuredKeys = new Set<string>()
|
|
|
|
|
let pending: { key: string; frame: number } | null = null
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
schedule(key: string, recordCount: number): () => void {
|
|
|
|
|
if (!enabled() || measuredKeys.has(key)) return () => undefined
|
|
|
|
|
if (pending) {
|
|
|
|
|
cancelFrame(pending.frame)
|
|
|
|
|
pending = null
|
|
|
|
|
}
|
|
|
|
|
const finishPerformanceEvent = startEvent({
|
|
|
|
|
operation: "session-messages.visible",
|
|
|
|
|
caller: "selected-session",
|
|
|
|
|
recordCount,
|
|
|
|
|
})
|
|
|
|
|
const frame = requestFrame(() => {
|
|
|
|
|
if (pending?.key !== key || pending.frame !== frame) return
|
|
|
|
|
pending = null
|
|
|
|
|
measuredKeys.add(key)
|
|
|
|
|
if (measuredKeys.size > MAX_EVENTS) {
|
|
|
|
|
measuredKeys.delete(measuredKeys.values().next().value!)
|
|
|
|
|
}
|
|
|
|
|
markVisible()
|
|
|
|
|
finishPerformanceEvent("complete")
|
|
|
|
|
})
|
|
|
|
|
pending = { key, frame }
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (pending?.key !== key || pending.frame !== frame) return
|
|
|
|
|
cancelFrame(frame)
|
|
|
|
|
pending = null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|