Skip to content

Develop - #28

Merged
sladojevicm merged 119 commits into
masterfrom
develop
Sep 23, 2026
Merged

sladojevicm merged 119 commits into
masterfrom
develop

Conversation

@sladojevicm

Copy link
Copy Markdown
Owner

No description provided.

sladojevicm and others added 30 commits September 11, 2026 14:03
…pod bootstrap clones master

- .claude/AGENTS.md: eight agents in four waves plus the conditional LiDAR
  feasibility track; maintainer decisions of 2026-09-11 recorded at the top.
- deploy/runpod/bootstrap.sh and README.md: default clone branch was the
  now-merged fix/phase3-pose-and-eval; it is master from here on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
The job API (src/api/) parses multipart uploads and streams archives to
disk; both need packages the serve extra does not carry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
Maintainer decision 3: prove whether the recon extra (open3d, cv2, trimesh,
scipy, skimage, pymeshfix) imports on a plain Ubuntu runner with libgl1 +
libglib2.0-0, and what the full pytest suite does there, BEFORE ci.yml is
written. Temporary workflow; findings go to docs/ci-probe.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
New src/telemetry/ package (stdlib + the existing jsonschema dependency):
configure_logging() with a text formatter that keeps the old console look and a
JSON-lines formatter selected by SOBA_LOG_JSON=1; stage_timer() context manager;
RunMetrics recording stage durations, gate decisions (verbatim gate_object
output, never recomputed), drops and remote calls, written as
run_metrics.json and validated against src/telemetry/run_metrics.schema.json.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
- jobs/store.py: JobRecord + guarded state machine (queued -> running:<stage>
  -> done|failed, history appended per transition); MemoryJobStore and
  SqliteJobStore (stdlib sqlite3, WAL, per-call connections).
- jobs/queue.py: three-method JobQueue contract (put/get/close) documented
  for a Redis drop-in; InProcessQueue.
- jobs/archive.py: validate_and_extract(), the single extension point for
  upload hardening: magic-byte sniff (zip/tar.gz/tar), zip-slip and
  link/device guards, size + member caps, bundle vs TUM layout detection
  with PerceptionBundle.open; TUM only when an imaging backend exists.
- jobs/worker_local.py: LocalWorker thread; modes mock (default, copies a
  fixture scene or writes a schema-valid empty one), real and gate-only
  (run_assemble.py subprocess, log under the job dir).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
Module-level `remote_call_hook(endpoint, seconds)`, called in a finally around
the urlopen so telemetry can account RunPod calls on success and failure.
No signature or return-value change; the runpod-orchestration agent keeps
ownership of the class.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
Open3D 0.19.0 imports on a plain ubuntu-latest runner with libgl1 +
libglib2.0-0; full suite 238 passed, 2 failed, 8 skipped. Both failures are
environment: torch + the dust3r checkout (test_slam mast3r test) and the
anthropic SDK (vlm_claude backend test); none of those is in an extra.
Skips: 7x gitignored out/scene_office_3, 1x plyfile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…renced with the pod template

Undotted name because .claude/settings.json denies tool access to .env.*
(which includes .env.example); rename is a maintainer decision.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…ncy audit 2026-09-11

gitleaks (122 commits + tree), pip-audit (venv + pyproject export) and
npm audit (prod + all): all clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…ndings

Unused imports/variables removed (no behaviour change; the two dead
assignments were pure reads), two bare `import open3d` guards in tests made
explicit as pytest.importorskip, and one F821 false positive in slam.py
(closure over the enclosing function's `out`) marked noqa. Style rules stay
off so the lint job never blocks on formatting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…, non-root, HEALTHCHECK /scene.json)

Editable install + PYTHONPATH=/app/src so src/server.py keeps finding
frontend/dist next to it (server.py is a top-level module, not a package).
SOBA_EXTRAS build arg switches to serve,api once the api extra lands.
No separate frontend container (maintainer decision 2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…; worker + serverless entrypoints)

