Skip to content

Provider UI becomes a React + Vite app built from reusable components - #296

Open
yayashuxue wants to merge 4 commits into
mainfrom
react-vite-shell
Open

Provider UI becomes a React + Vite app built from reusable components#296
yayashuxue wants to merge 4 commits into
mainfrom
react-vite-shell

Conversation

@yayashuxue

Copy link
Copy Markdown
Contributor

What

The reader chrome — top bar, comment layer, dialogs, sign-in, onboarding, owner access management, and the /me Docs Hub — was server-rendered HTML strings plus vanilla modules (server/chrome.js, manage.js, signin.js, onboard.js). It is now one React 19 application under shell/src, built by Vite into server/runtime/. Author HTML never sees React: it renders only inside the sandboxed /frame iframe, where server/frame-probe.js stays the sole injected script.

This is the full migration, not a hybrid: /me, the neutral landing, and the status page are React pages too, and the four legacy modules are deleted rather than left as unreferenced files.

Component layers

  • shell/src/ui/ — headless @base-ui/react primitives behind small facades (AppDialog, AppMenu/AppMenuItem, SegmentedControl). Focus trap, Escape, portal, aria come from one place.
  • shell/src/document/ — toolbar, dialogs, comment card/composer/layers, owner access; pure api.js and model.js.
  • shell/src/hooks/useComments, useFrameBridge, useNotifications, useDocsHub. Every session mutation runs through a hook that turns a failure into a toast and a 401 into the sign-in path, so page components never catch.
  • document-shell.jsx and docs-hub.jsx are page orchestrators only; Docs Hub rows/menus are shared components in docs-hub/rows.jsx.

Boot contract

server/shell.js emits an empty root, structured boot JSON, and the hashed asset tags; server/server.js and worker/worker.js share it. bin/tdoc-bundle embeds the runtime bytes into the Worker through replacement callbacks so minified $&-style sequences survive String.replace.

Why server/runtime/ is committed

Skill users run server/server.js straight from the checkout and the bundler runs without npm install. The Vite output is content-hashed and deterministic; CI's new shell runtime job rebuilds and fails on any byte drift. The machine-specific node_modules symlink that #217 committed is removed, since npm ci now needs a real directory.

Fixes found on the way

  • Device-flow sign-in polled with a null code forever (stale closure over React state) — the poll now carries the device code through the closure, and signin-github-tab.test.js guards it.
  • Docs Hub mutations had no error handling and could not see a 401.
  • SKILL.md still described the pre-Cross-origin iframe shell for author documents #217 injectOverlay mechanism; it now describes the /frame route + frameCspHeader.

Tests

Suites move from asserting server-rendered markup to the new boundary: boot payloads, the /frame route, and real-browser behavior. npm run test:all: 52/52 green (artifact 28/28, responsive 32/32, ui smoke, publish dialog, csp-xss).

TypeScript is deferred; the api/model/hook/component boundaries are the migration units.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DTsAHQ7Xmdghu9jStyx3WY

The top bar, comment layer, dialogs, sign-in, onboarding, owner access
management, and the /me Docs Hub were server-rendered HTML strings plus
vanilla modules (server/chrome.js, manage.js, signin.js, onboard.js). They
are now one React 19 application under shell/src, built by Vite into
server/runtime/. Author HTML never sees React: it renders only inside the
sandboxed /frame iframe, where server/frame-probe.js stays the sole script.

Component layers, so features stack instead of tangling:
- shell/src/ui/: headless @base-ui/react primitives behind small facades
  (AppDialog, AppMenu/AppMenuItem, SegmentedControl) — focus trap, Escape,
  portal, and aria come from one place.
- shell/src/document/: toolbar, dialogs, comment card/composer/layers,
  owner access, plus pure api.js and model.js modules.
- shell/src/hooks/: useComments, useFrameBridge, useNotifications,
  useDocsHub — every session mutation runs through a hook that turns a
  failure into a toast and a 401 into the sign-in path.
- document-shell.jsx and docs-hub.jsx are page orchestrators only.

server/shell.js emits an empty root, structured boot JSON, and the hashed
asset tags; server/server.js and worker/worker.js share it. bin/tdoc-bundle
embeds the runtime bytes into the Worker through replacement callbacks so
minified `$&`-style sequences survive String.replace.

