Skip to content

Commit 53a75e6

Browse files
committed
🤖 fix: initialize ORPCProvider in connected state when client prop provided
Fixes Storybook tests failing in CI. When a client prop is passed to ORPCProvider, the component now starts in "connected" state immediately instead of waiting for useEffect. This prevents a flash of null content that caused tests to see an empty storybook-root div. Change-Id: I048104e7f2fe434efcf9b50db0bae445d912b014 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 6157b56 commit 53a75e6

File tree

4 files changed

+33
-4
lines changed

4 files changed

+33
-4
lines changed

‎src/browser/orpc/react.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,16 @@ function createBrowserClient(authToken: string | null): {
7070
}
7171

7272
export const ORPCProvider = (props: ORPCProviderProps) => {
73-
const [state, setState] = useState<ConnectionState>({ status: "connecting" });
73+
// If client is provided externally, start in connected state immediately
74+
// This avoids a flash of null content on first render
75+
const [state, setState] = useState<ConnectionState>(() => {
76+
if (props.client) {
77+
// Also set the global client reference immediately
78+
window.__ORPC_CLIENT__ = props.client;
79+
return { status: "connected", client: props.client, cleanup: () => undefined };
80+
}
81+
return { status: "connecting" };
82+
});
7483
const [authToken, setAuthToken] = useState<string | null>(() => {
7584
// Check URL param first, then localStorage
7685
const urlParams = new URLSearchParams(window.location.search);

‎src/common/orpc/schemas.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,20 @@ export const ReasoningEndEventSchema = z.object({
469469
messageId: z.string(),
470470
});
471471

472+
// Usage schema matching LanguageModelV2Usage from @ai-sdk/provider
473+
export const LanguageModelUsageSchema = z.object({
474+
inputTokens: z.number().optional(),
475+
outputTokens: z.number().optional(),
476+
totalTokens: z.number().optional(),
477+
});
478+
479+
export const UsageDeltaEventSchema = z.object({
480+
type: z.literal("usage-delta"),
481+
workspaceId: z.string(),
482+
messageId: z.string(),
483+
usage: LanguageModelUsageSchema,
484+
});
485+
472486
export const WorkspaceInitEventSchema = z.discriminatedUnion("type", [
473487
z.object({
474488
type: z.literal("init-start"),
@@ -518,6 +532,7 @@ export const WorkspaceChatMessageSchema = z.union([
518532
ToolCallEndEventSchema,
519533
ReasoningDeltaEventSchema,
520534
ReasoningEndEventSchema,
535+
UsageDeltaEventSchema,
521536
// Flatten WorkspaceInitEventSchema members into this union if possible,
522537
// or just include it as a union member. Zod discriminated union is strict.
523538
// WorkspaceInitEventSchema is already a discriminated union.

‎src/common/orpc/types.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
ToolCallEndEvent,
1313
ReasoningDeltaEvent,
1414
ReasoningEndEvent,
15+
UsageDeltaEvent,
1516
} from "@/common/types/stream";
1617

1718
export type BranchListResult = z.infer<typeof schemas.BranchListResultSchema>;
@@ -76,6 +77,10 @@ export function isReasoningEnd(msg: WorkspaceChatMessage): msg is ReasoningEndEv
7677
return (msg as { type?: string }).type === "reasoning-end";
7778
}
7879

80+
export function isUsageDelta(msg: WorkspaceChatMessage): msg is UsageDeltaEvent {
81+
return (msg as { type?: string }).type === "usage-delta";
82+
}
83+
7984
export function isMuxMessage(msg: WorkspaceChatMessage): msg is MuxMessage {
8085
return "role" in msg && !("type" in (msg as { type?: string }));
8186
}

‎tests/integration/usageDelta.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
modelString,
66
assertStreamSuccess,
77
} from "./helpers";
8+
import { isUsageDelta } from "../../src/common/orpc/types";
89
import { KNOWN_MODELS } from "../../src/common/constants/knownModels";
910

1011
// Skip all tests if TEST_INTEGRATION is not set
@@ -45,16 +46,15 @@ describeIntegration("usage-delta events", () => {
4546

4647
// Verify usage-delta events were emitted
4748
const allEvents = collector.getEvents();
48-
const usageDeltas = allEvents.filter(
49-
(e) => "type" in e && e.type === "usage-delta"
50-
) as Array<{ type: "usage-delta"; usage: { inputTokens: number; outputTokens: number } }>;
49+
const usageDeltas = allEvents.filter(isUsageDelta);
5150

5251
// Multi-step stream should emit at least one usage-delta (on finish-step)
5352
expect(usageDeltas.length).toBeGreaterThan(0);
5453

5554
// Each usage-delta should have valid usage data
5655
for (const delta of usageDeltas) {
5756
expect(delta.usage).toBeDefined();
57+
// inputTokens should be present and > 0 (full context)
5858
expect(delta.usage.inputTokens).toBeGreaterThan(0);
5959
// outputTokens may be 0 for some steps, but should be defined
6060
expect(typeof delta.usage.outputTokens).toBe("number");

0 commit comments

Comments
 (0)