@bongos/core 1.17.0
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.
- package/.bongos-core.json +5727 -0
- package/.claude/skills/blocker-review/SKILL.md +91 -0
- package/.claude/skills/blocker-solve/SKILL.md +96 -0
- package/.claude/skills/builder-backup/SKILL.md +54 -0
- package/.claude/skills/builder-box/SKILL.md +45 -0
- package/.claude/skills/builder-claim/SKILL.md +76 -0
- package/.claude/skills/builder-connect/SKILL.md +59 -0
- package/.claude/skills/builder-cost/SKILL.md +47 -0
- package/.claude/skills/builder-end/SKILL.md +81 -0
- package/.claude/skills/builder-exit/SKILL.md +88 -0
- package/.claude/skills/builder-key/SKILL.md +40 -0
- package/.claude/skills/builder-reauth/SKILL.md +50 -0
- package/.claude/skills/builder-redteam/SKILL.md +109 -0
- package/.claude/skills/builder-release/SKILL.md +41 -0
- package/.claude/skills/builder-setup/SKILL.md +62 -0
- package/.claude/skills/builder-ship/SKILL.md +79 -0
- package/.claude/skills/builder-stage/SKILL.md +51 -0
- package/.claude/skills/builder-start/SKILL.md +46 -0
- package/.claude/skills/close-box/SKILL.md +43 -0
- package/.claude/skills/design-sync/SKILL.md +69 -0
- package/.claude/skills/dev/SKILL.md +49 -0
- package/.claude/skills/feedback/SKILL.md +32 -0
- package/.claude/skills/figma-design-sync/SKILL.md +74 -0
- package/.claude/skills/goal-review/SKILL.md +89 -0
- package/.claude/skills/idea-triage/SKILL.md +144 -0
- package/.claude/skills/ideate/SKILL.md +82 -0
- package/.claude/skills/merge-mode/SKILL.md +169 -0
- package/.claude/skills/new-project/SKILL.md +108 -0
- package/.claude/skills/otb-character-review/SKILL.md +71 -0
- package/.claude/skills/otb-design-review/SKILL.md +47 -0
- package/.claude/skills/otb-feedback-capture/SKILL.md +45 -0
- package/.claude/skills/otb-figma-sync/SKILL.md +54 -0
- package/.claude/skills/otb-tile-generate/SKILL.md +45 -0
- package/.claude/skills/paint/SKILL.md +70 -0
- package/.claude/skills/planning-session/SKILL.md +299 -0
- package/.claude/skills/priority-session/SKILL.md +134 -0
- package/.claude/skills/read-session-export/SKILL.md +58 -0
- package/.claude/skills/recall/SKILL.md +52 -0
- package/.claude/skills/session-handoff/SKILL.md +46 -0
- package/.claude/skills/status/SKILL.md +33 -0
- package/.devcontainer/Dockerfile +69 -0
- package/.devcontainer/README.md +73 -0
- package/.devcontainer/devcontainer-lock.json +19 -0
- package/.devcontainer/devcontainer.json +93 -0
- package/.devcontainer/healthcheck.sh +84 -0
- package/.devcontainer/init-firewall.sh +198 -0
- package/.devcontainer/post-create.sh +153 -0
- package/.dockerignore +36 -0
- package/.env.local.example +32 -0
- package/.gitattributes +83 -0
- package/.gitignore +91 -0
- package/CLAUDE.md +242 -0
- package/CODE_OF_CONDUCT.md +46 -0
- package/CONTRIBUTING.md +51 -0
- package/Dockerfile +46 -0
- package/GOVERNANCE.md +44 -0
- package/LICENSE +661 -0
- package/README.md +88 -0
- package/bin/bongos.js +222 -0
- package/clients/bongos-client/README.md +48 -0
- package/clients/bongos-client/bongos-client.global.js +617 -0
- package/clients/bongos-client/examples/hello-world.mjs +62 -0
- package/clients/bongos-client/index.cjs +619 -0
- package/clients/bongos-client/index.d.ts +793 -0
- package/clients/bongos-client/index.mjs +614 -0
- package/clients/bongos-client/package.json +32 -0
- package/config/branding.neutral.json +60 -0
- package/config/design-tokens.neutral.json +51 -0
- package/config/design-tokens.schema.json +96 -0
- package/config/modules.neutral.json +9 -0
- package/docker-compose.yml +52 -0
- package/docker-entrypoint.sh +30 -0
- package/docs/adr/0001-stack-choice-single-vps.md +45 -0
- package/docs/adr/0002-digitalocean-over-hetzner.md +27 -0
- package/docs/adr/0003-cloudflare-origin-cert-over-letsencrypt.md +35 -0
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +45 -0
- package/docs/adr/0005-invisible-other-players-in-v1.md +37 -0
- package/docs/adr/0006-singleton-worldroom-plus-queueroom.md +30 -0
- package/docs/adr/0007-persistent-identity-via-localstorage-uuid.md +31 -0
- package/docs/adr/0008-family-based-tile-generation.md +86 -0
- package/docs/adr/0008-google-chat-oauth-user-auth.md +37 -0
- package/docs/adr/0009-autotile-architecture.md +181 -0
- package/docs/adr/0010-pms-architecture.md +70 -0
- package/docs/adr/0011-terrain-id-rendering-model.md +174 -0
- package/docs/adr/0012-ground-object-cell-split.md +208 -0
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +158 -0
- package/docs/adr/0014-families-canonical-over-vocabulary.md +103 -0
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +125 -0
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +132 -0
- package/docs/adr/0017-code-simplification-and-repo-cleanliness.md +74 -0
- package/docs/adr/0018-three-rank-model-goes-live.md +97 -0
- package/docs/adr/0019-red-team-bounty-payout-table.md +52 -0
- package/docs/adr/0020-security-model-public-stub.md +70 -0
- package/docs/adr/0021-per-builder-skill-model-preferences.md +99 -0
- package/docs/adr/0022-secrets-policy.md +124 -0
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +118 -0
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +277 -0
- package/docs/adr/0024-multi-agent-system-architecture.md +125 -0
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +59 -0
- package/docs/adr/0025-structured-criterion-task-link.md +44 -0
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +117 -0
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +170 -0
- package/docs/adr/0028-honest-llm-spend-accounting.md +109 -0
- package/docs/adr/0029-status-page-outage-resilience.md +59 -0
- package/docs/adr/0030-builders-hall-live-updates.md +59 -0
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +222 -0
- package/docs/adr/0032-discord-mirror-of-gds.md +50 -0
- package/docs/adr/0033-discord-bot-service-principal.md +82 -0
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +53 -0
- package/docs/adr/0035-builder-onboarding-three-paths.md +128 -0
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +40 -0
- package/docs/adr/0037-discord-channel-management.md +51 -0
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +111 -0
- package/docs/adr/0039-setup-first-onboarding-ux.md +49 -0
- package/docs/adr/0040-remote-control-default-browser-onramp.md +47 -0
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +40 -0
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +82 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +74 -0
- package/docs/adr/0044-mediterranean-palette-replacement.md +73 -0
- package/docs/adr/0044-per-box-live-game-preview.md +57 -0
- package/docs/adr/0045-devbox-desktop-app.md +133 -0
- package/docs/adr/0046-sandbox-first-review-gate.md +72 -0
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +36 -0
- package/docs/adr/0048-works-in-progress-ia-split.md +32 -0
- package/docs/adr/0049-split-parallel-safety-contract.md +46 -0
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +43 -0
- package/docs/adr/0051-full-session-transcript-corpus.md +57 -0
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +70 -0
- package/docs/adr/0053-scoped-dev-box-session.md +55 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +63 -0
- package/docs/adr/0055-server-mediated-branch-publish.md +47 -0
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +62 -0
- package/docs/adr/0057-container-cost-ledger.md +28 -0
- package/docs/adr/0057-discord-archon-approval-channels.md +64 -0
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +64 -0
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +37 -0
- package/docs/adr/0060-gds-retrieval-layer.md +97 -0
- package/docs/adr/0061-context-layer-decomposition.md +61 -0
- package/docs/adr/0062-medusa-instance-model-decoupling.md +107 -0
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +44 -0
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +49 -0
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +69 -0
- package/docs/adr/0066-generated-file-map-sections.md +51 -0
- package/docs/adr/0067-session-start-fast-forward-pull.md +43 -0
- package/docs/adr/0068-shared-art-key-for-newcomers.md +38 -0
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +35 -0
- package/docs/adr/0070-deterministic-session-card-delivery.md +44 -0
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +59 -0
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +35 -0
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +39 -0
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +64 -0
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +37 -0
- package/docs/adr/0074-memory-map-stays-light.md +32 -0
- package/docs/adr/0075-per-system-cost-attribution.md +37 -0
- package/docs/adr/0076-in-session-deterministic-compaction.md +58 -0
- package/docs/adr/0077-content-addressed-llm-cache.md +32 -0
- package/docs/adr/0078-deterministic-first-cron.md +48 -0
- package/docs/adr/0079-value-gated-autonomous-engagement.md +39 -0
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +46 -0
- package/docs/adr/0081-tool-agnostic-design-layer.md +62 -0
- package/docs/adr/0082-productized-key-storage-provisioning.md +32 -0
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +55 -0
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +143 -0
- package/docs/adr/0084-auto-derived-per-task-rank-floor.md +86 -0
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +39 -0
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +123 -0
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +49 -0
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +65 -0
- package/docs/adr/0088-consistent-character-generative-animation.md +169 -0
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +179 -0
- package/docs/adr/0090-metic-task-authoring.md +73 -0
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +106 -0
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +117 -0
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +72 -0
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +49 -0
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +65 -0
- package/docs/adr/0095-cross-agent-context-management.md +58 -0
- package/docs/adr/0096-require-reward-before-workable.md +209 -0
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +46 -0
- package/docs/adr/0097-retroactive-reward-backfill.md +124 -0
- package/docs/adr/0098-public-mirror-publish-manifest.md +92 -0
- package/docs/adr/0099-delayed-redacted-mirror-export.md +55 -0
- package/docs/adr/0100-two-repo-instance-model.md +60 -0
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +90 -0
- package/docs/adr/0102-finish-bounding-the-kernel.md +36 -0
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +70 -0
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +28 -0
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +42 -0
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +47 -0
- package/docs/adr/0106-goal-owner-manager-authority.md +84 -0
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +83 -0
- package/docs/adr/0108-instance-composition-configurable-root.md +87 -0
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +44 -0
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +49 -0
- package/docs/adr/0111-instance-hosting-provisioning-module.md +135 -0
- package/docs/adr/0112-private-goals-invitations-join-requests.md +72 -0
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +41 -0
- package/docs/adr/0114-instance-docs-discovery-contract.md +32 -0
- package/docs/adr/0115-scheduled-routines-portability.md +31 -0
- package/docs/adr/0116-api-error-envelope-and-code-taxonomy.md +62 -0
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +50 -0
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +47 -0
- package/docs/adr/0119-api-pagination-contract.md +34 -0
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +108 -0
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +97 -0
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +43 -0
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +36 -0
- package/docs/adr/0124-typed-api-response-bodies.md +37 -0
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +36 -0
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +30 -0
- package/docs/adr/0127-cross-box-fleet-observability.md +64 -0
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +46 -0
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +34 -0
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +61 -0
- package/docs/adr/0131-rank-scoped-skill-visibility.md +47 -0
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +54 -0
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +42 -0
- package/docs/adr/0134-private-first-npm-distribution.md +38 -0
- package/docs/adr/README.md +208 -0
- package/docs/api/openapi.json +13720 -0
- package/docs/api-reference.md +437 -0
- package/docs/architecture.md +244 -0
- package/docs/branding-contract.md +66 -0
- package/docs/branding-fill-prompt.md +25 -0
- package/docs/canonical-permissions.md +241 -0
- package/docs/design/container-exterior-tiles.md +49 -0
- package/docs/design/container-exterior.md +81 -0
- package/docs/design/container-interior.md +77 -0
- package/docs/design/modular-architecture/00-research-report.md +107 -0
- package/docs/design/modular-architecture/01-architecture.md +125 -0
- package/docs/design/modular-architecture/02-module-map.md +116 -0
- package/docs/design/modular-architecture/03-builder-flows.md +104 -0
- package/docs/design/modular-architecture/04-module-lifecycle.md +62 -0
- package/docs/design/modular-architecture/README.md +27 -0
- package/docs/design/vanilla-hall-ui-redesign-scope.md +284 -0
- package/docs/design/world-map-scroll.md +41 -0
- package/docs/design-contract.md +320 -0
- package/docs/file-map.md +418 -0
- package/docs/handoff-template.md +92 -0
- package/docs/modules-contract.md +293 -0
- package/docs/onboarding/browser-terminal-guide.md +73 -0
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +28 -0
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +54 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +37 -0
- package/docs/onboarding/diagrams/04-architecture.mmd +51 -0
- package/docs/onboarding/diagrams/05-versioning.mmd +29 -0
- package/docs/onboarding/diagrams/README.md +94 -0
- package/docs/onboarding/diagrams/assertions.json +170 -0
- package/docs/onboarding/diagrams/puppeteer-config.json +1 -0
- package/docs/onboarding/diagrams/render-config.json +9 -0
- package/docs/onboarding/drachmae-and-karma.md +178 -0
- package/docs/onboarding/primer.md +202 -0
- package/docs/project-context.template.md +67 -0
- package/docs/recipes/README.md +35 -0
- package/docs/recipes/audit-replay.md +100 -0
- package/docs/recipes/bongos-cli-release.md +66 -0
- package/docs/recipes/builder-box-lifecycle.md +152 -0
- package/docs/recipes/claude-design-loop.md +143 -0
- package/docs/recipes/claude-gemini-bongos-doors.md +155 -0
- package/docs/recipes/cloudbongos-staging-gate.md +139 -0
- package/docs/recipes/cloudbongos-standup.md +111 -0
- package/docs/recipes/devbox-app-release.md +93 -0
- package/docs/recipes/devbox-app-windows-test.md +128 -0
- package/docs/recipes/failed-ship-recovery-proof.md +102 -0
- package/docs/recipes/figma-design-loop.md +129 -0
- package/docs/recipes/gds-db-backup.md +255 -0
- package/docs/recipes/instance-deploy.md +37 -0
- package/docs/recipes/managed-settings-remote-control.md +416 -0
- package/docs/recipes/memory-backup.md +71 -0
- package/docs/recipes/memory-sync-conflicts.md +98 -0
- package/docs/recipes/multi-builder-merge.md +78 -0
- package/docs/recipes/ops-gotchas.md +212 -0
- package/docs/recipes/overnight-builder.md +53 -0
- package/docs/recipes/packaging-the-core.md +49 -0
- package/docs/recipes/parallel-load-results-2026-05-30.md +63 -0
- package/docs/recipes/parallel-load-test.md +32 -0
- package/docs/recipes/pixel-art-pipeline.md +411 -0
- package/docs/recipes/private-npm-distribution.md +79 -0
- package/docs/recipes/search-index-ingestion.md +67 -0
- package/docs/recipes/self-host.md +60 -0
- package/docs/recipes/ship-and-regrade.md +72 -0
- package/docs/recipes/staging-data.md +50 -0
- package/docs/recipes/staging-environment.md +215 -0
- package/docs/recipes/upgrading-the-core.md +58 -0
- package/docs/research/cursor-competitive-analysis.md +255 -0
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +227 -0
- package/migrations/003_pms.sql +284 -0
- package/migrations/004_pms_cost_idempotency.sql +13 -0
- package/migrations/005_pms_smoke_filter.sql +44 -0
- package/migrations/006_pms_v2.sql +378 -0
- package/migrations/007_pms_v2_hierarchy_trigger.sql +59 -0
- package/migrations/008_pms_v2_migration_allocator.sql +27 -0
- package/migrations/009_pms_v2_blockers_table.sql +68 -0
- package/migrations/010_uptime.sql +29 -0
- package/migrations/011_backfill_v2_v3_done_when.sql +20 -0
- package/migrations/012_pms_v2_task_lifecycle_states.sql +82 -0
- package/migrations/013_claim_locking.sql +22 -0
- package/migrations/014_pms_task_discipline.sql +86 -0
- package/migrations/015_pms_estimation_calibration.sql +140 -0
- package/migrations/016_pms_task_grades.sql +60 -0
- package/migrations/017_version_progress_exclude_abandoned.sql +49 -0
- package/migrations/019_rename_pms_v3_to_gds_v3.sql +65 -0
- package/migrations/020_task_dependencies.sql +99 -0
- package/migrations/021_v3_renumber_and_deps.sql +224 -0
- package/migrations/022_v3_reconcile_dep_gated_status.sql +41 -0
- package/migrations/023_pms_rank.sql +30 -0
- package/migrations/025_builder_exit_status.sql +15 -0
- package/migrations/027_builders_preferred_disciplines.sql +46 -0
- package/migrations/028_tasks_parallel_safe.sql +71 -0
- package/migrations/029_rank_three_live.sql +32 -0
- package/migrations/030_vulnerability_reports.sql +61 -0
- package/migrations/031_reward_simplification.sql +57 -0
- package/migrations/032_repo_health_snapshots.sql +56 -0
- package/migrations/033_audit_log.sql +64 -0
- package/migrations/034_lifecycle_breakdown_leaf_only.sql +62 -0
- package/migrations/035_auth_token_hygiene.sql +66 -0
- package/migrations/037_builder_skill_model_prefs.sql +44 -0
- package/migrations/038_task_override_requests.sql +51 -0
- package/migrations/039_newcomer_friendly_column.sql +124 -0
- package/migrations/040_builder_karma.sql +58 -0
- package/migrations/041_kind_multipliers_and_idea_bonus.sql +42 -0
- package/migrations/042_criterion_proposal_workflow.sql +78 -0
- package/migrations/043_builder_onboarding_observability.sql +66 -0
- package/migrations/044_tasks_requires_rank.sql +97 -0
- package/migrations/045_builder_onboarding_state.sql +147 -0
- package/migrations/046_cost_log_builder_attribution.sql +42 -0
- package/migrations/054_builder_memory.sql +108 -0
- package/migrations/055_task_criteria.sql +90 -0
- package/migrations/058_peer_votes.sql +41 -0
- package/migrations/059_onboarding_pick_disciplines.sql +59 -0
- package/migrations/060_access_requests.sql +46 -0
- package/migrations/061_backfill_override_ship_attribution.sql +29 -0
- package/migrations/062_session_records.sql +34 -0
- package/migrations/063_session_records_tool_errors_idx.sql +11 -0
- package/migrations/064_grade_attempts.sql +18 -0
- package/migrations/065_bfg_principal.sql +58 -0
- package/migrations/066_live_notify_triggers.sql +69 -0
- package/migrations/067_builder_boxes.sql +117 -0
- package/migrations/067_discord_bot_foundation.sql +69 -0
- package/migrations/068_thetes_requires_rank.sql +27 -0
- package/migrations/069_builder_ssh_keys.sql +66 -0
- package/migrations/070_box_intents.sql +101 -0
- package/migrations/071_box_first_provision_approval.sql +46 -0
- package/migrations/072_onboarding_ship_three_remove_primer.sql +80 -0
- package/migrations/073_box_terminal_access.sql +46 -0
- package/migrations/075_builder_sound_prefs.sql +28 -0
- package/migrations/078_box_claude_active.sql +22 -0
- package/migrations/079_box_first_connected_at.sql +20 -0
- package/migrations/081_tasks_needs_migration.sql +34 -0
- package/migrations/084_tasks_security_sensitive.sql +24 -0
- package/migrations/085_security_engagement_docs.sql +34 -0
- package/migrations/086_rename_disciplines.sql +125 -0
- package/migrations/088_builders_anthropic_link.sql +28 -0
- package/migrations/089_builders_monthly_budget.sql +19 -0
- package/migrations/090_box_scoped_sessions.sql +38 -0
- package/migrations/091_session_token_reward.sql +44 -0
- package/migrations/093_tasks_sandbox_review.sql +22 -0
- package/migrations/102_remove_box_first_provision_approval.sql +58 -0
- package/migrations/103_search_chunks.sql +90 -0
- package/migrations/104_builder_box_blocked.sql +34 -0
- package/migrations/105_box_host_keys.sql +35 -0
- package/migrations/111_medusa_customer0_framing.sql +35 -0
- package/migrations/112_drop_track_check.sql +25 -0
- package/migrations/113_merge_lease.sql +16 -0
- package/migrations/114_cloudbongos_rename_live_strings.sql +32 -0
- package/migrations/118_currency_label_decoupling.sql +23 -0
- package/migrations/119_builder_behavior_prefs.sql +31 -0
- package/migrations/121_box_code_version.sql +37 -0
- package/migrations/122_builder_gemini_key.sql +30 -0
- package/migrations/123_cost_log_tiers.sql +33 -0
- package/migrations/124_cost_baseline.sql +45 -0
- package/migrations/125_llm_cache.sql +54 -0
- package/migrations/126_autonomy_runs.sql +33 -0
- package/migrations/143_cost_calibration.sql +63 -0
- package/migrations/145_backfill_task_requires_rank.sql +84 -0
- package/migrations/160_goal_tier.sql +88 -0
- package/migrations/163_polymorphic_dependencies.sql +187 -0
- package/migrations/164_event_sound_prefs.sql +20 -0
- package/migrations/165_task_module_key.sql +15 -0
- package/migrations/171_claims_published_head_sha.sql +24 -0
- package/migrations/174_close_orphaned_claims_on_terminal_tasks.sql +30 -0
- package/migrations/175_task_grades_diff_hash.sql +9 -0
- package/migrations/176_promotion_respects_open_blockers.sql +58 -0
- package/migrations/176_ui_discipline.sql +40 -0
- package/migrations/177_require_reward_before_workable.sql +76 -0
- package/migrations/178_reward_gate_auto_assign_revert_autopromote.sql +61 -0
- package/migrations/179_search_chunk_usefulness.sql +33 -0
- package/migrations/180_one_active_claim_per_session.sql +54 -0
- package/migrations/181_cost_log_skill_name.sql +33 -0
- package/migrations/184_claim_release_requested.sql +25 -0
- package/migrations/185_module_upstreaming_goal_wiring.sql +59 -0
- package/migrations/187_goal_sort_order.sql +30 -0
- package/migrations/187_tasks_kind_check_add_verify.sql +21 -0
- package/migrations/README.md +88 -0
- package/migrations/core_188_goal_visibility_and_membership_requests.sql +71 -0
- package/migrations/core_189_core_upgrades_ledger.sql +37 -0
- package/migrations/core_189_credit_log_task_id_index.sql +19 -0
- package/migrations/core_190_rebuild_claimable_task_views.sql +71 -0
- package/migrations/instance/018_pms_v3_planning_seed.sql +131 -0
- package/migrations/instance/080_gds_v4_planning_seed.sql +75 -0
- package/migrations/instance/110_medusa_planning_seed.sql +70 -0
- package/migrations/instance/120_bongos_v1_planning_seed.sql +69 -0
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +26 -0
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +29 -0
- package/migrations/instance/172_v2_world_planning_seed.sql +163 -0
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +33 -0
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +132 -0
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +116 -0
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +69 -0
- package/modules/autonomy/CLAUDE.md +49 -0
- package/modules/autonomy/db.js +50 -0
- package/modules/autonomy/module.json +13 -0
- package/modules/autonomy/pollers/routine-timers.js +146 -0
- package/modules/autonomy/routes/autonomy.js +37 -0
- package/modules/builder-settings/CLAUDE.md +58 -0
- package/modules/builder-settings/builder-needs.js +115 -0
- package/modules/builder-settings/db.js +168 -0
- package/modules/builder-settings/display-name.js +84 -0
- package/modules/builder-settings/event-sound-prefs.js +169 -0
- package/modules/builder-settings/interaction-prefs.js +201 -0
- package/modules/builder-settings/model-alloc-prefs.js +147 -0
- package/modules/builder-settings/module.json +10 -0
- package/modules/builder-settings/render-prefs.js +103 -0
- package/modules/builder-settings/routes/settings.js +417 -0
- package/modules/builder-settings/settings.js +52 -0
- package/modules/builder-settings/skill-prefs.js +196 -0
- package/modules/builder-settings/sound-prefs.js +178 -0
- package/modules/builder-settings/wandering-prefs.js +282 -0
- package/modules/dev-box/CLAUDE.md +38 -0
- package/modules/dev-box/box-access.js +275 -0
- package/modules/dev-box/box-credential.js +94 -0
- package/modules/dev-box/box-onboard.js +486 -0
- package/modules/dev-box/boxes.js +842 -0
- package/modules/dev-box/db.js +39 -0
- package/modules/dev-box/module.json +14 -0
- package/modules/dev-box/routes/box.js +977 -0
- package/modules/discord/CLAUDE.md +50 -0
- package/modules/discord/auth.js +54 -0
- package/modules/discord/bug-attachments.js +210 -0
- package/modules/discord/db.js +96 -0
- package/modules/discord/discord-approvals.js +145 -0
- package/modules/discord/discord-audit.js +29 -0
- package/modules/discord/discord-bot.js +545 -0
- package/modules/discord/discord-bugs.js +257 -0
- package/modules/discord/discord-channels.js +412 -0
- package/modules/discord/discord-inbound.js +147 -0
- package/modules/discord/discord-message-buffer.js +54 -0
- package/modules/discord/discord-reaction.js +186 -0
- package/modules/discord/discord-role-sync.js +318 -0
- package/modules/discord/module.json +14 -0
- package/modules/discord/pollers/discord-bot.js +19 -0
- package/modules/discord/routes/bug-attachments.js +44 -0
- package/modules/discord/routes/discord.js +102 -0
- package/modules/discord/ship-broadcast.js +78 -0
- package/modules/discord/webhook.js +74 -0
- package/modules/economy/CLAUDE.md +48 -0
- package/modules/economy/achievements.js +594 -0
- package/modules/economy/cost-classify.js +331 -0
- package/modules/economy/cost-summary-shape.js +90 -0
- package/modules/economy/cost.js +438 -0
- package/modules/economy/credits.js +430 -0
- package/modules/economy/migrations/economy_001_fix_gds_shipper_description.sql +91 -0
- package/modules/economy/module.json +10 -0
- package/modules/economy/reward.js +61 -0
- package/modules/economy/routes/cost.js +97 -0
- package/modules/economy/routes/leaderboard.js +26 -0
- package/modules/economy/routes/reward.js +42 -0
- package/modules/economy/streak.js +285 -0
- package/modules/grading/CLAUDE.md +42 -0
- package/modules/grading/db.js +39 -0
- package/modules/grading/grader-rubric.json +151 -0
- package/modules/grading/grader-workers/efficiency.js +213 -0
- package/modules/grading/grader-workers/extract-json.js +80 -0
- package/modules/grading/grader-workers/fence.js +100 -0
- package/modules/grading/grader-workers/hacker.js +239 -0
- package/modules/grading/grader-workers/index.js +183 -0
- package/modules/grading/grader-workers/narc.js +207 -0
- package/modules/grading/grader-workers/quality.js +198 -0
- package/modules/grading/grader-workers/rubric.js +20 -0
- package/modules/grading/grader-workers/worker-prompt.js +89 -0
- package/modules/grading/grader-workers/worker-verdict.js +50 -0
- package/modules/grading/grader.js +1790 -0
- package/modules/grading/module.json +12 -0
- package/modules/grading/routes/grade.js +51 -0
- package/modules/hall-ui/CLAUDE.md +61 -0
- package/modules/hall-ui/module.json +14 -0
- package/modules/hall-ui/public/atlas.html +180 -0
- package/modules/hall-ui/public/atlas.js +336 -0
- package/modules/hall-ui/public/box-panel.js +423 -0
- package/modules/hall-ui/public/brand-apply.js +117 -0
- package/modules/hall-ui/public/builders.js +3028 -0
- package/modules/hall-ui/public/chime.wav +0 -0
- package/modules/hall-ui/public/copy-on-click.js +174 -0
- package/modules/hall-ui/public/cursors/arrow.png +0 -0
- package/modules/hall-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier-out.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier.png +0 -0
- package/modules/hall-ui/public/diagram-viewer.css +125 -0
- package/modules/hall-ui/public/diagram-viewer.js +330 -0
- package/modules/hall-ui/public/diagrams.html +113 -0
- package/modules/hall-ui/public/dom-utils.js +212 -0
- package/modules/hall-ui/public/drachmae.html +68 -0
- package/modules/hall-ui/public/drachmae.js +136 -0
- package/modules/hall-ui/public/gate.html +143 -0
- package/modules/hall-ui/public/gate.js +228 -0
- package/modules/hall-ui/public/goals-page.js +1297 -0
- package/modules/hall-ui/public/goals.html +376 -0
- package/modules/hall-ui/public/goals.js +119 -0
- package/modules/hall-ui/public/hall-widgets.js +189 -0
- package/modules/hall-ui/public/harbor.html +167 -0
- package/modules/hall-ui/public/harbor.js +335 -0
- package/modules/hall-ui/public/index.html +116 -0
- package/modules/hall-ui/public/instances-panel.js +172 -0
- package/modules/hall-ui/public/md-reader.js +234 -0
- package/modules/hall-ui/public/modules.html +119 -0
- package/modules/hall-ui/public/modules.js +154 -0
- package/modules/hall-ui/public/new-project-wizard.js +186 -0
- package/modules/hall-ui/public/not-ready.html +48 -0
- package/modules/hall-ui/public/pair.html +119 -0
- package/modules/hall-ui/public/pair.js +132 -0
- package/modules/hall-ui/public/primer.css +404 -0
- package/modules/hall-ui/public/primer.html +85 -0
- package/modules/hall-ui/public/primer.js +641 -0
- package/modules/hall-ui/public/profile.html +85 -0
- package/modules/hall-ui/public/profile.js +236 -0
- package/modules/hall-ui/public/ranks.html +69 -0
- package/modules/hall-ui/public/ranks.js +206 -0
- package/modules/hall-ui/public/roadmap.html +209 -0
- package/modules/hall-ui/public/roadmap.js +375 -0
- package/modules/hall-ui/public/sessions.html +92 -0
- package/modules/hall-ui/public/sessions.js +222 -0
- package/modules/hall-ui/public/settings.html +640 -0
- package/modules/hall-ui/public/settings.js +1219 -0
- package/modules/hall-ui/public/shell.js +495 -0
- package/modules/hall-ui/public/style.css +1748 -0
- package/modules/hall-ui/public/task-detail.js +253 -0
- package/modules/hall-ui/public/task.html +83 -0
- package/modules/hall-ui/public/task.js +326 -0
- package/modules/hall-ui/public/watch.html +333 -0
- package/modules/hall-ui/public/watch.js +859 -0
- package/modules/hall-ui/public/work.html +469 -0
- package/modules/hall-ui/public/work.js +1292 -0
- package/modules/ideas/CLAUDE.md +37 -0
- package/modules/ideas/blockers.js +100 -0
- package/modules/ideas/inbox.js +459 -0
- package/modules/ideas/module.json +10 -0
- package/modules/ideas/routes/blockers.js +120 -0
- package/modules/ideas/routes/inbox.js +230 -0
- package/modules/lifecycle/CLAUDE.md +63 -0
- package/modules/lifecycle/conflict-resolve.js +436 -0
- package/modules/lifecycle/db.js +4970 -0
- package/modules/lifecycle/done-when.js +355 -0
- package/modules/lifecycle/github-push.js +969 -0
- package/modules/lifecycle/goal-conflicts.js +133 -0
- package/modules/lifecycle/hierarchy.js +104 -0
- package/modules/lifecycle/lifecycle.js +58 -0
- package/modules/lifecycle/merge-lock.js +261 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +28 -0
- package/modules/lifecycle/migrations/lifecycle_002_backfill_orphan_task_goals.sql +36 -0
- package/modules/lifecycle/module.json +13 -0
- package/modules/lifecycle/pollers/publish-reconciler.js +20 -0
- package/modules/lifecycle/publish-reconciler.js +418 -0
- package/modules/lifecycle/routes/analytics.js +63 -0
- package/modules/lifecycle/routes/claims.js +506 -0
- package/modules/lifecycle/routes/dependencies.js +82 -0
- package/modules/lifecycle/routes/done-when.js +136 -0
- package/modules/lifecycle/routes/gate-approvals.js +197 -0
- package/modules/lifecycle/routes/goals.js +1419 -0
- package/modules/lifecycle/routes/lifecycle.js +43 -0
- package/modules/lifecycle/routes/tasks.js +1703 -0
- package/modules/lifecycle/routes/versions.js +128 -0
- package/modules/lifecycle/ship-card.js +505 -0
- package/modules/lifecycle/ship-preflight.js +225 -0
- package/modules/lifecycle/task-classifier.js +512 -0
- package/modules/lifecycle/version-close.js +106 -0
- package/modules/memory/CLAUDE.md +40 -0
- package/modules/memory/learnings.js +275 -0
- package/modules/memory/memory.js +572 -0
- package/modules/memory/module.json +12 -0
- package/modules/memory/routes/learnings.js +90 -0
- package/modules/memory/routes/memory.js +916 -0
- package/modules/memory/routes/search.js +188 -0
- package/modules/memory/search-chunker.js +252 -0
- package/modules/memory/search.js +320 -0
- package/modules/onboarding/CLAUDE.md +55 -0
- package/modules/onboarding/approval-broadcast.js +59 -0
- package/modules/onboarding/db.js +54 -0
- package/modules/onboarding/module.json +10 -0
- package/modules/onboarding/newcomer-restock.js +212 -0
- package/modules/onboarding/onboarding-state.js +532 -0
- package/modules/onboarding/onboarding.js +508 -0
- package/modules/onboarding/port.js +63 -0
- package/modules/onboarding/routes/access-requests.js +208 -0
- package/modules/onboarding/routes/onboarding.js +54 -0
- package/modules/security/CLAUDE.md +44 -0
- package/modules/security/module.json +10 -0
- package/modules/security/routes/reports.js +251 -0
- package/modules/security/secret-scrub.js +201 -0
- package/modules/security/security.js +294 -0
- package/modules/sessions/CLAUDE.md +39 -0
- package/modules/sessions/bfg.js +357 -0
- package/modules/sessions/db.js +326 -0
- package/modules/sessions/module.json +10 -0
- package/modules/sessions/pulse.js +149 -0
- package/modules/sessions/routes/sessions.js +331 -0
- package/modules/status-ui/CLAUDE.md +47 -0
- package/modules/status-ui/module.json +14 -0
- package/modules/status-ui/public/README.md +29 -0
- package/modules/status-ui/public/cursors/arrow.png +0 -0
- package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/status-ui/public/index.html +363 -0
- package/modules/status-ui/public/snapshots/.gitkeep +0 -0
- package/modules/status-ui/public/status.js +1375 -0
- package/modules/status-ui/public/style.css +1342 -0
- package/package-lock.json +975 -0
- package/package.json +28 -0
- package/public-docs/index.html +35 -0
- package/public-docs/redoc.standalone.js +1838 -0
- package/public-docs/redoc.standalone.js.LICENSE.txt +102 -0
- package/public-landing/buddha.png +0 -0
- package/public-landing/create.html +868 -0
- package/public-landing/index.html +168 -0
- package/scripts/bongos-mcp.js +28 -0
- package/scripts/cron/style_guide_audit.sh +134 -0
- package/scripts/discord/README.md +52 -0
- package/scripts/discord/lib.js +80 -0
- package/scripts/discord/post.js +51 -0
- package/scripts/gds/CLAUDE.md +144 -0
- package/scripts/gds/abandon.js +58 -0
- package/scripts/gds/adopt-detect.js +123 -0
- package/scripts/gds/adopt-import.js +111 -0
- package/scripts/gds/adopt-preflight.js +121 -0
- package/scripts/gds/api.js +78 -0
- package/scripts/gds/architect-audit.js +288 -0
- package/scripts/gds/art-stage.js +152 -0
- package/scripts/gds/audit-authorship.sh +243 -0
- package/scripts/gds/audit-deps.js +121 -0
- package/scripts/gds/audit-exports.js +128 -0
- package/scripts/gds/audit-replay.sh +161 -0
- package/scripts/gds/audit-ships.js +251 -0
- package/scripts/gds/autonomy-gate.js +135 -0
- package/scripts/gds/backfill-cost.js +165 -0
- package/scripts/gds/backfill-sessions.js +351 -0
- package/scripts/gds/backfill-task-rewards.js +408 -0
- package/scripts/gds/backup.js +94 -0
- package/scripts/gds/bfg-principal-token.js +65 -0
- package/scripts/gds/box-auth-check.js +160 -0
- package/scripts/gds/box-connect-lib.js +453 -0
- package/scripts/gds/box.js +1258 -0
- package/scripts/gds/build-bongos.js +94 -0
- package/scripts/gds/capture.js +123 -0
- package/scripts/gds/cf-tunnel.js +419 -0
- package/scripts/gds/ci-grade.js +82 -0
- package/scripts/gds/claim-recall.js +112 -0
- package/scripts/gds/claim.js +333 -0
- package/scripts/gds/classify-discipline.js +153 -0
- package/scripts/gds/claude-materialize.js +176 -0
- package/scripts/gds/cleanup-orphans.js +74 -0
- package/scripts/gds/cli-lib.js +518 -0
- package/scripts/gds/code.js +101 -0
- package/scripts/gds/codemod-rename-src-gds.js +145 -0
- package/scripts/gds/conductor-dispatch.js +426 -0
- package/scripts/gds/context-pack.js +228 -0
- package/scripts/gds/coreB-carve-proof.sh +716 -0
- package/scripts/gds/cost.js +47 -0
- package/scripts/gds/db-backup-nightly.sh +160 -0
- package/scripts/gds/db-backup-offsite.sh +152 -0
- package/scripts/gds/demote.js +40 -0
- package/scripts/gds/design-sync.js +62 -0
- package/scripts/gds/diagnose-achievements.js +118 -0
- package/scripts/gds/diagram-facts.js +319 -0
- package/scripts/gds/discipline-modes.json +25 -0
- package/scripts/gds/discord-channels.js +66 -0
- package/scripts/gds/do-api.js +176 -0
- package/scripts/gds/docs-entropy.js +403 -0
- package/scripts/gds/doctor.js +389 -0
- package/scripts/gds/feedback-latest.js +94 -0
- package/scripts/gds/fetch-art-key.js +106 -0
- package/scripts/gds/fetch-sound-prefs.js +88 -0
- package/scripts/gds/figma-design-sync.js +61 -0
- package/scripts/gds/fitness.js +1446 -0
- package/scripts/gds/gate-review.js +322 -0
- package/scripts/gds/gating.js +325 -0
- package/scripts/gds/gds-db-backup.service +24 -0
- package/scripts/gds/gds-db-backup.timer +20 -0
- package/scripts/gds/gds-db-offsite.service +29 -0
- package/scripts/gds/gds-db-offsite.timer +18 -0
- package/scripts/gds/gen-api-client.js +508 -0
- package/scripts/gds/gen-api-docs.js +1037 -0
- package/scripts/gds/gen-atlas.js +366 -0
- package/scripts/gds/gen-diagrams.js +608 -0
- package/scripts/gds/gen-file-map.js +196 -0
- package/scripts/gds/gen-repo-map.js +519 -0
- package/scripts/gds/gen-session-index.js +210 -0
- package/scripts/gds/git-merge-regen.js +147 -0
- package/scripts/gds/grade-smoke.js +205 -0
- package/scripts/gds/hooks/pre-push.js +169 -0
- package/scripts/gds/http-api-client.js +88 -0
- package/scripts/gds/init.js +903 -0
- package/scripts/gds/install-git-hooks.js +114 -0
- package/scripts/gds/key-card.js +213 -0
- package/scripts/gds/key-status.js +126 -0
- package/scripts/gds/land-watch.js +168 -0
- package/scripts/gds/learning-capture.js +145 -0
- package/scripts/gds/lib.sh +44 -0
- package/scripts/gds/linkify-refs.js +473 -0
- package/scripts/gds/local-preview-lib.js +159 -0
- package/scripts/gds/local-preview.js +95 -0
- package/scripts/gds/login.js +225 -0
- package/scripts/gds/main-audit.js +279 -0
- package/scripts/gds/me.js +88 -0
- package/scripts/gds/memory-map.js +174 -0
- package/scripts/gds/merge-lock.js +71 -0
- package/scripts/gds/migration-namespace.js +200 -0
- package/scripts/gds/mirror-lineage-check.js +319 -0
- package/scripts/gds/mirror-redact.js +305 -0
- package/scripts/gds/module.js +482 -0
- package/scripts/gds/newcomer-floor.config.json +13 -0
- package/scripts/gds/newcomer-floor.js +415 -0
- package/scripts/gds/newcomer-floor.templates.json +86 -0
- package/scripts/gds/newcomer-floor.test.js +235 -0
- package/scripts/gds/next-number.js +163 -0
- package/scripts/gds/oauth-secret.js +197 -0
- package/scripts/gds/onboard.js +217 -0
- package/scripts/gds/onboarding-analyze.js +226 -0
- package/scripts/gds/onboarding-config.js +178 -0
- package/scripts/gds/onboarding-ship.js +92 -0
- package/scripts/gds/package-core.js +684 -0
- package/scripts/gds/paste-token.js +131 -0
- package/scripts/gds/portability-smoke-art.sh +85 -0
- package/scripts/gds/portability-smoke-memory.sh +147 -0
- package/scripts/gds/provision.js +1324 -0
- package/scripts/gds/publish-manifest.js +307 -0
- package/scripts/gds/pulse.js +309 -0
- package/scripts/gds/push-main.js +45 -0
- package/scripts/gds/read-session-export.js +359 -0
- package/scripts/gds/reauth.js +149 -0
- package/scripts/gds/recall.js +135 -0
- package/scripts/gds/release.js +71 -0
- package/scripts/gds/render-ideas.js +173 -0
- package/scripts/gds/repo-metrics.js +363 -0
- package/scripts/gds/resolve-skill-model.js +57 -0
- package/scripts/gds/run-routine.js +197 -0
- package/scripts/gds/run-unit-tests.js +178 -0
- package/scripts/gds/sandbox-stage.js +653 -0
- package/scripts/gds/search-ingest-db.js +209 -0
- package/scripts/gds/search-ingest-md.js +225 -0
- package/scripts/gds/security-baseline-check.sh +180 -0
- package/scripts/gds/seed-adopt-tasks.js +75 -0
- package/scripts/gds/seed-api-contract-goal-tasks.js +141 -0
- package/scripts/gds/seed-bongos-app-tasks.js +294 -0
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +81 -0
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +100 -0
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +307 -0
- package/scripts/gds/seed-bongos-module-system-tasks.js +346 -0
- package/scripts/gds/seed-bongos-tasks.js +214 -0
- package/scripts/gds/seed-gds-v4-tasks.js +624 -0
- package/scripts/gds/seed-governance-tasks.js +340 -0
- package/scripts/gds/seed-medusa-tasks.js +440 -0
- package/scripts/gds/seed-module-upstreaming.js +219 -0
- package/scripts/gds/seed-productization-tasks.js +235 -0
- package/scripts/gds/seed-provisioning-tasks.js +83 -0
- package/scripts/gds/seed-staging.sh +104 -0
- package/scripts/gds/seed-v2-world-tasks.js +283 -0
- package/scripts/gds/seed-v3-tasks.js +1981 -0
- package/scripts/gds/session-cost.js +393 -0
- package/scripts/gds/session-digest.js +179 -0
- package/scripts/gds/session-eval.js +576 -0
- package/scripts/gds/session.js +404 -0
- package/scripts/gds/setup.js +504 -0
- package/scripts/gds/shell.js +100 -0
- package/scripts/gds/ship.js +3618 -0
- package/scripts/gds/smoke-batch-claim.sh +213 -0
- package/scripts/gds/smoke-batch-release.sh +296 -0
- package/scripts/gds/smoke-box.sh +150 -0
- package/scripts/gds/smoke-claim-consistency.sh +212 -0
- package/scripts/gds/smoke-dependencies.sh +84 -0
- package/scripts/gds/smoke-gds.js +125 -0
- package/scripts/gds/smoke-write.sh +168 -0
- package/scripts/gds/start.js +782 -0
- package/scripts/gds/status.js +365 -0
- package/scripts/gds/triage.js +366 -0
- package/scripts/gds/upgrade.js +421 -0
- package/scripts/gds/validate-design.js +254 -0
- package/scripts/gds/version-close.js +549 -0
- package/scripts/migrate.sh +183 -0
- package/scripts/migrate_cost_ledger.sh +178 -0
- package/scripts/public-mirror-export.js +291 -0
- package/scripts/render-diagrams.sh +88 -0
- package/scripts/security/dep-audit.js +276 -0
- package/scripts/setup-builder.js +274 -0
- package/scripts/setup-builder.sh +28 -0
- package/scripts/snapshots/backfill-history.mjs +292 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +66 -0
- package/scripts/snapshots/render-pre-art-eras.mjs +329 -0
- package/scripts/snapshots/render-world.mjs +322 -0
- package/scripts/status-mirror-sync.js +192 -0
- package/scripts/status-probe.mjs +185 -0
- package/server.js +212 -0
- package/src/asset-version.js +71 -0
- package/src/bongos/CLAUDE.md +64 -0
- package/src/bongos/api-errors.js +281 -0
- package/src/bongos/api-path-404.js +67 -0
- package/src/bongos/api-prefix.js +38 -0
- package/src/bongos/app-pair.js +172 -0
- package/src/bongos/auth.js +979 -0
- package/src/bongos/cascade-dispatch.js +123 -0
- package/src/bongos/db-kernel.js +252 -0
- package/src/bongos/db.js +911 -0
- package/src/bongos/goal-scope-check.js +120 -0
- package/src/bongos/hierarchy-config.js +189 -0
- package/src/bongos/live-channel.js +145 -0
- package/src/bongos/llm-cache.js +260 -0
- package/src/bongos/llm-pricing.js +220 -0
- package/src/bongos/metrics.js +286 -0
- package/src/bongos/middleware/audit.js +274 -0
- package/src/bongos/middleware/error-envelope.js +93 -0
- package/src/bongos/middleware/rate-limit.js +195 -0
- package/src/bongos/module-scope-map.js +271 -0
- package/src/bongos/optimizer.js +166 -0
- package/src/bongos/path-match.js +47 -0
- package/src/bongos/permission-path-check.js +248 -0
- package/src/bongos/pool.js +55 -0
- package/src/bongos/repo-info.js +91 -0
- package/src/bongos/route-rank-check.js +420 -0
- package/src/bongos/routes/CLAUDE.md +43 -0
- package/src/bongos/routes/_helpers.js +369 -0
- package/src/bongos/routes/audit-log.js +59 -0
- package/src/bongos/routes/auth.js +755 -0
- package/src/bongos/routes/backup.js +104 -0
- package/src/bongos/routes/builders.js +526 -0
- package/src/bongos/routes/healthz.js +16 -0
- package/src/bongos/routes/instance.js +129 -0
- package/src/bongos/routes/live.js +125 -0
- package/src/bongos/routes/llm-cache.js +119 -0
- package/src/bongos/routes/me.js +380 -0
- package/src/bongos/routes/modules.js +131 -0
- package/src/bongos/routes/optimizer.js +54 -0
- package/src/bongos/routes/public.js +476 -0
- package/src/bongos/routes/security.js +207 -0
- package/src/bongos/routes.js +166 -0
- package/src/bongos/secret-box.js +177 -0
- package/src/bongos/sensitive-surfaces.js +30 -0
- package/src/bongos/serve-internal.js +889 -0
- package/src/bongos/uptime-poller.js +90 -0
- package/src/bongos-downloads.js +70 -0
- package/src/branding.js +182 -0
- package/src/build-info.js +77 -0
- package/src/devbox-downloads.js +67 -0
- package/src/instance-config.js +200 -0
- package/src/module-api.js +366 -0
- package/src/module-loader/loader.js +268 -0
- package/src/module-loader/manifest-schema.js +140 -0
- package/src/module-loader/provenance.js +28 -0
- package/src/module-loader/semver.js +100 -0
- package/src/module-seams.js +291 -0
- package/src/modules.js +254 -0
- package/src/platform-server.js +135 -0
- package/src/ui/adapters/claude-design/index.js +333 -0
- package/src/ui/adapters/figma/index.js +328 -0
- package/tests/abandon_task.mjs +138 -0
- package/tests/achievements_reconcile.mjs +167 -0
- package/tests/adopt_detect.mjs +141 -0
- package/tests/adopt_import.mjs +132 -0
- package/tests/adopt_preflight.mjs +105 -0
- package/tests/anti_grid.mjs +89 -0
- package/tests/api_client.mjs +231 -0
- package/tests/api_docs.mjs +283 -0
- package/tests/api_error_envelope_buildcheck.mjs +56 -0
- package/tests/api_error_envelope_middleware.mjs +99 -0
- package/tests/api_error_registry.mjs +108 -0
- package/tests/api_pagination_contract.mjs +51 -0
- package/tests/api_path_404.mjs +96 -0
- package/tests/app_pair.mjs +168 -0
- package/tests/architect.mjs +140 -0
- package/tests/art_key.mjs +121 -0
- package/tests/art_stage.mjs +121 -0
- package/tests/asset_version.mjs +94 -0
- package/tests/atlas.mjs +69 -0
- package/tests/audit_deps.mjs +74 -0
- package/tests/audit_middleware.mjs +385 -0
- package/tests/auth_admission_gate.mjs +164 -0
- package/tests/auth_anthropic_identity.mjs +95 -0
- package/tests/auth_duplicate_cookie.mjs +56 -0
- package/tests/auth_error_brand.mjs +77 -0
- package/tests/auth_flow_slice1.mjs +223 -0
- package/tests/auth_inactive_builder.mjs +94 -0
- package/tests/auth_page_gate.mjs +92 -0
- package/tests/auth_session_ttl.mjs +155 -0
- package/tests/autonomous_fit_protected_paths.mjs +115 -0
- package/tests/autonomy_gate.mjs +85 -0
- package/tests/autotile_variants.mjs +256 -0
- package/tests/autotile_xverify.mjs +133 -0
- package/tests/backfill_sessions.mjs +101 -0
- package/tests/backfill_task_rewards.mjs +152 -0
- package/tests/backup_cli.mjs +104 -0
- package/tests/bfg.mjs +327 -0
- package/tests/bfg_write.mjs +423 -0
- package/tests/bongos_box_connect.mjs +89 -0
- package/tests/bongos_dispatch.mjs +74 -0
- package/tests/bongos_downloads.mjs +118 -0
- package/tests/bongos_login.mjs +34 -0
- package/tests/box_access.mjs +186 -0
- package/tests/box_auth_check.mjs +114 -0
- package/tests/box_code_staleness.mjs +116 -0
- package/tests/box_connect_e2e.mjs +196 -0
- package/tests/box_cost.mjs +70 -0
- package/tests/box_dns_repoint.mjs +151 -0
- package/tests/box_host_keys.mjs +102 -0
- package/tests/box_onboard.mjs +317 -0
- package/tests/box_scope_session.mjs +115 -0
- package/tests/box_ship_permissions.mjs +71 -0
- package/tests/box_terminal_ssrf.mjs +90 -0
- package/tests/boxes.mjs +1255 -0
- package/tests/branding.mjs +88 -0
- package/tests/bug_attachments.mjs +189 -0
- package/tests/build_info.mjs +88 -0
- package/tests/builder_needs.mjs +99 -0
- package/tests/cap_newcomer_tasks.mjs +114 -0
- package/tests/cascade-dispatch.mjs +95 -0
- package/tests/cf_tunnel.mjs +459 -0
- package/tests/claim_recall.mjs +150 -0
- package/tests/claims_bind_session.mjs +47 -0
- package/tests/claude_materialize.mjs +148 -0
- package/tests/cli_box_scope.mjs +52 -0
- package/tests/cli_lib_formatters.mjs +87 -0
- package/tests/cli_token_reissue.mjs +147 -0
- package/tests/client_bundle_route.mjs +47 -0
- package/tests/cloud_bongos_pack.mjs +85 -0
- package/tests/conductor.mjs +496 -0
- package/tests/confirm_attribution.mjs +156 -0
- package/tests/conflict_resolve.mjs +462 -0
- package/tests/context_pack.mjs +240 -0
- package/tests/cost.mjs +94 -0
- package/tests/cost_audit.mjs +122 -0
- package/tests/cost_budget_status.mjs +78 -0
- package/tests/cost_reconcile.mjs +102 -0
- package/tests/cost_summary_shape.mjs +94 -0
- package/tests/credit_grant.mjs +376 -0
- package/tests/criterion_progress.mjs +220 -0
- package/tests/criterion_proposal.mjs +109 -0
- package/tests/criterion_ref_parse.mjs +53 -0
- package/tests/currency_label.mjs +119 -0
- package/tests/day_cycle.mjs +197 -0
- package/tests/dependencies_migration.mjs +77 -0
- package/tests/dependencies_polymorphic.mjs +212 -0
- package/tests/deploy_instance.mjs +78 -0
- package/tests/devbox_downloads.mjs +122 -0
- package/tests/diff_derived_signals.mjs +115 -0
- package/tests/discord_approvals.mjs +174 -0
- package/tests/discord_autojoin.mjs +158 -0
- package/tests/discord_bot.mjs +94 -0
- package/tests/discord_bugs.mjs +297 -0
- package/tests/discord_channels.mjs +337 -0
- package/tests/discord_foundation.mjs +76 -0
- package/tests/discord_inbound.mjs +190 -0
- package/tests/discord_message_buffer.mjs +87 -0
- package/tests/discord_oauth.mjs +55 -0
- package/tests/discord_reaction.mjs +170 -0
- package/tests/discord_role_sync.mjs +151 -0
- package/tests/display_name.mjs +123 -0
- package/tests/dns_preflight.mjs +74 -0
- package/tests/event_sound_prefs.mjs +194 -0
- package/tests/eviction_policy.mjs +279 -0
- package/tests/figma_adapter.mjs +199 -0
- package/tests/file_map.mjs +91 -0
- package/tests/first_admin_bootstrap.mjs +40 -0
- package/tests/fitness.mjs +435 -0
- package/tests/fitness_two_root.mjs +104 -0
- package/tests/fixtures/session-506-digest.json +34 -0
- package/tests/gate_approvals.mjs +451 -0
- package/tests/gate_review.mjs +152 -0
- package/tests/git_merge_regen.mjs +151 -0
- package/tests/github_push_ancestry.mjs +119 -0
- package/tests/github_push_land_proof.mjs +127 -0
- package/tests/github_push_merge_driver.mjs +175 -0
- package/tests/goal_abuse_matrix.mjs +187 -0
- package/tests/goal_achievement.mjs +146 -0
- package/tests/goal_conflicts.mjs +305 -0
- package/tests/goal_dependencies_read.mjs +73 -0
- package/tests/goal_member_roles.mjs +420 -0
- package/tests/goal_membership_requests.mjs +332 -0
- package/tests/goal_routes.mjs +203 -0
- package/tests/goal_scope_check.mjs +167 -0
- package/tests/goal_tier.mjs +312 -0
- package/tests/goal_wall_proof.mjs +112 -0
- package/tests/grade_attempts.mjs +73 -0
- package/tests/grade_attribution.mjs +117 -0
- package/tests/grade_diff_hash.mjs +191 -0
- package/tests/grade_server_authoritative.mjs +305 -0
- package/tests/grade_unavailable_display.mjs +66 -0
- package/tests/grader.mjs +1960 -0
- package/tests/grader_bypass.mjs +57 -0
- package/tests/grader_panel.mjs +252 -0
- package/tests/grader_prompt_fencing.mjs +192 -0
- package/tests/helpers.mjs +52 -0
- package/tests/hierarchy-config.mjs +81 -0
- package/tests/init.mjs +463 -0
- package/tests/instance_config.mjs +76 -0
- package/tests/instance_manifest.mjs +110 -0
- package/tests/interaction_prefs.mjs +72 -0
- package/tests/key-card.mjs +110 -0
- package/tests/key-status.mjs +45 -0
- package/tests/land_watch.mjs +115 -0
- package/tests/lifecycle-card.mjs +145 -0
- package/tests/lifecycle_breakdown_view.mjs +236 -0
- package/tests/linkify_refs.mjs +264 -0
- package/tests/live_channel.mjs +128 -0
- package/tests/live_sse.mjs +122 -0
- package/tests/llm-cache-route.mjs +110 -0
- package/tests/llm-cache.mjs +140 -0
- package/tests/llm-pricing.mjs +152 -0
- package/tests/local_preview.mjs +136 -0
- package/tests/main_audit.mjs +137 -0
- package/tests/memory_isolation.mjs +431 -0
- package/tests/memory_map.mjs +107 -0
- package/tests/memory_pull.mjs +429 -0
- package/tests/merge_lease.mjs +100 -0
- package/tests/merge_lock.mjs +312 -0
- package/tests/metrics.mjs +147 -0
- package/tests/migration_allocation.mjs +146 -0
- package/tests/migration_namespace.mjs +185 -0
- package/tests/migrations_fresh_replay_guard.mjs +38 -0
- package/tests/mirror_lineage_fulltree.mjs +45 -0
- package/tests/mirror_lineage_proof.mjs +72 -0
- package/tests/mirror_redact.mjs +193 -0
- package/tests/model_alloc_prefs.mjs +63 -0
- package/tests/module-scope-map.mjs +88 -0
- package/tests/module_api.mjs +110 -0
- package/tests/module_cli.mjs +292 -0
- package/tests/module_contributions.mjs +148 -0
- package/tests/module_loader.mjs +286 -0
- package/tests/module_manifest.mjs +194 -0
- package/tests/module_owned_tests.mjs +105 -0
- package/tests/module_provenance.mjs +34 -0
- package/tests/module_route_rank.mjs +147 -0
- package/tests/module_seams.mjs +68 -0
- package/tests/modules.mjs +156 -0
- package/tests/modules_route.mjs +38 -0
- package/tests/newcomer_floor.mjs +17 -0
- package/tests/newcomer_restock.mjs +80 -0
- package/tests/next_number.mjs +154 -0
- package/tests/oauth_secret.mjs +60 -0
- package/tests/onboard.mjs +70 -0
- package/tests/onboarding_gate.mjs +267 -0
- package/tests/onboarding_observability.mjs +344 -0
- package/tests/onboarding_route_signals.mjs +175 -0
- package/tests/onboarding_state.mjs +346 -0
- package/tests/one_active_claim_per_session.mjs +111 -0
- package/tests/orphaned_claim_ship.mjs +135 -0
- package/tests/override_attribution.mjs +142 -0
- package/tests/override_request_ownership.mjs +235 -0
- package/tests/package_core.mjs +435 -0
- package/tests/parallel-load.mjs +402 -0
- package/tests/path_match.mjs +62 -0
- package/tests/permission_path.mjs +151 -0
- package/tests/platform_boot.mjs +167 -0
- package/tests/pre_compact.mjs +60 -0
- package/tests/preview_server.mjs +127 -0
- package/tests/profile_route.mjs +197 -0
- package/tests/provision.mjs +805 -0
- package/tests/provisioning_github_app_routes.mjs +98 -0
- package/tests/public_mirror_export.mjs +124 -0
- package/tests/public_task_resolver.mjs +112 -0
- package/tests/publish_branch_route.mjs +422 -0
- package/tests/publish_manifest.mjs +184 -0
- package/tests/publish_reconciler.mjs +728 -0
- package/tests/rank_gate.mjs +463 -0
- package/tests/rate_limit_public.mjs +217 -0
- package/tests/read_session_export.mjs +89 -0
- package/tests/recall_cli.mjs +82 -0
- package/tests/recall_hints.mjs +119 -0
- package/tests/recent_shipped_window.mjs +80 -0
- package/tests/regrade_eligibility.mjs +202 -0
- package/tests/render_prefs.mjs +122 -0
- package/tests/repo_map.mjs +92 -0
- package/tests/reward_gate.mjs +284 -0
- package/tests/route_rank_check.mjs +243 -0
- package/tests/route_validation.mjs +490 -0
- package/tests/routine_timers.mjs +72 -0
- package/tests/rubric_fixtures.mjs +140 -0
- package/tests/run_routine.mjs +69 -0
- package/tests/sandbox_stage.mjs +582 -0
- package/tests/scrubber_corpus.mjs +257 -0
- package/tests/search_chunker.mjs +209 -0
- package/tests/search_ingest.mjs +96 -0
- package/tests/search_ingest_db.mjs +88 -0
- package/tests/search_isolation.mjs +204 -0
- package/tests/search_route.mjs +95 -0
- package/tests/secret_box.mjs +189 -0
- package/tests/security_docs.mjs +186 -0
- package/tests/security_route.mjs +108 -0
- package/tests/served_paths_shipped.mjs +118 -0
- package/tests/session-cards.mjs +142 -0
- package/tests/session_506_regression.mjs +69 -0
- package/tests/session_caps.mjs +238 -0
- package/tests/session_cost.mjs +133 -0
- package/tests/session_digest.mjs +73 -0
- package/tests/session_eval.mjs +386 -0
- package/tests/session_index.mjs +105 -0
- package/tests/session_records.mjs +369 -0
- package/tests/session_search.mjs +78 -0
- package/tests/session_start_freshness.mjs +123 -0
- package/tests/setup_smoke.mjs +154 -0
- package/tests/ship_autolinkify.mjs +60 -0
- package/tests/ship_automerge_injection.mjs +174 -0
- package/tests/ship_broadcast_routing.mjs +125 -0
- package/tests/ship_broadcast_server.mjs +175 -0
- package/tests/ship_card.mjs +280 -0
- package/tests/ship_ci_deploy.mjs +374 -0
- package/tests/ship_preflight.mjs +368 -0
- package/tests/ship_premerge.mjs +75 -0
- package/tests/ship_resume.mjs +129 -0
- package/tests/ship_upload_skip.mjs +89 -0
- package/tests/ship_zero_commit.mjs +60 -0
- package/tests/single_host_hall.mjs +98 -0
- package/tests/skill_prefs.mjs +150 -0
- package/tests/smoke_preflight.mjs +66 -0
- package/tests/snapshot_picker.mjs +233 -0
- package/tests/sound_prefs.mjs +198 -0
- package/tests/sprite_facing.mjs +35 -0
- package/tests/status-probe.mjs +89 -0
- package/tests/status_backfill.mjs +94 -0
- package/tests/stop_refcheck.mjs +127 -0
- package/tests/streak.mjs +175 -0
- package/tests/subdomain_auth.mjs +58 -0
- package/tests/task_classifier_vocab.mjs +41 -0
- package/tests/task_create_idempotency.mjs +96 -0
- package/tests/task_goal_link.mjs +93 -0
- package/tests/task_ownership_gate.mjs +125 -0
- package/tests/terrain_picker.mjs +298 -0
- package/tests/touches_overlap.mjs +75 -0
- package/tests/triage_development.mjs +140 -0
- package/tests/turn_in_place.mjs +280 -0
- package/tests/upgrade.mjs +357 -0
- package/tests/verify_task_guard.mjs +64 -0
- package/tests/version_close.mjs +76 -0
- package/tests/versions_route.mjs +93 -0
- package/tests/walkability.mjs +188 -0
- package/tests/wandering_prefs.mjs +176 -0
- package/tests/worktree_disambiguation.mjs +105 -0
- package/tests/worktree_guard.mjs +90 -0
|
@@ -0,0 +1,1375 @@
|
|
|
1
|
+
// status.amazonprimea.com — public dashboard JS.
|
|
2
|
+
// Polls /api/bongos/public/* every 60 seconds. No auth, no SPA framework.
|
|
3
|
+
|
|
4
|
+
// API base for the public read endpoints (#737). Empty = same-origin, the case
|
|
5
|
+
// when this file is served by the droplet itself (status.amazonprimea.com
|
|
6
|
+
// proxies /api/* to the same Node process). The OFF-BOX GitHub Pages mirror
|
|
7
|
+
// (lars589/amazonprimea-status, ADR 0029) injects window.STATUS_API_BASE =
|
|
8
|
+
// 'https://amazonprimea.com' in its <head> so the same byte-identical file
|
|
9
|
+
// fetches cross-origin from the droplet there (CORS is open). Keeping this file
|
|
10
|
+
// identical on both hosts is what lets the mirror sync be a pure copy.
|
|
11
|
+
const API = (typeof window !== 'undefined' && window.STATUS_API_BASE) || '';
|
|
12
|
+
|
|
13
|
+
// Instance currency label from the injected branding pack — never a hardcoded
|
|
14
|
+
// 'drachmae' (ADR 0062 §3, task 1705). Neutral fallback if the pack is absent.
|
|
15
|
+
function curLabel(caps) {
|
|
16
|
+
var b = (typeof window !== 'undefined' && window.__BRANDING__) || {};
|
|
17
|
+
var l = (b.currency && b.currency.label) || 'credits';
|
|
18
|
+
return caps ? l.charAt(0).toUpperCase() + l.slice(1) : l;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const els = {
|
|
22
|
+
uptime: document.getElementById('uptime'),
|
|
23
|
+
versions: document.getElementById('versions'),
|
|
24
|
+
costTotal: document.getElementById('cost-total'),
|
|
25
|
+
valueTotal: document.getElementById('value-total'),
|
|
26
|
+
costPercent: document.getElementById('cost-percent'),
|
|
27
|
+
costCats: document.getElementById('cost-categories'),
|
|
28
|
+
costBuilders: document.getElementById('cost-builders'),
|
|
29
|
+
costAttributionSplit: document.getElementById('cost-attribution-split'),
|
|
30
|
+
costChart: document.getElementById('cost-chart'),
|
|
31
|
+
shipped: document.getElementById('shipped'),
|
|
32
|
+
shippedPager: document.getElementById('shipped-pager'),
|
|
33
|
+
shippedPrev: document.getElementById('shipped-prev'),
|
|
34
|
+
shippedNext: document.getElementById('shipped-next'),
|
|
35
|
+
shippedPageInfo: document.getElementById('shipped-pageinfo'),
|
|
36
|
+
shippedRange: document.getElementById('shipped-range'),
|
|
37
|
+
// task 1466: treasury breakdown filters.
|
|
38
|
+
costFilterSource: document.getElementById('cost-filter-source'),
|
|
39
|
+
costFilterCategory: document.getElementById('cost-filter-category'),
|
|
40
|
+
costFilterWindow: document.getElementById('cost-filter-window'),
|
|
41
|
+
costFilterNote: document.getElementById('cost-filter-note'),
|
|
42
|
+
leaderboard: document.getElementById('leaderboard'),
|
|
43
|
+
lastUpdated: document.getElementById('last-updated'),
|
|
44
|
+
worldSnapshot: document.getElementById('world-snapshot'),
|
|
45
|
+
worldTimeline: document.getElementById('world-timeline'),
|
|
46
|
+
worldNote: document.getElementById('world-note'),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ANNUAL_BUDGET_USD = 5000;
|
|
50
|
+
let costChart = null;
|
|
51
|
+
|
|
52
|
+
// ---------- helpers ----------
|
|
53
|
+
|
|
54
|
+
function fmtUsd(n) {
|
|
55
|
+
if (n == null) return '—';
|
|
56
|
+
if (n < 100) return Number(n).toFixed(2);
|
|
57
|
+
return Math.round(Number(n)).toLocaleString();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fmtDr(n) {
|
|
61
|
+
if (n == null) return '—';
|
|
62
|
+
if (n < 100) return `${Number(n).toFixed(2)} dr.`;
|
|
63
|
+
return `${Math.round(Number(n)).toLocaleString()} dr.`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fmtRelativeTime(iso) {
|
|
67
|
+
if (!iso) return '';
|
|
68
|
+
const d = new Date(iso);
|
|
69
|
+
const sec = (Date.now() - d.getTime()) / 1000;
|
|
70
|
+
if (sec < 60) return 'just now';
|
|
71
|
+
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
72
|
+
if (sec < 86400) return `${Math.floor(sec / 3600)}h ago`;
|
|
73
|
+
if (sec < 7 * 86400) return `${Math.floor(sec / 86400)}d ago`;
|
|
74
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clear(el) {
|
|
78
|
+
while (el.firstChild) el.removeChild(el.firstChild);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function fetchJson(path) {
|
|
82
|
+
const res = await fetch(`${API}${path}`, { cache: 'no-store' });
|
|
83
|
+
if (!res.ok) throw new Error(`${path} → ${res.status}`);
|
|
84
|
+
return res.json();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------- sticky section nav (#409) ----------
|
|
88
|
+
//
|
|
89
|
+
// Same design pattern as the hall (public-builders/builders.js renderNav).
|
|
90
|
+
// Independent implementation in this file because the two surfaces don't
|
|
91
|
+
// share a script and per the #409 spec they should evolve in their own
|
|
92
|
+
// scope. Labels are shorter than the section headings — chips need to fit
|
|
93
|
+
// in ~756px of content width without wrapping.
|
|
94
|
+
|
|
95
|
+
const SECTIONS = [
|
|
96
|
+
{ id: 'headline-tablet', label: 'Pulse' },
|
|
97
|
+
{ id: 'uptime-tablet', label: 'Watch' },
|
|
98
|
+
{ id: 'leader-tablet', label: 'Architects' },
|
|
99
|
+
{ id: 'foundry-tablet', label: 'Foundry' },
|
|
100
|
+
{ id: 'versions-tablet', label: 'Works' },
|
|
101
|
+
{ id: 'cost-tablet', label: 'Treasury' },
|
|
102
|
+
{ id: 'shipped-tablet', label: 'Inscribed' },
|
|
103
|
+
{ id: 'world-tablet', label: 'World' },
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
let navObserver = null;
|
|
107
|
+
|
|
108
|
+
function renderNav() {
|
|
109
|
+
const list = document.getElementById('status-nav-list');
|
|
110
|
+
const nav = document.getElementById('status-nav');
|
|
111
|
+
if (!list || !nav) return;
|
|
112
|
+
list.innerHTML = SECTIONS
|
|
113
|
+
.filter((s) => document.getElementById(s.id))
|
|
114
|
+
.map((s) => `<li><a class="status-nav__chip" href="#${s.id}" data-section="${s.id}">${s.label}</a></li>`)
|
|
115
|
+
.join('');
|
|
116
|
+
nav.hidden = false;
|
|
117
|
+
setupNavObserver();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function setupNavObserver() {
|
|
121
|
+
if (navObserver) {
|
|
122
|
+
navObserver.disconnect();
|
|
123
|
+
navObserver = null;
|
|
124
|
+
}
|
|
125
|
+
const list = document.getElementById('status-nav-list');
|
|
126
|
+
if (!list) return;
|
|
127
|
+
const chipsBySection = new Map();
|
|
128
|
+
list.querySelectorAll('a[data-section]').forEach((a) => {
|
|
129
|
+
chipsBySection.set(a.dataset.section, a);
|
|
130
|
+
});
|
|
131
|
+
if (chipsBySection.size === 0) return;
|
|
132
|
+
const sections = Array.from(chipsBySection.keys())
|
|
133
|
+
.map((id) => document.getElementById(id))
|
|
134
|
+
.filter(Boolean);
|
|
135
|
+
if (sections.length === 0) return;
|
|
136
|
+
|
|
137
|
+
// Same rootMargin band as the hall: active flips when a section's headline
|
|
138
|
+
// crosses the upper third of the viewport, not when it merely peeks in.
|
|
139
|
+
navObserver = new IntersectionObserver((entries) => {
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
if (entry.isIntersecting) {
|
|
142
|
+
const id = entry.target.id;
|
|
143
|
+
chipsBySection.forEach((chip, sectionId) => {
|
|
144
|
+
chip.classList.toggle('status-nav__chip--active', sectionId === id);
|
|
145
|
+
});
|
|
146
|
+
// Auto-scroll the active chip into view on mobile.
|
|
147
|
+
const active = chipsBySection.get(id);
|
|
148
|
+
if (active && window.matchMedia('(max-width: 640px)').matches) {
|
|
149
|
+
active.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, {
|
|
155
|
+
rootMargin: '-40% 0px -55% 0px',
|
|
156
|
+
threshold: 0,
|
|
157
|
+
});
|
|
158
|
+
sections.forEach((sec) => navObserver.observe(sec));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------- headline strip (#409) ----------
|
|
162
|
+
//
|
|
163
|
+
// Above-the-fold answer to "is it alive?", "what shipped lately?", "where's
|
|
164
|
+
// the money?", "is quality holding?" — fed by four public endpoints in
|
|
165
|
+
// parallel. Standing aggregates across all services: any down → "Fallen",
|
|
166
|
+
// any degraded → "Faltering", all up → "Standing". The treasury card shows
|
|
167
|
+
// percent-of-purse with a thin progress bar underneath; bar turns amber at
|
|
168
|
+
// 75% and terracotta at 100%.
|
|
169
|
+
|
|
170
|
+
function aggregateStanding(services) {
|
|
171
|
+
if (!Array.isArray(services) || services.length === 0) {
|
|
172
|
+
return { label: '—', cls: '' };
|
|
173
|
+
}
|
|
174
|
+
let anyDown = false, anyDegraded = false, anyUnknown = false;
|
|
175
|
+
for (const s of services) {
|
|
176
|
+
const st = s.current_status;
|
|
177
|
+
if (st === 'down') anyDown = true;
|
|
178
|
+
else if (st === 'degraded') anyDegraded = true;
|
|
179
|
+
else if (st === 'unknown') anyUnknown = true;
|
|
180
|
+
}
|
|
181
|
+
if (anyDown) return { label: 'Fallen', cls: 'headline-stat--standing-down' };
|
|
182
|
+
if (anyDegraded) return { label: 'Faltering', cls: 'headline-stat--standing-warn' };
|
|
183
|
+
if (anyUnknown) return { label: '—', cls: '' };
|
|
184
|
+
return { label: 'Standing', cls: 'headline-stat--standing-up' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function loadHeadline() {
|
|
188
|
+
// 3 fetches in parallel — uptime + cost + grades. The 4th headline card
|
|
189
|
+
// (shipped count) is computed from loadShipped's data via
|
|
190
|
+
// updateHeadlineShippedFromItems() to avoid a duplicate /recent-shipped
|
|
191
|
+
// round trip every 60s. See the comment in loadShipped() for the math.
|
|
192
|
+
const [uptimeR, costR, gradeR] = await Promise.all([
|
|
193
|
+
fetchJson('/api/bongos/public/uptime').catch(() => null),
|
|
194
|
+
fetchJson('/api/bongos/public/cost-summary').catch(() => null),
|
|
195
|
+
fetchJson('/api/bongos/public/grades?days=7').catch(() => null),
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
// Standing card
|
|
199
|
+
const standingNum = document.getElementById('headline-standing-num');
|
|
200
|
+
const standingCard = document.getElementById('headline-standing');
|
|
201
|
+
if (standingNum && standingCard) {
|
|
202
|
+
const { label, cls } = aggregateStanding(uptimeR?.services || []);
|
|
203
|
+
standingNum.textContent = label;
|
|
204
|
+
// Reset state classes, apply new
|
|
205
|
+
standingCard.classList.remove('headline-stat--standing-up', 'headline-stat--standing-down', 'headline-stat--standing-warn');
|
|
206
|
+
if (cls) standingCard.classList.add(cls);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Treasury
|
|
210
|
+
const treasuryNum = document.getElementById('headline-treasury-num');
|
|
211
|
+
const treasuryLabel = document.getElementById('headline-treasury-label');
|
|
212
|
+
const treasuryBar = document.getElementById('headline-treasury-bar');
|
|
213
|
+
if (treasuryNum && treasuryBar && costR) {
|
|
214
|
+
const total = Number(costR.total_usd) || 0;
|
|
215
|
+
const pct = ANNUAL_BUDGET_USD > 0 ? (total / ANNUAL_BUDGET_USD) * 100 : 0;
|
|
216
|
+
treasuryNum.textContent = `${Math.round(pct)}%`;
|
|
217
|
+
treasuryBar.style.width = `${Math.min(100, Math.max(0, pct))}%`;
|
|
218
|
+
treasuryBar.classList.remove('headline-bar__fill--warn', 'headline-bar__fill--over');
|
|
219
|
+
if (pct >= 100) treasuryBar.classList.add('headline-bar__fill--over');
|
|
220
|
+
else if (pct >= 75) treasuryBar.classList.add('headline-bar__fill--warn');
|
|
221
|
+
if (treasuryLabel) {
|
|
222
|
+
treasuryLabel.textContent = `$${fmtUsd(total)} of $${ANNUAL_BUDGET_USD.toLocaleString()} spent`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Mason's Mark — pass rate over last 7 days
|
|
227
|
+
const gradeNum = document.getElementById('headline-grade-num');
|
|
228
|
+
if (gradeNum && gradeR) {
|
|
229
|
+
const rolling = gradeR.rolling || {};
|
|
230
|
+
const n = Number(rolling.n || 0);
|
|
231
|
+
const passed = Number(rolling.n_passed || 0);
|
|
232
|
+
if (n === 0) {
|
|
233
|
+
gradeNum.textContent = '—';
|
|
234
|
+
} else {
|
|
235
|
+
gradeNum.textContent = `${Math.round((passed / n) * 100)}%`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------- world snapshot ----------
|
|
241
|
+
|
|
242
|
+
function fmtSnapshotWhen(iso) {
|
|
243
|
+
if (!iso) return '';
|
|
244
|
+
const d = new Date(iso);
|
|
245
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
246
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
|
|
247
|
+
', ' +
|
|
248
|
+
d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function showSnapshot(snap) {
|
|
252
|
+
if (!els.worldSnapshot || !snap) return;
|
|
253
|
+
|
|
254
|
+
clear(els.worldSnapshot);
|
|
255
|
+
const img = document.createElement('img');
|
|
256
|
+
img.src = `${API}/snapshots/${snap.image}`;
|
|
257
|
+
img.alt = snap.label || `World map — ${snap.id}`;
|
|
258
|
+
img.width = 960;
|
|
259
|
+
img.height = 576;
|
|
260
|
+
img.loading = 'lazy';
|
|
261
|
+
els.worldSnapshot.appendChild(img);
|
|
262
|
+
|
|
263
|
+
const caption = document.createElement('p');
|
|
264
|
+
caption.className = 'world-snapshot__caption';
|
|
265
|
+
const when = fmtSnapshotWhen(snap.timestamp);
|
|
266
|
+
if (snap.label) {
|
|
267
|
+
const strong = document.createElement('strong');
|
|
268
|
+
strong.textContent = snap.label;
|
|
269
|
+
caption.appendChild(strong);
|
|
270
|
+
if (when) caption.append(` · ${when}`);
|
|
271
|
+
} else {
|
|
272
|
+
caption.textContent = when || snap.id;
|
|
273
|
+
}
|
|
274
|
+
if (snap.sha) {
|
|
275
|
+
const sha = document.createElement('span');
|
|
276
|
+
sha.className = 'world-snapshot__sha';
|
|
277
|
+
sha.textContent = ` · ${snap.sha.slice(0, 7)}`;
|
|
278
|
+
caption.appendChild(sha);
|
|
279
|
+
}
|
|
280
|
+
els.worldSnapshot.appendChild(caption);
|
|
281
|
+
|
|
282
|
+
if (els.worldTimeline) {
|
|
283
|
+
for (const item of els.worldTimeline.querySelectorAll('.world-timeline__item')) {
|
|
284
|
+
item.setAttribute('aria-current', item.dataset.id === snap.id ? 'true' : 'false');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function loadWorldSnapshots() {
|
|
290
|
+
if (!els.worldSnapshot) return;
|
|
291
|
+
|
|
292
|
+
let manifest;
|
|
293
|
+
try {
|
|
294
|
+
const res = await fetch(`${API}/snapshots/manifest.json`, { cache: 'no-store' });
|
|
295
|
+
if (!res.ok) throw new Error(`${res.status}`);
|
|
296
|
+
manifest = await res.json();
|
|
297
|
+
} catch (_) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// v2 format: { snapshots: [...] }. v1 (legacy): { dates: [...] }. Normalize.
|
|
302
|
+
let snapshots = [];
|
|
303
|
+
if (Array.isArray(manifest.snapshots)) {
|
|
304
|
+
snapshots = manifest.snapshots.slice();
|
|
305
|
+
} else if (Array.isArray(manifest.dates)) {
|
|
306
|
+
snapshots = manifest.dates.map((d) => ({
|
|
307
|
+
id: d,
|
|
308
|
+
timestamp: `${d}T00:00:00Z`,
|
|
309
|
+
image: `${d}.png`,
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
if (snapshots.length === 0) return;
|
|
313
|
+
|
|
314
|
+
// Oldest → newest already from server; reverse to put newest first in strip.
|
|
315
|
+
snapshots.sort((a, b) => (a.timestamp || '').localeCompare(b.timestamp || ''));
|
|
316
|
+
const newestFirst = snapshots.slice().reverse();
|
|
317
|
+
const latest = newestFirst[0];
|
|
318
|
+
|
|
319
|
+
showSnapshot(latest);
|
|
320
|
+
|
|
321
|
+
if (els.worldTimeline) {
|
|
322
|
+
clear(els.worldTimeline);
|
|
323
|
+
for (const snap of newestFirst) {
|
|
324
|
+
const item = document.createElement('button');
|
|
325
|
+
item.className = 'world-timeline__item';
|
|
326
|
+
item.dataset.id = snap.id;
|
|
327
|
+
item.setAttribute('aria-current', snap.id === latest.id ? 'true' : 'false');
|
|
328
|
+
item.type = 'button';
|
|
329
|
+
item.title = snap.label
|
|
330
|
+
? `${snap.label} — ${fmtSnapshotWhen(snap.timestamp)}`
|
|
331
|
+
: fmtSnapshotWhen(snap.timestamp);
|
|
332
|
+
|
|
333
|
+
const thumb = document.createElement('img');
|
|
334
|
+
thumb.className = 'world-timeline__thumb';
|
|
335
|
+
thumb.src = `${API}/snapshots/${snap.image}`;
|
|
336
|
+
thumb.alt = snap.label || snap.id;
|
|
337
|
+
thumb.width = 200;
|
|
338
|
+
thumb.height = 120;
|
|
339
|
+
thumb.loading = 'lazy';
|
|
340
|
+
|
|
341
|
+
const label = document.createElement('span');
|
|
342
|
+
label.className = 'world-timeline__date';
|
|
343
|
+
const d = new Date(snap.timestamp);
|
|
344
|
+
label.textContent = Number.isNaN(d.getTime())
|
|
345
|
+
? snap.id
|
|
346
|
+
: d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
347
|
+
|
|
348
|
+
item.appendChild(thumb);
|
|
349
|
+
item.appendChild(label);
|
|
350
|
+
item.addEventListener('click', () => showSnapshot(snap));
|
|
351
|
+
els.worldTimeline.appendChild(item);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (els.worldNote && snapshots.length > 1) {
|
|
356
|
+
els.worldNote.hidden = false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---------- uptime ----------
|
|
361
|
+
|
|
362
|
+
const SERVICE_LABELS = {
|
|
363
|
+
game_server: 'The World',
|
|
364
|
+
api: 'The Ledger',
|
|
365
|
+
database: 'The Vault',
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
async function loadUptime() {
|
|
369
|
+
let data;
|
|
370
|
+
try {
|
|
371
|
+
data = await fetchJson('/api/bongos/public/uptime');
|
|
372
|
+
} catch (_) {
|
|
373
|
+
// Endpoint not yet deployed (migration not applied) — show graceful placeholder
|
|
374
|
+
clear(els.uptime);
|
|
375
|
+
const p = document.createElement('p');
|
|
376
|
+
p.className = 'empty';
|
|
377
|
+
p.textContent = 'The sentinels have not yet taken their posts.';
|
|
378
|
+
els.uptime.appendChild(p);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const services = data.services || [];
|
|
383
|
+
clear(els.uptime);
|
|
384
|
+
|
|
385
|
+
for (const svc of services) {
|
|
386
|
+
const label = SERVICE_LABELS[svc.service] || svc.service;
|
|
387
|
+
const isUp = svc.current_status === 'up';
|
|
388
|
+
const isDegraded = svc.current_status === 'degraded';
|
|
389
|
+
const isUnknown = svc.current_status === 'unknown';
|
|
390
|
+
|
|
391
|
+
const row = document.createElement('div');
|
|
392
|
+
row.className = 'uptime-row';
|
|
393
|
+
|
|
394
|
+
// Top line: label + status badge + uptime %
|
|
395
|
+
const top = document.createElement('div');
|
|
396
|
+
top.className = 'uptime-row__top';
|
|
397
|
+
|
|
398
|
+
const nameWrap = document.createElement('div');
|
|
399
|
+
nameWrap.className = 'uptime-row__name-wrap';
|
|
400
|
+
|
|
401
|
+
const dot = document.createElement('span');
|
|
402
|
+
dot.className = `uptime-dot uptime-dot--${isUnknown ? 'unknown' : isUp ? 'up' : isDegraded ? 'degraded' : 'down'}`;
|
|
403
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
404
|
+
|
|
405
|
+
const name = document.createElement('span');
|
|
406
|
+
name.className = 'uptime-row__name';
|
|
407
|
+
name.textContent = label;
|
|
408
|
+
|
|
409
|
+
const badge = document.createElement('span');
|
|
410
|
+
badge.className = `uptime-badge uptime-badge--${isUnknown ? 'unknown' : isUp ? 'up' : isDegraded ? 'degraded' : 'down'}`;
|
|
411
|
+
badge.textContent = isUnknown ? 'awaiting watch' : isUp ? 'standing' : isDegraded ? 'strained' : 'fallen';
|
|
412
|
+
|
|
413
|
+
nameWrap.appendChild(dot);
|
|
414
|
+
nameWrap.appendChild(name);
|
|
415
|
+
nameWrap.appendChild(badge);
|
|
416
|
+
|
|
417
|
+
const pctEl = document.createElement('span');
|
|
418
|
+
pctEl.className = 'uptime-row__pct';
|
|
419
|
+
pctEl.textContent = svc.uptime_pct_90d != null
|
|
420
|
+
? `${svc.uptime_pct_90d}% over 90 days`
|
|
421
|
+
: 'no history yet';
|
|
422
|
+
|
|
423
|
+
top.appendChild(nameWrap);
|
|
424
|
+
top.appendChild(pctEl);
|
|
425
|
+
row.appendChild(top);
|
|
426
|
+
|
|
427
|
+
// History bars — last 90 days as hourly buckets collapsed into ~90 day-wide bars
|
|
428
|
+
const history = svc.history || [];
|
|
429
|
+
if (history.length > 0) {
|
|
430
|
+
// Collapse hourly rows into daily buckets for display (up to 90 bars)
|
|
431
|
+
const byDay = new Map();
|
|
432
|
+
for (const h of history) {
|
|
433
|
+
const day = new Date(h.hour).toISOString().slice(0, 10);
|
|
434
|
+
if (!byDay.has(day)) byDay.set(day, { up: 0, total: 0 });
|
|
435
|
+
byDay.get(day).up += h.up;
|
|
436
|
+
byDay.get(day).total += h.total;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Fill in any missing days in the last 90 so gaps show as unknown
|
|
440
|
+
const today = new Date();
|
|
441
|
+
const days = [];
|
|
442
|
+
for (let i = 89; i >= 0; i--) {
|
|
443
|
+
const d = new Date(today);
|
|
444
|
+
d.setDate(d.getDate() - i);
|
|
445
|
+
days.push(d.toISOString().slice(0, 10));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const bars = document.createElement('div');
|
|
449
|
+
bars.className = 'uptime-bars';
|
|
450
|
+
bars.setAttribute('aria-label', `90-day uptime history for ${label}`);
|
|
451
|
+
|
|
452
|
+
for (const day of days) {
|
|
453
|
+
const bucket = byDay.get(day);
|
|
454
|
+
const bar = document.createElement('span');
|
|
455
|
+
bar.className = 'uptime-bar';
|
|
456
|
+
if (!bucket) {
|
|
457
|
+
bar.classList.add('uptime-bar--unknown');
|
|
458
|
+
bar.title = `${day}: no data`;
|
|
459
|
+
} else {
|
|
460
|
+
const ratio = bucket.total > 0 ? bucket.up / bucket.total : 0;
|
|
461
|
+
if (ratio >= 0.99) {
|
|
462
|
+
bar.classList.add('uptime-bar--up');
|
|
463
|
+
} else if (ratio >= 0.5) {
|
|
464
|
+
bar.classList.add('uptime-bar--degraded');
|
|
465
|
+
} else {
|
|
466
|
+
bar.classList.add('uptime-bar--down');
|
|
467
|
+
}
|
|
468
|
+
bar.title = `${day}: ${Math.round(ratio * 100)}% (${bucket.up}/${bucket.total} checks)`;
|
|
469
|
+
}
|
|
470
|
+
bars.appendChild(bar);
|
|
471
|
+
}
|
|
472
|
+
row.appendChild(bars);
|
|
473
|
+
|
|
474
|
+
const barLabels = document.createElement('div');
|
|
475
|
+
barLabels.className = 'uptime-bars__labels';
|
|
476
|
+
const lbl90 = document.createElement('span');
|
|
477
|
+
lbl90.textContent = '90 days ago';
|
|
478
|
+
const lblNow = document.createElement('span');
|
|
479
|
+
lblNow.textContent = 'today';
|
|
480
|
+
barLabels.appendChild(lbl90);
|
|
481
|
+
barLabels.appendChild(lblNow);
|
|
482
|
+
row.appendChild(barLabels);
|
|
483
|
+
} else {
|
|
484
|
+
const noHist = document.createElement('p');
|
|
485
|
+
noHist.className = 'uptime-row__no-history';
|
|
486
|
+
noHist.textContent = 'History accumulating — returns after the first day of watch.';
|
|
487
|
+
row.appendChild(noHist);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
els.uptime.appendChild(row);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ---------- versions (active endeavours — a thin mirror of the work board) ----------
|
|
495
|
+
|
|
496
|
+
async function loadVersions() {
|
|
497
|
+
const data = await fetchJson('/api/bongos/public/progress');
|
|
498
|
+
const all = data.progress || [];
|
|
499
|
+
// This public section is a deliberately thin MIRROR of the builders' work
|
|
500
|
+
// board (task 938). The work board is the canonical home of Works in Progress
|
|
501
|
+
// — it carries every version with its criteria, the progress bars, and the
|
|
502
|
+
// live in-flight list. Here we show only the ACTIVE endeavours, title + short
|
|
503
|
+
// description, nothing more.
|
|
504
|
+
//
|
|
505
|
+
// Active = underway, not past: exclude shipped/frozen, and exclude any version
|
|
506
|
+
// with no done_when_criteria seeded yet — a "future placeholder" the senate
|
|
507
|
+
// has not scoped (e.g. the next-version stub created at version close). Its
|
|
508
|
+
// criteria_count is 0, so it stays off the public board until it's real.
|
|
509
|
+
const rows = all.filter(
|
|
510
|
+
(v) => v.status !== 'shipped' && v.status !== 'frozen' && Number(v.criteria_count) > 0
|
|
511
|
+
);
|
|
512
|
+
clear(els.versions);
|
|
513
|
+
if (rows.length === 0) {
|
|
514
|
+
const p = document.createElement('p');
|
|
515
|
+
p.className = 'empty';
|
|
516
|
+
p.textContent = 'No endeavors underway just now.';
|
|
517
|
+
els.versions.appendChild(p);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Goal layer (ADR 0086 §2 / BV1.R58): fetch each active version's goals so the
|
|
522
|
+
// card can show how its work is organised (Version → Goals). Best-effort + in
|
|
523
|
+
// parallel; the per-version feed is cached 60s server-side, and a failure just
|
|
524
|
+
// omits the goals line (the bars below are the canonical progress).
|
|
525
|
+
const goalsByVersion = {};
|
|
526
|
+
await Promise.all(rows.map(async (v) => {
|
|
527
|
+
try {
|
|
528
|
+
const dw = await fetchJson(`/api/bongos/versions/${encodeURIComponent(v.version_id)}/done-when`);
|
|
529
|
+
goalsByVersion[v.version_id] = Array.isArray(dw.goals) ? dw.goals : [];
|
|
530
|
+
} catch (_) {
|
|
531
|
+
goalsByVersion[v.version_id] = [];
|
|
532
|
+
}
|
|
533
|
+
}));
|
|
534
|
+
|
|
535
|
+
for (const v of rows) {
|
|
536
|
+
const row = document.createElement('div');
|
|
537
|
+
row.className = 'version-row';
|
|
538
|
+
|
|
539
|
+
const name = document.createElement('div');
|
|
540
|
+
name.className = 'version-row__name';
|
|
541
|
+
name.textContent = `${v.version_id} · ${v.name || ''}`;
|
|
542
|
+
row.appendChild(name);
|
|
543
|
+
|
|
544
|
+
// Stacked three-segment lifecycle bar (shipped / confirmed / completed),
|
|
545
|
+
// sized by raw task count so it matches the work board's bars.
|
|
546
|
+
const total = Number(v.total_count) || 0;
|
|
547
|
+
const counts = {
|
|
548
|
+
shipped: Number(v.lifecycle_shipped_count) || 0,
|
|
549
|
+
confirmed: Number(v.lifecycle_confirmed_count) || 0,
|
|
550
|
+
completed: Number(v.lifecycle_completed_count) || 0,
|
|
551
|
+
};
|
|
552
|
+
const pctOf = (n) => (total > 0 ? (n / total) * 100 : 0);
|
|
553
|
+
const bar = document.createElement('div');
|
|
554
|
+
bar.className = 'version-row__bar';
|
|
555
|
+
for (const [kind, label] of [
|
|
556
|
+
['shipped', 'shipped (deployed live)'],
|
|
557
|
+
['confirmed', 'confirmed (awaiting merge)'],
|
|
558
|
+
['completed', 'completed (awaiting verification)'],
|
|
559
|
+
]) {
|
|
560
|
+
const seg = document.createElement('div');
|
|
561
|
+
seg.className = `version-row__seg version-row__seg--${kind}`;
|
|
562
|
+
seg.style.width = `${pctOf(counts[kind])}%`;
|
|
563
|
+
seg.title = `${counts[kind]} ${label}`;
|
|
564
|
+
bar.appendChild(seg);
|
|
565
|
+
}
|
|
566
|
+
row.appendChild(bar);
|
|
567
|
+
|
|
568
|
+
const pct = Number(v.percent_complete) || 0;
|
|
569
|
+
const pctText = document.createElement('div');
|
|
570
|
+
pctText.className = 'version-row__pct';
|
|
571
|
+
pctText.textContent = `${pct}% complete · weighted by priority`;
|
|
572
|
+
row.appendChild(pctText);
|
|
573
|
+
|
|
574
|
+
// Goal layer (ADR 0086 §2): how this version's work is organised. Shown when
|
|
575
|
+
// the version has goals (every version has at least its default goal after the
|
|
576
|
+
// R56 backfill); becomes informative as real goals are created + achieved.
|
|
577
|
+
const goals = goalsByVersion[v.version_id] || [];
|
|
578
|
+
if (goals.length) {
|
|
579
|
+
const achieved = goals.filter((g) => g.status === 'achieved').length;
|
|
580
|
+
const goalLine = document.createElement('div');
|
|
581
|
+
goalLine.className = 'version-row__pct';
|
|
582
|
+
goalLine.textContent = `${goals.length} goal${goals.length === 1 ? '' : 's'} · ${achieved} achieved`;
|
|
583
|
+
row.appendChild(goalLine);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Short description (versions.done_when), folded into a collapsed dropdown so
|
|
587
|
+
// the page stays a skimmable list of bars but the detail is one tap away.
|
|
588
|
+
const doneWhen = (v.done_when || '').trim();
|
|
589
|
+
if (doneWhen) {
|
|
590
|
+
const details = document.createElement('details');
|
|
591
|
+
details.className = 'version-row__about';
|
|
592
|
+
const summary = document.createElement('summary');
|
|
593
|
+
summary.className = 'version-row__about-summary';
|
|
594
|
+
summary.textContent = 'The work, in brief';
|
|
595
|
+
const desc = document.createElement('p');
|
|
596
|
+
desc.className = 'version-row__desc';
|
|
597
|
+
desc.textContent = doneWhen;
|
|
598
|
+
details.appendChild(summary);
|
|
599
|
+
details.appendChild(desc);
|
|
600
|
+
row.appendChild(details);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
els.versions.appendChild(row);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ---------- costs ----------
|
|
608
|
+
|
|
609
|
+
// task 1466: build the ?source=&category=&days= query from the treasury filter
|
|
610
|
+
// controls. Empty selects / "all" window ⇒ no filter (the full breakdown).
|
|
611
|
+
function costFilterQuery() {
|
|
612
|
+
const p = new URLSearchParams();
|
|
613
|
+
const src = els.costFilterSource && els.costFilterSource.value;
|
|
614
|
+
const cat = els.costFilterCategory && els.costFilterCategory.value;
|
|
615
|
+
const win = els.costFilterWindow && els.costFilterWindow.value;
|
|
616
|
+
if (src) p.set('source', src);
|
|
617
|
+
if (cat) p.set('category', cat);
|
|
618
|
+
if (win && win !== 'all') p.set('days', win);
|
|
619
|
+
const s = p.toString();
|
|
620
|
+
return s ? `?${s}` : '';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Fill the source + category dropdowns from the (unfiltered) option lists the
|
|
624
|
+
// server returns. Runs once; preserves the "All …" selection so re-fetches on
|
|
625
|
+
// filter change don't wipe what the reader picked.
|
|
626
|
+
let _costFiltersPopulated = false;
|
|
627
|
+
function populateCostFilterOptions(opts) {
|
|
628
|
+
if (_costFiltersPopulated || !opts) return;
|
|
629
|
+
const fill = (sel, values) => {
|
|
630
|
+
if (!sel) return;
|
|
631
|
+
const current = sel.value;
|
|
632
|
+
for (const v of values || []) {
|
|
633
|
+
if (v == null || v === '') continue;
|
|
634
|
+
const o = document.createElement('option');
|
|
635
|
+
o.value = v; o.textContent = v;
|
|
636
|
+
sel.appendChild(o);
|
|
637
|
+
}
|
|
638
|
+
sel.value = current;
|
|
639
|
+
};
|
|
640
|
+
fill(els.costFilterSource, opts.sources);
|
|
641
|
+
fill(els.costFilterCategory, opts.categories);
|
|
642
|
+
_costFiltersPopulated = true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// One-line readout of the filtered subtotal vs the project total when a filter
|
|
646
|
+
// is active; cleared when the breakdown is the whole project.
|
|
647
|
+
function renderCostFilterNote(data) {
|
|
648
|
+
const note = els.costFilterNote;
|
|
649
|
+
if (!note) return;
|
|
650
|
+
const af = data.applied_filters || {};
|
|
651
|
+
if (!(af.source || af.category || af.days)) { note.textContent = ''; return; }
|
|
652
|
+
const parts = [];
|
|
653
|
+
if (af.source) parts.push(`source “${af.source}”`);
|
|
654
|
+
if (af.category) parts.push(`category “${af.category}”`);
|
|
655
|
+
if (af.days) parts.push(`last ${af.days} days`);
|
|
656
|
+
const filtered = Number(data.filtered_total_usd) || 0;
|
|
657
|
+
const whole = Number(data.total_usd) || 0;
|
|
658
|
+
const pct = whole > 0 ? Math.round((filtered / whole) * 100) : 0;
|
|
659
|
+
note.textContent = `Breakdown filtered to ${parts.join(' · ')}: ${fmtDr(filtered)} of ${fmtDr(whole)} (${pct}% of all spend).`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async function loadCosts() {
|
|
663
|
+
// task 1466: fetch WITH the active breakdown filter. The header total + chart
|
|
664
|
+
// below still read data.total_usd / data.daily (which stay project-wide); the
|
|
665
|
+
// by-category / by-builder chips read the now-filtered fields.
|
|
666
|
+
const data = await fetchJson(`/api/bongos/public/cost-summary${costFilterQuery()}`);
|
|
667
|
+
populateCostFilterOptions(data.filter_options);
|
|
668
|
+
renderCostFilterNote(data);
|
|
669
|
+
const total = Number(data.total_usd) || 0;
|
|
670
|
+
const totalValue = Number(data.total_value_credits) || 0;
|
|
671
|
+
els.costTotal.textContent = fmtUsd(total);
|
|
672
|
+
if (els.valueTotal) {
|
|
673
|
+
els.valueTotal.textContent = totalValue.toLocaleString();
|
|
674
|
+
}
|
|
675
|
+
const pct = ANNUAL_BUDGET_USD > 0 ? Math.round((total / ANNUAL_BUDGET_USD) * 100) : 0;
|
|
676
|
+
els.costPercent.textContent = `${pct}%`;
|
|
677
|
+
|
|
678
|
+
// by-category
|
|
679
|
+
clear(els.costCats);
|
|
680
|
+
const cats = data.by_category || [];
|
|
681
|
+
if (cats.length === 0) {
|
|
682
|
+
const tag = document.createElement('span');
|
|
683
|
+
tag.className = 'cost-cat';
|
|
684
|
+
tag.textContent = `No ${curLabel()} yet spent.`;
|
|
685
|
+
els.costCats.appendChild(tag);
|
|
686
|
+
} else {
|
|
687
|
+
for (const c of cats) {
|
|
688
|
+
const tag = document.createElement('span');
|
|
689
|
+
tag.className = 'cost-cat';
|
|
690
|
+
const strong = document.createElement('strong');
|
|
691
|
+
strong.textContent = c.category;
|
|
692
|
+
tag.appendChild(strong);
|
|
693
|
+
tag.append(` · ${fmtDr(c.total_usd)}`);
|
|
694
|
+
els.costCats.appendChild(tag);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// V3.R11 (#91): per-builder spend chips. Each builder with non-zero spend
|
|
699
|
+
// gets one chip; unattributed historical spend (pre-migration 046 rows or
|
|
700
|
+
// costs without a session) coalesces into a single "unattributed" chip.
|
|
701
|
+
// V3.R83 (#303): an attributed-vs-unattributed headline above the per-builder
|
|
702
|
+
// chips, so the community can read at a glance how much spend is owned by a
|
|
703
|
+
// specific hand vs. carried by the shared keep. Computed from the same
|
|
704
|
+
// by_builder split the audit (src/bongos/cost.js) uses: builder_id === null is
|
|
705
|
+
// the unattributed bucket.
|
|
706
|
+
if (els.costAttributionSplit) {
|
|
707
|
+
const all = data.by_builder || [];
|
|
708
|
+
let attributed = 0;
|
|
709
|
+
let unattributed = 0;
|
|
710
|
+
for (const b of all) {
|
|
711
|
+
const usd = Number(b?.total_usd) || 0;
|
|
712
|
+
if (b?.builder_id == null) unattributed += usd;
|
|
713
|
+
else attributed += usd;
|
|
714
|
+
}
|
|
715
|
+
const total = attributed + unattributed;
|
|
716
|
+
if (total > 0) {
|
|
717
|
+
const pct = Math.round((unattributed / total) * 100);
|
|
718
|
+
els.costAttributionSplit.textContent =
|
|
719
|
+
`Attributed to builders: ${fmtDr(attributed)} · Shared keep (unattributed): ${fmtDr(unattributed)} (${pct}%).`;
|
|
720
|
+
} else {
|
|
721
|
+
els.costAttributionSplit.textContent = '';
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (els.costBuilders) {
|
|
726
|
+
clear(els.costBuilders);
|
|
727
|
+
const builders = data.by_builder || [];
|
|
728
|
+
let rendered = 0;
|
|
729
|
+
for (const b of builders) {
|
|
730
|
+
if (!b || !(Number(b.total_usd) > 0)) continue;
|
|
731
|
+
const tag = document.createElement('span');
|
|
732
|
+
tag.className = 'cost-cat';
|
|
733
|
+
const strong = document.createElement('strong');
|
|
734
|
+
const label = b.builder_id == null
|
|
735
|
+
? 'unattributed'
|
|
736
|
+
: (b.display_name || b.github_login || `builder #${b.builder_id}`);
|
|
737
|
+
strong.textContent = label;
|
|
738
|
+
tag.appendChild(strong);
|
|
739
|
+
tag.append(` · ${fmtDr(b.total_usd)}`);
|
|
740
|
+
els.costBuilders.appendChild(tag);
|
|
741
|
+
rendered++;
|
|
742
|
+
}
|
|
743
|
+
if (rendered === 0) {
|
|
744
|
+
const tag = document.createElement('span');
|
|
745
|
+
tag.className = 'cost-cat';
|
|
746
|
+
tag.textContent = `No ${curLabel()} yet attributed to a hand.`;
|
|
747
|
+
els.costBuilders.appendChild(tag);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Build a unified day axis from the union of cost + credit days, then
|
|
752
|
+
// walk it once and produce two cumulative series. 1 credit = 1 USD per
|
|
753
|
+
// pms-v1.md, so both share the same numeric scale (left + right axes
|
|
754
|
+
// share scaling per the dual-axis config below).
|
|
755
|
+
//
|
|
756
|
+
// GDS #118: the server's /cost-summary now returns a gap-free day series
|
|
757
|
+
// (generate_series + LEFT JOIN, see src/bongos/db.js costSummary) so the
|
|
758
|
+
// cumulative line carries forward on idle days and reaches CURRENT_DATE.
|
|
759
|
+
// We still defensively forward-fill + extend-to-today client-side as a
|
|
760
|
+
// fallback for any deploy where the server is on older code.
|
|
761
|
+
const costByDay = new Map();
|
|
762
|
+
for (const d of (data.daily || [])) {
|
|
763
|
+
costByDay.set(toDayKey(d.day), Number(d.amount_usd) || 0);
|
|
764
|
+
}
|
|
765
|
+
const valueByDay = new Map();
|
|
766
|
+
for (const d of (data.daily_credits || [])) {
|
|
767
|
+
valueByDay.set(toDayKey(d.day), Number(d.credits) || 0);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Build the day axis spanning min(any-event-day) → today, inclusive.
|
|
771
|
+
// If the server already filled gaps, this is a no-op walk over the same
|
|
772
|
+
// set; if not, this is where the carry-forward gets enforced.
|
|
773
|
+
const unionDays = Array.from(new Set([...costByDay.keys(), ...valueByDay.keys()])).sort();
|
|
774
|
+
const todayKey = new Date().toISOString().slice(0, 10);
|
|
775
|
+
let allDays = unionDays;
|
|
776
|
+
if (unionDays.length > 0) {
|
|
777
|
+
const firstKey = unionDays[0];
|
|
778
|
+
const lastKey = unionDays[unionDays.length - 1] > todayKey
|
|
779
|
+
? unionDays[unionDays.length - 1]
|
|
780
|
+
: todayKey;
|
|
781
|
+
allDays = [];
|
|
782
|
+
const cursor = new Date(`${firstKey}T00:00:00Z`);
|
|
783
|
+
const end = new Date(`${lastKey}T00:00:00Z`);
|
|
784
|
+
while (cursor.getTime() <= end.getTime()) {
|
|
785
|
+
allDays.push(cursor.toISOString().slice(0, 10));
|
|
786
|
+
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const labels = [];
|
|
791
|
+
const cumCost = [];
|
|
792
|
+
const cumValue = [];
|
|
793
|
+
let runningCost = 0;
|
|
794
|
+
let runningValue = 0;
|
|
795
|
+
for (const dayKey of allDays) {
|
|
796
|
+
runningCost += costByDay.get(dayKey) || 0;
|
|
797
|
+
runningValue += valueByDay.get(dayKey) || 0;
|
|
798
|
+
labels.push(new Date(`${dayKey}T00:00:00Z`).toLocaleDateString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' }));
|
|
799
|
+
cumCost.push(Number(runningCost.toFixed(2)));
|
|
800
|
+
cumValue.push(Number(runningValue.toFixed(2)));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!window.Chart) return; // Chart.js failed to load — skip
|
|
804
|
+
|
|
805
|
+
const ctx = els.costChart.getContext('2d');
|
|
806
|
+
if (costChart) costChart.destroy();
|
|
807
|
+
costChart = new Chart(ctx, {
|
|
808
|
+
type: 'line',
|
|
809
|
+
data: {
|
|
810
|
+
labels,
|
|
811
|
+
datasets: [
|
|
812
|
+
{
|
|
813
|
+
label: `${curLabel()} spent`,
|
|
814
|
+
data: cumCost,
|
|
815
|
+
fill: true,
|
|
816
|
+
borderColor: '#2a2110',
|
|
817
|
+
backgroundColor: 'rgba(139, 111, 44, 0.16)',
|
|
818
|
+
tension: 0.2,
|
|
819
|
+
pointRadius: 2.5,
|
|
820
|
+
pointBackgroundColor: '#2a2110',
|
|
821
|
+
borderWidth: 1.5,
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
label: 'value wrought',
|
|
825
|
+
data: cumValue,
|
|
826
|
+
fill: false,
|
|
827
|
+
borderColor: '#2f5a3f',
|
|
828
|
+
backgroundColor: 'rgba(74, 125, 92, 0.18)',
|
|
829
|
+
borderDash: [5, 4],
|
|
830
|
+
tension: 0.2,
|
|
831
|
+
pointRadius: 2.5,
|
|
832
|
+
pointBackgroundColor: '#2f5a3f',
|
|
833
|
+
borderWidth: 1.5,
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
},
|
|
837
|
+
options: {
|
|
838
|
+
responsive: true,
|
|
839
|
+
maintainAspectRatio: false,
|
|
840
|
+
font: { family: '"EB Garamond", Georgia, serif' },
|
|
841
|
+
plugins: {
|
|
842
|
+
legend: {
|
|
843
|
+
display: true,
|
|
844
|
+
position: 'bottom',
|
|
845
|
+
labels: {
|
|
846
|
+
boxWidth: 22,
|
|
847
|
+
padding: 18,
|
|
848
|
+
font: { family: '"EB Garamond", Georgia, serif', size: 13, style: 'italic' },
|
|
849
|
+
color: '#6e5b3a',
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
tooltip: {
|
|
853
|
+
backgroundColor: 'rgba(42, 33, 16, 0.92)',
|
|
854
|
+
titleFont: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
|
|
855
|
+
bodyFont: { family: '"EB Garamond", Georgia, serif', size: 13 },
|
|
856
|
+
padding: 10,
|
|
857
|
+
callbacks: {
|
|
858
|
+
label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })} dr.`,
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
scales: {
|
|
863
|
+
y: {
|
|
864
|
+
beginAtZero: true,
|
|
865
|
+
title: {
|
|
866
|
+
display: true,
|
|
867
|
+
text: `${curLabel()} (cumulative)`,
|
|
868
|
+
font: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
|
|
869
|
+
color: '#735f3d',
|
|
870
|
+
},
|
|
871
|
+
ticks: {
|
|
872
|
+
callback: (v) => `${v} dr.`,
|
|
873
|
+
font: { family: '"EB Garamond", Georgia, serif', size: 12 },
|
|
874
|
+
color: '#6e5b3a',
|
|
875
|
+
},
|
|
876
|
+
grid: { color: 'rgba(139, 111, 44, 0.18)', drawBorder: false },
|
|
877
|
+
},
|
|
878
|
+
x: {
|
|
879
|
+
title: {
|
|
880
|
+
display: true,
|
|
881
|
+
text: 'date',
|
|
882
|
+
font: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
|
|
883
|
+
color: '#735f3d',
|
|
884
|
+
},
|
|
885
|
+
grid: { display: false },
|
|
886
|
+
ticks: {
|
|
887
|
+
maxTicksLimit: 10,
|
|
888
|
+
font: { family: '"EB Garamond", Georgia, serif', size: 12 },
|
|
889
|
+
color: '#6e5b3a',
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function toDayKey(d) {
|
|
898
|
+
// Postgres returns 'YYYY-MM-DD' as a string OR an ISO timestamp depending
|
|
899
|
+
// on transport — normalise to YYYY-MM-DD so dedupe works across formats.
|
|
900
|
+
if (!d) return '';
|
|
901
|
+
if (typeof d === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
|
|
902
|
+
const dt = new Date(d);
|
|
903
|
+
return dt.toISOString().slice(0, 10);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ---------- shipped feed ----------
|
|
907
|
+
|
|
908
|
+
const SHIPPED_PAGE_SIZE = 6;
|
|
909
|
+
let _shippedItems = [];
|
|
910
|
+
let _shippedPage = 0;
|
|
911
|
+
// GDS #191: default to the most useful window for daily readers. The select
|
|
912
|
+
// in index.html is also pre-selected to "30" so the two stay aligned.
|
|
913
|
+
let _shippedRange = '30';
|
|
914
|
+
|
|
915
|
+
function renderShippedPage() {
|
|
916
|
+
clear(els.shipped);
|
|
917
|
+
const items = _shippedItems;
|
|
918
|
+
if (items.length === 0) {
|
|
919
|
+
const li = document.createElement('li');
|
|
920
|
+
li.className = 'empty';
|
|
921
|
+
li.textContent = 'No works yet inscribed in this window.';
|
|
922
|
+
els.shipped.appendChild(li);
|
|
923
|
+
// GDS #191: pad with invisible rows so an empty window still occupies
|
|
924
|
+
// the same vertical space as a full page. Keeps the section height
|
|
925
|
+
// stable when the viewer switches between windows.
|
|
926
|
+
for (let i = 0; i < SHIPPED_PAGE_SIZE - 1; i++) {
|
|
927
|
+
const pad = document.createElement('li');
|
|
928
|
+
pad.className = 'empty-row';
|
|
929
|
+
pad.setAttribute('aria-hidden', 'true');
|
|
930
|
+
els.shipped.appendChild(pad);
|
|
931
|
+
}
|
|
932
|
+
if (els.shippedPager) els.shippedPager.hidden = true;
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const pageCount = Math.max(1, Math.ceil(items.length / SHIPPED_PAGE_SIZE));
|
|
937
|
+
if (_shippedPage >= pageCount) _shippedPage = pageCount - 1;
|
|
938
|
+
if (_shippedPage < 0) _shippedPage = 0;
|
|
939
|
+
|
|
940
|
+
const start = _shippedPage * SHIPPED_PAGE_SIZE;
|
|
941
|
+
const slice = items.slice(start, start + SHIPPED_PAGE_SIZE);
|
|
942
|
+
|
|
943
|
+
for (const t of slice) {
|
|
944
|
+
const li = document.createElement('li');
|
|
945
|
+
const title = document.createElement('p');
|
|
946
|
+
title.className = 'shipped__title';
|
|
947
|
+
const ver = document.createElement('span');
|
|
948
|
+
ver.className = 'shipped__version';
|
|
949
|
+
ver.textContent = t.version_id;
|
|
950
|
+
title.appendChild(ver);
|
|
951
|
+
title.append(t.value_summary || t.title);
|
|
952
|
+
const meta = document.createElement('p');
|
|
953
|
+
meta.className = 'shipped__meta';
|
|
954
|
+
const by = t.display_name || t.github_login;
|
|
955
|
+
meta.textContent = `${by ? `inscribed by ${by} · ` : ''}${fmtRelativeTime(t.shipped_at)}`;
|
|
956
|
+
li.appendChild(title);
|
|
957
|
+
li.appendChild(meta);
|
|
958
|
+
els.shipped.appendChild(li);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// GDS #191: pad the trailing rows on the last page (or any underfilled
|
|
962
|
+
// page) with invisible placeholders so the section height never jumps as
|
|
963
|
+
// the user pages forward / back. Empty rows are aria-hidden so screen
|
|
964
|
+
// readers don't announce phantom entries.
|
|
965
|
+
const padCount = SHIPPED_PAGE_SIZE - slice.length;
|
|
966
|
+
for (let i = 0; i < padCount; i++) {
|
|
967
|
+
const pad = document.createElement('li');
|
|
968
|
+
pad.className = 'empty-row';
|
|
969
|
+
pad.setAttribute('aria-hidden', 'true');
|
|
970
|
+
els.shipped.appendChild(pad);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (els.shippedPager) {
|
|
974
|
+
if (pageCount > 1) {
|
|
975
|
+
els.shippedPager.hidden = false;
|
|
976
|
+
if (els.shippedPageInfo) {
|
|
977
|
+
els.shippedPageInfo.textContent = `page ${_shippedPage + 1} of ${pageCount}`;
|
|
978
|
+
}
|
|
979
|
+
if (els.shippedPrev) els.shippedPrev.disabled = _shippedPage === 0;
|
|
980
|
+
if (els.shippedNext) els.shippedNext.disabled = _shippedPage >= pageCount - 1;
|
|
981
|
+
} else {
|
|
982
|
+
els.shippedPager.hidden = true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (els.shippedPrev) {
|
|
988
|
+
els.shippedPrev.addEventListener('click', () => {
|
|
989
|
+
if (_shippedPage > 0) {
|
|
990
|
+
_shippedPage--;
|
|
991
|
+
renderShippedPage();
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (els.shippedNext) {
|
|
996
|
+
els.shippedNext.addEventListener('click', () => {
|
|
997
|
+
_shippedPage++;
|
|
998
|
+
renderShippedPage();
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
if (els.shippedRange) {
|
|
1002
|
+
els.shippedRange.addEventListener('change', () => {
|
|
1003
|
+
_shippedRange = els.shippedRange.value || 'all';
|
|
1004
|
+
_shippedPage = 0;
|
|
1005
|
+
loadShipped();
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// task 1466: re-fetch the treasury breakdown whenever a spend filter changes.
|
|
1010
|
+
for (const sel of [els.costFilterSource, els.costFilterCategory, els.costFilterWindow]) {
|
|
1011
|
+
if (sel) sel.addEventListener('change', () => { loadCosts().catch((e) => console.error('costs', e)); });
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function loadShipped() {
|
|
1015
|
+
// GDS #191: forward the active window to the API. `all` returns everything;
|
|
1016
|
+
// numeric values restrict to a trailing N-day window server-side.
|
|
1017
|
+
const url = `/api/bongos/public/recent-shipped?days=${encodeURIComponent(_shippedRange)}`;
|
|
1018
|
+
const data = await fetchJson(url);
|
|
1019
|
+
_shippedItems = data.tasks || [];
|
|
1020
|
+
renderShippedPage();
|
|
1021
|
+
// #409 efficiency follow-up: piggyback the 7-day count for the headline
|
|
1022
|
+
// strip onto this fetch instead of a separate `?days=7` round trip.
|
|
1023
|
+
// Range options are 7|30|90|all — every choice covers at least 7 days, so
|
|
1024
|
+
// _shippedItems is always a superset of the last 7 days; client-side
|
|
1025
|
+
// filtering is exact. Avoids fetching the same task list twice per cycle.
|
|
1026
|
+
updateHeadlineShippedFromItems();
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function updateHeadlineShippedFromItems() {
|
|
1030
|
+
const el = document.getElementById('headline-shipped-num');
|
|
1031
|
+
if (!el) return;
|
|
1032
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
1033
|
+
let count = 0;
|
|
1034
|
+
for (const t of _shippedItems) {
|
|
1035
|
+
if (!t.shipped_at) continue;
|
|
1036
|
+
const ms = new Date(t.shipped_at).getTime();
|
|
1037
|
+
if (Number.isFinite(ms) && ms >= sevenDaysAgo) count++;
|
|
1038
|
+
}
|
|
1039
|
+
el.textContent = String(count);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// ---------- leaderboard ----------
|
|
1043
|
+
|
|
1044
|
+
// Set is locked at 8 for V2 (per limitations/pms-v2.md), so hardcoding the
|
|
1045
|
+
// emoji map here is cheaper than another API round-trip per page load.
|
|
1046
|
+
// Mirrors public-builders/builders.js — keep both in sync if the catalog
|
|
1047
|
+
// ever shifts (would require an ADR + version bump per the V2 contract).
|
|
1048
|
+
const ACH_EMOJI = {
|
|
1049
|
+
'first-ship': '🚢',
|
|
1050
|
+
'first-claim': '🪙',
|
|
1051
|
+
'first-parallel-claim': '⚓',
|
|
1052
|
+
'streak-3': '🔥',
|
|
1053
|
+
'streak-7': '🌋',
|
|
1054
|
+
'art-pipeline-shipper': '🎨',
|
|
1055
|
+
'gds-shipper': '⚙️',
|
|
1056
|
+
'breaking-the-100c-bar': '💯',
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
function renderAchievementRow(ids) {
|
|
1060
|
+
if (!Array.isArray(ids) || ids.length === 0) return null;
|
|
1061
|
+
const row = document.createElement('span');
|
|
1062
|
+
row.className = 'leader__achievements';
|
|
1063
|
+
row.setAttribute('aria-label', 'achievements');
|
|
1064
|
+
for (const id of ids) {
|
|
1065
|
+
const emoji = ACH_EMOJI[id];
|
|
1066
|
+
if (!emoji) continue;
|
|
1067
|
+
const span = document.createElement('span');
|
|
1068
|
+
span.className = 'leader__ach';
|
|
1069
|
+
span.title = id;
|
|
1070
|
+
span.setAttribute('aria-label', id);
|
|
1071
|
+
span.textContent = emoji;
|
|
1072
|
+
row.appendChild(span);
|
|
1073
|
+
}
|
|
1074
|
+
return row.children.length > 0 ? row : null;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
async function loadLeaderboard() {
|
|
1078
|
+
const data = await fetchJson('/api/bongos/public/leaderboard');
|
|
1079
|
+
const rows = data.leaderboard || [];
|
|
1080
|
+
clear(els.leaderboard);
|
|
1081
|
+
if (rows.length === 0) {
|
|
1082
|
+
const li = document.createElement('li');
|
|
1083
|
+
li.className = 'empty';
|
|
1084
|
+
li.textContent = 'No builders yet — be the first.';
|
|
1085
|
+
els.leaderboard.appendChild(li);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
// The API enrichment (#188) already trims to top 10 by total_credits and
|
|
1089
|
+
// the catalog is locked at 8, so paging here is forward-compat. Today's
|
|
1090
|
+
// builder count is <10; we render the whole array as-is.
|
|
1091
|
+
for (const b of rows) {
|
|
1092
|
+
const li = document.createElement('li');
|
|
1093
|
+
const avatar = document.createElement('img');
|
|
1094
|
+
avatar.className = 'leader__avatar';
|
|
1095
|
+
avatar.src = b.avatar_url || '';
|
|
1096
|
+
avatar.alt = '';
|
|
1097
|
+
avatar.referrerPolicy = 'no-referrer';
|
|
1098
|
+
if (!b.avatar_url) avatar.style.visibility = 'hidden';
|
|
1099
|
+
const name = document.createElement('div');
|
|
1100
|
+
name.className = 'leader__name';
|
|
1101
|
+
name.textContent = b.display_name;
|
|
1102
|
+
const login = document.createElement('span');
|
|
1103
|
+
login.className = 'leader__login';
|
|
1104
|
+
login.textContent = `@${b.github_login}`;
|
|
1105
|
+
name.appendChild(login);
|
|
1106
|
+
const badges = renderAchievementRow(b.achievements);
|
|
1107
|
+
if (badges) name.appendChild(badges);
|
|
1108
|
+
const credits = document.createElement('div');
|
|
1109
|
+
credits.className = 'leader__credits';
|
|
1110
|
+
credits.textContent = `${(Number(b.total_credits) || 0).toLocaleString()}`;
|
|
1111
|
+
const unit = document.createElement('span');
|
|
1112
|
+
unit.className = 'leader__credits-unit';
|
|
1113
|
+
unit.textContent = curLabel();
|
|
1114
|
+
credits.appendChild(unit);
|
|
1115
|
+
li.appendChild(avatar);
|
|
1116
|
+
li.appendChild(name);
|
|
1117
|
+
li.appendChild(credits);
|
|
1118
|
+
els.leaderboard.appendChild(li);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// ---------- main ----------
|
|
1123
|
+
|
|
1124
|
+
// Stamp the banner with the current local time. Called at the START of each
|
|
1125
|
+
// refresh cycle (before any awaits) so the displayed time matches when the
|
|
1126
|
+
// round trip was initiated, not when it completed. Stamping early also avoids
|
|
1127
|
+
// a race where two concurrent refresh() calls stamp in the wrong order (the
|
|
1128
|
+
// slower first call could overwrite a more-recent stamp from the faster second
|
|
1129
|
+
// call). null-guarded: els.lastUpdated is set once at module load and should
|
|
1130
|
+
// always be valid, but the guard makes future refactors safe.
|
|
1131
|
+
function stampRefreshTime() {
|
|
1132
|
+
if (!els.lastUpdated) return;
|
|
1133
|
+
const t = new Date().toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
1134
|
+
els.lastUpdated.textContent = `tablet last consulted at ${t}`;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// ---------- the Foundry — ecosystem dev curve (#291, C5) ----------
|
|
1138
|
+
//
|
|
1139
|
+
// Public, individuals-stripped aggregate of the builder "AI dev curve":
|
|
1140
|
+
// works shipped per day and average time per work over 90 days, plus the
|
|
1141
|
+
// kinds-of-work fingerprint. Fed by /api/bongos/public/analytics/ecosystem.
|
|
1142
|
+
// Inline SVG sparklines — no chart library for this tile.
|
|
1143
|
+
|
|
1144
|
+
function foundrySparkline(values, color) {
|
|
1145
|
+
const W = 320, H = 54, pad = 4;
|
|
1146
|
+
const nums = (values || []).map((v) => (Number.isFinite(Number(v)) ? Number(v) : 0));
|
|
1147
|
+
if (nums.length === 0) return '';
|
|
1148
|
+
const min = Math.min(...nums), max = Math.max(...nums), span = max - min || 1;
|
|
1149
|
+
const n = nums.length;
|
|
1150
|
+
const x = (i) => (n === 1 ? W / 2 : pad + (i * (W - 2 * pad)) / (n - 1));
|
|
1151
|
+
const y = (v) => H - pad - ((v - min) / span) * (H - 2 * pad);
|
|
1152
|
+
const line = nums.map((v, i) => `${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(' ');
|
|
1153
|
+
const area = `${pad},${H - pad} ${line} ${(W - pad)},${H - pad}`;
|
|
1154
|
+
return `<svg class="foundry-spark" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="trend">
|
|
1155
|
+
<polyline class="foundry-spark__area" points="${area}" fill="${color}" />
|
|
1156
|
+
<polyline class="foundry-spark__line" points="${line}" fill="none" stroke="${color}" />
|
|
1157
|
+
</svg>`;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
async function loadEcosystem() {
|
|
1161
|
+
const data = await fetchJson('/api/bongos/public/analytics/ecosystem?days=90').catch(() => null);
|
|
1162
|
+
const shippedEl = document.getElementById('foundry-shipped');
|
|
1163
|
+
const buildersEl = document.getElementById('foundry-builders');
|
|
1164
|
+
const durationEl = document.getElementById('foundry-duration');
|
|
1165
|
+
const chartsEl = document.getElementById('foundry-charts');
|
|
1166
|
+
const kindsEl = document.getElementById('foundry-kinds');
|
|
1167
|
+
const emptyEl = document.getElementById('foundry-empty');
|
|
1168
|
+
if (!chartsEl) return;
|
|
1169
|
+
|
|
1170
|
+
const h = data?.headline || {};
|
|
1171
|
+
const total = h.total_shipped || 0;
|
|
1172
|
+
if (!data || total === 0) {
|
|
1173
|
+
if (emptyEl) emptyEl.hidden = false;
|
|
1174
|
+
if (shippedEl) shippedEl.textContent = '—';
|
|
1175
|
+
if (buildersEl) buildersEl.textContent = '—';
|
|
1176
|
+
if (durationEl) durationEl.textContent = '—';
|
|
1177
|
+
chartsEl.innerHTML = '';
|
|
1178
|
+
if (kindsEl) kindsEl.innerHTML = '';
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (emptyEl) emptyEl.hidden = true;
|
|
1182
|
+
if (shippedEl) shippedEl.textContent = total.toLocaleString();
|
|
1183
|
+
if (buildersEl) buildersEl.textContent = (h.active_builders ?? 0).toLocaleString();
|
|
1184
|
+
if (durationEl) durationEl.textContent = h.avg_duration_min ? `${Math.round(h.avg_duration_min)}m` : '—';
|
|
1185
|
+
|
|
1186
|
+
const shipVals = (data.ship_rate || []).map((r) => Number(r.n));
|
|
1187
|
+
const durVals = (data.duration_trend || []).map((r) => Number(r.avg_actual));
|
|
1188
|
+
chartsEl.innerHTML =
|
|
1189
|
+
`<div class="foundry__chart"><div class="foundry__chart-title">Works shipped / day</div>${foundrySparkline(shipVals, '#2f5a3f') || '<p class="empty">no data</p>'}</div>` +
|
|
1190
|
+
`<div class="foundry__chart"><div class="foundry__chart-title">Avg time per work (min)</div>${foundrySparkline(durVals, '#2d6e7e') || '<p class="empty">no data</p>'}</div>`;
|
|
1191
|
+
|
|
1192
|
+
const kinds = (data.top_kinds || []).slice(0, 6);
|
|
1193
|
+
if (kindsEl) {
|
|
1194
|
+
if (kinds.length) {
|
|
1195
|
+
const maxN = Math.max(...kinds.map((k) => Number(k.n)));
|
|
1196
|
+
kindsEl.innerHTML = kinds.map((k) => {
|
|
1197
|
+
const w = maxN ? Math.round((Number(k.n) / maxN) * 100) : 0;
|
|
1198
|
+
return `<div class="foundry__kind"><span class="foundry__kind-label">${k.kind}</span>` +
|
|
1199
|
+
`<span class="foundry__kind-bar"><span class="foundry__kind-fill" style="width:${w}%"></span></span>` +
|
|
1200
|
+
`<span class="foundry__kind-n">${k.n}</span></div>`;
|
|
1201
|
+
}).join('');
|
|
1202
|
+
} else {
|
|
1203
|
+
kindsEl.innerHTML = '';
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async function refresh() {
|
|
1209
|
+
// Stamp the banner BEFORE the async work so it shows when this refresh
|
|
1210
|
+
// started, not when it finished. Fixes the apparent "wrong time" when
|
|
1211
|
+
// API calls are slow or when concurrent refresh() calls race.
|
|
1212
|
+
stampRefreshTime();
|
|
1213
|
+
const tasks = [
|
|
1214
|
+
loadHeadline().catch((e) => console.error('headline', e)),
|
|
1215
|
+
loadWorldSnapshots().catch((e) => console.error('snapshots', e)),
|
|
1216
|
+
loadUptime().catch((e) => console.error('uptime', e)),
|
|
1217
|
+
loadVersions().catch((e) => console.error('versions', e)),
|
|
1218
|
+
loadCosts().catch((e) => console.error('costs', e)),
|
|
1219
|
+
loadShipped().catch((e) => console.error('shipped', e)),
|
|
1220
|
+
loadLeaderboard().catch((e) => console.error('leaderboard', e)),
|
|
1221
|
+
loadEcosystem().catch((e) => console.error('ecosystem', e)),
|
|
1222
|
+
];
|
|
1223
|
+
await Promise.all(tasks);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// #409: render the sticky nav as soon as the DOM is parsed. It doesn't
|
|
1227
|
+
// depend on any data — sections are static in the HTML — so it can light
|
|
1228
|
+
// up immediately instead of waiting for the first /api/bongos/public/* round
|
|
1229
|
+
// trip. The observer wires itself.
|
|
1230
|
+
renderNav();
|
|
1231
|
+
|
|
1232
|
+
refresh();
|
|
1233
|
+
setInterval(refresh, 60_000);
|
|
1234
|
+
|
|
1235
|
+
// Re-refresh immediately when the tab regains focus after being in the
|
|
1236
|
+
// background. Browsers throttle setInterval in hidden tabs, so the banner
|
|
1237
|
+
// timestamp and data can lag by many minutes. This brings the page up to date
|
|
1238
|
+
// the moment the viewer looks at it again.
|
|
1239
|
+
document.addEventListener('visibilitychange', () => {
|
|
1240
|
+
if (document.visibilityState === 'visible') refresh();
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
// ---------- deep-link resolver (#654) ----------
|
|
1244
|
+
// A bare doc ref like "#173" links into the Builders' Hall, but a viewer can also
|
|
1245
|
+
// land on the PUBLIC status dashboard at status.amazonprimea.com/#/idea/173 (or
|
|
1246
|
+
// /#/task/N, /#/blocker/N). Resolve it to a MINIMAL card — id + status + type
|
|
1247
|
+
// only; an idea/blocker title is NEVER shown here (the public endpoints withhold
|
|
1248
|
+
// it), and a task title only when the projection already marked it public
|
|
1249
|
+
// (shipped/confirmed). The card links into the hall, where a signed-in builder
|
|
1250
|
+
// sees the full record.
|
|
1251
|
+
const DEEPLINK_RE = /^#\/(task|idea|blocker)\/(\d+)$/;
|
|
1252
|
+
const HALL_ORIGIN = `${API || (typeof location !== 'undefined' ? location.origin.replace(/^(https?:\/\/)status\./, '$1') : '')}/builders`;
|
|
1253
|
+
|
|
1254
|
+
function dismissStatusDeepCard() {
|
|
1255
|
+
const c = document.getElementById('status-deeplink');
|
|
1256
|
+
if (c) c.remove();
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
function clearStatusDeepLink() {
|
|
1260
|
+
dismissStatusDeepCard();
|
|
1261
|
+
// Drop the hash so back/refresh doesn't immediately re-open the card.
|
|
1262
|
+
if (DEEPLINK_RE.test(location.hash)) {
|
|
1263
|
+
history.replaceState(null, '', location.pathname + location.search);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function renderStatusDeepCard(kind, id, state) {
|
|
1268
|
+
dismissStatusDeepCard();
|
|
1269
|
+
const mk = (tag, cls, text) => { const e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; };
|
|
1270
|
+
const overlay = document.createElement('div');
|
|
1271
|
+
overlay.id = 'status-deeplink';
|
|
1272
|
+
overlay.className = 'deeplink';
|
|
1273
|
+
overlay.setAttribute('role', 'dialog');
|
|
1274
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
1275
|
+
overlay.setAttribute('aria-label', `${kind} #${id}`);
|
|
1276
|
+
const panel = mk('div', 'deeplink__panel');
|
|
1277
|
+
|
|
1278
|
+
const close = mk('button', 'deeplink__close', '×');
|
|
1279
|
+
close.type = 'button';
|
|
1280
|
+
close.setAttribute('aria-label', 'Close');
|
|
1281
|
+
close.addEventListener('click', clearStatusDeepLink);
|
|
1282
|
+
panel.appendChild(close);
|
|
1283
|
+
|
|
1284
|
+
const label = `#${id}`;
|
|
1285
|
+
panel.appendChild(mk('p', 'deeplink__kind', kind.toUpperCase()));
|
|
1286
|
+
if (state.loading) {
|
|
1287
|
+
panel.appendChild(mk('p', 'deeplink__title', `Consulting the archive for ${label}…`));
|
|
1288
|
+
} else if (state.notFound) {
|
|
1289
|
+
panel.appendChild(mk('p', 'deeplink__title', `${label} — no such ${kind}`));
|
|
1290
|
+
panel.appendChild(mk('p', 'deeplink__note', 'Records number tasks, ideas, and blockers separately from GitHub pull requests — this may be a PR, not a record here.'));
|
|
1291
|
+
} else if (state.error) {
|
|
1292
|
+
panel.appendChild(mk('p', 'deeplink__title', `Could not resolve ${label}`));
|
|
1293
|
+
panel.appendChild(mk('p', 'deeplink__note', 'The archive could not be reached. Try again in a moment.'));
|
|
1294
|
+
} else {
|
|
1295
|
+
const r = state.ref || {};
|
|
1296
|
+
panel.appendChild(mk('p', 'deeplink__title', r.title ? `${label} — ${r.title}` : label));
|
|
1297
|
+
const meta = [];
|
|
1298
|
+
if (r.status) meta.push(r.status);
|
|
1299
|
+
if (r.kind) meta.push(r.kind);
|
|
1300
|
+
if (r.version_id) meta.push(r.version_id);
|
|
1301
|
+
if (r.priority != null) meta.push(`P${r.priority}`);
|
|
1302
|
+
if (meta.length) panel.appendChild(mk('p', 'deeplink__meta', meta.join(' · ')));
|
|
1303
|
+
}
|
|
1304
|
+
const a = mk('a', 'deeplink__link', 'Open in the Builders’ Hall →');
|
|
1305
|
+
a.href = `${HALL_ORIGIN}#/${kind}/${id}`;
|
|
1306
|
+
a.target = '_blank';
|
|
1307
|
+
a.rel = 'noopener';
|
|
1308
|
+
panel.appendChild(a);
|
|
1309
|
+
|
|
1310
|
+
overlay.appendChild(panel);
|
|
1311
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) clearStatusDeepLink(); });
|
|
1312
|
+
document.body.appendChild(overlay);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
async function resolveStatusDeepLink() {
|
|
1316
|
+
const m = DEEPLINK_RE.exec(location.hash);
|
|
1317
|
+
if (!m) { dismissStatusDeepCard(); return; }
|
|
1318
|
+
const kind = m[1];
|
|
1319
|
+
const id = Number(m[2]);
|
|
1320
|
+
renderStatusDeepCard(kind, id, { loading: true });
|
|
1321
|
+
try {
|
|
1322
|
+
const path = kind === 'task' ? `/api/bongos/public/tasks/${id}`
|
|
1323
|
+
: kind === 'idea' ? `/api/bongos/public/ideas/${id}`
|
|
1324
|
+
: `/api/bongos/public/blockers/${id}`;
|
|
1325
|
+
const res = await fetch(`${API}${path}`, { cache: 'no-store' });
|
|
1326
|
+
if (res.status === 404) { renderStatusDeepCard(kind, id, { notFound: true }); return; }
|
|
1327
|
+
if (!res.ok) { renderStatusDeepCard(kind, id, { error: true }); return; }
|
|
1328
|
+
const data = await res.json();
|
|
1329
|
+
const ref = data.task || data.idea || data.blocker || null;
|
|
1330
|
+
renderStatusDeepCard(kind, id, ref ? { ref } : { notFound: true });
|
|
1331
|
+
} catch (_) {
|
|
1332
|
+
renderStatusDeepCard(kind, id, { error: true });
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
window.addEventListener('hashchange', resolveStatusDeepLink);
|
|
1337
|
+
resolveStatusDeepLink();
|
|
1338
|
+
|
|
1339
|
+
// ---------- accessibility zoom (task 1475 / idea 280) ----------
|
|
1340
|
+
// A one-click "make everything bigger" control for the public ledger. Cycles
|
|
1341
|
+
// 100% -> 125% -> 150% -> 100% via CSS `zoom` on <html> (enlarges px + rem
|
|
1342
|
+
// uniformly), persisted per-browser. Mirrors the hall's shell.js zoom; this
|
|
1343
|
+
// surface has its own localStorage origin + no theme toggle to hang it off, so
|
|
1344
|
+
// it's a small self-contained control.
|
|
1345
|
+
(function ledgerZoom() {
|
|
1346
|
+
const ZOOM_KEY = 'ledger-zoom';
|
|
1347
|
+
const STEPS = [1, 1.25, 1.5];
|
|
1348
|
+
function stored() {
|
|
1349
|
+
try { const v = parseFloat(window.localStorage.getItem(ZOOM_KEY)); return STEPS.indexOf(v) >= 0 ? v : 1; }
|
|
1350
|
+
catch (_) { return 1; }
|
|
1351
|
+
}
|
|
1352
|
+
function apply() {
|
|
1353
|
+
const z = stored();
|
|
1354
|
+
document.documentElement.style.zoom = z === 1 ? '' : String(z);
|
|
1355
|
+
const btn = document.getElementById('ledger-zoom');
|
|
1356
|
+
if (!btn) return;
|
|
1357
|
+
const pct = Math.round(z * 100);
|
|
1358
|
+
const next = STEPS[(STEPS.indexOf(z) + 1) % STEPS.length];
|
|
1359
|
+
btn.setAttribute('aria-label', `Text size ${pct}%. Tap to change to ${Math.round(next * 100)}%.`);
|
|
1360
|
+
btn.title = btn.getAttribute('aria-label');
|
|
1361
|
+
btn.classList.toggle('ledger-zoom--on', z !== 1);
|
|
1362
|
+
btn.innerHTML =
|
|
1363
|
+
'<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">' +
|
|
1364
|
+
'<circle cx="7.5" cy="7.5" r="4.5"/><path d="M11 11l3.5 3.5M5.5 7.5h4M7.5 5.5v4"/></svg>' +
|
|
1365
|
+
(z !== 1 ? `<span class="ledger-zoom__pct">${pct}%</span>` : '');
|
|
1366
|
+
}
|
|
1367
|
+
function cycle() {
|
|
1368
|
+
const next = STEPS[(STEPS.indexOf(stored()) + 1) % STEPS.length];
|
|
1369
|
+
try { window.localStorage.setItem(ZOOM_KEY, String(next)); } catch (_) { /* private mode */ }
|
|
1370
|
+
apply();
|
|
1371
|
+
}
|
|
1372
|
+
const btn = document.getElementById('ledger-zoom');
|
|
1373
|
+
if (btn) btn.addEventListener('click', cycle);
|
|
1374
|
+
apply();
|
|
1375
|
+
})();
|