Skip to content

Add a browser example - #97

Open
shibayan wants to merge 9 commits into
masterfrom
browser-example
Open

Add a browser example#97
shibayan wants to merge 9 commits into
masterfrom
browser-example

Conversation

@shibayan

@shibayan shibayan commented Aug 29, 2026

Copy link
Copy Markdown
Member

What this changes

Adds examples/05-browser — a multi-page Vite + vanilla TypeScript app whose pages each run the agent loop entirely in the browser. Closes #96.

  • Chat (/) — streaming into the DOM, client-side tools that reach browser APIs (restyle the page, read the visitor's clock), and session persistence in localStorage.
  • Canvas (/canvas.html) — an agent that paints: every drawing primitive is a tool() executing against a 2D canvas context.
  • Structured output (/structured.html) — a schema passed as responseFormat; the typed response.value fills the page's fields.
  • Anthropic (/anthropic.html) — the chat page with AnthropicChatClient swapped in; the agent, tools and streaming code are unchanged.

The example's build script runs vite build, so the workspace build gate keeps verifying that the runtime-agnostic packages bundle for a browser target.

Parity

  • Reference checked: none needed — example only; no runtime code changes.
  • Wire format affected: no
  • Public API affected: no
  • Breaking change: no

Checklist

  • pnpm check passes (lint, typecheck, build, test)

Add examples/05-browser: a Vite + vanilla TypeScript chat page that runs
the agent loop entirely in the browser — streaming into the DOM,
client-side tools that reach browser APIs, and session persistence in
localStorage. The example's build script runs vite build, so the
workspace build gate keeps verifying that the runtime-agnostic packages
bundle for a browser target.

Closes #96

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shibayan shibayan added the documentation Usage: [Issues, PRs], Target: documentation changes label Aug 29, 2026
Copilot AI lite review requested due to automatic review settings August 29, 2026 05:29
@shibayan shibayan added the documentation Usage: [Issues, PRs], Target: documentation changes label Aug 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a new examples/05-browser Vite + vanilla TypeScript demo that runs the full agent loop in a browser (streaming updates into the DOM, browser-native tools, and session/transcript persistence via localStorage) to validate that the published packages bundle for a browser target, addressing issue #96.

Changes:

  • Introduces examples/05-browser with a simple chat UI, browser tools (set_theme, get_local_time), and localStorage persistence.
  • Updates examples/README.md to document the new browser example.
  • Updates pnpm-lock.yaml to include the new example’s dependencies and reflect the Vite version used by the workspace.
File summaries
File Description
pnpm-lock.yaml Adds the new example importer and updates lockfile entries for the workspace dependency graph.
examples/README.md Documents the new browser example and how to run it.
examples/05-browser/tsconfig.json Sets up a browser-targeted TS config (neutral libs) for the example.
examples/05-browser/src/style.css Adds styling for the browser chat UI and themes.
examples/05-browser/src/main.ts Implements the in-browser agent loop, streaming rendering, browser tools, and persistence logic.
examples/05-browser/README.md Explains what the example demonstrates and includes API-key safety guidance.
examples/05-browser/package.json Defines the example’s Vite dev/build scripts and dependencies.
examples/05-browser/index.html Provides the chat UI markup and loads the example entrypoint.
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 7/8 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread examples/05-browser/index.html
Comment thread examples/05-browser/src/main.ts
Turn examples/05-browser into a multi-page Vite app: the existing chat
page, a canvas page whose drawing primitives are client-side tools, a
structured-output page that fills the page's fields from the typed
response.value, and an Anthropic page that swaps AnthropicChatClient
into the otherwise identical chat code. Shared DOM and storage helpers
move to src/ui.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 05:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 15/16 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread examples/05-browser/src/main.ts Outdated
Label the chat, drawing and extraction inputs for screen readers,
recover from an unrestorable saved session by starting a fresh one, and
skip persisting an assistant transcript entry when a run produced no
text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

examples/05-browser/src/anthropic.ts:6

  • The doc comment says the page code is identical to the OpenAI chat page except for client construction, but this implementation omits the OpenAI page’s localStorage transcript/session persistence and Clear button flow. Either bring this page in sync with main.ts or adjust the comment to avoid claiming identical code.
 * `AnthropicChatClient` is a `ChatClient` like any other, so the page code is identical to the
 * OpenAI chat — only the client construction differs. The Anthropic SDK has the same explicit
 * browser opt-in as OpenAI's, and CORS on the Anthropic API allows direct calls from a page.

examples/README.md:89

  • The Browser section says the Anthropic page code is otherwise identical to the chat page, but the Anthropic page omits chat persistence (localStorage transcript/session) and the Clear button. Please adjust this row or bring the Anthropic page in sync so this summary is accurate.
| `/anthropic.html` | The chat page with `AnthropicChatClient` swapped in; the page code is otherwise identical |

examples/05-browser/anthropic.html:22

  • This warning text claims “the rest of the code is identical”, but unlike the OpenAI chat page this Anthropic page does not include the localStorage transcript/session persistence (or the Clear button). Update the copy or align the implementation so the statement is true.
          This page is the chat example with <code>AnthropicChatClient</code> swapped in — the rest
          of the code is identical. Keys pasted here stay in memory — in production, run agents
          server-side.

examples/05-browser/README.md:17

  • This row says the Anthropic page is identical to the chat page aside from the client swap, but the Anthropic page currently lacks the chat page’s localStorage transcript/session persistence and Clear button. Please tweak the wording or align the page implementation.
| [`/anthropic.html`](anthropic.html) — Anthropic | The chat page with `AnthropicChatClient` swapped in — the rest of the code is identical, which is the point |
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

…page

The page intentionally omits the chat page's localStorage persistence
and Clear button, so the docs no longer call the code identical: the
agent, tools and streaming code are what carries over unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (5)

Previously missed (5) — in code that hasn't changed since the last review.

examples/05-browser/index.html:39

  • #log is marked as a live region, but the chat page streams by repeatedly updating the assistant bubble’s text. That can cause screen readers to announce partial chunks as they arrive. Consider limiting announcements to newly added bubbles by setting aria-relevant="additions" (and optionally role="log").
      <section id="log" aria-live="polite"></section>

examples/05-browser/canvas.html:39

  • #log is a live region, but this page streams updates by mutating existing bubble text. Screen readers may announce each incremental mutation. Setting aria-relevant="additions" (and role="log") keeps announcements to newly appended entries instead.
      <section id="log" aria-live="polite"></section>

examples/05-browser/structured.html:45

  • #log is a live region; while this page doesn’t stream text, it shares the same DOM structure as the streaming pages. Adding aria-relevant="additions" (and role="log") makes the behavior consistent and avoids announcing incremental text mutations if this page ever adds streaming or progressive updates.
      <section id="log" aria-live="polite"></section>

examples/05-browser/tsconfig.json:7

  • vite.config.ts is TypeScript but isn’t included in the example’s tsc project, so pnpm --filter example-05-browser typecheck won’t catch type errors in the Vite config. Consider adding it to include so it’s typechecked along with src/.
  "compilerOptions": {
    "isolatedDeclarations": false,
    "noEmit": true
  },
  "include": ["src/**/*.ts"]

examples/05-browser/vite.config.ts:2

  • If vite.config.ts is added to the project’s tsc include, it will need Node types to resolve node:path and import.meta.dirname. A file-scoped /// <reference types="node" /> keeps the app src/ Node-free while still typechecking the config.
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
  • Files reviewed: 15/16 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread examples/05-browser/anthropic.html Outdated
Streaming mutates the assistant bubble's text in place, which a plain
polite live region re-announces chunk by chunk. role="log" with
aria-relevant="additions" limits announcements to newly added entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

examples/05-browser/src/main.ts:136

  • Streaming text is appended via reply.textContent += update.text, which re-serializes the element’s full text content on every chunk and can get expensive for long/fast streams. Appending the chunk as a text node avoids repeatedly rewriting the entire element.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.textContent += update.text;
        reply.scrollIntoView({ block: 'end' });

examples/05-browser/src/anthropic.ts:100

  • Streaming text is appended via reply.textContent += update.text, which rewrites the whole element text on every chunk and can be unnecessarily expensive for long/fast streams. Appending the chunk avoids repeated full rewrites.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.textContent += update.text;
        reply.scrollIntoView({ block: 'end' });

examples/05-browser/src/canvas.ts:150

  • Streaming text is appended via reply.textContent += update.text, which rewrites the whole element text on every chunk and can be unnecessarily expensive for long/fast streams. Appending the chunk avoids repeated full rewrites.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.textContent += update.text;
        reply.scrollIntoView({ block: 'end' });
  • Files reviewed: 15/16 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread examples/05-browser/src/ui.ts
Append each streamed chunk as a text node instead of rewriting the
bubble's whole text content per chunk, and scroll newly added tool
chips into view so tool-only turns stay visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

examples/05-browser/src/main.ts:58

  • The transcript restored from localStorage is assumed to match TranscriptEntry[]. If the stored JSON is corrupted or has an unexpected shape (but still parses), this can render undefined bubbles or odd classes instead of failing gracefully. Consider validating the parsed value and clearing the stored transcript when it’s not an array of { role, text } entries.
// The session is the source of truth the model sees; the transcript only redraws past bubbles.
const transcript: TranscriptEntry[] = loadJson<TranscriptEntry[]>(TRANSCRIPT_KEY) ?? [];
for (const entry of transcript) {
  bubble(log, entry.role, entry.text);
}
if (transcript.length > 0) {
  chip(log, 'conversation restored from localStorage');
}

examples/05-browser/src/main.ts:122

  • The Clear button remains enabled while a send is in progress. If the user clears mid-stream, the in-flight loop can repopulate the transcript/session (and re-save them) after the clear, leaving storage and UI in an unexpected state. Disabling Clear while sending avoids this race in the example.

This issue also appears on line 147 of the same file.

  promptInput.value = '';
  sendButton.disabled = true;
  bubble(log, 'user', text);
  transcript.push({ role: 'user', text });

examples/05-browser/src/main.ts:150

  • Follow-up to disabling Clear while sending: re-enable it in the finally block so the UI doesn’t get stuck after a run completes or fails.
  } finally {
    sendButton.disabled = false;
    promptInput.focus();
  }
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Validate the transcript restored from localStorage and drop it when it
is not the shape this page writes, and disable the Clear button while a
send is in flight so clearing cannot race the streaming loop that
repopulates the log and storage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

