Skip to content
Merged
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
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This repository is the public implementation for the 2026 OpenAI Build Week hack

## Product contract

- One selected performer and model carry each turn; the live adapter never silently delegates to another model.
- One selected performer carries the journey; the audience may choose a different model between turns, and the live adapter never silently delegates to another model.
- Research activity and sources are observable; hidden chain-of-thought is not exposed.
- Every ready turn ends with exactly two distinct next questions.
- The journey advances only after an explicit audience action.
Expand All @@ -28,6 +28,35 @@ This repository is the public implementation for the 2026 OpenAI Build Week hack
- Dispatch-owned Sign in with ChatGPT seam for durable identity
- GitHub Actions for lint, build, and rendered-output tests

## Architecture

The checked-in implementation runs as one public ChatGPT Site. The browser talks only to Sites routes; those routes own identity, authorization, foreground research orchestration, validation, and persistence. Live provider work goes directly to the OpenAI Responses API, while Sites-managed D1 is the canonical product database. There is no queue, scheduler, background continuation, provider fan-out, R2 bucket, Supabase project, or separately operated backend in the current deployment.

![WonderDrive system landscape](design/wonderdrive-architecture-01-system-landscape.png)

The editable source is [the system-landscape Excalidraw board](design/wonderdrive-architecture-01-system-landscape.excalidraw). The complete implementation views are documented in [the architecture decisions](docs/architecture.md#architecture-views).

<details>
<summary>One foreground research turn</summary>

![WonderDrive foreground research sequence](design/wonderdrive-architecture-02-research-turn.png)

</details>

<details>
<summary>Inside the WonderDrive application boundary</summary>

![WonderDrive internal components](design/wonderdrive-architecture-03-inside-wonderdrive.png)

</details>

<details>
<summary>Build and deployment topology</summary>

![WonderDrive deployment topology](design/wonderdrive-architecture-04-deployment-topology.png)

</details>

## Local development

Requirements: Node.js `22.13.0` or newer.
Expand Down Expand Up @@ -74,12 +103,13 @@ tests/ Rendered production and fixture checks
- [Phase 2 live research contract](docs/phase-2.md)
- [V3 implementation contract](docs/v3-implementation.md)
- [Final architecture decisions](docs/architecture.md)
- [Current architecture views](docs/architecture.md#architecture-views)
- [Current code index](docs/code-index.md)
- [Final product and engineering blueprint](docs/WonderDrive_Final_Product_and_Engineering_Blueprint_v3_Research_First.docx)

## Status and scope

Every journey uses a user-selected, compatible OpenAI model through one foreground Responses request with built-in text and optional image search. Presets cap tool calls, output tokens, reasoning effort, and wall time; guest and signed-in identities also have rolling live-run and estimated-spend limits. Consulted URLs, cited relations, sourced image results, provider request ID, complete usage, price snapshot, prompt/performer/model versions, and research handoff are saved with the committed turn.
Every turn starts one foreground Responses stream with the audience-selected, compatible OpenAI model, built-in text search, and optional image results. A bounded citation-repair or evidence-recovery call may follow when the initial draft does not satisfy the source contract. The model can be changed before any later turn without rewriting earlier turn metadata. Presets cap tool calls, output tokens, reasoning effort, and wall time; guest and signed-in identities also have rolling live-run and estimated-spend limits. Consulted URLs, cited relations, sourced image results, provider request IDs, complete usage, price snapshots, prompt/performer/model versions, research handoffs, and all provider-call analytics are persisted in D1.

Automatic journeys, scheduled/background continuation, Trigger.dev, provider fan-out, and live parallel comparison are outside the hackathon scope.

Expand Down
8 changes: 8 additions & 0 deletions app/api/diagnostics/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { query } from "../../../lib/api";
import { getDiagnostics } from "../../../lib/diagnostics";

export const dynamic = "force-dynamic";

export async function GET() {
return query((viewer) => getDiagnostics(viewer));
}
4 changes: 3 additions & 1 deletion app/api/journeys/[journeyId]/advance/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ export async function POST(request: Request, context: Context) {
const body = await readJson<AdvanceJourneyRequest>(request);
return advanceJourney(viewer, journeyId, body, async ({ journey, turn }) =>
runLiveRedraw({
identityId: viewer.identityId,
journeyId,
turn,
performerId: journey.performerId,
modelId: journey.modelId,
modelId: body.modelId ?? journey.modelId,
rejectedQuestions: [
...await listRejectedQuestions(viewer, journeyId),
...turn.options.map((option) => option.question),
Expand Down
11 changes: 7 additions & 4 deletions app/api/research/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,13 @@ export async function POST(request: Request) {
if (!closed) {
send({
type: "error",
error: publicError(
error,
"WonderDrive could not complete live research. No partial journey was saved.",
),
error: {
...publicError(
error,
"WonderDrive could not complete live research. No partial journey was saved.",
),
diagnosticId: preparation.prepared.requestId,
},
});
}
} finally {
Expand Down
18 changes: 17 additions & 1 deletion app/client-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,15 @@ export type LiveResearchState = {
status: "running" | "complete" | "error";
result: JourneyDetail | null;
error: string | null;
diagnosticId: string | null;
};

export function starterRecommendationsUrl(performerId: PerformerId, forceRefresh = false) {
const query = new URLSearchParams({ performer: performerId });
if (forceRefresh) query.set("refresh", "1");
return `/api/starters?${query.toString()}`;
}

export async function api<T>(url: string, init?: RequestInit): Promise<ApiSuccess<T>> {
const response = await fetch(url, {
...init,
Expand Down Expand Up @@ -67,7 +74,12 @@ export async function streamLiveResearch(
if (!data) continue;
const event = JSON.parse(data) as LiveResearchStreamEvent;
if (event.type === "started") {
setState((current) => current && { ...current, question: event.question, message: event.message });
setState((current) => current && {
...current,
question: event.question,
message: event.message,
diagnosticId: event.requestId,
});
} else if (event.type === "activity") {
setState((current) => current && {
...current,
Expand All @@ -76,6 +88,10 @@ export async function streamLiveResearch(
: [...current.events, event.event],
});
} else if (event.type === "error") {
setState((current) => current && {
...current,
diagnosticId: event.error.diagnosticId ?? current.diagnosticId,
});
throw new Error(event.error.message);
} else if (event.type === "complete") {
complete = event;
Expand Down
49 changes: 46 additions & 3 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ a { color: inherit; text-decoration: none; }
.active-journey-shell { min-height: calc(100vh - 115px); }
.journey-view-switcher { align-items: center; background: color-mix(in srgb, var(--paper-light) 82%, transparent); border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; min-height: 42px; padding: 5px clamp(24px, 4.8vw, 72px); }
.journey-view-switcher > span { color: var(--muted); font-size: .58rem; max-width: 45vw; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
.journey-model-switcher { align-items: center; display: flex; gap: 8px; margin-left: auto; margin-right: 14px; }
.journey-model-switcher > span { color: var(--muted); font-size: .5rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.journey-model-switcher select { background: var(--paper-light); border: 1px solid var(--ink); color: var(--ink); font-size: .58rem; font-weight: 700; max-width: 190px; min-height: 28px; padding: 4px 25px 4px 8px; }
.journey-model-switcher select:focus-visible { outline: 3px solid var(--acid); outline-offset: 2px; }
.journey-model-switcher select:disabled { cursor: wait; opacity: .55; }
.journey-view-switcher > div { border: 1px solid var(--ink); display: flex; }
.journey-view-switcher button { background: transparent; border: 0; font-size: .58rem; font-weight: 700; min-width: 94px; padding: 6px 12px; text-transform: uppercase; }
.journey-view-switcher button + button { border-left: 1px solid var(--ink); }
Expand All @@ -66,7 +71,7 @@ a { color: inherit; text-decoration: none; }
.identity-control > span:nth-child(2) { display: flex; flex-direction: column; font-size: .7rem; line-height: 1.2; max-width: 150px; }
.identity-control strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.identity-control small { color: var(--muted); font-size: .62rem; margin-top: 3px; }
.identity-control a { border-bottom: 1px solid currentColor; font-size: .67rem; font-weight: 600; margin-left: 7px; }
.identity-action { border-bottom: 1px solid currentColor; font-size: .67rem; font-weight: 600; margin-left: 7px; white-space: nowrap; }
.identity-dot { background: var(--muted); border: 3px solid var(--paper); border-radius: 50%; box-shadow: 0 0 0 1px var(--ink); height: 10px; width: 10px; }
.identity-dot.guest { background: var(--acid); }
.identity-dot.chatgpt { background: var(--sky); }
Expand Down Expand Up @@ -810,6 +815,32 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius
.settings-form label { border-bottom: 1px solid var(--line); padding-bottom: 16px; }
.settings-form small { color: var(--muted); font-size: .62rem; }
.settings-form .launch-button { grid-column: 1 / -1; }
.diagnostics-console { background: var(--ink); border: 1px solid var(--ink); color: var(--paper-light); margin-top: 28px; padding: clamp(24px, 4vw, 44px); }
.diagnostics-console > header { align-items: flex-end; display: flex; gap: 24px; justify-content: space-between; }
.diagnostics-console h2 { font-family: var(--font-display); font-size: clamp(1.8rem, 4vw, 3.4rem); font-weight: 400; letter-spacing: -.035em; margin: 5px 0 0; }
.diagnostics-console > header button { background: var(--acid); border: 1px solid var(--paper-light); color: var(--ink); font-size: .62rem; font-weight: 700; padding: 10px 13px; text-transform: uppercase; }
.diagnostics-summary { display: grid; grid-template-columns: repeat(4, 1fr); margin-top: 30px; }
.diagnostics-summary div { border: 1px solid rgba(255,255,255,.25); display: flex; flex-direction: column; min-height: 90px; padding: 15px; }
.diagnostics-summary strong { color: var(--acid); font-family: var(--font-display); font-size: 2rem; font-weight: 400; }
.diagnostics-summary span { color: rgba(255,255,255,.62); font-size: .55rem; text-transform: uppercase; }
.diagnostics-alert { background: var(--coral); border: 1px solid var(--paper-light); color: var(--ink); display: flex; flex-direction: column; gap: 3px; margin-top: 18px; padding: 14px 16px; }
.diagnostics-alert strong { font-size: .65rem; text-transform: uppercase; }
.diagnostics-alert span { font-size: .7rem; }
.incident-list { margin-top: 18px; }
.incident-row { border-top: 1px solid rgba(255,255,255,.3); }
.incident-row:last-child { border-bottom: 1px solid rgba(255,255,255,.3); }
.incident-row summary { align-items: center; cursor: pointer; display: grid; gap: 12px; grid-template-columns: 110px 1fr 150px 190px; padding: 14px 4px; }
.incident-row summary code { color: var(--acid); font-size: .68rem; }
.incident-row summary strong { font-size: .65rem; }
.incident-row summary span, .incident-row summary time { color: rgba(255,255,255,.62); font-size: .6rem; }
.incident-row dl { display: grid; gap: 0 18px; grid-template-columns: repeat(2, 1fr); margin: 0; padding: 4px 0 14px; }
.incident-row dl div { border-top: 1px solid rgba(255,255,255,.14); padding: 8px 4px; }
.incident-row dt { color: rgba(255,255,255,.5); font-size: .5rem; text-transform: uppercase; }
.incident-row dd { font-size: .65rem; margin: 3px 0 0; overflow-wrap: anywhere; }
.incident-row > p { background: rgba(255,255,255,.07); font-size: .68rem; margin: 0 0 15px; padding: 12px; }
.diagnostics-empty { border: 1px dashed rgba(255,255,255,.3); color: rgba(255,255,255,.7); margin: 24px 0 0; padding: 18px; }
.diagnostics-privacy { color: rgba(255,255,255,.48); font-size: .55rem; margin: 14px 0 0; }
.buffering-error code { color: var(--muted); display: block; font-size: .64rem; margin-top: 8px; }

@keyframes pulse { 0%, 100% { opacity: .45; transform: scale(.85); } 50% { opacity: 1; transform: scale(1.15); } }
@keyframes spin { to { transform: rotate(360deg); } }
Expand All @@ -833,12 +864,12 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius

@media (max-width: 820px) {
.app-header { min-height: 70px; padding-inline: 16px; }
.wordmark small, .identity-control small, .identity-control a { display: none; }
.wordmark small, .identity-control small { display: none; }
.identity-control > span:nth-child(2) { display: flex; max-width: 92px; }
.app-nav { display: grid; grid-template-columns: repeat(4, 1fr); margin-inline: -16px; overflow: visible; width: calc(100% + 32px); }
.app-nav button { min-height: 43px; min-width: 0; padding-inline: 8px; }
.app-nav button:nth-child(4n) { border-inline-end: 1px solid var(--line); }
.journey-view-switcher { padding-inline: 16px; }
.journey-view-switcher { gap: 8px; padding-inline: 16px; }
.phase-ribbon { justify-content: flex-start; overflow: hidden; padding-inline: 15px; white-space: nowrap; }
.phase-ribbon span { margin-left: 0; }
.phase-ribbon::after { display: none; }
Expand Down Expand Up @@ -892,6 +923,9 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius
.library-grid, .compare-picker { grid-template-columns: 1fr; }
.library-filters, .settings-form { grid-template-columns: 1fr; }
.settings-form .launch-button { grid-column: 1; }
.diagnostics-summary { grid-template-columns: repeat(2, 1fr); }
.incident-row summary { align-items: start; grid-template-columns: 1fr 1fr; }
.incident-row dl { grid-template-columns: 1fr; }
.choice-settings { grid-template-columns: 1fr; }
.metadata-grid { grid-template-columns: 1fr; }
.library-card, .library-card:nth-child(2n), .library-card:nth-child(3n) { border-inline-end: 1px solid var(--line-strong); }
Expand All @@ -913,6 +947,7 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius
.contract-strip span { border-bottom: 1px solid var(--line); border-right: 0; min-height: 42px; }
.console-heading { align-items: flex-start; flex-direction: column; justify-content: center; gap: 4px; }
.performance-stage { padding-inline: 16px; }
.diagnostics-console > header { align-items: flex-start; flex-direction: column; }
.performance-header h1 { font-size: 3rem; }
.buffering-header { gap: 9px; grid-template-columns: minmax(0, 1fr) 124px; min-height: 86px; padding-block: 9px; }
.buffering-header h1 { font-size: 1.22rem; }
Expand All @@ -926,6 +961,9 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius
.article-journey-header .stage-metrics span { min-width: 48px; padding: 5px 6px; }
.article-journey-header .stage-metrics strong { font-size: 1.05rem; }
.journey-view-switcher > span { display: none; }
.journey-view-switcher { display: grid; gap: 6px; grid-template-columns: 1fr; padding-block: 6px; }
.journey-model-switcher { margin: 0; width: 100%; }
.journey-model-switcher select { flex: 1; max-width: none; }
.journey-view-switcher > div { display: grid; grid-template-columns: 1fr 1fr; width: 100%; }
.journey-view-switcher button { min-width: 0; }
.contained-answer-card { padding: 17px 16px 14px 21px; }
Expand Down Expand Up @@ -990,6 +1028,11 @@ legend > span { align-items: center; border: 1px solid var(--ink); border-radius
.upgrade-banner { align-items: flex-start; flex-direction: column; gap: 7px; }
}

@media (max-width: 520px) {
.identity-control > span:nth-child(2) { display: none; }
.identity-action { margin-left: 2px; }
}

@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; }
.starter-marquee-window { overflow-x: auto; }
Expand Down
Loading
Loading