Provider UI becomes a React + Vite app built from reusable components - #296
Open
yayashuxue wants to merge 4 commits into
Open
Provider UI becomes a React + Vite app built from reusable components#296yayashuxue wants to merge 4 commits into
yayashuxue wants to merge 4 commits into
Conversation
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
PreviewOpen 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. |
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
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
| 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The reader chrome — top bar, comment layer, dialogs, sign-in, onboarding, owner access management, and the
/meDocs 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 undershell/src, built by Vite intoserver/runtime/. Author HTML never sees React: it renders only inside the sandboxed/frameiframe, whereserver/frame-probe.jsstays 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/reactprimitives 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; pureapi.jsandmodel.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.jsxanddocs-hub.jsxare page orchestrators only; Docs Hub rows/menus are shared components indocs-hub/rows.jsx.Boot contract
server/shell.jsemits an empty root, structured boot JSON, and the hashed asset tags;server/server.jsandworker/worker.jsshare it.bin/tdoc-bundleembeds the runtime bytes into the Worker through replacement callbacks so minified$&-style sequences surviveString.replace.Why
server/runtime/is committedSkill users run
server/server.jsstraight from the checkout and the bundler runs withoutnpm install. The Vite output is content-hashed and deterministic; CI's new shell runtime job rebuilds and fails on any byte drift. The machine-specificnode_modulessymlink that #217 committed is removed, sincenpm cinow needs a real directory.Fixes found on the way
nullcode forever (stale closure over React state) — the poll now carries the device code through the closure, andsignin-github-tab.test.jsguards it.SKILL.mdstill described the pre-Cross-origin iframe shell for author documents #217injectOverlaymechanism; it now describes the/frameroute +frameCspHeader.Tests
Suites move from asserting server-rendered markup to the new boundary: boot payloads, the
/frameroute, 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