Skip to content

Commit c3dea2d

Browse files
mpstatonclaude
andcommitted
milestone(boot-timing): instrument the boot path — measure before we refactor — Fixes #80
Step 0 of Refactoring-for-API-Speed: the shell wasn't timing itself, so "it's slow" had no shape. Adds a dependency-free bootMark/bootSummary timer to @augment-it/workspace and stamps every boot milestone: shell:mount → ws:connect-start → ws:open → ws:session-verified → workspace.list:sent/returned → workspace.activate:sent/returned → workspaces:ready (+ a console.table summary) Each mark logs per-step and cumulative ms. On by default; silence with localStorage 'augment_boot_timing'='off'. Marks live at the real seams — transport.ts (connect/open/session frame) and state.svelte.ts loadWorkspaces — so the numbers are the actual wall-clock, not a guess. Verified: packages/workspace tsc + the transport property test (4) green; shell rsbuild build green. The timeline prints to the browser console on next load (after redeploy, or in local dev) — that's the measurement that decides whether the 60s is the readiness race (a day's fix) or a deeper refactor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UYTYu4MAFZ7iyr2VTo2kq
1 parent 55ee61c commit c3dea2d

5 files changed

Lines changed: 73 additions & 1 deletion

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// boot-timing.ts — dead-simple boot-path instrumentation. No library.
2+
//
3+
// Step 0 of context-v/issues/Refactoring-for-API-Speed.md: the wall-clock the
4+
// operator feels lives in the browser, and the shell wasn't timing itself.
5+
// bootMark() stamps a milestone with performance.now() and logs elapsed ms, so
6+
// "it's slow" becomes "workspace.activate took 58s and everything else 300ms".
7+
//
8+
// One timeline per page load (module singleton). Each federation container has
9+
// its own copy — the shell's timeline is the boot the user waits on. On by
10+
// default; silence with localStorage.setItem('augment_boot_timing','off').
11+
12+
type Mark = { name: string; at: number };
13+
14+
function now(): number {
15+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
16+
}
17+
18+
let t0: number | null = null;
19+
let last = 0;
20+
const marks: Mark[] = [];
21+
22+
function enabled(): boolean {
23+
try {
24+
return typeof localStorage === 'undefined' || localStorage.getItem('augment_boot_timing') !== 'off';
25+
} catch {
26+
return true;
27+
}
28+
}
29+
30+
/** Stamp a boot milestone. The first mark anchors T0 for the whole timeline. */
31+
export function bootMark(name: string): void {
32+
if (!enabled()) return;
33+
const at = now();
34+
if (t0 === null) {
35+
t0 = at;
36+
last = at;
37+
}
38+
const sincePrev = at - last;
39+
const sinceStart = at - t0;
40+
last = at;
41+
marks.push({ name, at });
42+
console.info(
43+
`%c[boot]%c ${name.padEnd(26)} +${String(Math.round(sincePrev)).padStart(6)}ms (T+${Math.round(sinceStart)}ms)`,
44+
'color:#8b5cf6;font-weight:bold',
45+
'color:inherit',
46+
);
47+
}
48+
49+
/** Print the whole timeline as a table. Call at the final milestone. */
50+
export function bootSummary(label = 'boot complete'): void {
51+
if (!enabled() || t0 === null) return;
52+
bootMark(label);
53+
const base = t0;
54+
const rows = marks.map((m, i) => ({
55+
milestone: m.name,
56+
'Δprev (ms)': i === 0 ? 0 : Math.round(m.at - marks[i - 1].at),
57+
'T+ (ms)': Math.round(m.at - base),
58+
}));
59+
console.table?.(rows);
60+
}

packages/workspace/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { workspace, WORKSPACE_CHANGED_EVENT } from './state.svelte';
2+
export { bootMark, bootSummary } from './boot-timing';
23
export { createAdapter } from './adapter';
34
export { createTransport } from './transport';
45
export { suggest } from './anticipation';

packages/workspace/src/state.svelte.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// rule in https://svelte.dev/e/state_invalid_placement.
1616