examples/05-browser/package.json:24

  • vite declares a peer dependency on @types/node (see pnpm-lock.yaml vite@8.2.2 peerDependencies). Adding it here avoids relying on hoisted/workspace-provided types and matches the other examples’ devDependency setup.
  "devDependencies": {
    "typescript": "catalog:",
    "vite": "^8.2.2"
  }
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Vite lists @types/node as an optional peer; declaring it matches the
other examples instead of relying on workspace hoisting. The example's
sources still typecheck without Node types via the neutral tsconfig.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

examples/05-browser/src/main.ts:159

  • Streaming text is appended as a new text node for every chunk (reply.append(update.text)), which can create hundreds/thousands of DOM nodes for long responses and slow the page. Consider appending once, then mutating the existing trailing Text node for subsequent chunks.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.append(update.text);
        reply.scrollIntoView({ block: 'end' });

examples/05-browser/src/canvas.ts:150

  • Streaming text is appended as a new text node for every chunk (reply.append(update.text)), which can create many DOM nodes for long responses and degrade performance. Consider appending once, then mutating the existing trailing Text node for subsequent chunks.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.append(update.text);
        reply.scrollIntoView({ block: 'end' });

examples/05-browser/src/anthropic.ts:100

  • Streaming text is appended as a new text node for every chunk (reply.append(update.text)), which can create many DOM nodes for long responses and degrade performance. Consider appending once, then mutating the existing trailing Text node for subsequent chunks.
      if (update.text !== '') {
        reply ??= bubble(log, 'assistant');
        reply.append(update.text);
        reply.scrollIntoView({ block: 'end' });
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Appending one text node per streamed chunk builds up thousands of DOM
nodes on long responses. A shared streamingBubble helper now creates
the bubble on the first chunk and grows one Text node via appendData,
replacing the hand-rolled reply handling on all three streaming pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 29, 2026 06:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 15/16 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a browser example

2 participants