Skip to content

Feat/embed advantagescope - #3

Merged
jeffzakrzewski merged 13 commits into
mainfrom
feat/embed-advantagescope
Apr 24, 2026
Merged

Feat/embed advantagescope#3
jeffzakrzewski merged 13 commits into
mainfrom
feat/embed-advantagescope

Conversation

@jeffzakrzewski

Copy link
Copy Markdown
Contributor

No description provided.

Five inlined workspace-scoped session SELECTs across routes/sessions.ts
and routes/wpilog.ts collapse into one helper at auth/session-owner.ts.
Behavior is byte-identical -- all 124 worker tests green, including
cross-workspace 404 coverage at sessions.test.ts:281,353,403 and
wpilog-route.test.ts.

Prepares the ground for the /v/:id/* route group in U6, where reusing
this exact auth posture is a hard requirement (R5 of the embed plan).
Cache-check + regenerate-on-miss logic lifts out of routes/wpilog.ts
into wpilog/get-or-build.ts, returning a discriminated
{ ok, body, cached } | { ok: false, response } so callers set their own
response headers without sharing any cache-policy code.

This lets the upcoming /v/:id/logs/<name> handler (U6) reuse the exact
same cache behavior as /api/sessions/:id/wpilog -- no policy drift
structurally possible. Existing 9 wpilog-route tests still pass
byte-identical, including the golden-file regression at
convert.test.ts:130.
hubPort in ~/src/1310/AdvantageScope/src/main/lite/main.ts:43 is a
module-scoped let, not attached to window. An injection-only bootstrap
can't reach it -- the plan's main.ts.patch fallback is required.

Recommended patch is a ~5-line URL-query-param auto-open hook in
initHub() that dispatches open-files when ?log=<name> is set. Cleaner
than exposing hubPort globally, idempotent on reload, and directly
upstreamable as a deep-link feature.

Consequence: bootstrap.js is no longer a separate artifact. U2/U2b
simplify to just shipping the patch alongside the publish workflow.
Iframe src in U7 becomes /v/:id/?log=<sessionId>.wpilog.
Adds the four session-scoped dynamic endpoints AS Lite's frontend
speaks -- /logs listing, /logs/<name> streaming, /assets manifest,
/assets/<path> file -- plus a static-proxy catch-all for everything
else under /v/:id/*. Auth is byte-identical to
/api/sessions/:id/wpilog via the U4 loadOwnedSession helper; WPILog
streaming reuses U5's getOrBuildWpilog so the two routes are
structurally guaranteed to return identical bytes (verified by the
golden cross-route test).

wrangler.toml run_worker_first extended to ["/api/*", "/v/*"] so the
SPA fallback doesn't swallow /v paths (same failure mode the existing
comment at wrangler.toml:10-14 already warned about for /api).

Static-proxy handler rejects path traversal (".." or leading "/") and
content-type-sanity-checks the env.ASSETS.fetch response to defeat the
not_found_handling="single-page-application" silent-HTML fallback.
Static-proxy integration tests are deferred until U2 populates
packages/web/public/advantagescope/ with a real AS Lite tree; they
can't run in miniflare today without an ASSETS binding bound to a
real directory.

Tests added: 10 (auth matrix on listing + stream, cross-workspace
404, unauthenticated 401, byte-identical cross-route golden test,
run_worker_first negative test asserting 404-json not HTML).
Full suite: 134/134 green.
Adds /sessions/:id/view full-bleed route hosting the AdvantageScope
Lite iframe. sessionViewerUrl helper produces /v/:id/?log=session.wpilog
-- the ?log= param is consumed by AS Lite's RavenScope-applied main.ts
patch (U2b) to auto-open the session's single log on boot, and the
filename is arbitrary since /v/:id/logs/<name> ignores it.

iframe sandbox is allow-scripts + allow-same-origin only. Same-origin
is required for the session cookie to flow on AS Lite's relative
fetches under /v/:id/*; allow-downloads / allow-popups / allow-forms
are intentionally omitted for v1.

Session detail page gets an "Open viewer" primary button next to
"Download .wpilog" (demoted to secondary variant so viewing is the new
default primary action).

Route is inside the <AuthGate /> tree so expired cookies redirect to
/sign-in?next=/sessions/<id>/view.

Tests: 4 new (iframe src contract, sandbox attrs, Back link target,
id url-encoding). Full web suite: 16/16.
Adds the two-step bundle pipeline:

  pnpm publish:advantagescope-bundle
      Runs once per AS version bump. Uses AS_PATH + activated
      Emscripten (4.0.12) to build AS Lite from a pinned tag, apply
      RavenScope's main.ts patch, tar up lite/static/, compute SHA-256,
      and update packages/web/advantagescope/checksums.txt.

  pnpm fetch:advantagescope
      Runs at developer setup and in CI. Reads the pinned version +
      checksum, pulls the tarball from the local cache (or a release
      URL once we start publishing), verifies SHA, extracts into
      packages/web/public/advantagescope/ with --strip-components=1,
      and generates assets-manifest.json in the shape AS Lite's
      Python server would return for GET /assets.

The patch at packages/web/advantagescope/main.ts.patch adds ~8 lines
to AS's initHub() that read ?log=<name> from the URL and dispatch
open-files to the hub -- this is what makes /v/:id/?log=... auto-open
the session's single log with no user interaction. Upstreamable as a
deep-link feature.

First bundle built: AS v27.0.0-alpha-4 -> 61MB tarball ->
packages/web/public/advantagescope/ 79MB unpacked. Vite build succeeds
with the AS tree in place; dist/ grows to 121MB (mostly 3D field
models). All 150 tests still green.

packages/web/public/advantagescope/ and .advantagescope-cache/ are
.gitignored -- the repo stays source-only. Commit contains only pinned
version + patch + scripts.

Bundles currently live in the local cache; to promote to CI-reachable,
run the printed "gh release create" command and add release-url to
version.txt. Not blocking for local dev or QA.
New top-level ATTRIBUTION.md reproduces the AdvantageScope BSD-3
copyright + conditions (required for binary redistribution), calls out
the ~8-line main.ts patch RavenScope applies, and notes that the
transitive-deps notice file (docs/build/licenses.html) ships as part
of the bundle itself. Includes attribution for AdvantageScopeAssets.

README opens by mentioning the embedded viewer as the first-class
read-path, and the stale "No AdvantageScope deep-link" caveat is
replaced with an accurate description of the beta-track binding + a
step-by-step version-bump workflow (emsdk install, publish-bundle,
fetch, build).

CI integration (pnpm fetch:advantagescope as a pre-build step in
.github/workflows/) is intentionally deferred until a RavenScope GH
release hosts the tarball -- the fetch script's release-url branch
is already wired and tested, just waiting for the URL.
Integrates the workspace-members feature (multi-member workspaces,
owner-role gating, invites, R2 quota wrappers, workspace settings page)
with the AdvantageScope embed branch.

Conflicts resolved:
- .gitignore: both stanzas kept (ours: AS bundle ignore; theirs:
  .claude/ + .context/).
- packages/worker/src/index.ts: both route groups registered
  (/api/workspaces, /api/invites from main; /v from this branch).
- packages/worker/src/routes/sessions.ts: U4 loadOwnedSession helper
  kept alongside main's requireOwnerRole middleware on PATCH/DELETE.
  The refactor + the role-gate are complementary, not in tension --
  loadOwnedSession enforces workspace ownership, requireOwnerRole
  enforces the owner role for writes.
- packages/worker/src/routes/wpilog.ts: U5 getOrBuildWpilog extraction
  preserved. Main's quota-aware R2 wrapper (getBlob) is adopted inside
  get-or-build.ts so all R2 reads on the wpilog path go through the
  instrumented choke point.

Tests: 203 worker + 34 web = 237 passing (up from 150 pre-merge; main
brought 87 new tests, all green). Build is clean; AS Lite bundle still
ships to /advantagescope/* on the origin via Workers Static Assets.
The iframe in SessionView uses `/v/:id/?log=...` (with trailing
slash). That URL flowed through the `/:id/*` catch-all with an empty
sub-path, which built `/advantagescope/` and hit Workers Static Assets
directory-serve behavior -- the response was RavenScope SPA
index.html (text/html). The content-type sanity check then rejected
it as the SPA-fallback-swallowing-the-request case and returned 404.

Treat empty sub-path in the catch-all as the iframe root and serve
AS Lite's index.html with allowHtml=true. Added a regression test
that hits /v/:id/ with trailing slash explicitly.

Local-dev repro: click "Open viewer" on a session detail page with a
browser pointed at wrangler dev -> {"error":"not_found"}. After this
fix, the iframe renders AS Lite correctly.
Now that the tarball lives at a stable RavenScope GH release URL,
fresh clones and CI can `pnpm fetch:advantagescope` without a local
cache. Release:
https://github.com/RunnymedeRobotics1310/RavenScope/releases/tag/advantagescope-lite-v27.0.0-alpha-4

Verified end-to-end: removed the local cache + extracted dir, ran the
fetch script cold, it pulled from the release URL, verified the
SHA-256 in checksums.txt, extracted into
packages/web/public/advantagescope/, and regenerated
assets-manifest.json. Patch lines (URLSearchParams(location.search))
present in the downloaded bundles/main.js.
Two false positives in proxyStatic's SPA-fallback guard surfaced
during local browser QA:

1. Workers Static Assets serves .js/.mjs with text/javascript, but
   the check demanded application/javascript -- every AS Lite
   bundles/*.js hit 404. Fix: accept both text/javascript and
   application/javascript.

2. AS Lite has real HTML sub-pages (www/hub.html, www/download.html,
   etc.) that must be served. The SPA-fallback guard was rejecting
   every text/html response regardless of whether the request had
   actually asked for HTML. Fix: split into two branches -- if the
   request has a known extension, the content-type must be in the
   accept-list for that extension (so .html gets text/html, .js gets
   application/javascript|text/javascript, etc); only the
   extensionless path runs the anti-HTML-fallback heuristic.

Observed symptom before fix (wrangler dev logs):
  GET /v/:id/bundles/main.js  -> 404
  GET /v/:id/www/hub.html     -> 404

Also broadened the accept-lists for .glb / .gltf / .woff / .woff2 /
.ttf to tolerate the runtime-dependent variants (octet-stream for
.glb, application/font-woff for legacy .woff, font/sfnt for .ttf on
Cloudflare's edge, etc.) so the same class of false-positive doesn't
bite us on 3D model and font loads.

Tests: 11/11 advantagescope route tests still green.
Adds `pnpm fetch:advantagescope` to both CI and deploy workflows,
with a cache of the 61 MB tarball keyed on the pinned version +
checksum so unchanged bundles skip the GH-release download.

Now that packages/web/advantagescope/version.txt carries a
release-url pointing at
RunnymedeRobotics1310/RavenScope/releases/download/...,
fresh clones and CI runners can populate
packages/web/public/advantagescope/ without any local cache or
Emscripten toolchain. Version bumps are still the developer ritual
documented in README (publish-advantagescope-bundle), but
consumer-side the bundle just shows up.

Deploy integration: the worker's [assets] directory points at
packages/web/dist, so once Vite copies public/advantagescope into
dist/advantagescope, the Worker's /v/:id/* route proxy can serve
AS Lite verbatim via env.ASSETS.fetch on prod.
@jeffzakrzewski
jeffzakrzewski merged commit 9f088f8 into main Apr 24, 2026
1 check passed
@jeffzakrzewski
jeffzakrzewski deleted the feat/embed-advantagescope branch April 24, 2026 21:01
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