server/runtime/ is committed on purpose: skill users run server.js from
the checkout and the bundler runs without npm install. The Vite output is
deterministic, and CI's new `shell runtime` job rebuilds and fails on any
byte drift. The machine-specific node_modules symlink that #217 committed
is removed, since `npm ci` now needs a real directory.

Tests move from asserting server-rendered markup to the new boundary:
boot payloads, the /frame route, and real-browser behavior (artifact
28/28, responsive 32/32, ui smoke, publish dialog). Device-flow sign-in
had a stale-closure bug that polled with a null code forever; the poll now
carries the device code through the closure and the suite guards it.

TypeScript is deferred; the api/model/hook/component boundaries are the
migration units.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTsAHQ7Xmdghu9jStyx3WY
Copilot AI lite review requested due to automatic review settings August 28, 2026 08:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

Preview

Open this: https://pr-296-tdoc-preview.jyshi1107.workers.dev/d/conway-life/v/2

This link is unique to this PR. New commits update the same URL. It is not tdoc.dev.

Preview has no Durable Object — concurrent comments use the KV fallback. Data expires in 14 days.

Comment thread test/jul36-owner-manage.test.js Fixed
Comment thread test/tornado-doc-landing.test.js Fixed
CodeQL flagged the two `includes('https://…')` assertions as incomplete URL
sanitization. They are source-text assertions, not URL checks, but the exact
call-site regexes are also the stronger assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTsAHQ7Xmdghu9jStyx3WY
Copilot AI review requested due to automatic review settings August 28, 2026 08:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Two Playwright harnesses (test/visual/) run the pre-React shell and this one
on the same fixture doc, same viewport, same actions — Local Studio, and the
Worker in-process behind a fake-KV bridge with owner/reader/anonymous
sessions — and write composite old|new screenshots per scene. Everything
below was found by that comparison, not by eye.

- Dialogs: AppDialog's popup now carries chrome.css's `.tdoc-modal`, so the
  Publish/Share/Sign-in/Notifications/Docs Hub dialogs get the legacy type,
  padding, buttons, and dark-mode treatment. The owner Share panel had reused
  the legacy class names (`manage-section`, `field`, `tdoc-token-field`,
  `tdoc-seg`, `tdoc-adv`) without the `.tdoc-modal` scope they are defined
  under, which is why it rendered unstyled. Its layout, section order, and
  collapsed-by-default Advanced now match; the invite autocomplete uses the
  legacy `.tdoc-ac-list/.tdoc-ac-item` markup.
- Menus: items use the legacy `.tdoc-menu` metrics (13px, 7px/10px rows) —
  they were 44px tall — and are plain text like before; the version switcher
  is monospace with the current version in accent.
- Comment card: the added close button is gone (outside click / Escape close
  it, as before); Reply/delete/“1 reply” were <button>s wearing UA borders
  and now read as the legacy text controls; the card is clamped to its
  measured height so a long thread or the reply form never runs off screen,
  and it no longer overflows the right edge; replies start collapsed (the
  deep-link check was inverted). Pins render the anonymous dot again.
- Phone drawer: the visually-hidden title has its CSS; the handle is unboxed.
- Notifications rows and the sign-in steps use the legacy row/step markup.
- Publish dialog copy and slug row match the legacy text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTsAHQ7Xmdghu9jStyx3WY
Copilot AI review requested due to automatic review settings August 28, 2026 08:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

headers.set('host', 'tdoc.dev');
let out;
try { out = await mod.default.fetch(new Request(`https://tdoc.dev${req.url}`, { method: req.method, headers, body: ['GET', 'HEAD'].includes(req.method) ? undefined : body, redirect: 'manual' }), env, { waitUntil() {}, passThroughOnException() {} }); }
catch (e) { res.writeHead(500); res.end(String(e)); return; }
The comment composer rendered its close as a <button> with an icon, so it
picked up the UA button border inside the dark popup. It is the plain ×
from the legacy popup again (still a button for keyboard/screen readers),
and a truncated quote ends with an ellipsis as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTsAHQ7Xmdghu9jStyx3WY
Copilot AI review requested due to automatic review settings August 28, 2026 17:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants