Context
The repo is deliberately zero-framework, zero-runtime-dependency. That posture is load-bearing for the author/agent-generated reader HTML (supply-chain surface, arbitrary HTML coexistence, BYOK copy-files portability). But since the cross-origin iframe shell migration (#217), the trust boundary is explicit: author HTML lives in an iframe on another origin; everything outside it is first-party product UI.
That first-party surface is now ~2,500+ lines of hand-rolled imperative DOM, and it grows with every PR:
| file |
lines |
what |
server/shell.js |
~830 |
comment rails, pins, frame messaging |
server/manage.js |
~360 |
Share panel |
server/chrome.js + chrome.css |
~800 |
top bar |
server/signin.js / onboard.js |
~490 |
auth + onboarding modals |
/me (inline in worker/worker.js) |
~600 |
catalog: tabs, folders, stars, sort, batch select, 3 modals |
The /me docs-hub work (PR #286) made the cost concrete: tab state, selection sets, folder filtering, and modals are all manual classList.toggle + a hand-rolled applySearch() re-computation — classic framework territory, with none of the zero-dep benefits applying (this code is ours, not the author's).
Real constraints (what a framework must satisfy)
- Inline-able:
bin/tdoc-bundle is string substitution into the worker; pages run under nonce + strict-dynamic CSP, no CDN.
- BYOK portability: deploy = copy files; no
npm install, no build chain for self-hosters.
- Agent-maintainable: plain, greppable code; static-guard tests (
me-management.test.js etc.) slice source by string.
This rules out React+Vite-style stacks, but not a vendored micro-framework.
Proposal (two independent steps)
- Extract
/me out of worker.js into server/me.js via the existing bundle-placeholder mechanism (chrome.js/shell.js already work this way). Purely mechanical, no behavior change; move the string-slicing guard tests along with it. Worth doing regardless of step 2.
- Vendor Preact + htm (~5KB total, tagged templates, no JSX, no build step) as
server/vendor/, inlined by tdoc-bundle like everything else. Scope: first-party chrome only — /me, Share panel, eventually the comment rails. The reader HTML and frame-probe.js stay framework-free forever. No rewrite of working code: first use it for the next new component or when /me next grows.
Non-goals
- No framework, ever, inside author documents.
- No npm runtime dependencies, no build-tool requirement for BYOK deploys.
- No big-bang rewrite of
shell.js.
Not urgent — filed to capture the design discussion from PR #286 before it evaporates.
🤖 Generated with Claude Code
Context
The repo is deliberately zero-framework, zero-runtime-dependency. That posture is load-bearing for the author/agent-generated reader HTML (supply-chain surface, arbitrary HTML coexistence, BYOK copy-files portability). But since the cross-origin iframe shell migration (#217), the trust boundary is explicit: author HTML lives in an iframe on another origin; everything outside it is first-party product UI.
That first-party surface is now ~2,500+ lines of hand-rolled imperative DOM, and it grows with every PR:
server/shell.jsserver/manage.jsserver/chrome.js+chrome.cssserver/signin.js/onboard.js/me(inline inworker/worker.js)The
/medocs-hub work (PR #286) made the cost concrete: tab state, selection sets, folder filtering, and modals are all manualclassList.toggle+ a hand-rolledapplySearch()re-computation — classic framework territory, with none of the zero-dep benefits applying (this code is ours, not the author's).Real constraints (what a framework must satisfy)
bin/tdoc-bundleis string substitution into the worker; pages run under nonce + strict-dynamic CSP, no CDN.npm install, no build chain for self-hosters.me-management.test.jsetc.) slice source by string.This rules out React+Vite-style stacks, but not a vendored micro-framework.
Proposal (two independent steps)
/meout ofworker.jsintoserver/me.jsvia the existing bundle-placeholder mechanism (chrome.js/shell.jsalready work this way). Purely mechanical, no behavior change; move the string-slicing guard tests along with it. Worth doing regardless of step 2.server/vendor/, inlined bytdoc-bundlelike everything else. Scope: first-party chrome only —/me, Share panel, eventually the comment rails. The reader HTML andframe-probe.jsstay framework-free forever. No rewrite of working code: first use it for the next new component or when/menext grows.Non-goals
shell.js.Not urgent — filed to capture the design discussion from PR #286 before it evaporates.
🤖 Generated with Claude Code