Skip to content

fix: audit batch — #133 #129#138

Merged
almogdepaz merged 7 commits into
mainfrom
fix/audit-issues-batch
May 13, 2026
Merged

fix: audit batch — #133 #129#138
almogdepaz merged 7 commits into
mainfrom
fix/audit-issues-batch

Conversation

@almogdepaz
Copy link
Copy Markdown
Owner

@almogdepaz almogdepaz commented May 13, 2026

Two small fixes from the open audit issues. Each is a separate commit with its own regression test. #130 and #131 left open — see below.

Closes

  • L16: audit-regressions.test.ts inlines a copy of validateProjectDir #133 refactor: extract validateProjectDir to pure module
    The HTTP-flavored validateProjectDir in routes.ts had its core logic
    duplicated inside tests/unit/audit-regressions.test.ts so the test could
    exercise it without an HTTP response object — drift risk on a
    security-sensitive path-containment boundary. Extracted to
    src/server/validate-project-dir.ts returning a discriminated result;
    routes.ts wraps it; regression test now imports the real function.

  • M5: quiescence loop produces stale snapshot for animated TUIs #129 fix(ws): cap quiescence wait at 200ms for animated TUIs
    Always-busy sessions (spinners, htop, watch, claude's pulse spinner)
    never drop below QUIESCE_BYTE_THRESHOLD, so the prefill settle loop
    always waited the full 800ms before snapshotting — capturing a
    mid-redraw frame the live stream then had to overwrite. New
    QUIESCE_ANIMATED_CAP_MS = 200 short-circuits that wait. Common case
    (apps that quiesce) is unchanged. Decision extracted to pure
    quiescenceDecision() for unit testing.

Reverted in this branch

  • M10: forceRepaint() accesses ghostty-web private internals #130 ghostty-web private internals — initial attempt (eb0ae2d) added
    a warn-once shim around the existing try { ... } catch {}. Theater:
    a console.warn in a mobile PWA fixes nothing. Reverted in 13cd446.
    Real risk surface: ghostty-web is pinned to 0.4.0, no runtime
    auto-update; only a deliberate dep bump can break the internals. Right
    fix is either a CI test against the bundle or a comment at the
    version-bump site, not defensive code in the hot path. See updated
    issue comment.

Left open

  • L10: broker WRITER_QUEUE_CAPACITY can backpressure the read loop #131 broker WRITER_QUEUE_CAPACITY backpressure — issue's user-impact
    claim ("slow clients lose keyboard input + resize") is wrong:
    Frame::InputBinary calls session.write_stdin directly, and
    Frame::ControlRequest runs router.handle synchronously (resize is
    applied before its response queues). Only control-response delivery
    sits behind a full queue, and SubscriptionDropped recovery exists.
    Needs issue rewrite before deciding between hardening and wontfix.

Verification

  • bun test — 1483 pass / 0 fail (after revert)
  • bunx tsc --noEmit — 32 errors, all pre-existing on main, zero new
  • scripts/bundle-app.ts regenerates clean

Out of scope (closed during triage as stale)

The HTTP-flavored validateProjectDir in routes.ts had its core logic
duplicated as an inlined copy inside tests/unit/audit-regressions.test.ts
so the test could exercise it without an HTTP response object. The
comment in the test acknowledged the drift risk on a security-sensitive
path-containment boundary.

Extract the pure validation to src/server/validate-project-dir.ts
returning a discriminated result. routes.ts wraps it with the existing
HTTP response surface; the regression test now imports the real function
instead of a copy.
…loses #130)

forceRepaint() pokes ghostty-web private fields (renderer, wasmTerm,
viewportY) because no stable upstream API exists. A future bundle update
that renames or restructures those fields would silently turn the call
into a no-op, leaving stale frames after attach with no signal.

- New public/terminal-repaint.ts exports hasGhosttyRepaintHook(term)
  feature-detecting the expected shape (pure, no side-effects).
- forceRepaint() now probes via the helper and emits a single
  console.warn on shape drift before bailing — silent degradation
  becomes loud and diagnosable.
- tests/unit/terminal-repaint.test.ts covers the positive case plus
  every drift mode (missing renderer, renamed field, non-function
  render, missing wasmTerm, missing viewportY, viewportY === 0).
Animated TUIs (spinners, htop, watch, claude's pulse spinner) never let
the broker byte rate drop below QUIESCE_BYTE_THRESHOLD, so the settle
loop in the prefill path always waited the full QUIESCE_TIMEOUT_MS
(800ms) before snapshotting. The snapshot was taken mid-redraw and the
live stream then had to overwrite the garbled frame — visible UX
artifact lasting 150–800ms on attach.

- Add QUIESCE_ANIMATED_CAP_MS = 200. When byte rate stays above the
  threshold through the entire window, snapshot at 200ms instead of
  waiting out the full timeout. Cuts the worst-case mid-redraw prefill
  window 4x.
- Common case (apps that actually quiesce) is unchanged — they exit
  via the 'quiet' branch within ~100ms, well before the cap.
- Extract the decision into a pure quiescenceDecision() function so
  the loop's three (now four) exit conditions are testable in
  isolation without a real broker. The setTimeout(16) wait and resize
  side-effects stay at the call site.
- Add log.debug for animated_cap / timeout exits so operators can spot
  always-busy sessions in traces.
@almogdepaz almogdepaz changed the title fix: audit batch — #133 #130 #129 fix: audit batch — #133 #129 May 13, 2026
README: 302 → 155 lines.

- Lead with what it does + why (one paragraph), not marketing tagline.
- Primary install is the no-deps curl|bash; bunx/npx hidden in <details>.
- Drop tailscale 'optional' hedging — it's how you actually use this.
- Promote security setup ('Secure It' with JWT) above features so anyone
  deploying on a tailnet sees it before scrolling past the demos.
- Collapse the Features grab-bag (Session Management / Desktop / Mobile /
  Multi-Machine / Other / Remote Access / Security all flat-bulleted) into
  one terse 'What It Does' list, ~8 cross-cutting bullets.
- Drop Contributing / Testing / Building / Asset Pipeline / PR Conventions /
  migrate-plan — all moved to CONTRIBUTING.md.
- Drop 'Community & Support' boilerplate, garbled emoji, 'star the repo'
  growth-hack copy.
- Cut from 4 screenshots to 2 (one desktop, one mobile).
- Keep wolf ASCII art + architecture diagram + config example.

CONTRIBUTING.md: new. Holds dev setup, testing, asset pipeline, build,
PR conventions, and the migrate-plan utility.

Verified empirically before writing: both image refs exist, all 7 CLI
commands exist in src/cli/index.ts, all 4 JWT env vars exist in
src/auth.ts, both doc links resolve.
…aming

Tailscale already gates network reachability — for the common solo-user
case that's enough. JWT is now framed as opt-in (shared tailnets,
defense-in-depth), positioned after Architecture rather than between
First Run and What It Does.
@almogdepaz almogdepaz merged commit 5ac108e into main May 13, 2026
1 check passed
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.

1 participant