[recon,serve,dev] plus coacd and runpod, which no extra provides; the
Open3D CUDA tensor check warns at build time (CI has no GPU) and is
re-runnable for real with `docker run --gpus all soba-pipeline check`.
Weights and model checkouts are mounted under /workspace, never baked.
Model-pin summary in the header; docs/model-pins.md is the table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…iff against the committed bundle

CI guard for CLAUDE.md invariants 5/6: any byte difference fails the build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…kefile (build/up/test/lint/loadtest)

The worker command is a placeholder until feat/job-api and
feat/runpod-orchestration land; make loadtest exits 2 until feat/load-testing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
….json

telemetry.configure_logging() replaces basicConfig; the body moves into
_run(args, metrics) and main() wraps it in try/except/finally so
<out>/run_metrics.json is written on success, --gate-only and failure
(run.status = failed + error). Stages timed: run, bundle_open, tier_params,
engine_init, gate_cache_key, observed_cloud, gate, generation,
generate_object, tsdf_fuse, assemble, eval. One gate event per routed object
carrying exactly what confidence.gate_object returned (tier 1: None metrics)
plus routed (forced strategy) and cached (gate-cache hit). Drops recorded:
cloud_too_small, generation_rejected_cached, engine_declined,
empty_tsdf_mesh, nothing_to_assemble. RunPod wall time flows through
generative.remote_call_hook. All 16 prints are now log.info with the same
text; CLI flags and exit codes unchanged.

Smoke test stubs open3d in sys.modules, loads the script and fakes the
pipeline calls, so the instrumentation is verified on this GPU-less box.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
… builds, frontend-check); probe workflow removed

Per docs/ci-probe.md: Open3D imports on ubuntu-latest, so pytest runs the
full suite there; the single test that imports torch + the dust3r checkout
is marked `gpu` and tests/conftest.py skips it when SOBA_SKIP_GPU_TESTS=1
(only CI sets it). anthropic is installed so the VLM backend test runs.
The API image is started and must reach healthy on /scene.json as a
non-root uid; the pipeline image is built on a CUDA base after freeing
runner disk and its entrypoint modes are exercised without a GPU.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…ecorded

stage_timer around assemble (outer), ground, vlm_infer, each _assemble_object
(id/track/class/strategy), the engine completion call, CoACD decompose and
deoverlap; one `object` event per assembled object and a `scene_written`
event. The empty-input filter and the 12-object cap now record `empty_input`
/ `over_cap` drops on the active run instead of vanishing silently.
No behaviour change; assemble() keeps its signature.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
No `telemetry` extra: the package needs only the stdlib and the jsonschema
dependency the contract layer already has.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
docs/log/2026-09-11-observability-a.md records the design, the
run_metrics.json contract (schema 1) with an example, the gate-recording
rule, and what only the GPU machine can verify (with the pod command).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…server.py is a thin shim

src/api/app.py composes three route groups in a fixed order: the legacy
root scene routes (byte-for-byte the Phase-10 handlers), the job API
(POST/GET /api/jobs, GET/DELETE /api/jobs/{id}) and the same scene routes
again under /jobs/{id}/ resolved through the job store, then the static
mount. Later agents add a module and one registration line here.

routes/scene.py parameterises the old handlers by a resolve(request)
callable and, for job routes, a watch callable so /jobs/{id}/events also
emits job_status events and announces objects as the worker writes them.
An explicit /jobs/{id} -> /jobs/{id}/ redirect is needed because the
static catch-all mount pre-empts Starlette's redirect_slashes.

routes/jobs.py streams the upload to disk (aiofiles), validates and
extracts it in a threadpool so a bad archive is a synchronous 4xx
(400/413/415/422 with one JSON error envelope), then creates the record
and enqueues the id. The in-process worker re-enqueues jobs left
`queued` by a previous process at startup, since the in-process queue is
not persisted.

server.py keeps `create_app`, `app`, `main()` and the `_ID_RE`/`_HULL_RE`
re-exports so tests/test_server.py and scripts/serve.py are unchanged;
main() gains --jobs-dir and --worker-mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…d to end

