README.md now focuses on user-facing content:
- General description and live demo link
- Installation instructions (npm, Bun, npx, bunx)
- CLI usage examples (file, stdin, --serve)
- CLI flags table
- Dev setup with all relevant commands and scripts
- Link to ARCHITECTURE.md
ARCHITECTURE.md contains all technical implementation details:
- High-level architecture diagram
- Full project structure tree
- Runtime and dev dependencies
- Protocol spec (packet format, constants, metadata wrapping)
- Algorithm deep dives (RLNC, GF256, QR encode/decode, GIF, scheduling)
- Sender and receiver data flow diagrams
- Design decisions and rationale
- Common pitfalls
- Rename CLI entry from qr-terminal.ts to qr-stream.ts
- Rename package to qr-stream, version 0.6.0
- Add --serve / -s flag to start built-in static file server for web UI preview
- Include dist/ and src/ in npm files so consumers get both bundled CLI and source code
- Add static_server.ts helper using Node built-in http module
- Update help text and tests for new name and --serve flag
- Remove old qr-terminal.js from deployment
- Add MIT LICENSE
- Remove "private": true
- Set name to qr-transfer-terminal, version 0.5.0
- Add files, engines, keywords, repository, bugs, homepage fields
- Move typescript from peerDependencies to devDependencies
- Add prepublishOnly script to ensure fresh CLI bundle
- Add 'bin' field to package.json so npm/npx/bunx can invoke it as
'qr-terminal'. The bin points to dist/qr-terminal.js.
- Change shebang to '#!/usr/bin/env node' for universal compatibility.
- Fix displaced chars: each frame write now ends with '\n' and
moveCursorUp() appends '\x1b[G' to move to column 0. Previously the
cursor stayed at the end of the previous last line, causing redraws
to start mid-line.
- Position QR at top-left (removed padTop/padLeft centre logic) since
the alt-buffer is clean and only the QR is shown.
- Update help text to show npx/bunx invocations.
- Enter alternate screen buffer (\x1b[?1049h) before drawing and exit
it (\x1b[?1049l) on cleanup. This preserves the user's terminal
content when the app quits.
- Clear screen once after entering alt buffer so drawing starts from
the top-left.
- Add -h and --help flags that print usage, stdin/file modes, and
controls (q / Ctrl-C to quit).
- Update tests for new alt-buffer helpers and help text.
- Rebuild CLI bundle (dist/qr-terminal.js 95.4kb) and redeploy webapp.
- Stop clearing the entire screen every frame — now clears once on first
draw, then uses cursor-up (\x1b[nA) to overwrite the QR block in place.
- Removed frame counter, generation info, and 'press q' text — only the
QR code is shown, keeping the display minimal and static-area free.
- Simplified buildFrames return type (Uint8Array[] only).
- Added moveCursorUp() helper to terminal_raster.ts.
Terminal block characters render in the foreground colour; on dark-themed
terminals the QR looked inverted. Now every line is wrapped with
\x1b[47m\x1b[30m (white bg / black fg) so dark modules appear black and
light modules appear white on any terminal theme.
- renderToTerminal now pads with a 4-module quiet zone (white border)
horizontally and vertically, matching the webapp rasterizer.
- Keyboard input now tries /dev/tty first so q/Ctrl-C works even when
stdin is a pipe (e.g. echo ... | bun run src/cli/qr-terminal.ts).
- Update all CLI renderer tests for padded output; add tests for custom
and zero quiet zone.
- Increase outer_ec_benefit test timeout to 15s to avoid flaky failures.
- Rebuild CLI bundle and webapp; redeploy to nginx.
The transfer stats bar was wrapped in {scanning && ...} so elapsed
time and KB/s vanished when scanning auto-stopped on completion.
Add a persistent summary section that stays visible after scanning
stops, showing final transfer time, average throughput, and data size.
Uses formatDuration, formatBytes utility helpers already in the file.
- Add outer Reed-Solomon error correction across generations
(3% overhead, 1 parity gen per ~33 source gens)
- Fix progress display: show generations solved/needed instead of
raw packet counts, making outer EC benefit visible
- Add decode timer: tracks elapsed time from first frame to completion
and shows throughput in KB/s
- Add benchmark test confirming outer EC reduces tail waiting time
under biased frame loss
- Remove per-generation hash verification (superseded by outer RS)
- Update deployment base path to /hermes-web-demos/qr/
- Hash routing: #sender / #receiver, persists across refresh
- App reads window.location.hash on mount and listens to hashchange
- Sender: resetOutput() helper revokes blob URLs and wipes all output state
- Sender resets when: mode changes, file changes, text changes, generate clicked
- Fixes 'object error' caused by stale revoked blob URLs staying in DOM
- Removed session tracking entirely; single global decode state
- Receiver UI: no sessions table, no big progress bar
- Inline stats above video: scanned / useful / need · gen X of Y
- Sender passes filename and mimeType to encode worker
- Packetizer wraps file payloads with [filenameLen][name][mimeLen][mime] header
- Decode worker parses metadata and uses it for download name/type
- Fixed generation counter bug: final progress was skipped when reconstruction
succeeded; now reportProgress is called right before early return
- Frame count now shows: scanned / useful (linearly independent) / minimal needed
- Camera zoom via getUserMedia track.applyConstraints when supported
- Software crop fallback with visible dashed-square overlay on video
- Corner markers on scan region for visual feedback
- qr_decode accepts inversionAttempts option for faster GIF decoding
- Preserved attemptBoth for camera scanning (handles glare/reflections)
- Remove CBOR manifest and manifest.ts entirely
- Fixed 18-byte packet header: sessionId, generationIndex, symbolIndex,
packetType, totalGenerations, dataLength, flags, reserved
- Single hardcoded profile: QR V10, ECC M, K=16, R=8, payload=191 bytes
- Add isText flag (bit 0 of flags) for text vs binary routing
- Remove hash computation and profile selection UI
- Update all tests to match new protocol (33 tests passing)
- Fix generateCoefficients arg order bug in complete.test.ts
- Fix createQRGif dimension bug in tests
- Make prod_roundtrip frame loss deterministic
All 33 tests pass. Build succeeds.
The dedup key was `::`, which
caused manifest fragment 0 to collide with systematic symbol 0. The RLNC
decoder then never reached full rank, so generations never solved and the
receiver appeared to hang.
- Add packetType to dedup key so manifest/systematic/coded symbols are unique
- Add completed session flag to ignore late frames after reconstruction
- Prevent trailing progress message from overwriting complete status
- Add frame_decode and prod_roundtrip tests
- Rewrote GIF LZW decoder (gif_parser.ts) — old version produced
wrong pixel indices for larger images (V20 QR frames), causing
all QR decodes to fail after GIF roundtrip
- Added full end-to-end test: encode → QR → GIF → parse GIF →
decode QR → RLNC decode → reconstruct (94 tests passing)
- Added GIF File input mode to receiver for debug/testing
- Fixed receiver stale closure bug (scanning state captured
as false, preventing any frame capture)
- GIF parser handles: LZW, interlaced, local/global palettes,
Graphics Control Extension, disposal methods
- Fix stale closure bug in camera capture loop (scanning state
captured as false, preventing any frame capture)
- Add GIF File input mode to receiver — upload a QR-over-GIF
directly to test the decode pipeline without camera
- Update decode worker to accept imageData or frameData
- Receiver now shows two input modes: Camera and GIF File
The rAF capture loop checked where
was a state variable captured in a stale closure.
The loop was created when scanning=false, so it immediately
exited on every frame and never captured any camera frames.
Fix: use scanningRef (useRef) instead of state for the loop
guard, so the rAF callback always reads the live value.