1717
import { createTransport, type ChatTurnReply, type ChatTurnRequest, type Transport, type TransportConfig } from './transport';
18+
import { bootMark, bootSummary } from './boot-timing';
1819
import type { ActiveView, JobEvent, PromptTemplate, RecordSet, Row, ServerFrame, UserContext, WorkspaceSummary } from './types';
1920

2021
// Where the browser stashes the operator's active workspace pick. Survives
@@ -178,11 +179,13 @@ class AugmentItWorkspace {
178179
this.workspaces_error = null;
179180
try {
180181
console.info('[workspace] loadWorkspaces → workspace.list');
182+
bootMark('workspace.list:sent');
181183
const result = (await this.invoke('workspace.list', {})) as {
182184
workspaces: WorkspaceSummary[];
183185
active_client_id: string | null;
184186
pinned?: boolean;
185187
};
188+
bootMark('workspace.list:returned');
186189
console.info('[workspace] workspace.list returned', result);
187190
this.workspaces = result.workspaces;
188191
this.pinned = result.pinned ?? false;
@@ -198,12 +201,15 @@ class AugmentItWorkspace {
198201
// split-brain (browser shows one tenant, domain services scope to
199202
// another) until the operator manually re-picks in the switcher.
200203
if (resolved && resolved !== result.active_client_id) {
204+
bootMark('workspace.activate:sent');
201205
await this.invoke('workspace.activate', { client_id: resolved });
206+
bootMark('workspace.activate:returned');
202207
}
203208
if (resolved !== persisted) {
204209
this.setActiveClientId(resolved);
205210
}
206211
this.workspaces_status = 'ready';
212+
bootSummary('workspaces:ready');
207213
return resolved;
208214
} catch (err) {
209215
const msg = err instanceof Error ? err.message : String(err);

packages/workspace/src/transport.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {
2626
ResultFrame,
2727
ServerFrame,
2828
} from './types';
29+
import { bootMark } from './boot-timing';
2930

3031
export type TransportConfig = {
3132
url: string; // e.g. 'ws://localhost:3001/ws'
@@ -121,6 +122,7 @@ export function createTransport(config: TransportConfig): Transport {
121122
? `${config.url}?token=${encodeURIComponent(token)}`
122123
: config.url;
123124

125+
bootMark('ws:connect-start');
124126
config.onStatus?.('connecting');
125127
ws = new WebSocket(url);
126128

@@ -158,6 +160,7 @@ export function createTransport(config: TransportConfig): Transport {
158160
backoff = reconnectInitialMs;
159161
authDead = false;
160162
authRefreshTried = false;
163+
bootMark('ws:open');
161164
config.onStatus?.('open');
162165
// Frames still in the queue were never delivered — flush re-sends
163166
// them as ordinary invokes. Pending entries NOT in the queue were
@@ -185,6 +188,7 @@ export function createTransport(config: TransportConfig): Transport {
185188
}
186189

187190
if (frame.kind === 'session') {
191+
bootMark('ws:session-verified');
188192
config.saveToken(frame.token);
189193
} else if (frame.kind === 'result') {
190194
const p = pending.get(frame.id);

shell/src/App.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import SignInWall from './SignInWall.svelte';
99
import JumboPopdown, { type PopdownItem } from './JumboPopdown.svelte';
1010
import ToggleHeader from '@augment-it/shared-ui/ToggleHeader__PromptOrPackage--Icons.svelte';
11-
import { workspace } from '@augment-it/workspace';
11+
import { workspace, bootMark } from '@augment-it/workspace';
1212
import {
1313
PAIRINGS,
1414
CHAT_REMOTE,
@@ -357,6 +357,7 @@
357357
// on the singleton, so if a remote raced us and connected first the
358358
// second call is a no-op.
359359
onMount(() => {
360+
bootMark('shell:mount'); // T0 for the boot timeline — see Refactoring-for-API-Speed
360361
const TOKEN_KEY = 'augment_it_session_token';
361362
// Fire BEFORE/alongside connect(), not after — an anonymous WS upgrade
362363
// against a DIDI_AUTH=required instance is rejected (4401) before any

0 commit comments

Comments
 (0)