Skip to content

feat(web): add embeddable thread route /embed/thread/:environmentId/:threadId#2

Merged
rororowyourboat merged 26 commits intomainfrom
feat/embed-thread-route
Apr 9, 2026
Merged

feat(web): add embeddable thread route /embed/thread/:environmentId/:threadId#2
rororowyourboat merged 26 commits intomainfrom
feat/embed-thread-route

Conversation

@rororowyourboat
Copy link
Copy Markdown
Owner

Summary

Adds a standalone route that renders the existing `ChatView` without the app sidebar chrome, for use inside iframes. The primary consumer is t3-canvas (a canvas-first agent workbench) which embeds T3 Code threads as tiles on an infinite canvas.

Closes #1

Changes

  • New route: `apps/web/src/routes/embed.thread.$environmentId.$threadId.tsx`
    • URL: `/embed/thread/:environmentId/:threadId?minimal=1`
    • Renders `` with the existing bootstrap checks (environment connected, thread exists)
    • Shows "Loading thread…" while the environment bootstraps
    • Shows a clear error state for invalid thread refs or missing threads
    • Wraps in a `position: fixed; inset: 0` container with `data-t3-embed` attributes for future targeted CSS
  • Root route update: `apps/web/src/routes/__root.tsx`
    • Any pathname starting with `/embed/` now renders `` directly instead of wrapping in ``
    • The environment connection, websocket surface, toasts, and event router still initialize — only the sidebar/diff/plan chrome is skipped
    • Auth gate still enforced (unauthenticated users see the outlet per existing behavior)
  • `routeTree.gen.ts` regenerated by `@tanstack/router-plugin` (via `bun run dev` tick)

Follow-ups not in this PR

The `minimal=1` query param is parsed and exposed via `data-t3-embed-minimal="true"` on the container, but the actual chrome hiding (BranchToolbar, PlanSidebar, ThreadTerminalDrawer) inside `ChatView` is not yet implemented. These components render by default. Two reasonable paths, pick later:

  1. Add a `minimal?: boolean` prop to `ChatView` that conditionally skips those sections
  2. Scoped CSS on `[data-t3-embed-minimal="true"]` hiding `[data-branch-toolbar]` etc. (requires adding stable data attributes to those components)

For the MVP of t3-canvas agent tiles, showing the full ChatView inside an iframe is acceptable — the iframe is a large tile so the chrome is visible but not blocking.

Test plan

  • `bun fmt` ✅ passes
  • `bun lint` ✅ passes (0 warnings, 0 errors)
  • `bun typecheck` ✅ passes (all 8 packages, ~37s)
  • Manual: `bun run dev`, create a thread in the main UI, navigate to `http://localhost:/embed/thread//?minimal=1`, verify the thread renders without the sidebar and messages stream normally
  • Manual: embed the URL inside a plain `<iframe>` on another local-origin page, verify same-origin iframe interaction still works (clicks, form input, scrolling)
  • Manual: hit `/embed/thread/bogus/bogus` → verify graceful error state

Out of scope

  • Chrome hiding for minimal mode (see follow-ups above)
  • Cross-origin iframe hardening (same-origin only for MVP)
  • Auth handling customization (unauthenticated users get whatever the root route serves)
  • Multi-thread views or embed customization beyond `minimal`

juliusmarminge and others added 26 commits April 5, 2026 16:53
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…#1303)

Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: justsomelegs <145564979+justsomelegs@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius@macmini.local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius@macmini.local>
Adds /embed/thread/:environmentId/:threadId — a standalone route that
renders the existing ChatView without the app sidebar chrome. This is the
iframe target for t3-canvas agent shapes (see rororowyourboat/t3-canvas#3).

- New file-based route embed.thread.\$environmentId.\$threadId.tsx
- __root.tsx bypasses AppSidebarLayout for any /embed/* pathname so the
  environment connection + websocket surface + toasts still initialize
  but the sidebar/diff/plan chrome does not render
- minimal=1 search param is parsed and wired to a data attribute on the
  container for future targeted CSS; chrome hiding (BranchToolbar,
  PlanSidebar, ThreadTerminalDrawer) stays as a follow-up pass
- routeTree.gen.ts regenerated by the @tanstack/router-plugin
@github-actions github-actions bot added size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Apr 9, 2026
@rororowyourboat rororowyourboat merged commit 3826ab2 into main Apr 9, 2026
6 of 7 checks passed
@rororowyourboat rororowyourboat deleted the feat/embed-thread-route branch April 9, 2026 22:30
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 892e8de502

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +58 to +59
if (!bootstrapComplete) {
return <EmbedMessage label="Loading thread…" />;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle unknown embed environments instead of perpetual loading

This gate returns the loading state whenever bootstrapComplete is false, but unknown environmentId values never bootstrap (the store falls back to an initial environment state with bootstrapComplete: false). As a result, /embed/thread/<bogus>/<bogus> never reaches the not-found branch and spins forever, which breaks the route’s intended graceful error behavior for invalid thread references.

Useful? React with 👍 / 👎.

Comment on lines +1700 to +1702
const nextEnvironmentState = updateThreadState(
getStoredEnvironmentState(state, state.activeEnvironmentId),
threadId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope thread error writes to the thread environment

setError updates only state.activeEnvironmentId, but ChatView calls it with just a threadId for the currently viewed route thread. In multi-environment usage, when the viewed thread belongs to a non-active environment, this write targets the wrong environment slice (or no-ops), so transport/send errors are not persisted on the actual thread the user is looking at.

Useful? React with 👍 / 👎.

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

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): add embeddable thread route /embed/thread/:id for canvas tile hosts

10 participants