79 CPU-only tests (no open3d, no cv2): synthetic PerceptionBundle archives
in zip/tar.gz/tar, upload -> 202 + id, every rejection path (missing
field, bad tier, MP4 -> 415, non-bundle/zip-slip/links -> 422, size cap
-> 413) and that rejected uploads leave no job dir; worker stages and
history (queued -> running:validating -> running:assembling -> done |
failed), the running:<stage> status read mid-job, the lifespan worker
thread, re-enqueue on restart, real/gate-only modes against a stub
run_assemble.py; job-scoped viewer/scene.json/meshes/hulls/eval/events;
delete; and that the legacy root routes and server re-exports are
untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…epo does not record

Finding: no git commit, checkpoint sha256 or HuggingFace revision is pinned
for YOLO, SAM2, MASt3R, TripoSG, Hunyuan3D 2.1, PatchComplete, PoinTr or
ComPC (all clones --depth 1 of the default branch, all HF downloads main).
Recorded: filenames, HF ids, run parameters, library versions from the
BENCHMARK header, which section used which model on which machine, and the
recovery commands for the 4060 and the pod volume. coacd is required by the
benchmark's hull colliders but is in no pyproject extra.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…del-pins.md

STATUS.md current-state block gains one line: the frontend bundle is served
by the API image; CDN offload is deferred until there is real traffic
(maintainer decision 2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…s under /jobs/<id>/

`sceneUrl()` in viewer/loaders.ts prefixes a scene path with the job
prefix taken from the page location (`/jobs/<id>`), or nothing at the
root; the four fetch/EventSource/loadAsync call sites in SobaViewer.ts and
the eval.json fetch in EvalReportPanel.tsx go through it. Bundle assets
stay absolute (/assets/...) so no Vite `base` change. The dev proxy also
forwards /api and /jobs. React/Three separation, physics semantics and the
window.__soba shape are unchanged; dist/ rebuilt with `npm ci && npm run
build` (tsc type-check included).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…T, poll)

`--path /jobs/<id>/` opens the viewer at a job URL on the spawned server
(which reopens out/jobs/jobs.sqlite); the default path keeps the old
behaviour byte for byte. submit_job.py is stdlib-only: zips a bundle dir
(or sends an archive as-is), POSTs it to /api/jobs and polls the status
to done/failed, printing each transition and the viewer URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
…en3D wheel finding) + index line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K7VMdJGk925chC4GgUhym
Job API: upload a bundle/TUM archive, job status, job-scoped viewer (Wave 0)
sladojevicm and others added 29 commits September 17, 2026 00:28
…-input-and-pins) into develop

Prep A: _v2 bundle sync guide + one-shot benchmark pin recovery script
Log index: kept both sides.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NU5Whvd1GH2wKy8L5QYBiY
…d (prep/key-plumbing-and-docs) into develop

Prep B: VLM-key plumbing in the GPU guide; STATUS + runbook §6 reconciled with the pod runs
…must not silently run Poisson)

PatchComplete is the completion band at tiers 2-4 (pipeline.yaml, make_engine
default), i.e. in BENCHMARK §3 tier 2 and §4. Without its checkout/weights the
engine returned None and the assembler repaired with Poisson while the job
reported done; both 2026-09-16 pod runs did exactly that.

- deploy/runpod/setup_patchcomplete.sh (+ SETUP_PATCHCOMPLETE=1 in bootstrap):
  clone yuchenrao/PatchComplete, fetch the authors' trained_models.zip, place
  multi_res.pt + patch_learning_res_{32,8,4}.pt, record SHA256SUMS, smoke-load.
- telemetry: RunMetrics.record_completion / telemetry.completion ->
  run_metrics.json `completion` section (schema updated); /metrics gains
  soba_completion_total{method}; GET /api/jobs/{id} gains `run` (status, gate,
  completion, drops) from run_metrics.json; OpenAPI Job.run. scene.json is
  untouched (SceneObject.source forbids extra fields, invariant 1).
