Streaming markdown reader (LLM-typewriter) built as a VectoJS forge. Hybrid shell: traditional HTML chrome (header file/controls/rate/theme, no ribbon) wrapping a VectoJS canvas core (#river-canvas + #river-a11y-root) — ribbon removed CTX-0066 (reader has no explorer, wastes 48px).
- Family:
vectojs-native/river/(container; each child is its own git repo:river/,river-core/,river-docs/). - Deployed: https://river.vectojs.org (Cloudflare Pages project
river— not yet live until CTX bootstrap; localbun run previewon 3518). - Gallery Stream Reader reference:
vectojs-gallery/src/creations/chat/— drop a.md/.txtand replay at adjustable rate with@vectojs/markdown createStream+river-coretokenize/tickStream.
bun+vite+typescript(strict, noUnusedLocals/Params)@vectojs/core1.39.1,@vectojs/markdown0.23.1,@vectojs/styles0.3.3,@vectojs/ui2.20.2 (exact-pinned, neverworkspace:*),marked^18.0.10,@vectojs/devtools0.11.2oxfmt0.64.0 /oxlint1.79.0 /biome2.5.6 /markdownlint-cli2/lefthook/commitlint/vite-plugin-pwa1.3.0
bun install
bun run dev # http://localhost:3518 (vite, HMR)
bun run check # oxfmt --check + oxlint --deny-warnings + markdownlint
bun run test # bun test (0 files expected in app; 34 in river-core)
bun run build # tsc && vite build → dist/ (PWA 8 entries, manifest.webmanifest)
bun run test:e2e # playwright test (chromium, via vite preview on 3518)Append ?debug to attach @vectojs/devtools (attachDevtools(scene)) and expose window.__app = { scene, markdown, previewScroll, state, perf }. Smoke tests assert auditScene(scene) returns [].
- Outer chrome is plain HTML/CSS flex (
#river-headeronly —#river-ribbonremoved CTX-0066). - Center
#river-stagehosts<canvas id="river-canvas">+<div id="river-a11y-root">for the VectoJSScene. SceneusesdisableWindowResize:true+ResizeObserveron#river-stage(seescribe/src/main.ts:671-700):isValidStageSizeguard →scene.resize(w,h)→previewScrollcentered (max 860) →markdown.setMaxWidth+scroll.updateContentSize().disableWindowResize:truemandatory; window resize owned by observer.- Stream ticker:
StreamTicker extends Entityinside Scene onDemand;hasPendingAnimations = status===streaming;tickStream(state, dt)owns rate/pause;Markdown.createStream({ incompleteMode: 'optimistic', maxBufferedChars })handles parsing. - DropZone, ScrollBar HTML thumb, SearchBar, ContextMenu are HTML overlays; VectoJS owns only
#river-canvaspixels.
Public assets live in cdn-vectojs bucket namespace river/* (e.g. river/logo.svg). Upload with wrangler r2 object put river/logo.svg --remote and verify 200 + content-type; link https://cdn.vectojs.org/river/.... Never commit static copies.
- CTX-0053 scaffold ✅ — CTX-0054 river-core streaming pipeline ✅ — CTX-0055 Hybrid shell & canvas ✅ — CTX-0056 aux features ✅ — CTX-0057 polish, audit, e2e smoke, docs & handoff ✅
- Next: R2 logo upload, GitHub repo creation (
vectojs/river), Cloudflare Pages deploy, dark theme, i18n (en only today).
MIT — see LICENSE (when published to github.com/vectojs/river).
Header buttons have aria-label, search input aria-label, context menu role="menu" + role="menuitem" (separator role="separator"), dropzone role="button" + tabindex toggle via aria-hidden, search bar role="search", slider aria-label="Token rate". en only; no i18n yet (documented in river-docs/TODO.md).
vectojs-gallery/src/creations/chat/— Stream Reader product reference (state, parser, AsyncGeneration, pacing).vectojs-native/scribe/— scaffold template for Hybrid shell, tooling, CI, and PWA pattern.vectojs-docs/forge/findings/— engine findings (none blocking for River 0.1).