diff --git a/backend/festival4d/capsule.py b/backend/festival4d/capsule.py index bc94fd1..d8e4f0e 100644 --- a/backend/festival4d/capsule.py +++ b/backend/festival4d/capsule.py @@ -1,4 +1,4 @@ -"""Memory capsule: zero-backend shareable export (spec M17, lane G). STUB — lane G fills. +"""Memory capsule: zero-backend shareable export (spec M17, lane G). Contract: :func:`build_capsule` writes a self-contained bundle to ``out_dir`` (default ``config.CAPSULE_DIR``): @@ -16,13 +16,300 @@ Contract: :func:`build_capsule` writes a self-contained bundle to ``out_dir`` (d Skip what doesn't exist (no splat -> no ``api/splat``) — degrade, don't block. Returns a summary dict (bundle path, file count, bytes) for the CLI log. + +Layout mirror (why paths look the way they do): the frontend resolves every URL as +``API_BASE + path`` with API_BASE == "" in a capsule, so requests hit the bundle root: +``fetch("/api/manifest")`` (no extension — bake the file extension-less; serve.py maps +extension-less ``api/*`` files to a JSON content type), ``video.src = "/media/"`` +(manifest ``url`` fields are ``/media/{filename}`` — media files are copied under +``media/``), PLY loader hits ``/api/pointcloud``, the WebAudio clock fetches +``/api/audio/{id}``. Saved camera paths (M16) are read-only in a capsule, so +``api/paths`` + ``api/paths/{id}`` are baked too — the load dropdown keeps working. """ from __future__ import annotations +import json +import logging +import shutil from pathlib import Path +from festival4d import config, db -def build_capsule(out_dir: str | Path | None = None) -> dict: - """Build the static shareable bundle (see module doc).""" - raise NotImplementedError("memory capsule (lane G / M17)") +log = logging.getLogger("festival4d.capsule") + +# Injected verbatim (phase-5 contract #5): a runtime override that beats the build-time +# VITE_API_BASE, turning the same dist bundle zero-backend. Must land in index.html before +# the app module reads it (modules are deferred, but we inject ahead of the first ' + + +# --------------------------------------------------------------------------- +# serve.py shipped in the bundle root (stdlib-only, Range-capable — pitfall #4) +# --------------------------------------------------------------------------- +SERVE_PY = r'''#!/usr/bin/env python3 +"""Serve this Festival 4D memory capsule locally. + +Plain `python -m http.server` cannot answer HTTP Range requests, and