- assembler._record_completion labels each completion-band object
  (patchcomplete | poisson_fallback[engine_declined|no_engine] | poisson_local);
  SOBA_COMPLETION_STRICT=1 raises on any fallback.
- docs: PatchComplete weights recorded as a permanent, unpinnable limitation
  (model-pins, STATUS); run-2 log corrected; guide's tier-2-capable setup.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NU5Whvd1GH2wKy8L5QYBiY
…based pins reconstruction

sync_bundles.md: the "copy from the 4060" plan was dead (that pod's volume is
gone). Now: Replica v1 download, selective extraction of the 8 rooms'
habitat/ files, match-test gate on office_3, render --frames 200, validate
with BUNDLE_DIR=bundles/room_2_v2 and SOBA_COMPLETION_STRICT=1.

scripts/reconstruct_pins.py: default-branch commit / HF revision as of the
date each model is first logged running; printed as a table labelled
"reconstructed by date"; MASt3R checkpoint kept as the one exact pin
(sha256); PatchComplete weights marked unpinnable. Run here; table pasted
into docs/model-pins.md with its reading.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NU5Whvd1GH2wKy8L5QYBiY
… (feat/patchcomplete-tier2) into develop

PatchComplete on the pod; completion fallbacks visible in run_metrics, /metrics and the job record
…eplica-and-date-pins) into develop

Regenerate _v2 bundles from Replica v1 (no copy source exists); date-based model pins reconstruction
… the gate; completion was Poisson

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NU5Whvd1GH2wKy8L5QYBiY
…(aria2c if present); MASt3R exact pins recorded

The TUM server delivered ~350 KB/s on the pod (1.8 GB = over an hour). The
script wrote straight to trained_models.zip and treated any non-empty file as
complete, so an interrupted run would have unzipped a partial archive. Now:
download to .part, resume on every rerun, rename only once the zip opens;
use aria2c with 8 ranges when installed (the throttle is per connection).

docs/model-pins.md: MASt3R checkpoint sha256 e28f91b4... observed on the pod;
the mast3r/dust3r commits cloned there equal the date-reconstructed rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… (PatchComplete verified)

Run 4 on the pod: PatchComplete ran for 4/4 completion objects under
SOBA_COMPLETION_STRICT=1, the pod suite is 510 green, MASt3R pins observed.
But all 10 generative objects were dropped as "engine_declined": TripoSG's
Python deps had not been reinstalled after the container reset, the engine
swallowed the import error, and the record could not tell "unavailable" from
"rejected" (run_assemble would also have cached it as a rejection).

- LocalGpuEngine.last_decline ("rejected: ..." | "unavailable: ..."), forwarded
  by SplitEngine.
- run_assemble: drop reason `generation_unavailable` (never cached) apart from
  `engine_declined`; SOBA_GENERATION_STRICT=1 raises.
- bootstrap: TripoSG set up before the slow PatchComplete download.
- docs: run-4 log, STATUS, guide (strict flags; rerun bootstrap after a pod stop).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…chComplete code + weight sha256, libraries)

From pins.txt of run 4 (2026-09-17). The three code commits equal the
date-reconstructed rows; the PatchComplete weight hashes pin what runs from
now on (their identity with the July weights stays unknowable).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… develop

Generation that cannot run is visible (generation_unavailable, SOBA_GENERATION_STRICT); run 4 recorded
…ume) under one SOBA_STRICT; TSDF-on-GPU claim retracted

A sweep of every exception handler around an ML/geometry call found four
silent fallbacks on the demo's critical path; all four now record, per object,
which implementation actually ran (run_metrics.json `steps`,
soba_pipeline_step_total{step,method}, run.steps on the job record):

- fusion: `_fuse_and_seal`'s bare except is gone. `fused` (completion_source
  engine|poisson, fused_triangles) vs `plain_finalize_fallback` with the
  exception text: only the former means the completion reached the final mesh.
