bobframes 0.2.7__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bobframes-0.2.7 → bobframes-0.2.8}/CHANGELOG.md +31 -1
- {bobframes-0.2.7 → bobframes-0.2.8}/PKG-INFO +17 -1
- {bobframes-0.2.7 → bobframes-0.2.8}/README.md +16 -0
- bobframes-0.2.8/bobframes/_version.py +1 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/cli.py +22 -13
- bobframes-0.2.8/bobframes/serve.py +36 -0
- bobframes-0.2.8/bobframes/tests/_ui_util.py +57 -0
- bobframes-0.2.8/bobframes/tests/test_ui_ab_theme.py +162 -0
- bobframes-0.2.8/bobframes/tests/test_ui_browser.py +78 -0
- bobframes-0.2.8/bobframes/tests/test_ui_jobs.py +82 -0
- bobframes-0.2.8/bobframes/tests/test_ui_js_parses.py +34 -0
- bobframes-0.2.8/bobframes/tests/test_ui_progress.py +64 -0
- bobframes-0.2.8/bobframes/tests/test_ui_security.py +44 -0
- bobframes-0.2.8/bobframes/tests/test_ui_share.py +135 -0
- bobframes-0.2.8/bobframes/tests/test_ui_smoke.py +45 -0
- bobframes-0.2.8/bobframes/tests/test_ui_state.py +33 -0
- bobframes-0.2.8/bobframes/ui/__init__.py +17 -0
- bobframes-0.2.8/bobframes/ui/assets/panel.css +50 -0
- bobframes-0.2.8/bobframes/ui/assets/panel.js +151 -0
- bobframes-0.2.8/bobframes/ui/jobs.py +119 -0
- bobframes-0.2.8/bobframes/ui/progress.py +64 -0
- bobframes-0.2.8/bobframes/ui/server.py +513 -0
- bobframes-0.2.7/bobframes/_version.py +0 -1
- {bobframes-0.2.7 → bobframes-0.2.8}/.gitignore +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/LICENSE +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/_default_config.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/aggregates.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/catalog.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/config.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derive_post_merge.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/classifier.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/draw_classifier.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/pass_class_breakdown.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/presets/custom-template.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/presets/godot.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/presets/unity.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/derives/texture_usage.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/discovery.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/errors.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/global_entities.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/health.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/html/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/html/template.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/lint.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/lint_banlist.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/manifest.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/package.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/parquetize.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/parsers/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/parsers/derive_program_transitions.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/parsers/parse_init_state.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/paths.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/probes/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/probes/whatif.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/qrd_harness.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/query_examples.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/rdcmd.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/replay/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/replay/replay_main.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/_tokens.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/ab.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/Inter-OFL.txt +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/README.md +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/chrome.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/components.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/components.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/container.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/design_tokens.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/icon_sprite.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/inter-subset.woff2 +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/link_kind.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/per_drop.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/print.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/rdc_table.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/rdc_table.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/assets/sticky.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/base.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/cache.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/charts.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/chrome.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/cli.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/dashboard.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/delta.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/design_tokens.toml +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/discovery.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/draws_by_class.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/formatters.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/instancing_opportunities.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/orchestrator.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/overdraw.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/pass_gpu.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/preview.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/shader_hotlist.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/summary.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/reports/trend_table.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/resource_labels.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/run.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/schemas.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/stable_keys.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/__init__.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/_render_util.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_pagedata/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/buffers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/clears.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/counters_per_event.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/descriptor_access.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/dispatches.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draw_bindings.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draws.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/fbos.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/frame_totals.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/ibo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/passes.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/pixel_history.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/post_vs_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/program_transitions.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/programs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/render_targets.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/resource_creation.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/rt_event_timeline.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/samplers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/shaders.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/state_change_events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/texture_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/textures.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vbo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vertex_inputs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/run/2026-05-27_r110565/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/_reports/trend_table.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/README.txt +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_pagedata/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/buffers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/clears.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/counters_per_event.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/descriptor_access.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/dispatches.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draw_bindings.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draws.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/fbos.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/frame_totals.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/ibo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/passes.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/pixel_history.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/post_vs_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/program_transitions.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/programs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/render_targets.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/resource_creation.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/rt_event_timeline.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/samplers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/shaders.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/state_change_events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/texture_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/textures.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vbo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vertex_inputs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/drill/District 01/2026-05-28_r110600/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/run/2026-05-27_r110565/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/_reports/trend_table.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/redacted/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/README.txt +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_assets/catalog.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_assets/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_assets/report.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_assets/report.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_pagedata/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/buffers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/clears.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/counters_per_event.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/descriptor_access.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/dispatches.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draw_bindings.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draws.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/fbos.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/frame_totals.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/ibo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/passes.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/pixel_history.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/post_vs_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/program_transitions.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/programs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/render_targets.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/resource_creation.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/rt_event_timeline.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/samplers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/shaders.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/state_change_events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/texture_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/textures.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vbo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vertex_inputs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/drill/District 01/2026-05-28_r110600/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/run/2026-05-27_r110565/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/_reports/trend_table.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/README.txt +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_assets/catalog.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_assets/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_assets/report.css +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_assets/report.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_pagedata/catalog.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/buffers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/clears.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/counters_per_event.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/descriptor_access.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/dispatches.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draw_bindings.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/draws.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/fbos.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/frame_totals.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/ibo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/passes.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/pixel_history.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/post_vs_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/program_transitions.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/programs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/render_targets.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/resource_creation.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/rt_event_timeline.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/samplers.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/shaders.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/state_change_events.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/texture_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/textures.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vbo_samples.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/_pagedata/vertex_inputs.js +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/drill/District 01/2026-05-28_r110600/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/draws_by_class.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/instancing_opportunities.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/overdraw.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/pass_gpu.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/run/2026-05-27_r110565/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/shader_hotlist.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/summary.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/_reports/trend_table.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_package/shared_redacted/index.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_parquet/digests.json +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/golden_preview/_chrome_preview.html +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/_manifest.json +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/buffers.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/clears.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/counters_per_event.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/descriptor_access.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/dispatches.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draw_bindings.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draws.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/events.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/fbos.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/frame_totals.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/ibo_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/indirect_args.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/passes.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/pixel_history.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/post_vs_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/program_transitions.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/programs.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/render_targets.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/resource_creation.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/rt_event_timeline.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/samplers.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/shaders.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/state_change_events.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/texture_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/textures.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vbo_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vertex_inputs.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/_manifest.json +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/buffers.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/clears.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/counters_per_event.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/descriptor_access.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/dispatches.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draw_bindings.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draws.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/events.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/fbos.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/frame_totals.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/ibo_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/indirect_args.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/passes.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/pixel_history.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/post_vs_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/program_transitions.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/programs.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/render_targets.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/resource_creation.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/rt_event_timeline.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/samplers.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/shaders.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/state_change_events.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/texture_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/textures.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vbo_samples.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vertex_inputs.parquet +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/make_golden.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/make_package_golden.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/make_parquet_golden.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/make_preview_golden.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/make_synthetic.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/smoke.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_aggregates.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_assets.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_browser_shots.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_cache.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_catalog_backup_dedup.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_charts.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_classifier.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_components.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_config.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_contrast.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_delta.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_design_tokens.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_determinism.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_discovery.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_draws_by_class_kpis.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_element_builder.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_fonts.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_hardening.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_head_assets.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_health.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_js_coupled_classes.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_manifest_guard.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_multicapture_normalize.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_package.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_parity.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_parquet_parity.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_parquetize.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_perf.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_replay_drift.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_report_polish.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_report_structure.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_run_model.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_schemas.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_schemas_unit.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_stable_keys.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_summary.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_table_component.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_theme.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_token_guard.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/bobframes/tests/test_trend_regression_basis.py +0 -0
- {bobframes-0.2.7 → bobframes-0.2.8}/pyproject.toml +0 -0
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.8] - 2026-06-24
|
|
11
|
+
|
|
12
|
+
A guided local-web control panel (`bobframes ui`) for QA and product teammates who are not comfortable
|
|
13
|
+
in a terminal. Zero new runtime dependency (stdlib `http.server` only) and no report-output change --
|
|
14
|
+
the panel drives the existing verbs and emits no report HTML, so the rendered HTML and parquet digests
|
|
15
|
+
stay byte-identical on the same captures. No schema change (still schema 3). `_version` 0.2.7 -> 0.2.8.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `bobframes ui`: a zero-dependency local-web control panel that drives the whole pipeline from a
|
|
19
|
+
browser (ADR-47). It detects the RenderDoc tools and discovers capture drops, runs ingest /
|
|
20
|
+
re-generate / package / A-B as subprocesses with live per-capture progress streamed over Server-Sent
|
|
21
|
+
Events, opens or serves the rendered report, applies accent theming to a re-render, and can scaffold a
|
|
22
|
+
convention-correct capture folder. Server-rendered HTML + vanilla JS -- no framework, no build step,
|
|
23
|
+
no client router (the hard governance line in ADR-47). Binds `127.0.0.1` only and gates every
|
|
24
|
+
`/api/*` call with a per-session token. Install with `pipx install bobframes`, then run `bobframes ui`.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- The static preview server body moved from `cli._cmd_serve` into a reusable `serve.make_server` /
|
|
28
|
+
`serve.serve_forever` so the panel's background serve and the `bobframes serve` verb share it; the
|
|
29
|
+
`serve` verb's behavior is unchanged.
|
|
30
|
+
- The control panel's JS and CSS are now served as static files (`bobframes/ui/assets/panel.{js,css}`,
|
|
31
|
+
at `GET /panel.js` / `GET /panel.css`) instead of being embedded in a Python string -- so the client
|
|
32
|
+
is a real file that `node --check`/lint can validate (no build step; still no framework or router).
|
|
33
|
+
|
|
34
|
+
### Tests / CI
|
|
35
|
+
- Added an automated guard that the panel JS parses and runs: a `node --check` gate (a pytest test +
|
|
36
|
+
an unconditional CI step) and an opt-in `browser`-marked headless smoke that drives the live panel in
|
|
37
|
+
Chrome and asserts it populates -- closing the gap that let a broken `<script>` ship undetected.
|
|
38
|
+
|
|
10
39
|
## [0.2.7] - 2026-06-24
|
|
11
40
|
|
|
12
41
|
An aggregation-consistency pass plus four report/sharing correctness fixes. Every KPI now names its
|
|
@@ -160,7 +189,8 @@ First standalone release. v1 is Windows-only (the replay stage drives `qrenderdo
|
|
|
160
189
|
`python -m _analysis.*` entry points no longer work; switch to the `bobframes` commands (see the
|
|
161
190
|
migration table in the README). This is a hard rename with no compatibility shim.
|
|
162
191
|
|
|
163
|
-
[Unreleased]: https://github.com/altpsyche/bobframes/compare/v0.2.
|
|
192
|
+
[Unreleased]: https://github.com/altpsyche/bobframes/compare/v0.2.8...HEAD
|
|
193
|
+
[0.2.8]: https://github.com/altpsyche/bobframes/compare/v0.2.7...v0.2.8
|
|
164
194
|
[0.2.7]: https://github.com/altpsyche/bobframes/compare/v0.2.6...v0.2.7
|
|
165
195
|
[0.2.6]: https://github.com/altpsyche/bobframes/compare/v0.2.0...v0.2.6
|
|
166
196
|
[0.2.0]: https://github.com/altpsyche/bobframes/compare/v0.1.0...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bobframes
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: RenderDoc capture pipeline: ingest, analyze, render.
|
|
5
5
|
Project-URL: Homepage, https://github.com/altpsyche/bobframes
|
|
6
6
|
Project-URL: Issues, https://github.com/altpsyche/bobframes/issues
|
|
@@ -55,6 +55,21 @@ bobframes check
|
|
|
55
55
|
`bobframes check` prints the resolved paths for `renderdoccmd` and `qrenderdoc` and exits non-zero if
|
|
56
56
|
either is missing, so you can confirm the toolchain before a long ingest.
|
|
57
57
|
|
|
58
|
+
## Guided mode (recommended for QA / product)
|
|
59
|
+
|
|
60
|
+
Not comfortable in a terminal? `bobframes ui` opens a local-web control panel in your browser that
|
|
61
|
+
drives the whole pipeline point-and-click: detect the RenderDoc tools, pick a capture folder, ingest
|
|
62
|
+
with live per-capture progress, then open / serve / package the report or compare two runs.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pipx install bobframes
|
|
66
|
+
bobframes ui # opens http://127.0.0.1:8765 in your browser
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The panel binds `127.0.0.1` only and carries a one-time session token in the opened URL, so only your
|
|
70
|
+
own browser can drive it. It is stdlib-only -- one `pipx install` gives every feature, no extra setup --
|
|
71
|
+
and it emits no report files of its own; it just runs the same verbs as the CLI.
|
|
72
|
+
|
|
58
73
|
## Quickstart
|
|
59
74
|
|
|
60
75
|
```
|
|
@@ -78,6 +93,7 @@ time to rebuild the HTML from existing Parquet without re-replaying captures.
|
|
|
78
93
|
| `lint <file>...` | Check HTML or markdown against the banlist. |
|
|
79
94
|
| `check` | Print resolved tool paths; non-zero when a tool is missing. |
|
|
80
95
|
| `serve [root] [--port 8000] [--bind 127.0.0.1]` | Static preview via the stdlib HTTP server. |
|
|
96
|
+
| `ui [root] [--port 8765] [--bind 127.0.0.1] [--no-open]` | Guided local-web control panel (ADR-47): ingest / render / package / A-B / open / serve / scaffold from a browser, with live progress. Localhost-bound + per-session token; emits no report output of its own. |
|
|
81
97
|
| `package [root] [--inline] [--light] [--redact] [--redact-paths {strip,fail}] [--out PATH] [--run KEY] [--no-summary-file] [--stage]` | Bundle a rendered tree into a shareable `<project>-<rundate>-report.zip` + a standalone `<project>-<rundate>-summary.html`, both written OUTSIDE `<root>` (non-mutating). `--redact` scrubs device/host provenance + absolute paths for external sharing. |
|
|
82
98
|
| `preview [root] [--accent OKLCH] [--accent-data OKLCH]` | Render the chrome gallery to `_reports/_chrome_preview.html`; no capture data needed. `--accent` previews a theme override before you commit it. |
|
|
83
99
|
| `export-tokens [--format toml\|json\|css] [--theme-template]` | Print the design tokens to stdout in the chosen format. `--theme-template` emits a paste-ready `[theme]` block for `.bobframes.toml`. |
|
|
@@ -24,6 +24,21 @@ bobframes check
|
|
|
24
24
|
`bobframes check` prints the resolved paths for `renderdoccmd` and `qrenderdoc` and exits non-zero if
|
|
25
25
|
either is missing, so you can confirm the toolchain before a long ingest.
|
|
26
26
|
|
|
27
|
+
## Guided mode (recommended for QA / product)
|
|
28
|
+
|
|
29
|
+
Not comfortable in a terminal? `bobframes ui` opens a local-web control panel in your browser that
|
|
30
|
+
drives the whole pipeline point-and-click: detect the RenderDoc tools, pick a capture folder, ingest
|
|
31
|
+
with live per-capture progress, then open / serve / package the report or compare two runs.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
pipx install bobframes
|
|
35
|
+
bobframes ui # opens http://127.0.0.1:8765 in your browser
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The panel binds `127.0.0.1` only and carries a one-time session token in the opened URL, so only your
|
|
39
|
+
own browser can drive it. It is stdlib-only -- one `pipx install` gives every feature, no extra setup --
|
|
40
|
+
and it emits no report files of its own; it just runs the same verbs as the CLI.
|
|
41
|
+
|
|
27
42
|
## Quickstart
|
|
28
43
|
|
|
29
44
|
```
|
|
@@ -47,6 +62,7 @@ time to rebuild the HTML from existing Parquet without re-replaying captures.
|
|
|
47
62
|
| `lint <file>...` | Check HTML or markdown against the banlist. |
|
|
48
63
|
| `check` | Print resolved tool paths; non-zero when a tool is missing. |
|
|
49
64
|
| `serve [root] [--port 8000] [--bind 127.0.0.1]` | Static preview via the stdlib HTTP server. |
|
|
65
|
+
| `ui [root] [--port 8765] [--bind 127.0.0.1] [--no-open]` | Guided local-web control panel (ADR-47): ingest / render / package / A-B / open / serve / scaffold from a browser, with live progress. Localhost-bound + per-session token; emits no report output of its own. |
|
|
50
66
|
| `package [root] [--inline] [--light] [--redact] [--redact-paths {strip,fail}] [--out PATH] [--run KEY] [--no-summary-file] [--stage]` | Bundle a rendered tree into a shareable `<project>-<rundate>-report.zip` + a standalone `<project>-<rundate>-summary.html`, both written OUTSIDE `<root>` (non-mutating). `--redact` scrubs device/host provenance + absolute paths for external sharing. |
|
|
51
67
|
| `preview [root] [--accent OKLCH] [--accent-data OKLCH]` | Render the chrome gallery to `_reports/_chrome_preview.html`; no capture data needed. `--accent` previews a theme override before you commit it. |
|
|
52
68
|
| `export-tokens [--format toml\|json\|css] [--theme-template]` | Print the design tokens to stdout in the chosen format. `--theme-template` emits a paste-ready `[theme]` block for `.bobframes.toml`. |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.8"
|
|
@@ -251,20 +251,19 @@ def _cmd_check(args: argparse.Namespace) -> int:
|
|
|
251
251
|
|
|
252
252
|
|
|
253
253
|
def _cmd_serve(args: argparse.Namespace) -> int:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
import
|
|
254
|
+
# The static preview server body lives in serve.serve_forever so the `ui` panel can reuse the
|
|
255
|
+
# same builder (serve.make_server) for its background click-to-serve (v028_3).
|
|
256
|
+
from . import serve
|
|
257
|
+
return serve.serve_forever(os.path.abspath(args.root), bind=args.bind, port=args.port)
|
|
257
258
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return 4
|
|
267
|
-
return 0
|
|
259
|
+
|
|
260
|
+
def _cmd_ui(args: argparse.Namespace) -> int:
|
|
261
|
+
# ADR-47: a zero-dependency local-web control panel that DRIVES the verbs for non-terminal users.
|
|
262
|
+
# A frontend SURFACE above the verb taxonomy -- it emits no report HTML (golden gate untouched) and
|
|
263
|
+
# pulls no dep into core. Lazy-imported so a core install never loads the ui package.
|
|
264
|
+
from .ui import server
|
|
265
|
+
return server.serve(os.path.abspath(args.root), bind=args.bind, port=args.port,
|
|
266
|
+
open_browser=not args.no_open)
|
|
268
267
|
|
|
269
268
|
|
|
270
269
|
def _cmd_package(args: argparse.Namespace) -> int:
|
|
@@ -380,6 +379,16 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
380
379
|
sp.add_argument('--bind', default='127.0.0.1')
|
|
381
380
|
sp.set_defaults(func=_cmd_serve)
|
|
382
381
|
|
|
382
|
+
sp = sub.add_parser('ui', parents=[common],
|
|
383
|
+
help='guided local-web control panel (ingest/generate/package in a browser)')
|
|
384
|
+
sp.add_argument('root', nargs='?', default='.')
|
|
385
|
+
sp.add_argument('--port', type=int, default=8765)
|
|
386
|
+
sp.add_argument('--bind', default='127.0.0.1',
|
|
387
|
+
help='bind address; localhost only by design (ADR-47)')
|
|
388
|
+
sp.add_argument('--no-open', action='store_true',
|
|
389
|
+
help="don't auto-open the browser")
|
|
390
|
+
sp.set_defaults(func=_cmd_ui)
|
|
391
|
+
|
|
383
392
|
sp = sub.add_parser('package', parents=[common],
|
|
384
393
|
help='bundle a rendered tree into a shareable zip + standalone summary')
|
|
385
394
|
sp.add_argument('root', nargs='?', default='.')
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Static preview file server (extracted from ``cli._cmd_serve`` for reuse by ``bobframes ui``).
|
|
2
|
+
|
|
3
|
+
A stdlib ``http.server`` serving ``<root>`` read-only over localhost. ``make_server`` builds (does not
|
|
4
|
+
start) the server; ``serve_forever`` builds it and blocks until Ctrl+C -- the body of the ``serve`` verb.
|
|
5
|
+
The ``ui`` panel reuses ``make_server`` to run the static server in a background daemon thread alongside
|
|
6
|
+
the control panel (so a click-to-serve returns a URL instead of blocking the request handler).
|
|
7
|
+
|
|
8
|
+
No new dependency; behavior is identical to the prior inlined ``_cmd_serve`` (same ``socketserver``
|
|
9
|
+
TCPServer + ``SimpleHTTPRequestHandler(directory=root)`` + ``[HH:MM:SS]`` log line + exit codes).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import functools
|
|
14
|
+
import http.server
|
|
15
|
+
import logging
|
|
16
|
+
import socketserver
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger('bobframes')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_server(root: str, *, bind: str = '127.0.0.1', port: int = 8000) -> socketserver.TCPServer:
|
|
22
|
+
"""Build (do not start) a static file server over ``<root>``. ``port=0`` binds an ephemeral port
|
|
23
|
+
(read it back from ``server.server_address[1]``); used by the panel's background serve."""
|
|
24
|
+
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=root)
|
|
25
|
+
return socketserver.TCPServer((bind, port), handler)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def serve_forever(root: str, *, bind: str = '127.0.0.1', port: int = 8000) -> int:
|
|
29
|
+
"""Serve ``<root>`` and block until Ctrl+C. Returns a CLI exit code (0 clean, 4 interrupted)."""
|
|
30
|
+
try:
|
|
31
|
+
with make_server(root, bind=bind, port=port) as httpd:
|
|
32
|
+
log.info(f'serving {root} at http://{bind}:{port} (Ctrl+C to stop)')
|
|
33
|
+
httpd.serve_forever()
|
|
34
|
+
except KeyboardInterrupt:
|
|
35
|
+
return 4
|
|
36
|
+
return 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Shared helpers for the `bobframes ui` panel tests (v028_1).
|
|
2
|
+
|
|
3
|
+
Starts the stdlib panel server on an ephemeral port in a daemon thread and hits it with `urllib`,
|
|
4
|
+
so the tests exercise the real HTTP surface (token gating, JSON shape) without a browser or GPU.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import contextlib
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import threading
|
|
12
|
+
import urllib.request
|
|
13
|
+
|
|
14
|
+
from ..ui import server as _server
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_capture_root(tmp_path) -> str:
|
|
18
|
+
"""A tmp project root with the `<Area>/<YYYY-MM-DD[_label]>/*.rdc` convention (mirrors
|
|
19
|
+
test_discovery): Town has the newest dated drop with 3 captures; Bay a single undated-label drop."""
|
|
20
|
+
root = str(tmp_path / 'proj')
|
|
21
|
+
layout = (('Town', '2026-05-27_old', ('1',)),
|
|
22
|
+
('Town', '2026-05-28_new', ('1', '2', '10')),
|
|
23
|
+
('Bay', '2026-01-01', ('cap',)))
|
|
24
|
+
for area, key, caps in layout:
|
|
25
|
+
for cap in caps:
|
|
26
|
+
p = os.path.join(root, area, key, f'{cap}.rdc')
|
|
27
|
+
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
28
|
+
open(p, 'w', encoding='utf-8').close()
|
|
29
|
+
return root
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextlib.contextmanager
|
|
33
|
+
def running(root: str):
|
|
34
|
+
"""Run the panel server on an ephemeral port; yield (httpd, port)."""
|
|
35
|
+
httpd = _server.build_server(root, port=0)
|
|
36
|
+
t = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
37
|
+
t.start()
|
|
38
|
+
try:
|
|
39
|
+
yield httpd, httpd.server_address[1]
|
|
40
|
+
finally:
|
|
41
|
+
_server._shutdown_aux(httpd) # stop the background static serve if /api/serve started one
|
|
42
|
+
httpd.shutdown()
|
|
43
|
+
t.join(timeout=5)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get(port: int, path: str, headers: dict | None = None):
|
|
47
|
+
"""GET http://127.0.0.1:<port><path>; returns the response (raises HTTPError on >=400)."""
|
|
48
|
+
req = urllib.request.Request(f'http://127.0.0.1:{port}{path}', headers=headers or {})
|
|
49
|
+
return urllib.request.urlopen(req, timeout=5)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def post(port: int, path: str, token: str, body: dict):
|
|
53
|
+
"""POST JSON with the session token; returns the response (raises HTTPError on >=400)."""
|
|
54
|
+
req = urllib.request.Request(
|
|
55
|
+
f'http://127.0.0.1:{port}{path}', data=json.dumps(body).encode('utf-8'), method='POST',
|
|
56
|
+
headers={'Content-Type': 'application/json', 'X-Bobframes-Token': token})
|
|
57
|
+
return urllib.request.urlopen(req, timeout=5)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""v028_4: A/B comparison + accent theming + the opt-in capture-folder scaffold.
|
|
2
|
+
|
|
3
|
+
A/B and the accent re-render are streamed subprocess jobs mocked at the spawn seam (no GPU/RenderDoc --
|
|
4
|
+
ADR-6): `jobs.spawn` (render reuses `python -m bobframes.run`) and `jobs.spawn_cli` (`bobframes ab`)
|
|
5
|
+
are monkeypatched with a fake process. The run list backing the picker is asserted via `/api/state`
|
|
6
|
+
(`discovery.discover_drops` monkeypatched). Scaffold creates a real folder under a tmp root.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import urllib.error
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from ..ui import jobs as _jobs
|
|
17
|
+
from ._ui_util import get, post, running
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _FakeProc:
|
|
21
|
+
def __init__(self, lines, rc=0):
|
|
22
|
+
self.stdout = iter(lines)
|
|
23
|
+
self.returncode = None
|
|
24
|
+
self._rc = rc
|
|
25
|
+
|
|
26
|
+
def wait(self):
|
|
27
|
+
self.returncode = self._rc
|
|
28
|
+
return self._rc
|
|
29
|
+
|
|
30
|
+
def poll(self):
|
|
31
|
+
return self.returncode
|
|
32
|
+
|
|
33
|
+
def terminate(self):
|
|
34
|
+
self.returncode = -15
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _FakeRun:
|
|
38
|
+
def __init__(self, key, label, date, n):
|
|
39
|
+
self.key, self.label, self.date, self.n_captures = key, label, date, n
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- argv builders ------------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def test_build_render_argv_threads_accent():
|
|
45
|
+
argv = _jobs.build_render_argv('/proj', accent='oklch(0.5 0.2 250)', accent_data='red')
|
|
46
|
+
assert '--render-only' in argv
|
|
47
|
+
assert argv[argv.index('--accent') + 1] == 'oklch(0.5 0.2 250)'
|
|
48
|
+
assert argv[argv.index('--accent-data') + 1] == 'red'
|
|
49
|
+
plain = _jobs.build_render_argv('/proj')
|
|
50
|
+
assert '--accent' not in plain and '--accent-data' not in plain and '--render-only' in plain
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_build_ab_argv_mirrors_ab_verb():
|
|
54
|
+
argv = _jobs.build_ab_argv('/proj', baseline_label='r1', compare_label='r2',
|
|
55
|
+
baseline_date='2026-05-27', compare_date='2026-05-28')
|
|
56
|
+
assert argv[0] == 'ab'
|
|
57
|
+
assert argv[argv.index('--baseline-label') + 1] == 'r1'
|
|
58
|
+
assert argv[argv.index('--compare-label') + 1] == 'r2'
|
|
59
|
+
assert argv[argv.index('--baseline-date') + 1] == '2026-05-27'
|
|
60
|
+
assert argv[argv.index('--compare-date') + 1] == '2026-05-28'
|
|
61
|
+
bare = _jobs.build_ab_argv('/proj', baseline_label='r1', compare_label='r2')
|
|
62
|
+
assert '--baseline-date' not in bare and '--compare-date' not in bare
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# --- /api/render accent -------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
def test_render_threads_accent_into_spawn(tmp_path, monkeypatch):
|
|
68
|
+
seen = {}
|
|
69
|
+
|
|
70
|
+
def fake_spawn(argv):
|
|
71
|
+
seen['argv'] = argv
|
|
72
|
+
return _FakeProc(["[10:00:00] render-only done"])
|
|
73
|
+
monkeypatch.setattr(_jobs, 'spawn', fake_spawn)
|
|
74
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
75
|
+
jid = json.loads(post(port, '/api/render', httpd.bobframes_token,
|
|
76
|
+
{'accent': 'oklch(0.6 0.2 30)', 'accent_data': ''}).read())['job']
|
|
77
|
+
stream = get(port, f'/api/stream/{jid}?t={httpd.bobframes_token}').read().decode('utf-8')
|
|
78
|
+
assert '--render-only' in seen['argv']
|
|
79
|
+
assert seen['argv'][seen['argv'].index('--accent') + 1] == 'oklch(0.6 0.2 30)'
|
|
80
|
+
assert '--accent-data' not in seen['argv'] # blank field -> flag omitted
|
|
81
|
+
assert '"rc": 0' in stream
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# --- /api/ab ------------------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def test_ab_spawns_cli_and_streams(tmp_path, monkeypatch):
|
|
87
|
+
seen = {}
|
|
88
|
+
|
|
89
|
+
def fake_spawn_cli(argv):
|
|
90
|
+
seen['argv'] = argv
|
|
91
|
+
return _FakeProc(["[10:00:00] a/b: 2026-05-27_r1 (2 captures) vs 2026-05-28_r2 (3 captures)"])
|
|
92
|
+
monkeypatch.setattr(_jobs, 'spawn_cli', fake_spawn_cli)
|
|
93
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
94
|
+
body = {'baseline_label': 'r1', 'baseline_date': '2026-05-27',
|
|
95
|
+
'compare_label': 'r2', 'compare_date': '2026-05-28'}
|
|
96
|
+
jid = json.loads(post(port, '/api/ab', httpd.bobframes_token, body).read())['job']
|
|
97
|
+
stream = get(port, f'/api/stream/{jid}?t={httpd.bobframes_token}').read().decode('utf-8')
|
|
98
|
+
assert seen['argv'][0] == 'ab'
|
|
99
|
+
assert seen['argv'][seen['argv'].index('--baseline-label') + 1] == 'r1'
|
|
100
|
+
assert seen['argv'][seen['argv'].index('--compare-label') + 1] == 'r2'
|
|
101
|
+
assert 'a/b:' in stream and '"rc": 0' in stream
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_ab_requires_both_runs(tmp_path):
|
|
105
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
106
|
+
with pytest.raises(urllib.error.HTTPError) as e:
|
|
107
|
+
post(port, '/api/ab', httpd.bobframes_token, {'baseline_label': 'r1'}) # no compare
|
|
108
|
+
assert e.value.code == 400
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# --- /api/state runs (the picker source) --------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def test_state_lists_rendered_runs(tmp_path, monkeypatch):
|
|
114
|
+
from ..reports import discovery as rdisc
|
|
115
|
+
runs = [_FakeRun('2026-05-27_r1', 'r1', '2026-05-27', 2),
|
|
116
|
+
_FakeRun('2026-05-28_r2', 'r2', '2026-05-28', 3)]
|
|
117
|
+
monkeypatch.setattr(rdisc, 'discover_drops', lambda root: runs)
|
|
118
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
119
|
+
s = json.load(get(port, '/api/state?t=' + httpd.bobframes_token))
|
|
120
|
+
assert [r['key'] for r in s['runs']] == ['2026-05-27_r1', '2026-05-28_r2']
|
|
121
|
+
assert s['runs'][1]['n_captures'] == 3
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_state_runs_empty_without_catalog(tmp_path):
|
|
125
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
126
|
+
s = json.load(get(port, '/api/state?t=' + httpd.bobframes_token))
|
|
127
|
+
assert s['runs'] == [] # no catalog yet -> no runs
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --- /api/scaffold ------------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def test_scaffold_creates_convention_folder(tmp_path):
|
|
133
|
+
root = str(tmp_path / 'proj')
|
|
134
|
+
os.makedirs(root)
|
|
135
|
+
with running(root) as (httpd, port):
|
|
136
|
+
r = json.loads(post(port, '/api/scaffold', httpd.bobframes_token,
|
|
137
|
+
{'area': 'Town', 'date': '2026-06-24', 'label': 'r110600'}).read())
|
|
138
|
+
assert r['created'] is True
|
|
139
|
+
assert os.path.basename(r['path']) == '2026-06-24_r110600'
|
|
140
|
+
assert os.path.isdir(os.path.join(root, 'Town', '2026-06-24_r110600'))
|
|
141
|
+
# idempotent: a second call reports already-exists.
|
|
142
|
+
again = json.loads(post(port, '/api/scaffold', httpd.bobframes_token,
|
|
143
|
+
{'area': 'Town', 'date': '2026-06-24', 'label': 'r110600'}).read())
|
|
144
|
+
assert again['created'] is False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_scaffold_rejects_bad_date_and_traversal(tmp_path):
|
|
148
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
149
|
+
with pytest.raises(urllib.error.HTTPError) as bad_date:
|
|
150
|
+
post(port, '/api/scaffold', httpd.bobframes_token, {'area': 'Town', 'date': 'June 24'})
|
|
151
|
+
assert bad_date.value.code == 400
|
|
152
|
+
with pytest.raises(urllib.error.HTTPError) as traversal:
|
|
153
|
+
post(port, '/api/scaffold', httpd.bobframes_token, {'area': '../escape', 'date': '2026-06-24'})
|
|
154
|
+
assert traversal.value.code == 400
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_ab_and_scaffold_require_token(tmp_path):
|
|
158
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
159
|
+
for path in ('/api/ab', '/api/scaffold'):
|
|
160
|
+
with pytest.raises(urllib.error.HTTPError) as e:
|
|
161
|
+
post(port, path, 'wrong-token', {})
|
|
162
|
+
assert e.value.code == 403, path
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""v028_7: opt-in headless-browser smoke that the panel JS actually RUNS and populates state.
|
|
2
|
+
|
|
3
|
+
Marked ``browser`` -- DESELECTED by the default suite (``-m "not browser"``) and only run on demand
|
|
4
|
+
(``pytest -m browser``) where a local Chrome exists, mirroring ``test_browser_shots.py`` (ADR-43 gate-d).
|
|
5
|
+
This is the gate the v028_6 bug needed and node --check cannot give: ``node --check`` proves the script
|
|
6
|
+
PARSES; this proves it RUNS. Chrome loads the LIVE panel over http (token in the URL), the page's
|
|
7
|
+
``loadState()`` fetches ``/api/state``, and we assert the DOM populated (root path + tool rows + drops
|
|
8
|
+
table) -- i.e. the script parsed AND executed AND wired the fetch->render path.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import pathlib
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from ._ui_util import make_capture_root, running
|
|
18
|
+
|
|
19
|
+
_TOOLS = pathlib.Path(__file__).resolve().parents[2] / 'tools'
|
|
20
|
+
sys.path.insert(0, str(_TOOLS))
|
|
21
|
+
import shoot # noqa: E402 (path injected above; dev-only CDP harness, never shipped)
|
|
22
|
+
|
|
23
|
+
pytestmark = pytest.mark.browser
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _eval(chrome, expr, *, await_promise=False):
|
|
27
|
+
"""Runtime.evaluate in the attached page; returns the by-value result. Raises on a JS exception
|
|
28
|
+
(a thrown panel script is a failure, not a None)."""
|
|
29
|
+
r = chrome.cdp.call('Runtime.evaluate',
|
|
30
|
+
{'expression': expr, 'returnByValue': True, 'awaitPromise': await_promise},
|
|
31
|
+
session=chrome.session)
|
|
32
|
+
if 'exceptionDetails' in r:
|
|
33
|
+
raise AssertionError(f'panel JS threw: {r["exceptionDetails"]}')
|
|
34
|
+
return r['result'].get('value')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# An awaited promise that resolves once render() has replaced the "Loading..." placeholder (loadState's
|
|
38
|
+
# fetch is async, so it completes AFTER Page.loadEventFired). Times out to a {timeout:true} marker so a
|
|
39
|
+
# dead script fails the assert instead of hanging.
|
|
40
|
+
_WAIT_POPULATED = """
|
|
41
|
+
new Promise(function(resolve){
|
|
42
|
+
var waited = 0, step = 50, deadline = 5000;
|
|
43
|
+
var timer = setInterval(function(){
|
|
44
|
+
var root = document.getElementById('root');
|
|
45
|
+
var ready = root && root.textContent.indexOf('Loading') === -1;
|
|
46
|
+
if (ready) {
|
|
47
|
+
clearInterval(timer);
|
|
48
|
+
resolve({
|
|
49
|
+
root: root.textContent,
|
|
50
|
+
tools: document.getElementById('tools').innerHTML,
|
|
51
|
+
drops: document.getElementById('drops').innerHTML
|
|
52
|
+
});
|
|
53
|
+
} else if ((waited += step) >= deadline) {
|
|
54
|
+
clearInterval(timer);
|
|
55
|
+
resolve({timeout: true, root: root ? root.textContent : null});
|
|
56
|
+
}
|
|
57
|
+
}, step);
|
|
58
|
+
})
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_panel_js_runs_and_populates_state(tmp_path):
|
|
63
|
+
if not shoot.find_chrome():
|
|
64
|
+
pytest.skip('Chrome not found')
|
|
65
|
+
root = make_capture_root(tmp_path) # Town + Bay drops with .rdc files
|
|
66
|
+
with running(root) as (httpd, port):
|
|
67
|
+
url = f'http://127.0.0.1:{port}/?t={httpd.bobframes_token}'
|
|
68
|
+
with shoot.Chrome() as chrome:
|
|
69
|
+
s = chrome.session
|
|
70
|
+
chrome.cdp.call('Page.navigate', {'url': url}, session=s)
|
|
71
|
+
chrome.cdp.wait_event('Page.loadEventFired', session=s)
|
|
72
|
+
state = _eval(chrome, _WAIT_POPULATED, await_promise=True)
|
|
73
|
+
|
|
74
|
+
assert not state.get('timeout'), f'panel never populated -- the JS did not run: {state!r}'
|
|
75
|
+
# The script parsed, ran, fetched /api/state, and render() filled the DOM:
|
|
76
|
+
assert 'Loading' not in state['root'] and 'proj' in state['root'], state['root']
|
|
77
|
+
assert 'renderdoccmd' in state['tools'], 'tool rows did not render'
|
|
78
|
+
assert 'Town' in state['drops'] or 'Bay' in state['drops'], 'drops table did not render'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""v028_2: the subprocess job runner + SSE (mocked spawn -- no GPU/RenderDoc, the ADR-6 discipline).
|
|
2
|
+
|
|
3
|
+
`jobs.spawn` is monkeypatched with a fake process that emits a scripted stdout transcript; the test
|
|
4
|
+
asserts POST /api/ingest starts a job and GET /api/stream/<job> relays the classified lines + a final
|
|
5
|
+
`done` event carrying the return code.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import urllib.error
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from ..ui import jobs as _jobs
|
|
15
|
+
from ._ui_util import get, post, running
|
|
16
|
+
|
|
17
|
+
_SCRIPT = [
|
|
18
|
+
"[10:00:00] pipeline: 1 drop(s); root=.",
|
|
19
|
+
"[10:00:00] replay: 2 captures (sequential)",
|
|
20
|
+
"[10:00:10] 1: rc=0 10.0s",
|
|
21
|
+
"[10:00:20] 2: rc=0 10.0s",
|
|
22
|
+
"[10:00:21] merge + parquetize",
|
|
23
|
+
"[10:00:22] pipeline done: 1 drops processed",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _FakeProc:
|
|
28
|
+
def __init__(self, lines, rc=0):
|
|
29
|
+
self.stdout = iter(lines)
|
|
30
|
+
self.returncode = None
|
|
31
|
+
self._rc = rc
|
|
32
|
+
|
|
33
|
+
def wait(self):
|
|
34
|
+
self.returncode = self._rc
|
|
35
|
+
return self._rc
|
|
36
|
+
|
|
37
|
+
def poll(self):
|
|
38
|
+
return self.returncode
|
|
39
|
+
|
|
40
|
+
def terminate(self):
|
|
41
|
+
self.returncode = -15
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_build_run_argv_mirrors_ingest():
|
|
45
|
+
argv = _jobs.build_run_argv('/proj', force=True, render_only=False, workers=8, pixel_grid=2)
|
|
46
|
+
assert '--root' in argv and '--force' in argv
|
|
47
|
+
assert argv[argv.index('--workers') + 1] == '8'
|
|
48
|
+
assert argv[argv.index('--pixel-grid') + 1] == '2'
|
|
49
|
+
assert '--render-only' not in argv
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_ingest_streams_stdout_and_return_code(tmp_path, monkeypatch):
|
|
53
|
+
monkeypatch.setattr(_jobs, 'spawn', lambda argv: _FakeProc(list(_SCRIPT), rc=0))
|
|
54
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
55
|
+
started = json.loads(post(port, '/api/ingest', httpd.bobframes_token, {'render_only': True}).read())
|
|
56
|
+
assert 'job' in started
|
|
57
|
+
stream = get(port, f"/api/stream/{started['job']}?t={httpd.bobframes_token}").read().decode('utf-8')
|
|
58
|
+
assert 'replay: 2 captures' in stream # raw line relayed verbatim
|
|
59
|
+
assert '"replay_total": 2' in stream and '"replay_done": 2' in stream
|
|
60
|
+
assert 'event: done' in stream and '"rc": 0' in stream
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_ingest_reports_nonzero_rc(tmp_path, monkeypatch):
|
|
64
|
+
monkeypatch.setattr(_jobs, 'spawn', lambda argv: _FakeProc(['[10:00:00] boom'], rc=1))
|
|
65
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
66
|
+
jid = json.loads(post(port, '/api/ingest', httpd.bobframes_token, {}).read())['job']
|
|
67
|
+
stream = get(port, f'/api/stream/{jid}?t={httpd.bobframes_token}').read().decode('utf-8')
|
|
68
|
+
assert 'event: done' in stream and '"rc": 1' in stream
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_ingest_requires_token(tmp_path):
|
|
72
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
73
|
+
with pytest.raises(urllib.error.HTTPError) as e:
|
|
74
|
+
post(port, '/api/ingest', 'wrong-token', {})
|
|
75
|
+
assert e.value.code == 403
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_stream_unknown_job_404(tmp_path):
|
|
79
|
+
with running(str(tmp_path)) as (httpd, port):
|
|
80
|
+
with pytest.raises(urllib.error.HTTPError) as e:
|
|
81
|
+
get(port, f'/api/stream/nope?t={httpd.bobframes_token}')
|
|
82
|
+
assert e.value.code == 404
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""v028_7/8: the panel JS must PARSE -- an automated gate that runs the parser (the v028_6 lesson).
|
|
2
|
+
|
|
3
|
+
pytest exercises the panel's HTTP surface in Python but never parses or executes the panel JS; the
|
|
4
|
+
v028_2 bug (a normal triple-quoted page literal turned the embedded JS ``"\\n"`` into a REAL newline
|
|
5
|
+
mid-string-literal -> the WHOLE script was a syntax error -> nothing populated) shipped undetected
|
|
6
|
+
through five commits for exactly that reason. v028_8 externalized the JS to
|
|
7
|
+
``bobframes/ui/assets/panel.js`` (a real file -- the embedding bug class is now impossible), and this
|
|
8
|
+
runs ``node --check`` on it (syntax-only, so browser globals like ``document`` / ``fetch`` /
|
|
9
|
+
``EventSource`` are fine -- it parses, it does not resolve).
|
|
10
|
+
|
|
11
|
+
Skips when node is absent locally; CI (`windows-latest` ships node) also runs an UNCONDITIONAL
|
|
12
|
+
``node --check bobframes/ui/assets/panel.js`` step + ``node --version``, so the gate can never silently
|
|
13
|
+
skip everywhere (ADR-23).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import pathlib
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
|
|
21
|
+
import pytest
|
|
22
|
+
|
|
23
|
+
from ..ui import server as _server
|
|
24
|
+
|
|
25
|
+
_PANEL_JS = pathlib.Path(_server.__file__).parent / 'assets' / 'panel.js'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_panel_js_parses_with_node():
|
|
29
|
+
node = shutil.which('node')
|
|
30
|
+
if node is None:
|
|
31
|
+
pytest.skip('node not installed (CI runs an unconditional node --check step)')
|
|
32
|
+
assert _PANEL_JS.exists(), f'missing {_PANEL_JS}'
|
|
33
|
+
proc = subprocess.run([node, '--check', str(_PANEL_JS)], capture_output=True, text=True)
|
|
34
|
+
assert proc.returncode == 0, f'node --check rejected panel.js:\n{proc.stdout}\n{proc.stderr}'
|