hyperweave 0.2.6__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.
- {hyperweave-0.2.6 → hyperweave-0.2.8}/CHANGELOG.md +43 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/PKG-INFO +1 -1
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/_version.py +2 -2
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/context.py +11 -7
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/resolver.py +53 -61
- hyperweave-0.2.8/src/hyperweave/compose/rhythm.py +109 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/config/settings.py +5 -1
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/base.py +63 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/github.py +197 -32
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/state.py +6 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/mcp/server.py +22 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/chart_engine.py +172 -194
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/contract.py +1 -1
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-area.svg.j2 +11 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-axes.svg.j2 +2 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-empty-state.svg.j2 +8 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-gridlines.svg.j2 +2 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-markers/circle.svg.j2 +2 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-markers/diamond.svg.j2 +7 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-markers/endpoint-diamond.svg.j2 +14 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-markers/endpoint-rect.svg.j2 +12 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-markers/rect.svg.j2 +12 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-milestone.svg.j2 +7 -0
- hyperweave-0.2.8/src/hyperweave/templates/components/chart-polyline.svg.j2 +10 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/treemap.svg.j2 +2 -2
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/chart/brutalist-content.j2 +28 -9
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/chart/chrome-content.j2 +27 -8
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/receipt.svg.j2 +7 -1
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_chart_engine.py +131 -50
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_chart_frame.py +5 -5
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_connectors.py +503 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_mcp.py +23 -0
- hyperweave-0.2.8/tests/test_resolver_rhythm.py +158 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_state_inference.py +22 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_telemetry.py +3 -3
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_telemetry_integration.py +166 -2
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.dockerignore +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.github/workflows/ci.yml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.github/workflows/deploy.yml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.github/workflows/publish.yml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.gitignore +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/.pre-commit-config.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/Dockerfile +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/LICENSE +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/README.md +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/buttons/button-liquid.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/cards/card-butterfly.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/cards/card-python.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/cards/card-sunflower.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/cards/card-waves.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/badge_critical.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/badge_passing.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/badge_warning.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/banner.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/icons/discord.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/icons/notion.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/icons/reddit.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/icons/spotify.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/icons/youtube.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/marquee_counter.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/marquee_horizontal.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/marquee_vertical.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/profile-cards/chart_stars_full.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/profile-cards/stats.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/brutalist-emerald/strip.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/badge_critical.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/badge_passing.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/badge_warning.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/banner.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/icons/bluesky.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/icons/github.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/icons/instagram.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/icons/mastodon.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/icons/x.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/marquee_counter.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/marquee_horizontal.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/marquee_vertical.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/profile-cards/chart_stars_full.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/profile-cards/stats.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/chrome-horizon/strip.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/telemetry/master_card.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/telemetry/receipt.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/examples/telemetry/rhythm_strip.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/footers/inneraura-footer-liquid.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/footers/inneraura-footer-purple.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/footers/inneraura-footer.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/hyperweave-banner.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-discord.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-docs.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-github.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-instagram.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-linkedin.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-tiktok.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-x.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/icons/cobalt-sapphire-youtube.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/marquees/badge-showcase-triple.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/marquees/genome-marquee-triple.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/marquees/sample-badges.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/404.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/callout-icons.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/divider.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/favicon.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/hero-banner.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/hyperweave-banner-v2.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/hyperweave-navbar-logo.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/loader.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/mintlify-assets/og-image.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/assets/timelines/hyperweave-roadmap.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/assets/favicon.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/assets/hyperweave-banner.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/assets/hyperweave-navbar-logo.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/assets/og-image.svg +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/explanation/architecture.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/explanation/camo-compatibility.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/explanation/cim-compliance.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/explanation/genome-profile-system.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/explanation/metadata-tiers.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/how-to/add-motion-to-badges.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/how-to/create-session-receipts.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/how-to/set-up-claude-code-hooks.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/how-to/use-live-data-badges.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/introduction.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/mint.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/quickstart.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/cli.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/compose-spec.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/genomes.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/http-api.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/mcp-tools.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/motions.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/reference/telemetry-contract.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/spec/hyperweave-protocol.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/tutorials/build-an-artifact-kit.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/tutorials/compose-your-first-badge.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/docs/tutorials/install-session-telemetry.mdx +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/fly.toml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/hooks/install.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/hooks/session_end.sh +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/justfile +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/pyproject.toml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/scripts/extract_font_metrics.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/scripts/extract_glyphs.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/scripts/generate_hw_compliant.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/scripts/generate_proofset.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/scripts/stress_test.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/__main__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/cli.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/assembler.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/engine.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/lanes.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/resolvers/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/resolvers/chart.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/resolvers/stats.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/resolvers/timeline.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/compose/validate_paradigms.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/config/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/config/genome_validator.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/config/loader.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/config/registry.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/arxiv.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/cache.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/connectors/rest.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/color.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/contracts.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/enums.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/font_metrics.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/models.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/paradigm.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/schema.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/text.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/core/thresholds.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/accessibility.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/bridge.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/expression.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/phase-colors.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/status.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/css/telemetry.css +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/font-metrics/inter.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/font-metrics/jetbrains-mono.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/font-metrics/orbitron.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/fonts/jetbrains-mono.b64 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/fonts/jetbrains-mono.meta.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/fonts/orbitron.b64 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/fonts/orbitron.meta.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/genomes/brutalist-emerald.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/genomes/chrome-horizon.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/genomes/telemetry-void.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/glyphs.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/border/chromatic-pulse.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/border/corner-trace.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/border/dual-orbit.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/border/entanglement.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/border/rimrun.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/bars.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/breach.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/broadcast.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/cascade.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/collapse.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/converge.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/crash.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/drop.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/kinetic/pulse.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/motions/static.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/paradigms/brutalist.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/paradigms/chrome.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/paradigms/default.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/policies/normal.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/policies/permissive.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/policies/ungoverned.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/profiles/brutalist.contract.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/profiles/brutalist.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/profiles/chrome.contract.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/profiles/chrome.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/specimens.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/telemetry/model-pricing.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/telemetry/stage-config.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/telemetry/stage-labels.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/telemetry/tool-classes.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/telemetry/tool-colors.yaml +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/arrow.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/aurora.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/beacon.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/bracket3d.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/chevron.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/crosshair.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/diamond.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rocket.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/bar.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/dashed.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/five-wave.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/spectral.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/straight.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/rules/wave.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/data/terminals/stijl.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/kit.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/mcp/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/mcp/__main__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/py.typed +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/fonts.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/glyphs.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/motion.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/render/templates.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/serve/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/serve/app.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/capture.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/corrections.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/cost.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/models.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/parser.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/telemetry/stages.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/filter.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/glyph-inline.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/glyph.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/gradient.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/metadata.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/metric.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/phase-segment.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/rule.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/status.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/components/terminal.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/document.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/error-badge.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/default-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge/default-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/badge.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner/default-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner/default-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/banner.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/catalog.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/chart/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/chart/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/chart.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/divider.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/default-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon/default-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/icon.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-counter/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-counter/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-counter/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-counter/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-counter.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-horizontal/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-horizontal/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-horizontal/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-horizontal.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-vertical/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-vertical/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-vertical/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-vertical/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/marquee-vertical.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/master-card.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/rhythm-strip.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/stats/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/stats/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/stats/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/stats/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/stats.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/brutalist-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/brutalist-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/brutalist-status.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/chrome-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/chrome-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/chrome-status.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/default-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/default-defs.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip/default-status.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/strip.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/timeline/default-content.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/frames/timeline.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/border/chromatic-pulse.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/border/corner-trace.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/border/dual-orbit.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/border/entanglement.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/border/rimrun.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/bars.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/breach.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/broadcast.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/cascade.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/collapse.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/converge.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/crash.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/drop.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/src/hyperweave/templates/motions/kinetic/pulse.svg.j2 +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/__init__.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/conftest.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/fixtures/github_contributions/synthetic.html +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/fixtures/session.json +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/fixtures/session.jsonl +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/helpers.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_assembler.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_core.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_github_scrape.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_kit.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_paradigm_dispatch.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_paradigm_extensibility.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_proofset.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_render.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_serve.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_serve_live_state.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_stats_card.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/tests/test_timeline.py +0 -0
- {hyperweave-0.2.6 → hyperweave-0.2.8}/uv.lock +0 -0
|
@@ -5,6 +5,49 @@ All notable changes to HyperWeave are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.8] - 2026-04-20
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Star history charts now render readably on large repositories.** Previously, repos with more than ~40,000 stars produced a flat-then-vertical "hockey stick" because GitHub's stargazer REST endpoint hard-caps deep pagination, so the only data reachable was a small cluster of early-history stars followed by a single jump to today. Star history now sources from GitHub's GraphQL API and adapts its sampling window to the repo: small repos continue to get full lifetime views, large repos get a detailed recent-growth curve similar to the one star-history.com shows.
|
|
13
|
+
- **Star-count milestone labels no longer stack on top of each other.** When sampled points clustered temporally, the milestone labels (`500`, `1K`, `5K`, `10K`, ...) would render in a single illegible pile on top of the crossing point. Milestones now maintain a minimum 40-pixel horizontal gap; any labels that would overlap with an already-placed one are dropped in favor of the first (lowest-threshold) milestone in the cluster.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **All chart rendering now flows through template partials.** The chart engine's remaining rendering paths — axes, gridlines, polyline, area fill, milestones, and the empty-state overlay — joined the marker layer in Jinja. The engine module returns structured Python data; Jinja partials under `templates/components/` produce the final SVG. No visual change; output is byte-identical for the shipped genomes on unchanged inputs.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`fetch_graphql` connector primitive.** New async helper in the connector layer that handles POST with JSON bodies, GitHub bearer-token rotation, circuit-breaker coordination, and SSRF validation — mirroring the existing `fetch_json` contract. This unlocks future migration of the stats card's six REST sub-fetches to a single GraphQL call, and keeps a single canonical place for authenticated-POST plumbing.
|
|
22
|
+
|
|
23
|
+
### Dev
|
|
24
|
+
|
|
25
|
+
- GraphQL stargazer pipeline is covered by unit tests for the primary path, REST fallback on failure, no-token skip, mega-repo window cap, small-repo history exhaustion, downsampling correctness, and current-UTC now-point stamping.
|
|
26
|
+
|
|
27
|
+
## [0.2.7] - 2026-04-20
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Star history charts now end at the current date.** On very large repositories (40k+ stars), GitHub caps stargazer pagination at roughly the first 40,000 stars, so the latest sample pulled from the API was often years old and the polyline terminated in the past. The chart now always appends a final data point stamped at the current time, while still reporting the real total star count. Small repos are unaffected.
|
|
32
|
+
- **Session receipt labels are honest again.** The "N corrections" line previously counted every user turn — any pushback, redirect, or elaboration — and rendered them next to tool-failure marks (`✗N`) on the token treemap, which created the impression that the two numbers should reconcile. They're now split into "N user turns" and "N tool errors", with tool-error counts tinted red to match the cell marks. A new legend — `✗N = failed tool calls` — appears above the token map so the red marks are self-explanatory.
|
|
33
|
+
- **Session receipt hero no longer misreports the dominant work phase.** The hero badge previously showed the label of the first detected stage, even when that stage lasted two minutes and a later stage dominated the session. It now uses the stage with the largest share of tool calls, and falls back to `MIXED` when no single stage exceeds 20%.
|
|
34
|
+
- **Rhythm bars stop overflowing their track on long sessions.** On sessions with many stages (~30+), the rightmost rhythm bar on the receipt could render hundreds of pixels past the track's right edge. Receipt and rhythm-strip now share a single layout routine with a proper gap budget and a post-hoc rescale, so bars always fit the track regardless of stage count.
|
|
35
|
+
- **Rhythm bars encode time, not tool-call share.** When the telemetry contract carries start and end timestamps per stage, bar widths are now proportional to stage duration, so the time-axis labels (`0m · 104m · 209m`) actually correspond to bar positions. Bar heights are now uniform; the previous height scaling was effectively noise because most bars hit the minimum-height floor.
|
|
36
|
+
- **Live badges now recognize `building` as a state.** A badge with `value="building"` (or `"rebuilding"` / `"build"`) now renders as the building state instead of falling through to the default `active`. Longer phrases containing the word "build" are not affected — only those three exact tokens.
|
|
37
|
+
- **Deploying `HW_GITHUB_TOKENS` no longer crashes the app.** A vestigial `github_tokens` field on the settings schema was trying to parse the environment variable as JSON on startup, so setting the plain comma-separated secret that the token-rotation code actually expects caused a 500. The field has been removed; the connector reads the secret directly, and plain comma-separated deployments now work as documented.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`hw_discover` advertises the `stats`, `chart`, and `timeline` routes.** MCP clients calling `hw_discover(what="url_grammar")` now receive URL patterns, example URLs, and method hints for the three routes that shipped in v0.2.0 but were missing from discovery.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- **Chart markers render through template partials.** Marker shapes (rect, circle, diamond, and their endpoint variants) now live as Jinja partials under `templates/components/chart-markers/`, matching how the rest of the rendering pipeline handles SVG. No visual change; output is byte-identical for the shipped genomes.
|
|
46
|
+
|
|
47
|
+
### Dev
|
|
48
|
+
|
|
49
|
+
- GitHub token pool rotation is now covered by unit tests (`HW_GITHUB_TOKENS` comma-separated list, `GITHUB_TOKEN` single-token fallback, whitespace tolerance, empty-env behavior).
|
|
50
|
+
|
|
8
51
|
## [0.2.6] - 2026-04-19
|
|
9
52
|
|
|
10
53
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperweave
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Headless visual output layer for AI agents. One API call → self-contained SVG.
|
|
5
5
|
Project-URL: Repository, https://github.com/InnerAura/hyperweave
|
|
6
6
|
Project-URL: Issues, https://github.com/InnerAura/hyperweave/issues
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.8'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 8)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -337,12 +337,16 @@ def _ctx_chart(spec: ComposeSpec, resolved: ResolvedArtifact, css: dict[str, str
|
|
|
337
337
|
ctx["chart_viewport_w"] = 0
|
|
338
338
|
ctx["chart_viewport_h"] = 0
|
|
339
339
|
ctx["chart_defs"] = ""
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
ctx["
|
|
344
|
-
ctx["
|
|
345
|
-
ctx["
|
|
340
|
+
# Post-v0.2.8: axes / gridlines / milestones / markers all return structured
|
|
341
|
+
# lists; polyline / area / empty_state are dicts or None so templates can
|
|
342
|
+
# use ``{% if %}`` to guard includes without a StrictUndefined trap.
|
|
343
|
+
ctx["chart_axes"] = []
|
|
344
|
+
ctx["chart_gridlines"] = []
|
|
345
|
+
ctx["chart_area"] = None
|
|
346
|
+
ctx["chart_polyline"] = None
|
|
347
|
+
ctx["chart_markers"] = []
|
|
348
|
+
ctx["chart_milestones"] = []
|
|
349
|
+
ctx["chart_empty_state"] = None
|
|
346
350
|
ctx["data_hw_status"] = "fresh"
|
|
347
351
|
ctx.update(resolved.frame_context)
|
|
348
352
|
return ctx
|
|
@@ -371,7 +375,7 @@ def _ctx_stats(spec: ComposeSpec, resolved: ResolvedArtifact, css: dict[str, str
|
|
|
371
375
|
ctx["embedded_chart_defs"] = ""
|
|
372
376
|
ctx["embedded_chart_area"] = ""
|
|
373
377
|
ctx["embedded_chart_polyline"] = ""
|
|
374
|
-
ctx["embedded_chart_markers"] =
|
|
378
|
+
ctx["embedded_chart_markers"] = []
|
|
375
379
|
ctx.update(resolved.frame_context)
|
|
376
380
|
return ctx
|
|
377
381
|
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
|
+
from hyperweave.compose.rhythm import layout_rhythm_bars
|
|
9
10
|
from hyperweave.core.enums import (
|
|
10
11
|
FrameType,
|
|
11
12
|
GlyphMode,
|
|
@@ -1112,7 +1113,7 @@ def resolve_receipt(
|
|
|
1112
1113
|
profile_data: dict[str, Any] = tel.get("profile", {})
|
|
1113
1114
|
tools_raw = tel.get("tools", {})
|
|
1114
1115
|
stages_raw: list[dict[str, Any]] = tel.get("stages", [])
|
|
1115
|
-
|
|
1116
|
+
user_events: list[dict[str, Any]] = tel.get("user_events", [])
|
|
1116
1117
|
agents: list[dict[str, Any]] = tel.get("agents", [])
|
|
1117
1118
|
|
|
1118
1119
|
# ── Normalize tools: contract produces dict keyed by name, templates need list ──
|
|
@@ -1122,7 +1123,8 @@ def resolve_receipt(
|
|
|
1122
1123
|
tools = list(tools_raw)
|
|
1123
1124
|
|
|
1124
1125
|
# ── Normalize stages: contract produces {label, dominant_class, start, end, tools} ──
|
|
1125
|
-
# Templates need {name, pct, tool_class}
|
|
1126
|
+
# Templates need {name, pct, tool_class}; start/end are preserved so
|
|
1127
|
+
# :func:`layout_rhythm_bars` can lay out time-proportional x/w.
|
|
1126
1128
|
total_stage_tools = sum(s.get("tools", 1) for s in stages_raw) or 1
|
|
1127
1129
|
stages: list[dict[str, Any]] = [
|
|
1128
1130
|
{
|
|
@@ -1130,6 +1132,8 @@ def resolve_receipt(
|
|
|
1130
1132
|
"pct": round(s.get("tools", 1) / total_stage_tools * 100),
|
|
1131
1133
|
"label": s.get("label", ""),
|
|
1132
1134
|
"tool_class": s.get("dominant_class", "explore"),
|
|
1135
|
+
"start": s.get("start"),
|
|
1136
|
+
"end": s.get("end"),
|
|
1133
1137
|
}
|
|
1134
1138
|
for s in stages_raw
|
|
1135
1139
|
]
|
|
@@ -1161,21 +1165,43 @@ def resolve_receipt(
|
|
|
1161
1165
|
"Send": "coordinate",
|
|
1162
1166
|
}
|
|
1163
1167
|
|
|
1168
|
+
# ── Dominant phase (drives hero badge + bottom-right phase label) ──
|
|
1169
|
+
# Using stages[0] was the old bug — for a session where the first 2-minute
|
|
1170
|
+
# stage classified as "validation" but the dominant (45% of tool calls)
|
|
1171
|
+
# was "implementation", the hero badge lied. When no single stage owns
|
|
1172
|
+
# at least 20% of the tool calls, fall back to "MIXED" to avoid
|
|
1173
|
+
# overclaiming a dominant phase that doesn't exist.
|
|
1174
|
+
dominant = max(stages, key=lambda s: s.get("pct", 0)) if stages else None
|
|
1175
|
+
dominant_label = (dominant.get("label") or dominant.get("name") or "") if dominant else ""
|
|
1176
|
+
dominant_pct = dominant.get("pct", 0) if dominant else 0
|
|
1177
|
+
|
|
1164
1178
|
# ── Hero row ──
|
|
1165
1179
|
hero_headline = f"{_fmt_tok(total_tok)} tokens billed · ${total_cost:.2f}"
|
|
1166
1180
|
dur_label = f"{int(duration_m)}m" if duration_m else "—"
|
|
1167
1181
|
hero_subline = f"{model} · {dur_label} · {calls} calls"
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1182
|
+
if not dominant:
|
|
1183
|
+
hero_profile = "SESSION"
|
|
1184
|
+
elif dominant_pct < 20:
|
|
1185
|
+
hero_profile = "MIXED"
|
|
1186
|
+
else:
|
|
1187
|
+
hero_profile = dominant_label.upper()
|
|
1188
|
+
hero_tool_class = dominant["tool_class"] if dominant else "explore"
|
|
1189
|
+
# Split "pushbacks" into distinct signals so the card stops labeling them
|
|
1190
|
+
# as one opaque "N corrections" lie. user_events counts every non-continuation
|
|
1191
|
+
# user turn (corrections + redirects + elaborations); tool errors count
|
|
1192
|
+
# failing/blocked tool calls (the red ✗N cell marks reconcile to this).
|
|
1193
|
+
n_user_turns = len(user_events)
|
|
1194
|
+
n_tool_errors = sum(t.get("errors", 0) + t.get("blocked", 0) for t in tools)
|
|
1171
1195
|
n_agents = len(agents)
|
|
1172
|
-
hero_right = [
|
|
1173
|
-
f"{_fmt_tok(total_input)} in / {_fmt_tok(total_output)} out",
|
|
1196
|
+
hero_right: list[dict[str, str]] = [
|
|
1197
|
+
{"text": f"{_fmt_tok(total_input)} in / {_fmt_tok(total_output)} out"},
|
|
1174
1198
|
]
|
|
1175
1199
|
if total_cache_read or total_cache_create:
|
|
1176
|
-
hero_right.append(f"{_fmt_tok(total_cache_read)} cached / {_fmt_tok(total_cache_create)} written")
|
|
1177
|
-
if
|
|
1178
|
-
hero_right.append(f"{
|
|
1200
|
+
hero_right.append({"text": f"{_fmt_tok(total_cache_read)} cached / {_fmt_tok(total_cache_create)} written"})
|
|
1201
|
+
if n_user_turns:
|
|
1202
|
+
hero_right.append({"text": f"{n_user_turns} user turn{'s' if n_user_turns != 1 else ''}"})
|
|
1203
|
+
if n_tool_errors:
|
|
1204
|
+
hero_right.append({"text": f"{n_tool_errors} tool errors", "accent": "failing"})
|
|
1179
1205
|
|
|
1180
1206
|
# ── Treemap layout (3-tier, 752px wide, token-proportional) ──
|
|
1181
1207
|
content_w = 752
|
|
@@ -1253,29 +1279,16 @@ def resolve_receipt(
|
|
|
1253
1279
|
)
|
|
1254
1280
|
x += min(w, 180) + 4
|
|
1255
1281
|
|
|
1256
|
-
# ── Rhythm bars
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
rx = 0
|
|
1282
|
+
# ── Rhythm bars ──
|
|
1283
|
+
# Delegated to the shared helper so receipt + rhythm-strip can't drift.
|
|
1284
|
+
# See src/hyperweave/compose/rhythm.py for the two-pass algorithm.
|
|
1260
1285
|
bar_area_h = 92
|
|
1261
|
-
|
|
1262
|
-
pct = s.get("pct", 0)
|
|
1263
|
-
w = max(int(content_w * pct / total_stage_pct), 6)
|
|
1264
|
-
h = max(int(bar_area_h * (pct / 50)), 8)
|
|
1265
|
-
y = bar_area_h - h
|
|
1266
|
-
tc = s.get("tool_class", s.get("name", "explore"))
|
|
1267
|
-
rhythm_bars.append({"x": rx, "y": y, "w": w, "h": h, "tool_class": tc})
|
|
1268
|
-
rx += w + 2
|
|
1286
|
+
rhythm_bars = layout_rhythm_bars(stages, area_w=content_w, area_h=bar_area_h)
|
|
1269
1287
|
|
|
1270
1288
|
# ── Legend entries ──
|
|
1271
1289
|
used_classes = sorted({c["tool_class"] for c in treemap_cells}) if treemap_cells else ["explore"]
|
|
1272
1290
|
treemap_legend = [{"tool_class": tc, "label": tc} for tc in used_classes]
|
|
1273
1291
|
|
|
1274
|
-
# ── Dominant phase ──
|
|
1275
|
-
dominant = max(stages, key=lambda s: s.get("pct", 0)) if stages else {"name": "", "pct": 0}
|
|
1276
|
-
dominant_label = dominant.get("label", dominant.get("name", ""))
|
|
1277
|
-
dominant_pct = dominant.get("pct", 0)
|
|
1278
|
-
|
|
1279
1292
|
# ── Metadata band (v0.4): provenance row between rhythm legend and footer ──
|
|
1280
1293
|
session_id = session.get("id", "")
|
|
1281
1294
|
session_id_short = session_id[:8].rstrip("-") if session_id else ""
|
|
@@ -1307,11 +1320,14 @@ def resolve_receipt(
|
|
|
1307
1320
|
metadata_right = " · ".join(metadata_right_parts)
|
|
1308
1321
|
|
|
1309
1322
|
# ── Footer: session stats (left) + brand anchor (right) ──
|
|
1323
|
+
# Same split as hero so nothing in the card claims "corrections" anymore.
|
|
1310
1324
|
footer_parts = []
|
|
1311
|
-
if
|
|
1312
|
-
footer_parts.append(f"{
|
|
1325
|
+
if n_user_turns:
|
|
1326
|
+
footer_parts.append(f"{n_user_turns} user turn{'s' if n_user_turns != 1 else ''}")
|
|
1327
|
+
if n_tool_errors:
|
|
1328
|
+
footer_parts.append(f"{n_tool_errors} tool error{'s' if n_tool_errors != 1 else ''}")
|
|
1313
1329
|
if n_agents:
|
|
1314
|
-
footer_parts.append(f"{n_agents}
|
|
1330
|
+
footer_parts.append(f"{n_agents} agent{'s' if n_agents != 1 else ''}")
|
|
1315
1331
|
footer_left = " · ".join(footer_parts) if footer_parts else ""
|
|
1316
1332
|
footer_right = "hyperweave.app"
|
|
1317
1333
|
|
|
@@ -1325,7 +1341,7 @@ def resolve_receipt(
|
|
|
1325
1341
|
"hero_tool_class": hero_tool_class,
|
|
1326
1342
|
"hero_headline": hero_headline,
|
|
1327
1343
|
"hero_subline": hero_subline,
|
|
1328
|
-
"hero_right_stats":
|
|
1344
|
+
"hero_right_stats": hero_right,
|
|
1329
1345
|
"treemap_legend": treemap_legend,
|
|
1330
1346
|
"treemap_cells": treemap_cells,
|
|
1331
1347
|
"stage_count": len(stages),
|
|
@@ -1368,12 +1384,16 @@ def resolve_rhythm_strip(
|
|
|
1368
1384
|
tools = list(tools_raw)
|
|
1369
1385
|
|
|
1370
1386
|
# ── Normalize stages ──
|
|
1387
|
+
# start/end preserved so :func:`layout_rhythm_bars` can lay out
|
|
1388
|
+
# time-proportional widths when the contract carries them.
|
|
1371
1389
|
total_stage_tools = sum(s.get("tools", 1) for s in stages_raw) or 1
|
|
1372
1390
|
stages: list[dict[str, Any]] = [
|
|
1373
1391
|
{
|
|
1374
1392
|
"name": s.get("dominant_class", s.get("label", "explore")),
|
|
1375
1393
|
"pct": round(s.get("tools", 1) / total_stage_tools * 100),
|
|
1376
1394
|
"tool_class": s.get("dominant_class", "explore"),
|
|
1395
|
+
"start": s.get("start"),
|
|
1396
|
+
"end": s.get("end"),
|
|
1377
1397
|
}
|
|
1378
1398
|
for s in stages_raw
|
|
1379
1399
|
]
|
|
@@ -1389,38 +1409,10 @@ def resolve_rhythm_strip(
|
|
|
1389
1409
|
|
|
1390
1410
|
# Rhythm bars — must match template bar_w = sw - stats_w - right_w - 16.
|
|
1391
1411
|
# With sw=800, stats_w=180, right_w=120: bar_area_w = 484.
|
|
1392
|
-
#
|
|
1393
|
-
# overflow into the right-side loop-status panel.
|
|
1412
|
+
# Delegated to the shared helper — see src/hyperweave/compose/rhythm.py.
|
|
1394
1413
|
bar_area_w = 484
|
|
1395
1414
|
bar_area_h = 42
|
|
1396
|
-
|
|
1397
|
-
n_stages = len(stages)
|
|
1398
|
-
gap_budget = gap_px * max(n_stages - 1, 0)
|
|
1399
|
-
available_w = max(bar_area_w - gap_budget, bar_area_w // 2)
|
|
1400
|
-
min_bar_w = max(2, available_w // max(n_stages, 1) // 3) if n_stages else 6
|
|
1401
|
-
total_stage_pct = sum(s.get("pct", 0) for s in stages) or 100
|
|
1402
|
-
|
|
1403
|
-
raw_bars: list[dict[str, Any]] = []
|
|
1404
|
-
for s in stages:
|
|
1405
|
-
pct = s.get("pct", 0)
|
|
1406
|
-
w = max(int(available_w * pct / total_stage_pct), min_bar_w)
|
|
1407
|
-
h = max(int(bar_area_h * (pct / 50)), 6)
|
|
1408
|
-
y = bar_area_h - h
|
|
1409
|
-
tc = s.get("tool_class", "explore")
|
|
1410
|
-
raw_bars.append({"w": w, "h": h, "y": y, "tool_class": tc})
|
|
1411
|
-
|
|
1412
|
-
# Post-hoc uniform scale if floor-pressure still exceeds budget
|
|
1413
|
-
raw_total = sum(b["w"] for b in raw_bars)
|
|
1414
|
-
if raw_total > available_w and raw_total > 0:
|
|
1415
|
-
scale = available_w / raw_total
|
|
1416
|
-
for b in raw_bars:
|
|
1417
|
-
b["w"] = max(int(b["w"] * scale), 2)
|
|
1418
|
-
|
|
1419
|
-
rhythm_bars: list[dict[str, Any]] = []
|
|
1420
|
-
rx = 0
|
|
1421
|
-
for b in raw_bars:
|
|
1422
|
-
rhythm_bars.append({"x": rx, "y": b["y"], "w": b["w"], "h": b["h"], "tool_class": b["tool_class"]})
|
|
1423
|
-
rx += b["w"] + gap_px
|
|
1415
|
+
rhythm_bars = layout_rhythm_bars(stages, area_w=bar_area_w, area_h=bar_area_h)
|
|
1424
1416
|
|
|
1425
1417
|
# Velocity estimate
|
|
1426
1418
|
vel = int(total_tok / max(duration_m, 1)) if duration_m else 0
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Rhythm-bar layout: time-proportional x/w with uniform heights.
|
|
2
|
+
|
|
3
|
+
Shared between :func:`resolve_receipt` and :func:`resolve_rhythm_strip`
|
|
4
|
+
so the receipt's "79-stage bars overflow the track" bug can't diverge
|
|
5
|
+
from rhythm-strip's correct two-pass algorithm.
|
|
6
|
+
|
|
7
|
+
Algorithm (two passes):
|
|
8
|
+
1. Reserve a gap budget up front so ``n_bars x gap_px`` never steals
|
|
9
|
+
from the width budget. Compute a minimum bar width floor based on
|
|
10
|
+
``n_bars`` so each bar is still visible under extreme stage counts.
|
|
11
|
+
2. Raw widths: time-proportional when every stage carries ISO
|
|
12
|
+
``start``/``end`` timestamps (preferred — the time-axis labels
|
|
13
|
+
then agree with the bar positions); fall back to tool-call-share
|
|
14
|
+
pct when timestamps are missing (legacy contract).
|
|
15
|
+
3. Post-hoc uniform rescale if the raw sum still exceeds the gap
|
|
16
|
+
budget (happens when the min-bar-width floor dominates).
|
|
17
|
+
4. Emit ``{x, y, w, h, tool_class}`` with ``h = BAR_HEIGHT`` uniform.
|
|
18
|
+
|
|
19
|
+
Uniform height is intentional. The old resolver's
|
|
20
|
+
``h = max(int(bar_area_h * (pct / 50)), 8)`` made ~76 of 79 bars hit
|
|
21
|
+
the 8px floor, implying a signal dimension the data didn't carry.
|
|
22
|
+
Three channels (time x category x color) is already enough; the
|
|
23
|
+
fourth (height) had no clear semantic and was therefore noise.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
BAR_HEIGHT = 14
|
|
32
|
+
"""Uniform rhythm-bar height. Shared by receipt (area_h=92) and rhythm-strip
|
|
33
|
+
(area_h=42): both align the bars flush to the bottom of their track."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def layout_rhythm_bars(
|
|
37
|
+
stages: list[dict[str, Any]],
|
|
38
|
+
area_w: int,
|
|
39
|
+
area_h: int,
|
|
40
|
+
gap_px: int = 2,
|
|
41
|
+
) -> list[dict[str, Any]]:
|
|
42
|
+
"""Lay out rhythm bars into a bounded track with a gap budget + rescale.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
stages: Normalized stage dicts. Each MAY carry ``start``/``end``
|
|
46
|
+
ISO strings for time-proportional layout; otherwise layout
|
|
47
|
+
falls back to ``pct`` (tool-call share).
|
|
48
|
+
area_w: Track width in pixels.
|
|
49
|
+
area_h: Track height in pixels. Used only for ``y`` (bars align
|
|
50
|
+
bottom-flush); ``h`` is always :data:`BAR_HEIGHT`.
|
|
51
|
+
gap_px: Inter-bar gap in pixels. Reserved upfront in the budget
|
|
52
|
+
so many-stage sessions don't overflow into adjacent panels.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
One dict per stage: ``{x, y, w, h, tool_class}``. Empty list when
|
|
56
|
+
``stages`` is empty.
|
|
57
|
+
"""
|
|
58
|
+
if not stages:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
n = len(stages)
|
|
62
|
+
gap_budget = gap_px * max(n - 1, 0)
|
|
63
|
+
# Cap the worst case: never give up more than half the track to gaps.
|
|
64
|
+
available_w = max(area_w - gap_budget, area_w // 2)
|
|
65
|
+
|
|
66
|
+
has_timestamps = all(s.get("start") and s.get("end") for s in stages)
|
|
67
|
+
if has_timestamps:
|
|
68
|
+
t0 = datetime.fromisoformat(stages[0]["start"])
|
|
69
|
+
t_end = datetime.fromisoformat(stages[-1]["end"])
|
|
70
|
+
total_s = max((t_end - t0).total_seconds(), 1.0)
|
|
71
|
+
raw_w = [
|
|
72
|
+
max(
|
|
73
|
+
int(
|
|
74
|
+
available_w
|
|
75
|
+
* (datetime.fromisoformat(s["end"]) - datetime.fromisoformat(s["start"])).total_seconds()
|
|
76
|
+
/ total_s
|
|
77
|
+
),
|
|
78
|
+
2,
|
|
79
|
+
)
|
|
80
|
+
for s in stages
|
|
81
|
+
]
|
|
82
|
+
else:
|
|
83
|
+
total_pct = sum(s.get("pct", 0) for s in stages) or 100
|
|
84
|
+
# Per-bar minimum: 1/3 of the equal-share width so tiny stages
|
|
85
|
+
# stay visible without swallowing the whole budget.
|
|
86
|
+
min_bar_w = max(2, available_w // max(n, 1) // 3)
|
|
87
|
+
raw_w = [max(int(available_w * s.get("pct", 0) / total_pct), min_bar_w) for s in stages]
|
|
88
|
+
|
|
89
|
+
# Post-hoc uniform rescale: if the floor pressure drove the sum over budget.
|
|
90
|
+
raw_total = sum(raw_w)
|
|
91
|
+
if raw_total > available_w and raw_total > 0:
|
|
92
|
+
scale = available_w / raw_total
|
|
93
|
+
raw_w = [max(int(w * scale), 2) for w in raw_w]
|
|
94
|
+
|
|
95
|
+
y = area_h - BAR_HEIGHT
|
|
96
|
+
bars: list[dict[str, Any]] = []
|
|
97
|
+
rx = 0
|
|
98
|
+
for s, w in zip(stages, raw_w, strict=True):
|
|
99
|
+
bars.append(
|
|
100
|
+
{
|
|
101
|
+
"x": rx,
|
|
102
|
+
"y": y,
|
|
103
|
+
"w": w,
|
|
104
|
+
"h": BAR_HEIGHT,
|
|
105
|
+
"tool_class": s.get("tool_class", s.get("dominant_class", "explore")),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
rx += w + gap_px
|
|
109
|
+
return bars
|
|
@@ -37,7 +37,11 @@ class HyperWeaveSettings(BaseSettings):
|
|
|
37
37
|
data_cache_ttl: int = Field(default=300, description="Data-bound artifact max-age (5 min)")
|
|
38
38
|
|
|
39
39
|
# -- Connectors --
|
|
40
|
-
|
|
40
|
+
# NOTE: GitHub token rotation lives in ``connectors.base._get_github_token``,
|
|
41
|
+
# which reads ``HW_GITHUB_TOKENS`` as a plain comma-separated string directly
|
|
42
|
+
# from ``os.environ``. Do NOT add a ``github_tokens`` Pydantic field here —
|
|
43
|
+
# Pydantic Settings would auto-map the same env var and try to JSON-parse
|
|
44
|
+
# the CSV value, crashing app startup.
|
|
41
45
|
connect_timeout: float = Field(default=10.0, description="HTTP connect timeout in seconds")
|
|
42
46
|
total_timeout: float = Field(default=15.0, description="HTTP total timeout in seconds")
|
|
43
47
|
|
|
@@ -216,3 +216,66 @@ async def fetch_text(
|
|
|
216
216
|
merged.update(headers)
|
|
217
217
|
response = await fetch(url, provider=provider, headers=merged)
|
|
218
218
|
return response.text
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# GraphQL POST
|
|
222
|
+
|
|
223
|
+
_GITHUB_GRAPHQL_URL: str = "https://api.github.com/graphql"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def fetch_graphql(
|
|
227
|
+
query: str,
|
|
228
|
+
variables: dict[str, Any] | None = None,
|
|
229
|
+
*,
|
|
230
|
+
provider: str = "github",
|
|
231
|
+
url: str = _GITHUB_GRAPHQL_URL,
|
|
232
|
+
) -> dict[str, Any]:
|
|
233
|
+
"""POST a GraphQL query and return the parsed JSON response.
|
|
234
|
+
|
|
235
|
+
Mirrors :func:`fetch_json`'s contract (SSRF validation, per-provider
|
|
236
|
+
circuit breaker, timeouts, GitHub Bearer-token injection) but speaks
|
|
237
|
+
POST with a JSON body. The single GraphQL endpoint for GitHub is
|
|
238
|
+
baked into the default URL so callers don't have to remember it.
|
|
239
|
+
|
|
240
|
+
GraphQL always returns HTTP 200 with a ``data`` field and an optional
|
|
241
|
+
``errors`` list — HTTP-level failures trip the breaker, but query-level
|
|
242
|
+
errors (invalid field, missing perms) come through as response body
|
|
243
|
+
and are the caller's responsibility to inspect.
|
|
244
|
+
"""
|
|
245
|
+
validate_url(url)
|
|
246
|
+
|
|
247
|
+
breaker = get_breaker(provider)
|
|
248
|
+
if not breaker.allow_request():
|
|
249
|
+
raise CircuitOpenError(
|
|
250
|
+
f"Circuit breaker open for provider {provider!r}. Retry after {breaker.recovery_timeout}s."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
merged_headers: dict[str, str] = {
|
|
254
|
+
"User-Agent": f"HyperWeave/{__version__} (https://hyperweave.app)",
|
|
255
|
+
"Accept": "application/json",
|
|
256
|
+
"Content-Type": "application/json",
|
|
257
|
+
}
|
|
258
|
+
if provider == "github":
|
|
259
|
+
token = _get_github_token()
|
|
260
|
+
if token:
|
|
261
|
+
merged_headers["Authorization"] = f"Bearer {token}"
|
|
262
|
+
|
|
263
|
+
timeout = httpx.Timeout(
|
|
264
|
+
connect=CONNECT_TIMEOUT,
|
|
265
|
+
read=TOTAL_TIMEOUT,
|
|
266
|
+
write=TOTAL_TIMEOUT,
|
|
267
|
+
pool=TOTAL_TIMEOUT,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
body: dict[str, Any] = {"query": query, "variables": variables or {}}
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
274
|
+
response = await client.post(url, headers=merged_headers, json=body)
|
|
275
|
+
response.raise_for_status()
|
|
276
|
+
breaker.record_success()
|
|
277
|
+
data: dict[str, Any] = response.json()
|
|
278
|
+
return data
|
|
279
|
+
except (httpx.HTTPStatusError, httpx.RequestError) as exc:
|
|
280
|
+
breaker.record_failure()
|
|
281
|
+
raise ConnectorError(f"GraphQL fetch failed for {provider!r}: {exc}") from exc
|