- keep-band / seal repair: pymeshfix vs poisson_fallback (+reason, context).
- CoACD: decomp.decompose_info -> coacd (parts=n) vs single_hull_fallback
  (not installed | raised | no parts).
- mass volume: mass.finalize_mesh_info -> already_watertight | poisson_repair |
  hull_volume_fallback (+reason).
SOBA_STRICT=1 (telemetry.strict; per-kind flags kept) fails the run on any
completion, generation or geometry fallback; strict errors are re-raised
through the old handlers. scene.json is untouched (invariant 1).

Docs: STATUS.md, runbook §6, the guide and the run-1 log said "TSDF runs on
the GPU". Only the CUDA capability was checked; tsdf.fuse defaults to CPU:0
and run_assemble.py passes no device, so fusion executes on CPU. Reworded,
and the scripts' S1 message no longer implies otherwise. The device is not
changed here. The rest of the sweep is deferred (maintainer decision).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tracted into develop

Geometry fallbacks visible (fusion, keep repair, CoACD, volume) under one SOBA_STRICT; TSDF-on-GPU claim retracted
…A_STRICT=1 on the pod)

LocalGpuEngine.regenerate classified every exception from _run_gen as
"unavailable", including "no crop staged for this object" (crop staging found
no usable view: mask under min_area_px). Under SOBA_STRICT=1 that failed the
whole run on track 94 of office_3 before assembly. It is a verdict on the
object: recorded as engine_declined with its reason, cacheable, not strict.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A missing crop is a rejection, not an unavailable model (found by strict mode on the pod)
…les regenerated (measured numbers)

Run 6b (strict): 4/4 PatchComplete meshes fused from the engine, pymeshfix
seals, 10/10 CoACD colliders, 7 TripoSG objects, no fallback, 880 s. Strict
first failed on a misclassified missing crop (fixed in PR #24). The 8 _v2
bundles were regenerated on the pod: match-test 0.0 mm, 12 min for all rooms,
2.5 GB; sync_bundles.md now carries measured sizes/times and --no-same-owner.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
…enerative 2, all fused, no fallback)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
… a live Anthropic key

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
…o fallback; mass plausibility notes)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
…ever use a live Anthropic key

Run 8 (VLM physics) recorded; test suite never uses a live Anthropic key
…ail is gpu-marked)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
STATUS: WSL box venv is complete
…-backed inspector

scene.json's frozen geometry_source cannot tell kept from completed, so the
inspector labelled every PatchComplete-completed object "keep · fused tsdf".
The server now serves the scene dir's run_metrics.json (same absent-is-200
contract as eval.json, root and job-scoped, documented in openapi.yaml) and
the inspector reads route, coverage and completeness from it; without
telemetry it falls back to "measured · tsdf".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
… then the viewer

Client-side only. The file is described, never uploaded; the staged screens show
the numbers the recorded run wrote to run_metrics.json, advance only when the
presenter does, and carry a permanent "staged replay, nothing is computed live"
banner. The viewer is not mounted before the reveal; "/" stays the plain viewer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
…terial/route, reset

Viewer gains setAutoOrbit, focusObject (eased), setTints and resetObjects, all
behind store actions. Presentation mode scales the rem-based tokens from one
root attribute and swaps the panels for a large-type story card that keeps the
"recorded run" label and the "8 of 12 objects" curation on screen. serve.py
rejects a --scene path that is not a directory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
…pinned nav, safe centring

A fixed 150% root zoom overflowed a laptop at 150% display scaling (1280x670
viewport): cards covered the banner and the Next button left the screen. The
scale is now clamp(16px, min(1.5vw, 2.67vh), 32px), the staged screens scroll
inside a safely-centred area, and the navigation bar is always visible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4fWTqvtzrSuC3ZmHemmA6
Conference demo flow (/?demo) + truthful gate label from run_metrics
@sladojevicm
sladojevicm merged commit 179fa3a into master Sep 23, 2026
8 checks 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