@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,1292 @@
|
|
|
1
|
+
// public-builders/work.js — the Work Board (/builders/work, served at
|
|
2
|
+
// builders.amazonprimea.com/work).
|
|
3
|
+
//
|
|
4
|
+
// Signed-in builders only. The page is the builder-facing SOURCE OF TRUTH for
|
|
5
|
+
// "works in progress"; it gathers two views the hall + status dashboard used to
|
|
6
|
+
// scatter:
|
|
7
|
+
//
|
|
8
|
+
// 1. WORKS IN PROGRESS — the live in-flight list, every open claim across all
|
|
9
|
+
// builders (active_claims from GET /api/bongos/tasks/claimable). Version-level
|
|
10
|
+
// progress + goals moved to the Roadmap page (task 1757).
|
|
11
|
+
// 2. WORKS WAITING TO BE CLAIMED — the claimable queue, filterable, with the
|
|
12
|
+
// copy-prompt action (same shape as the hall's Claim section).
|
|
13
|
+
//
|
|
14
|
+
// Read-only across the wire EXCEPT the per-task copy-prompt button, which is
|
|
15
|
+
// purely client-side (writes to the clipboard, no server call).
|
|
16
|
+
//
|
|
17
|
+
// Auth: the page itself is gated server-side (server.js requireBuilderPage), so
|
|
18
|
+
// an unauthenticated visitor never reaches this script — but on a 401 from any
|
|
19
|
+
// fetch (expired session) we still bounce into the web sign-in flow. The
|
|
20
|
+
// claimable feed is additionally primer-gated server-side; a signed-in builder
|
|
21
|
+
// who hasn't acknowledged the primer gets a friendly gate notice instead of the
|
|
22
|
+
// queue.
|
|
23
|
+
|
|
24
|
+
(() => {
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const API = '/api/bongos';
|
|
28
|
+
// Generated typed client (task 1996 / R09), served as window.BongosClient by
|
|
29
|
+
// /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
|
|
30
|
+
// builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
31
|
+
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
32
|
+
const AUTH_NEEDED = 'auth-needed';
|
|
33
|
+
|
|
34
|
+
// escapeHtml / $ / fmtNum / fmtDate / toast now come from the shared
|
|
35
|
+
// window.OTB toolkit (dom-utils.js, loaded first). Thin wrappers keep this
|
|
36
|
+
// file's exact variants (date-only fmtDate; a 2200ms clearing toast).
|
|
37
|
+
// fmtAgo / toAuth / getJSON / postJSON stay local below.
|
|
38
|
+
const { escapeHtml, $, fmtNum } = window.OTB;
|
|
39
|
+
const fmtDate = (iso) => window.OTB.fmtDate(iso);
|
|
40
|
+
const toast = (msg) => window.OTB.toast(msg, { duration: 2200, clear: true });
|
|
41
|
+
const curLabel = (caps) => { const l = (window.__BRANDING__ && window.__BRANDING__.currency && window.__BRANDING__.currency.label) || 'credits'; return caps ? l.charAt(0).toUpperCase() + l.slice(1) : l; };
|
|
42
|
+
|
|
43
|
+
// Coarse relative time ("just now", "3h", "2d") for how long a claim's been
|
|
44
|
+
// borne. Kept compact so the in-flight table stays narrow.
|
|
45
|
+
function fmtAgo(iso) {
|
|
46
|
+
if (!iso) return '—';
|
|
47
|
+
const then = new Date(iso).getTime();
|
|
48
|
+
if (Number.isNaN(then)) return '—';
|
|
49
|
+
const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
|
|
50
|
+
if (secs < 60) return 'just now';
|
|
51
|
+
const mins = Math.floor(secs / 60);
|
|
52
|
+
if (mins < 60) return `${mins}m`;
|
|
53
|
+
const hrs = Math.floor(mins / 60);
|
|
54
|
+
if (hrs < 24) return `${hrs}h`;
|
|
55
|
+
const days = Math.floor(hrs / 24);
|
|
56
|
+
return `${days}d`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toAuth() {
|
|
60
|
+
window.location.assign(`${API}/auth/web/start`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// getJSON with the hall's auth posture: softAuth surfaces a typed error so the
|
|
64
|
+
// caller can decide (here, /me uses it to render nothing fatal); everything
|
|
65
|
+
// else redirects into sign-in on a 401. A 403 is surfaced (not redirected) so
|
|
66
|
+
// the primer gate can be handled gracefully.
|
|
67
|
+
async function getJSON(path, opts = {}) {
|
|
68
|
+
const res = await api.request('GET', path);
|
|
69
|
+
if (res.status === 401) {
|
|
70
|
+
if (opts.softAuth) {
|
|
71
|
+
const err = new Error(AUTH_NEEDED);
|
|
72
|
+
err.code = AUTH_NEEDED;
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
toAuth();
|
|
76
|
+
throw new Error('redirecting to auth');
|
|
77
|
+
}
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const err = new Error(`${path} → ${res.status}`);
|
|
80
|
+
err.status = res.status;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
return res.data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Mutating call (claim / release). Cookie-authed, same posture as the settings
|
|
87
|
+
// page. Resolves to the parsed body on 2xx; on a non-2xx it throws an Error
|
|
88
|
+
// carrying { status, data } so callers can map the server's typed error code
|
|
89
|
+
// (ALREADY_CLAIMED, TOUCHES_CONFLICT, …) to a friendly message. A 401 still
|
|
90
|
+
// bounces to sign-in — a mutation with a dead session is unrecoverable here.
|
|
91
|
+
async function postJSON(path, body) {
|
|
92
|
+
const res = await api.request('POST', path, { body: body || {} });
|
|
93
|
+
if (res.status === 401) { toAuth(); throw new Error('redirecting to auth'); }
|
|
94
|
+
let data = null;
|
|
95
|
+
try { data = res.data; } catch (_) { /* empty/non-JSON body */ }
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
const err = new Error(`${path} → ${res.status}`);
|
|
98
|
+
err.status = res.status;
|
|
99
|
+
err.data = data || {};
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- paginated lists: per-page dropdown + Prev/Next (task 1931/1955) -------
|
|
106
|
+
// All four Work-page lists (In Progress, Ready, Completed, Backlog) paginate
|
|
107
|
+
// the same way, through one shared helper. Each list owns a `pager` (page +
|
|
108
|
+
// per-page, persisted to its own localStorage key) and a DOM id `prefix`;
|
|
109
|
+
// renderPagerBar paints `#<prefix>-{pager,range,page,prev,next,per-page}`.
|
|
110
|
+
// Page/per-page live in module scope so a background poll / action refresh
|
|
111
|
+
// re-renders on the SAME page — only an explicit filter/per-page change resets.
|
|
112
|
+
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
|
|
113
|
+
const MIN_PER_PAGE = PER_PAGE_OPTIONS[0]; // ≤ this fits one page — hide the bar
|
|
114
|
+
const DEFAULT_PER_PAGE = 25;
|
|
115
|
+
function loadPerPage(key, fallback) {
|
|
116
|
+
try {
|
|
117
|
+
const v = Number(window.localStorage.getItem(key));
|
|
118
|
+
return PER_PAGE_OPTIONS.includes(v) ? v : fallback;
|
|
119
|
+
} catch (_) { return fallback; }
|
|
120
|
+
}
|
|
121
|
+
function makePager(prefix, key) {
|
|
122
|
+
return { prefix, key, page: 1, perPage: loadPerPage(key, DEFAULT_PER_PAGE) };
|
|
123
|
+
}
|
|
124
|
+
const pagers = {
|
|
125
|
+
wip: makePager('wip', 'otb.work.wipPerPage'),
|
|
126
|
+
ready: makePager('ready', 'otb.work.readyPerPage'),
|
|
127
|
+
done: makePager('done', 'otb.work.donePerPage'),
|
|
128
|
+
backlog: makePager('backlog', 'otb.work.backlogPerPage'),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// A section-header count pill: reveal it and set its number once data lands.
|
|
132
|
+
function setCountPill(sel, n) {
|
|
133
|
+
const el = $(sel);
|
|
134
|
+
if (!el) return;
|
|
135
|
+
el.hidden = false;
|
|
136
|
+
el.textContent = String(n);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Slice `list` to the pager's current page (clamped — the filtered list may
|
|
140
|
+
// have shrunk under a poll), paint the bar, and return the slice to render.
|
|
141
|
+
// The bar hides when the whole list fits one minimum page.
|
|
142
|
+
function paginate(list, p) {
|
|
143
|
+
const total = list.length;
|
|
144
|
+
const totalPages = Math.max(1, Math.ceil(total / p.perPage));
|
|
145
|
+
if (p.page > totalPages) p.page = totalPages;
|
|
146
|
+
if (p.page < 1) p.page = 1;
|
|
147
|
+
const start = (p.page - 1) * p.perPage;
|
|
148
|
+
const slice = list.slice(start, start + p.perPage);
|
|
149
|
+
renderPagerBar(p, total, totalPages, start, slice.length);
|
|
150
|
+
return slice;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderPagerBar(p, total, totalPages, start, shownCount) {
|
|
154
|
+
const pager = $(`#${p.prefix}-pager`);
|
|
155
|
+
if (!pager) return;
|
|
156
|
+
if (total <= MIN_PER_PAGE) { pager.hidden = true; return; }
|
|
157
|
+
pager.hidden = false;
|
|
158
|
+
const range = $(`#${p.prefix}-range`);
|
|
159
|
+
if (range) range.textContent = total
|
|
160
|
+
? `Showing ${start + 1}–${start + shownCount} of ${total}`
|
|
161
|
+
: 'Showing 0 of 0';
|
|
162
|
+
const ind = $(`#${p.prefix}-page`);
|
|
163
|
+
if (ind) ind.textContent = `Page ${p.page} of ${totalPages}`;
|
|
164
|
+
const prev = $(`#${p.prefix}-prev`);
|
|
165
|
+
if (prev) prev.disabled = p.page <= 1;
|
|
166
|
+
const next = $(`#${p.prefix}-next`);
|
|
167
|
+
if (next) next.disabled = p.page >= totalPages;
|
|
168
|
+
const sel = $(`#${p.prefix}-per-page`);
|
|
169
|
+
if (sel && sel.value !== String(p.perPage)) sel.value = String(p.perPage);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Wire a pager's controls once (per-page dropdown + Prev/Next). `rerender`
|
|
173
|
+
// re-renders that list; a per-page change resets to page 1 and persists.
|
|
174
|
+
function wirePager(p, rerender) {
|
|
175
|
+
$(`#${p.prefix}-per-page`)?.addEventListener('change', (e) => {
|
|
176
|
+
const v = Number(e.target.value);
|
|
177
|
+
if (!PER_PAGE_OPTIONS.includes(v)) return;
|
|
178
|
+
p.perPage = v; p.page = 1;
|
|
179
|
+
try { window.localStorage.setItem(p.key, String(v)); } catch (_) { /* private mode → in-memory only */ }
|
|
180
|
+
rerender();
|
|
181
|
+
});
|
|
182
|
+
$(`#${p.prefix}-prev`)?.addEventListener('click', () => { if (p.page > 1) { p.page -= 1; rerender(); } });
|
|
183
|
+
$(`#${p.prefix}-next`)?.addEventListener('click', () => { p.page += 1; rerender(); });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---- 1a. live in-flight list (active claims) -----------------------------
|
|
187
|
+
|
|
188
|
+
function renderInFlight(active) {
|
|
189
|
+
if (Array.isArray(active)) liveState.active = active;
|
|
190
|
+
const tbody = $('#wip-table tbody');
|
|
191
|
+
if (!tbody) return;
|
|
192
|
+
// task 1899: sort the viewer's own claims first (mine-first), preserving
|
|
193
|
+
// server order within each group (JS sort is stable). The count pill always
|
|
194
|
+
// reflects the TOTAL active-claim count, even on the empty path below.
|
|
195
|
+
const all = liveState.active;
|
|
196
|
+
const isMine = (c) => viewer.id != null && Number(c.builder_id) === Number(viewer.id);
|
|
197
|
+
const rows = all.slice().sort((a, b) => (isMine(b) ? 1 : 0) - (isMine(a) ? 1 : 0));
|
|
198
|
+
setCountPill('#wip-count', all.length);
|
|
199
|
+
if (!rows.length) {
|
|
200
|
+
tbody.innerHTML = `<tr><td colspan="6" class="ledger__empty">No tasks are in progress right now.</td></tr>`;
|
|
201
|
+
pagers.wip.page = 1;
|
|
202
|
+
const wp = $('#wip-pager');
|
|
203
|
+
if (wp) wp.hidden = true;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const shown = paginate(rows, pagers.wip);
|
|
207
|
+
tbody.innerHTML = shown.map((c) => {
|
|
208
|
+
const who = escapeHtml(c.display_name || c.github_login || 'a builder');
|
|
209
|
+
const handle = c.github_login && c.display_name && c.display_name !== c.github_login
|
|
210
|
+
? ` <small>@${escapeHtml(c.github_login)}</small>`
|
|
211
|
+
: '';
|
|
212
|
+
const ver = c.version_id ? `<span class="task__tag">${escapeHtml(c.version_id)}</span>` : '';
|
|
213
|
+
const mine = viewer.id != null && Number(c.builder_id) === Number(viewer.id);
|
|
214
|
+
const ageTitle = c.claimed_at ? ` title="claimed ${escapeHtml(fmtDate(c.claimed_at))}"` : '';
|
|
215
|
+
// task 1759: bulk-release checkbox — offered ONLY on the viewer's own rows,
|
|
216
|
+
// same scoping rule as the single Release button below.
|
|
217
|
+
const claimIdStr = escapeHtml(String(c.claim_id));
|
|
218
|
+
const selectCb = mine
|
|
219
|
+
? `<input type="checkbox" class="wip__select" data-claim-id="${claimIdStr}" data-task-id="${escapeHtml(String(c.task_id))}" aria-label="Select task ${escapeHtml(String(c.task_id))} for bulk release"${selection.release.has(claimIdStr) ? ' checked' : ''}>`
|
|
220
|
+
: '';
|
|
221
|
+
// Release is offered ONLY on the viewer's own claims (the server scopes it
|
|
222
|
+
// too via NOT_YOUR_CLAIM, but we don't dangle a button others can't use).
|
|
223
|
+
const action = mine
|
|
224
|
+
? `<button class="wip__release" type="button" data-claim-id="${claimIdStr}" data-task-id="${escapeHtml(String(c.task_id))}">Release</button>`
|
|
225
|
+
: '<span class="wip__mine-none" aria-hidden="true">—</span>';
|
|
226
|
+
return `
|
|
227
|
+
<tr${mine ? ' class="wip__row--mine"' : ''}>
|
|
228
|
+
<td class="wip__select-cell">${selectCb}</td>
|
|
229
|
+
<td>#${escapeHtml(String(c.task_id))}</td>
|
|
230
|
+
<td>${escapeHtml(c.title || '(untitled)')} ${ver}${mine ? ' <span class="task__tag task__tag--mine">yours</span>' : ''}</td>
|
|
231
|
+
<td class="wip-builder">${who}${handle}</td>
|
|
232
|
+
<td${ageTitle}>${escapeHtml(fmtAgo(c.claimed_at))}</td>
|
|
233
|
+
<td class="wip__action">${action}</td>
|
|
234
|
+
</tr>
|
|
235
|
+
`;
|
|
236
|
+
}).join('');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---- 2. claimable queue (filter + sort + copy prompt) --------------------
|
|
240
|
+
|
|
241
|
+
const taskState = { all: [], versions: [], backlog: [] };
|
|
242
|
+
// Live view context. viewerId lets renderInFlight decide which active claims
|
|
243
|
+
// are the viewer's own (→ a Release action). active holds the last in-flight
|
|
244
|
+
// payload so a poll can re-render claim ages without another round-trip.
|
|
245
|
+
const viewer = { id: null };
|
|
246
|
+
const liveState = { active: [] };
|
|
247
|
+
// Guards against double-firing an action while its request is in flight.
|
|
248
|
+
const pending = new Set();
|
|
249
|
+
|
|
250
|
+
// task 1759: multi-select + the bottom bulk toolbar. `claim` holds
|
|
251
|
+
// String(task_id) from the claimable queue; `release` maps
|
|
252
|
+
// String(claim_id) -> task_id for the viewer's OWN in-flight claims (release
|
|
253
|
+
// is only ever offered on those rows to begin with — see renderInFlight).
|
|
254
|
+
const selection = { claim: new Set(), release: new Map() };
|
|
255
|
+
// Last server dry-run (POST /claims/batch/validate) result, keyed by the
|
|
256
|
+
// sorted selected-id set so an unrelated re-render doesn't refire it.
|
|
257
|
+
const validateCache = { key: '', parallelSafe: null, byId: new Map() };
|
|
258
|
+
let validateDebounce = null;
|
|
259
|
+
// Per-task dependency edges for the "can these be sequenced?" cycle check —
|
|
260
|
+
// fetched lazily via GET /tasks/:id (task-detail.js's call) since the
|
|
261
|
+
// claimable feed doesn't carry dependencies[] per row. Stable for a ready
|
|
262
|
+
// task within one page session, so caching by id is safe.
|
|
263
|
+
const depsCache = new Map(); // task_id -> [dependency task_id, ...]
|
|
264
|
+
const sequentialCache = { key: '', order: null };
|
|
265
|
+
let sequentialDebounce = null;
|
|
266
|
+
|
|
267
|
+
function creditsPerMinute(t) {
|
|
268
|
+
const c = Number(t.credits_reward);
|
|
269
|
+
const m = Number(t.est_minutes);
|
|
270
|
+
if (!c || !m || m <= 0) return -Infinity;
|
|
271
|
+
return c / m;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function priorityRank(p) {
|
|
275
|
+
if (!p) return 99;
|
|
276
|
+
const m = /^P(\d+)/i.exec(String(p));
|
|
277
|
+
return m ? Number(m[1]) : 99;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// task 1756: two tasks can't run in parallel if their touches[] overlap — exact
|
|
281
|
+
// path match or a directory-prefix either way (mirrors the server's touchesOverlap
|
|
282
|
+
// / the goals-page conflict estimate). Empty touches never conflict.
|
|
283
|
+
function touchesConflict(aList, bList) {
|
|
284
|
+
const norm = (p) => String(p).replace(/\/+$/, '');
|
|
285
|
+
for (const a of (Array.isArray(aList) ? aList : [])) {
|
|
286
|
+
const na = norm(a);
|
|
287
|
+
for (const b of (Array.isArray(bList) ? bList : [])) {
|
|
288
|
+
const nb = norm(b);
|
|
289
|
+
if (na === nb || na.startsWith(nb + '/') || nb.startsWith(na + '/')) return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// task 1752 (+ 1498 / ADR 0084): the minimum rank to claim a task, as a tag shown
|
|
296
|
+
// on EVERY task row. 'xenos' is the open default → a muted "Any rank" tag (open to
|
|
297
|
+
// all, not a warning); 'archon' shows bare; other ranks show 'Rank+'. Kept here for
|
|
298
|
+
// now; the shared task-detail view (task 1753) will lift this into the common
|
|
299
|
+
// toolkit so every task surface renders rank identically.
|
|
300
|
+
function rankTagHtml(requiresRank) {
|
|
301
|
+
const rr = (requiresRank || 'xenos').toLowerCase();
|
|
302
|
+
if (rr === 'xenos') {
|
|
303
|
+
return '<span class="task__tag task__tag--rank-open" title="No rank floor — any builder can claim this task">Any rank</span>';
|
|
304
|
+
}
|
|
305
|
+
const label = rr === 'archon' ? 'Archon' : rr.charAt(0).toUpperCase() + rr.slice(1) + '+';
|
|
306
|
+
return `<span class="task__tag task__tag--rank" title="Minimum rank required to claim this task">${escapeHtml(label)}</span>`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// task 1829: free-text search over the claimable/backlog lists. Matches the
|
|
310
|
+
// query (case-insensitive substring) against the task id (with or without a
|
|
311
|
+
// leading '#'), the title, and the goal title (taskState.goalTitles — the
|
|
312
|
+
// same map that labels the Goal filter, task 1755). Empty query matches all,
|
|
313
|
+
// so clearing the box restores the full (still dropdown-filtered) list.
|
|
314
|
+
function searchQuery() {
|
|
315
|
+
const el = $('#f-search');
|
|
316
|
+
return el ? el.value.trim().toLowerCase() : '';
|
|
317
|
+
}
|
|
318
|
+
function matchesSearch(t, q) {
|
|
319
|
+
if (!q) return true;
|
|
320
|
+
const needle = q.replace(/^#/, '');
|
|
321
|
+
if (!needle) return true;
|
|
322
|
+
if (String(t.id).includes(needle)) return true;
|
|
323
|
+
if ((t.title || '').toLowerCase().includes(needle)) return true;
|
|
324
|
+
const goalTitle = (taskState.goalTitles || {})[String(t.goal_id)] || '';
|
|
325
|
+
if (goalTitle.toLowerCase().includes(needle)) return true;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function renderClaimable() {
|
|
330
|
+
const root = $('#task-list');
|
|
331
|
+
if (!root) return;
|
|
332
|
+
const vFilter = $('#f-version').value;
|
|
333
|
+
const gFilter = $('#f-goal') ? $('#f-goal').value : '';
|
|
334
|
+
const kFilter = $('#f-kind').value;
|
|
335
|
+
const dFilter = $('#f-discipline').value;
|
|
336
|
+
const nFilter = $('#f-newcomer') ? $('#f-newcomer').value : '';
|
|
337
|
+
const sortBy = $('#f-sort').value;
|
|
338
|
+
const sQuery = searchQuery();
|
|
339
|
+
|
|
340
|
+
let list = taskState.all.filter((t) => {
|
|
341
|
+
if (!matchesSearch(t, sQuery)) return false;
|
|
342
|
+
if (vFilter && t.version_id !== vFilter) return false;
|
|
343
|
+
// task 1755: goal filter. '__none__' isolates tasks with no goal.
|
|
344
|
+
if (gFilter === '__none__' && t.goal_id != null) return false;
|
|
345
|
+
if (gFilter && gFilter !== '__none__' && String(t.goal_id) !== gFilter) return false;
|
|
346
|
+
if (kFilter && t.kind !== kFilter) return false;
|
|
347
|
+
if (dFilter && (t.discipline || 'unclassified') !== dFilter) return false;
|
|
348
|
+
if (nFilter === 'yes' && !t.newcomer_friendly) return false;
|
|
349
|
+
return true;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// task 1756: run-mode filter — a task is "parallel-safe" when its touches[]
|
|
353
|
+
// don't overlap any OTHER task in the currently-visible set; "conflict" is the
|
|
354
|
+
// complement (would collide, so run them sequentially). Computed over the
|
|
355
|
+
// already-filtered list so it answers "of what I'm looking at, what can run
|
|
356
|
+
// together." O(n²) over touches — fine at board scale.
|
|
357
|
+
const fitFilter = $('#f-fit') ? $('#f-fit').value : '';
|
|
358
|
+
if (fitFilter === 'parallel' || fitFilter === 'conflict') {
|
|
359
|
+
const base = list;
|
|
360
|
+
list = base.filter((t) => {
|
|
361
|
+
const conflicts = base.some((o) => o !== t && touchesConflict(t.touches, o.touches));
|
|
362
|
+
return fitFilter === 'parallel' ? !conflicts : conflicts;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (sortBy === 'cpm') {
|
|
367
|
+
list = list.slice().sort((a, b) => creditsPerMinute(b) - creditsPerMinute(a));
|
|
368
|
+
} else if (sortBy === 'credits') {
|
|
369
|
+
list = list.slice().sort((a, b) => Number(b.credits_reward || 0) - Number(a.credits_reward || 0));
|
|
370
|
+
} else if (sortBy === 'minutes') {
|
|
371
|
+
list = list.slice().sort((a, b) => (Number(a.est_minutes) || Infinity) - (Number(b.est_minutes) || Infinity));
|
|
372
|
+
} else if (sortBy === 'priority') {
|
|
373
|
+
list = list.slice().sort((a, b) => priorityRank(a.priority) - priorityRank(b.priority));
|
|
374
|
+
} else if (sortBy === 'id') {
|
|
375
|
+
// task 1830: sort by task number, ascending (lowest/oldest first).
|
|
376
|
+
list = list.slice().sort((a, b) => Number(a.id) - Number(b.id));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const count = $('#claim-count');
|
|
380
|
+
if (count) count.textContent = `${list.length} ${list.length === 1 ? 'task' : 'tasks'} waiting`;
|
|
381
|
+
// task 1899: header count pill reflects the filtered-list length.
|
|
382
|
+
setCountPill('#ready-count', list.length);
|
|
383
|
+
|
|
384
|
+
if (!list.length) {
|
|
385
|
+
root.innerHTML = `<div class="profile__placeholder">No tasks match these filters. Try widening them.</div>`;
|
|
386
|
+
pagers.ready.page = 1;
|
|
387
|
+
const pager = $('#ready-pager');
|
|
388
|
+
if (pager) pager.hidden = true;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// task 1931/1955: paginate via the shared helper. renderClaimable never
|
|
393
|
+
// RESETS the page (only an explicit filter/sort/per-page change does), so a
|
|
394
|
+
// background poll keeps the viewer on their current page.
|
|
395
|
+
const shown = paginate(list, pagers.ready);
|
|
396
|
+
root.innerHTML = shown.map((t) => {
|
|
397
|
+
const cpm = creditsPerMinute(t);
|
|
398
|
+
const cpmText = Number.isFinite(cpm) ? `${cpm.toFixed(2)} d/min` : '— d/min';
|
|
399
|
+
const minutesText = t.est_minutes ? `${t.est_minutes}m` : '—';
|
|
400
|
+
const discipline = t.discipline || 'unclassified';
|
|
401
|
+
const disciplineTag = discipline !== 'unclassified'
|
|
402
|
+
? `<span class="task__tag">${escapeHtml(discipline)}</span>`
|
|
403
|
+
: '';
|
|
404
|
+
const newcomerTag = t.newcomer_friendly
|
|
405
|
+
? '<span class="task__tag task__tag--newcomer">newcomer</span>'
|
|
406
|
+
: '';
|
|
407
|
+
const rankTag = rankTagHtml(t.requires_rank);
|
|
408
|
+
const touches = Array.isArray(t.touches) ? t.touches : [];
|
|
409
|
+
const touchesLine = touches.length
|
|
410
|
+
? `<div class="task__touches" title="Files this task is responsible for">✎ ${touches.map(escapeHtml).join(', ')}</div>`
|
|
411
|
+
: '';
|
|
412
|
+
const selected = selection.claim.has(String(t.id));
|
|
413
|
+
return `
|
|
414
|
+
<div class="task${selected ? ' task--selected' : ''}" data-task-id="${t.id}">
|
|
415
|
+
<div class="task__id">
|
|
416
|
+
<input type="checkbox" class="task__select" data-task-id="${t.id}" aria-label="Select task ${t.id} for bulk actions"${selected ? ' checked' : ''}>
|
|
417
|
+
<span>#${t.id}</span>
|
|
418
|
+
<button class="task__open" type="button" data-task-id="${t.id}" title="Open task details" aria-label="Open details for task ${t.id}">⤢</button>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="task__body">
|
|
421
|
+
<p class="task__title">${escapeHtml(t.title)}</p>
|
|
422
|
+
<div class="task__meta">
|
|
423
|
+
<span class="task__tag">${escapeHtml(t.version_id || '?')}</span>
|
|
424
|
+
<span class="task__tag">${escapeHtml(t.kind || 'unclassified')}</span>
|
|
425
|
+
${disciplineTag}
|
|
426
|
+
${newcomerTag}
|
|
427
|
+
${rankTag}
|
|
428
|
+
${t.priority ? `<span class="task__tag">${escapeHtml(t.priority)}</span>` : ''}
|
|
429
|
+
<span>${minutesText}</span>
|
|
430
|
+
<span>${cpmText}</span>
|
|
431
|
+
</div>
|
|
432
|
+
${touchesLine}
|
|
433
|
+
</div>
|
|
434
|
+
<div class="task__credits">
|
|
435
|
+
<span class="task__credits-num">${fmtNum(window.OTBBranding?.taskCreditValue?.(t) ?? t.credits_reward)}</span>
|
|
436
|
+
<span class="task__credits-label">${window.OTBBranding?.taskCreditLabel?.(t) ?? ((window.OTBBranding?.currencyLabel?.() ?? 'credits') + ' Est.')}</span>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="task__actions">
|
|
439
|
+
<button class="task__claim" type="button" data-task-id="${t.id}" aria-label="Claim task ${t.id}">Claim</button>
|
|
440
|
+
<button class="task__copy" type="button" data-task-id="${t.id}" title="Copy the task prompt to your clipboard" aria-label="Copy prompt for task ${t.id}"><span class="task__copy-icon" aria-hidden="true">📋</span><span class="task__copy-label">Copy prompt</span></button>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
`;
|
|
444
|
+
}).join('');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function populateVersionFilter(versions) {
|
|
448
|
+
const sel = $('#f-version');
|
|
449
|
+
if (!sel) return;
|
|
450
|
+
for (const v of versions) {
|
|
451
|
+
const opt = document.createElement('option');
|
|
452
|
+
opt.value = v.id;
|
|
453
|
+
opt.textContent = v.id;
|
|
454
|
+
if (v.name && v.name !== v.id) opt.title = v.name;
|
|
455
|
+
sel.appendChild(opt);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// task 1755: the Goal filter — options are the goals present on the claimable
|
|
460
|
+
// feed, labeled by title (falls back to 'Goal #id' before titles load).
|
|
461
|
+
// '__none__' isolates ungoaled tasks. Populated once after the feed loads.
|
|
462
|
+
function populateGoalFilter() {
|
|
463
|
+
const sel = $('#f-goal');
|
|
464
|
+
if (!sel) return;
|
|
465
|
+
const cur = sel.value;
|
|
466
|
+
const titles = taskState.goalTitles || {};
|
|
467
|
+
const ids = [...new Set(taskState.all.map((t) => t.goal_id).filter((x) => x != null).map(String))];
|
|
468
|
+
ids.sort((a, b) => String(titles[a] || `Goal #${a}`).localeCompare(String(titles[b] || `Goal #${b}`)));
|
|
469
|
+
sel.length = 1; // keep the leading "— all goals —" option
|
|
470
|
+
for (const id of ids) {
|
|
471
|
+
const opt = document.createElement('option');
|
|
472
|
+
opt.value = id;
|
|
473
|
+
opt.textContent = titles[id] || `Goal #${id}`;
|
|
474
|
+
sel.appendChild(opt);
|
|
475
|
+
}
|
|
476
|
+
if (taskState.all.some((t) => t.goal_id == null)) {
|
|
477
|
+
const opt = document.createElement('option');
|
|
478
|
+
opt.value = '__none__';
|
|
479
|
+
opt.textContent = 'No goal';
|
|
480
|
+
sel.appendChild(opt);
|
|
481
|
+
}
|
|
482
|
+
if (cur) sel.value = cur;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Editors and the Claude desktop/CLI app autolink any bare "#NNN" in a session
|
|
486
|
+
// transcript to the dev box's GitHub remote (…/issues/NNN), which 404s because
|
|
487
|
+
// our records number tasks separately from GitHub. So the scroll we hand the
|
|
488
|
+
// builder must never carry a bare "#NNN": write "task NNN" + the clickable hall
|
|
489
|
+
// URL, and strip "#NNN" out of the (free-text) task description too.
|
|
490
|
+
function deHashRefs(text) {
|
|
491
|
+
return String(text == null ? '' : text).replace(/#(\d{1,5})\b/g, 'task $1');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function buildPromptForTask(t) {
|
|
495
|
+
const desc = deHashRefs((t.description || '').trim());
|
|
496
|
+
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
497
|
+
const hallUrl = `${hallBase}#/task/${t.id}`;
|
|
498
|
+
return [
|
|
499
|
+
`I am working on GDS task ${t.id} (${t.title}) — ${hallUrl}`,
|
|
500
|
+
`Read the task description, follow its acceptance criteria, stay strictly within touches[], and ship via /builder-ship when done.`,
|
|
501
|
+
``,
|
|
502
|
+
`Task description:`,
|
|
503
|
+
desc || '(no description)',
|
|
504
|
+
].join('\n');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function copyPromptForTask(taskId, btn) {
|
|
508
|
+
const t = taskState.all.find((x) => String(x.id) === String(taskId));
|
|
509
|
+
if (!t) return;
|
|
510
|
+
const prompt = buildPromptForTask(t);
|
|
511
|
+
const icon = btn.querySelector('.task__copy-icon');
|
|
512
|
+
const label = btn.querySelector('.task__copy-label');
|
|
513
|
+
try {
|
|
514
|
+
await navigator.clipboard.writeText(prompt);
|
|
515
|
+
btn.dataset.copied = '1';
|
|
516
|
+
if (icon) icon.textContent = '✓';
|
|
517
|
+
if (label) label.textContent = 'Copied';
|
|
518
|
+
setTimeout(() => {
|
|
519
|
+
btn.dataset.copied = '0';
|
|
520
|
+
if (icon) icon.textContent = '📋';
|
|
521
|
+
if (label) label.textContent = 'Copy prompt';
|
|
522
|
+
}, 1600);
|
|
523
|
+
toast(`Prompt for #${taskId} copied to your clipboard.`);
|
|
524
|
+
} catch (err) {
|
|
525
|
+
try {
|
|
526
|
+
const ta = document.createElement('textarea');
|
|
527
|
+
ta.value = prompt;
|
|
528
|
+
ta.style.position = 'fixed';
|
|
529
|
+
ta.style.opacity = '0';
|
|
530
|
+
document.body.appendChild(ta);
|
|
531
|
+
ta.select();
|
|
532
|
+
document.execCommand('copy');
|
|
533
|
+
ta.remove();
|
|
534
|
+
toast(`Prompt for #${taskId} copied.`);
|
|
535
|
+
} catch (_) {
|
|
536
|
+
toast('Could not copy to the clipboard. Try again.');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ---- claim / release actions ---------------------------------------------
|
|
542
|
+
|
|
543
|
+
// Map the server's typed error code to a builder-readable line. The codes are
|
|
544
|
+
// exactly those POST /claims and POST /claims/:id/resolve can return.
|
|
545
|
+
function friendlyActionError(status, data) {
|
|
546
|
+
const code = data && data.error;
|
|
547
|
+
switch (code) {
|
|
548
|
+
case 'ALREADY_CLAIMED':
|
|
549
|
+
return 'Someone else claimed that task first. The board will refresh.';
|
|
550
|
+
case 'TASK_NOT_READY':
|
|
551
|
+
return 'That task is no longer ready to be claimed.';
|
|
552
|
+
case 'TOUCHES_CONFLICT': {
|
|
553
|
+
const titles = Array.isArray(data.conflicts) ? data.conflicts.map((c) => c.title).filter(Boolean) : [];
|
|
554
|
+
return titles.length
|
|
555
|
+
? `That task overlaps an active claim: ${titles.join('; ')}. Wait for it to ship.`
|
|
556
|
+
: 'That task overlaps another active claim. Wait for it to ship.';
|
|
557
|
+
}
|
|
558
|
+
case 'DEPS_NOT_SHIPPED':
|
|
559
|
+
return 'That task waits on unshipped dependencies. Pick another for now.';
|
|
560
|
+
case 'rank_too_low_for_task':
|
|
561
|
+
case 'XENOS_RESTRICTED':
|
|
562
|
+
case 'INSUFFICIENT_RANK':
|
|
563
|
+
return 'Your rank is not yet high enough for that task.';
|
|
564
|
+
case 'newcomer_must_ship_first':
|
|
565
|
+
return data.what_to_do || 'Ship your current claim before opening another.';
|
|
566
|
+
case 'NOT_YOUR_CLAIM':
|
|
567
|
+
return 'That claim is not yours to release.';
|
|
568
|
+
case 'ALREADY_RESOLVED':
|
|
569
|
+
return 'That claim was already resolved. The board will refresh.';
|
|
570
|
+
case 'BUILDER_INACTIVE':
|
|
571
|
+
return 'Your account is inactive. Contact an Archon.';
|
|
572
|
+
default:
|
|
573
|
+
return `The board could not complete that (${status}). Try again.`;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Maps a single batch-item failure reason (POST /claims/batch,
|
|
578
|
+
// /claims/batch/validate) to a builder-readable fragment. Distinct from
|
|
579
|
+
// friendlyActionError above because a batch item's shape differs (reason,
|
|
580
|
+
// not error; extra fields like conflictsWithTaskId/module_key).
|
|
581
|
+
function friendlyBatchReason(item) {
|
|
582
|
+
switch (item && item.reason) {
|
|
583
|
+
case 'TASK_NOT_FOUND': return 'no longer exists';
|
|
584
|
+
case 'TASK_NOT_READY': return `no longer ready${item.currentStatus ? ` (${item.currentStatus})` : ''}`;
|
|
585
|
+
case 'ALREADY_CLAIMED': return 'already claimed';
|
|
586
|
+
case 'TOUCHES_CONFLICT': return `overlaps ${item.conflictTaskTitle || 'an active claim'}`;
|
|
587
|
+
case 'BATCH_TOUCHES_CONFLICT': return `overlaps task #${item.conflictsWithTaskId} in this selection`;
|
|
588
|
+
case 'MODULE_CONFLICT': return item.conflictsWithTaskId
|
|
589
|
+
? `shares module "${item.module_key}" with task #${item.conflictsWithTaskId} in this selection`
|
|
590
|
+
: `shares module "${item.module_key}" with an active claim`;
|
|
591
|
+
case 'DEPS_NOT_SHIPPED': return 'waits on unshipped dependencies';
|
|
592
|
+
case 'rank_too_low_for_task':
|
|
593
|
+
case 'XENOS_RESTRICTED':
|
|
594
|
+
case 'INSUFFICIENT_RANK': return 'rank too low';
|
|
595
|
+
case 'BUILDER_INACTIVE': return 'account inactive';
|
|
596
|
+
default: return (item && item.reason) || 'not claimable';
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function copyText(text) {
|
|
601
|
+
try { await navigator.clipboard.writeText(text); return true; }
|
|
602
|
+
catch (_) {
|
|
603
|
+
try {
|
|
604
|
+
const ta = document.createElement('textarea');
|
|
605
|
+
ta.value = text;
|
|
606
|
+
ta.style.position = 'fixed';
|
|
607
|
+
ta.style.opacity = '0';
|
|
608
|
+
document.body.appendChild(ta);
|
|
609
|
+
ta.select();
|
|
610
|
+
const ok = document.execCommand('copy');
|
|
611
|
+
ta.remove();
|
|
612
|
+
return ok;
|
|
613
|
+
} catch (_e) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ---- bulk selection (task 1759) -------------------------------------------
|
|
620
|
+
|
|
621
|
+
function selectionTotal() { return selection.claim.size + selection.release.size; }
|
|
622
|
+
|
|
623
|
+
// task 1869: the single place that flips a card's selection — used by both
|
|
624
|
+
// the checkbox itself (forceChecked = its post-click .checked) and a click
|
|
625
|
+
// anywhere else on the card body (forceChecked omitted = toggle). Keeping
|
|
626
|
+
// one path means the checkbox's visual state, the card's highlight class,
|
|
627
|
+
// and the selection Set can never drift out of sync with each other.
|
|
628
|
+
function toggleTaskSelection(card, forceChecked) {
|
|
629
|
+
const cb = card.querySelector('.task__select');
|
|
630
|
+
if (!cb) return;
|
|
631
|
+
const next = forceChecked != null ? forceChecked : !cb.checked;
|
|
632
|
+
cb.checked = next;
|
|
633
|
+
const id = String(cb.dataset.taskId);
|
|
634
|
+
if (next) selection.claim.add(id); else selection.claim.delete(id);
|
|
635
|
+
card.classList.toggle('task--selected', next);
|
|
636
|
+
renderBulkToolbar();
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Drops any selected id that's fallen out from under the selection (claimed
|
|
640
|
+
// by someone else, shipped, released elsewhere, …) so a stale checkbox never
|
|
641
|
+
// outlives the row it pointed at. Called after every live refresh.
|
|
642
|
+
function pruneSelection() {
|
|
643
|
+
const claimableIds = new Set(taskState.all.map((t) => String(t.id)));
|
|
644
|
+
for (const id of Array.from(selection.claim)) {
|
|
645
|
+
if (!claimableIds.has(id)) selection.claim.delete(id);
|
|
646
|
+
}
|
|
647
|
+
const mineClaimIds = new Set(
|
|
648
|
+
liveState.active
|
|
649
|
+
.filter((c) => viewer.id != null && Number(c.builder_id) === Number(viewer.id))
|
|
650
|
+
.map((c) => String(c.claim_id))
|
|
651
|
+
);
|
|
652
|
+
for (const claimId of Array.from(selection.release.keys())) {
|
|
653
|
+
if (!mineClaimIds.has(claimId)) selection.release.delete(claimId);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function selectedClaimIds() { return [...selection.claim].map(Number); }
|
|
658
|
+
function selectionKey(ids) { return ids.slice().sort((a, b) => a - b).join(','); }
|
|
659
|
+
|
|
660
|
+
async function scheduleValidate(key, ids) {
|
|
661
|
+
if (validateDebounce) clearTimeout(validateDebounce);
|
|
662
|
+
validateDebounce = setTimeout(async () => {
|
|
663
|
+
try {
|
|
664
|
+
const result = await postJSON(`${API}/claims/batch/validate`, { task_ids: ids });
|
|
665
|
+
validateCache.key = key;
|
|
666
|
+
validateCache.parallelSafe = !!result.parallel_safe;
|
|
667
|
+
validateCache.byId = new Map((result.results || []).map((r) => [String(r.task_id), r]));
|
|
668
|
+
} catch (_) {
|
|
669
|
+
// Best-effort — leave Parallel disabled-with-a-generic-title on failure
|
|
670
|
+
// rather than risk copying a prompt for tasks we couldn't verify.
|
|
671
|
+
validateCache.key = key;
|
|
672
|
+
validateCache.parallelSafe = false;
|
|
673
|
+
validateCache.byId = new Map();
|
|
674
|
+
}
|
|
675
|
+
renderBulkToolbar();
|
|
676
|
+
}, 350);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function fetchDeps(taskId) {
|
|
680
|
+
if (depsCache.has(taskId)) return depsCache.get(taskId);
|
|
681
|
+
let deps = [];
|
|
682
|
+
try {
|
|
683
|
+
const d = await getJSON(`${API}/tasks/${encodeURIComponent(taskId)}`, { softAuth: true });
|
|
684
|
+
if (d && d.task && Array.isArray(d.task.dependencies)) {
|
|
685
|
+
deps = d.task.dependencies.map((x) => Number(x.id)).filter((x) => Number.isFinite(x));
|
|
686
|
+
}
|
|
687
|
+
} catch (_) { /* treat as no known dependencies */ }
|
|
688
|
+
depsCache.set(taskId, deps);
|
|
689
|
+
return deps;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Kahn's algorithm restricted to edges BETWEEN the selected ids (an
|
|
693
|
+
// out-of-selection dependency doesn't block sequencing the selection
|
|
694
|
+
// itself). Returns the topological order, or null if the restricted graph
|
|
695
|
+
// has a cycle (can't be sequenced).
|
|
696
|
+
function topoOrder(ids, edgesByTask) {
|
|
697
|
+
const remaining = new Set(ids);
|
|
698
|
+
const order = [];
|
|
699
|
+
let progressed = true;
|
|
700
|
+
while (remaining.size && progressed) {
|
|
701
|
+
progressed = false;
|
|
702
|
+
for (const id of Array.from(remaining)) {
|
|
703
|
+
const deps = (edgesByTask.get(id) || []).filter((d) => remaining.has(d));
|
|
704
|
+
if (deps.length === 0) {
|
|
705
|
+
order.push(id);
|
|
706
|
+
remaining.delete(id);
|
|
707
|
+
progressed = true;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return remaining.size ? null : order;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function scheduleSequentialCheck(key, ids) {
|
|
715
|
+
if (sequentialDebounce) clearTimeout(sequentialDebounce);
|
|
716
|
+
sequentialDebounce = setTimeout(async () => {
|
|
717
|
+
const edgesByTask = new Map();
|
|
718
|
+
await Promise.all(ids.map(async (id) => { edgesByTask.set(id, await fetchDeps(id)); }));
|
|
719
|
+
sequentialCache.key = key;
|
|
720
|
+
sequentialCache.order = topoOrder(ids, edgesByTask);
|
|
721
|
+
renderBulkToolbar();
|
|
722
|
+
}, 350);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function renderBulkToolbar() {
|
|
726
|
+
const bar = $('#bulk-toolbar');
|
|
727
|
+
if (!bar) return;
|
|
728
|
+
if (selectionTotal() === 0) { bar.hidden = true; return; }
|
|
729
|
+
bar.hidden = false;
|
|
730
|
+
|
|
731
|
+
const countEl = $('#bulk-count');
|
|
732
|
+
if (countEl) {
|
|
733
|
+
const parts = [];
|
|
734
|
+
if (selection.claim.size) parts.push(`${selection.claim.size} to claim`);
|
|
735
|
+
if (selection.release.size) parts.push(`${selection.release.size} to release`);
|
|
736
|
+
countEl.textContent = parts.join(' · ');
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const claimBtn = $('#bulk-claim-btn');
|
|
740
|
+
if (claimBtn) {
|
|
741
|
+
claimBtn.disabled = selection.claim.size === 0;
|
|
742
|
+
claimBtn.textContent = selection.claim.size > 1 ? `Bulk claim (${selection.claim.size})` : 'Bulk claim';
|
|
743
|
+
}
|
|
744
|
+
const releaseBtn = $('#bulk-release-btn');
|
|
745
|
+
if (releaseBtn) {
|
|
746
|
+
releaseBtn.disabled = selection.release.size === 0;
|
|
747
|
+
releaseBtn.textContent = selection.release.size > 1 ? `Bulk release (${selection.release.size})` : 'Bulk release';
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const parallelBtn = $('#bulk-parallel-btn');
|
|
751
|
+
if (parallelBtn) {
|
|
752
|
+
if (selection.claim.size < 2) {
|
|
753
|
+
parallelBtn.disabled = true;
|
|
754
|
+
parallelBtn.title = 'Select at least 2 waiting tasks to copy a parallel-session prompt.';
|
|
755
|
+
} else {
|
|
756
|
+
const ids = selectedClaimIds();
|
|
757
|
+
const key = selectionKey(ids);
|
|
758
|
+
if (validateCache.key === key && validateCache.parallelSafe != null) {
|
|
759
|
+
parallelBtn.disabled = !validateCache.parallelSafe;
|
|
760
|
+
if (!validateCache.parallelSafe) {
|
|
761
|
+
const bad = [...validateCache.byId.values()].filter((r) => !r.valid);
|
|
762
|
+
const detail = bad.map((r) => `#${r.task_id}: ${(r.reasons || []).map(friendlyBatchReason).join(', ')}`).join(' · ');
|
|
763
|
+
parallelBtn.title = detail || 'Selected tasks conflict and cannot run in parallel.';
|
|
764
|
+
} else {
|
|
765
|
+
parallelBtn.title = '';
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
parallelBtn.disabled = true;
|
|
769
|
+
parallelBtn.title = 'Checking for conflicts…';
|
|
770
|
+
scheduleValidate(key, ids);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const sequentialBtn = $('#bulk-sequential-btn');
|
|
776
|
+
if (sequentialBtn) {
|
|
777
|
+
if (selection.claim.size < 2) {
|
|
778
|
+
sequentialBtn.disabled = true;
|
|
779
|
+
sequentialBtn.title = 'Select at least 2 waiting tasks to copy a sequential prompt.';
|
|
780
|
+
} else {
|
|
781
|
+
const ids = selectedClaimIds();
|
|
782
|
+
const key = selectionKey(ids);
|
|
783
|
+
if (sequentialCache.key === key) {
|
|
784
|
+
sequentialBtn.disabled = sequentialCache.order == null;
|
|
785
|
+
sequentialBtn.title = sequentialCache.order == null
|
|
786
|
+
? 'These tasks depend on each other in a cycle and cannot be ordered.'
|
|
787
|
+
: '';
|
|
788
|
+
} else {
|
|
789
|
+
sequentialBtn.disabled = true;
|
|
790
|
+
sequentialBtn.title = 'Checking dependency order…';
|
|
791
|
+
scheduleSequentialCheck(key, ids);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function clearSelection() {
|
|
798
|
+
selection.claim.clear();
|
|
799
|
+
selection.release.clear();
|
|
800
|
+
renderClaimable();
|
|
801
|
+
renderInFlight();
|
|
802
|
+
renderBulkToolbar();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function buildBulkParallelPrompt(tasks) {
|
|
806
|
+
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
807
|
+
const lines = [
|
|
808
|
+
`I want to work on ${tasks.length} GDS tasks IN PARALLEL — open ${tasks.length} separate Claude Code sessions, each in its own worktree, one per task below, so they can run at the same time without touching each other's files.`,
|
|
809
|
+
'',
|
|
810
|
+
];
|
|
811
|
+
tasks.forEach((t, i) => {
|
|
812
|
+
lines.push(`Session ${i + 1} — task ${t.id} (${t.title}) — ${hallBase}#/task/${t.id}`);
|
|
813
|
+
lines.push(`Claim it with /builder-claim ${t.id}, follow its acceptance criteria, stay within touches[], ship via /builder-ship when done.`);
|
|
814
|
+
lines.push('');
|
|
815
|
+
});
|
|
816
|
+
return lines.join('\n').trim();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function buildBulkSequentialPrompt(tasks) {
|
|
820
|
+
const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
|
|
821
|
+
const lines = [
|
|
822
|
+
`I want to work on ${tasks.length} GDS tasks IN ORDER, one after another in this same session — claim, build, and ship each before moving to the next.`,
|
|
823
|
+
'',
|
|
824
|
+
];
|
|
825
|
+
tasks.forEach((t, i) => {
|
|
826
|
+
lines.push(`${i + 1}. Task ${t.id} (${t.title}) — ${hallBase}#/task/${t.id}`);
|
|
827
|
+
});
|
|
828
|
+
lines.push('');
|
|
829
|
+
lines.push('For each: /builder-claim <id>, follow its acceptance criteria, stay within touches[], /builder-ship <id> before starting the next.');
|
|
830
|
+
return lines.join('\n');
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async function doBulkClaim() {
|
|
834
|
+
const ids = selectedClaimIds();
|
|
835
|
+
if (!ids.length) return;
|
|
836
|
+
const btn = $('#bulk-claim-btn');
|
|
837
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
|
|
838
|
+
try {
|
|
839
|
+
if (ids.length === 1) {
|
|
840
|
+
await postJSON(`${API}/claims`, { task_id: ids[0], bind_session: false });
|
|
841
|
+
toast(`You claimed #${ids[0]}. Open a terminal to start work.`);
|
|
842
|
+
} else {
|
|
843
|
+
await postJSON(`${API}/claims/batch`, { task_ids: ids, bind_session: false });
|
|
844
|
+
toast(`Claimed ${ids.length} tasks. Open a terminal to start work.`);
|
|
845
|
+
}
|
|
846
|
+
selection.claim.clear();
|
|
847
|
+
await refreshLive();
|
|
848
|
+
} catch (err) {
|
|
849
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
850
|
+
if (err.status === 409 && err.data && err.data.error === 'batch_failed') {
|
|
851
|
+
const failures = Array.isArray(err.data.failures) ? err.data.failures : [];
|
|
852
|
+
const lines = failures.map((f) => `#${f.task_id} (${friendlyBatchReason(f)})`);
|
|
853
|
+
toast(lines.length
|
|
854
|
+
? `The batch could not be claimed — ${lines.join('; ')}.`
|
|
855
|
+
: 'The batch could not be claimed. It was left untouched (all-or-nothing).');
|
|
856
|
+
await refreshLive().catch(() => {});
|
|
857
|
+
} else {
|
|
858
|
+
toast(friendlyActionError(err.status, err.data));
|
|
859
|
+
if (err.status === 409) await refreshLive().catch(() => {});
|
|
860
|
+
}
|
|
861
|
+
} finally {
|
|
862
|
+
renderBulkToolbar();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
async function doBulkRelease() {
|
|
867
|
+
const entries = [...selection.release.entries()]; // [claimId, taskId]
|
|
868
|
+
if (!entries.length) return;
|
|
869
|
+
const label = entries.length === 1 ? `#${entries[0][1]}` : `${entries.length} tasks`;
|
|
870
|
+
const pronoun = entries.length === 1 ? 'It returns' : 'They return';
|
|
871
|
+
if (!window.confirm(`Release ${label}? ${pronoun} to the queue and no ${curLabel()} are earned.`)) return;
|
|
872
|
+
const btn = $('#bulk-release-btn');
|
|
873
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
|
|
874
|
+
try {
|
|
875
|
+
if (entries.length === 1) {
|
|
876
|
+
await postJSON(`${API}/claims/${encodeURIComponent(entries[0][0])}/resolve`, { outcome: 'abandoned' });
|
|
877
|
+
toast(`Released #${entries[0][1]}. It returns to the queue.`);
|
|
878
|
+
} else {
|
|
879
|
+
const result = await postJSON(`${API}/claims/batch/release`, { claim_ids: entries.map(([cid]) => Number(cid)) });
|
|
880
|
+
const released = Array.isArray(result.released) ? result.released.length : 0;
|
|
881
|
+
const failed = Array.isArray(result.failures) ? result.failures.length : 0;
|
|
882
|
+
toast(failed
|
|
883
|
+
? `Released ${released} of ${entries.length} — ${failed} could not be released.`
|
|
884
|
+
: `Released ${released} tasks. They return to the queue.`);
|
|
885
|
+
}
|
|
886
|
+
selection.release.clear();
|
|
887
|
+
await refreshLive();
|
|
888
|
+
} catch (err) {
|
|
889
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
890
|
+
toast(friendlyActionError(err.status, err.data));
|
|
891
|
+
} finally {
|
|
892
|
+
renderBulkToolbar();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async function doClaim(taskId, btn) {
|
|
897
|
+
const key = `claim:${taskId}`;
|
|
898
|
+
if (pending.has(key)) return;
|
|
899
|
+
pending.add(key);
|
|
900
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
|
|
901
|
+
try {
|
|
902
|
+
// bind_session:false — a web claim has no working session, so leave it
|
|
903
|
+
// unbound (see claims.js resolveCreatorSessionId) and the builder can ship
|
|
904
|
+
// it later from any of their own terminals.
|
|
905
|
+
await postJSON(`${API}/claims`, { task_id: Number(taskId), bind_session: false });
|
|
906
|
+
toast(`You claimed #${taskId}. Open a terminal to start work.`);
|
|
907
|
+
await refreshLive();
|
|
908
|
+
} catch (err) {
|
|
909
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
910
|
+
toast(friendlyActionError(err.status, err.data));
|
|
911
|
+
// A conflict/already-claimed means our view is stale — pull fresh truth.
|
|
912
|
+
if (err.status === 409) await refreshLive().catch(() => {});
|
|
913
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Claim'; }
|
|
914
|
+
} finally {
|
|
915
|
+
pending.delete(key);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async function doRelease(claimId, taskId, btn) {
|
|
920
|
+
const key = `release:${claimId}`;
|
|
921
|
+
if (pending.has(key)) return;
|
|
922
|
+
if (!window.confirm(`Release your claim on #${taskId}? It returns to the queue and no ${curLabel()} are earned.`)) return;
|
|
923
|
+
pending.add(key);
|
|
924
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
|
|
925
|
+
try {
|
|
926
|
+
// No DELETE /claims/:id exists — release is resolve(outcome=abandoned),
|
|
927
|
+
// the same path /builder-release uses. Scoped to the caller's own claim
|
|
928
|
+
// server-side (NOT_YOUR_CLAIM).
|
|
929
|
+
await postJSON(`${API}/claims/${encodeURIComponent(claimId)}/resolve`, { outcome: 'abandoned' });
|
|
930
|
+
toast(`Released #${taskId}. It returns to the queue.`);
|
|
931
|
+
await refreshLive();
|
|
932
|
+
} catch (err) {
|
|
933
|
+
if (err && err.message === 'redirecting to auth') return;
|
|
934
|
+
toast(friendlyActionError(err.status, err.data));
|
|
935
|
+
if (err.status === 409) await refreshLive().catch(() => {});
|
|
936
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Release'; }
|
|
937
|
+
} finally {
|
|
938
|
+
pending.delete(key);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ---- recent history (completed / shipped) --------------------------------
|
|
943
|
+
|
|
944
|
+
// task 1899: module-level store so the Completed pager can re-render without a
|
|
945
|
+
// param (mirrors renderInFlight's liveState.active pattern).
|
|
946
|
+
let historyRows = [];
|
|
947
|
+
|
|
948
|
+
// ---- task 1469: project-wide "works completed" momentum summary ----------
|
|
949
|
+
//
|
|
950
|
+
// A compact card above the Completed list summarizing the WHOLE project's
|
|
951
|
+
// shipped output (NOT the viewer's own — that lives on the profile page): total
|
|
952
|
+
// works shipped (all-time, summed from /public/progress lifecycle_shipped_count),
|
|
953
|
+
// total <currency> awarded (/public/cost-summary total_value_credits), and the
|
|
954
|
+
// most recent ship (the head of the recent-shipped feed already loaded for the
|
|
955
|
+
// history list). All three are public reads; no per-builder data.
|
|
956
|
+
let worksSummary = { progress: null, cost: null };
|
|
957
|
+
|
|
958
|
+
function summaryTile(label, num, sub, subTitle) {
|
|
959
|
+
const subHtml = sub
|
|
960
|
+
? `<span class="stat-tile__sub" ${subTitle ? `title="${escapeHtml(subTitle)}" ` : ''}style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(sub)}</span>`
|
|
961
|
+
: '';
|
|
962
|
+
return `<div class="stat-tile"><span class="stat-tile__label">${escapeHtml(label)}</span>` +
|
|
963
|
+
`<span class="stat-tile__num">${escapeHtml(String(num))}</span>${subHtml}</div>`;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function renderWorksSummary() {
|
|
967
|
+
const root = $('#works-summary');
|
|
968
|
+
if (!root) return;
|
|
969
|
+
const versions = worksSummary.progress && Array.isArray(worksSummary.progress.progress)
|
|
970
|
+
? worksSummary.progress.progress : null;
|
|
971
|
+
// Wait for the shipped-count feed; the card stays hidden until then (and on a
|
|
972
|
+
// project that has shipped nothing).
|
|
973
|
+
if (!versions) { root.hidden = true; return; }
|
|
974
|
+
const shipped = versions.reduce((n, v) => n + (Number(v.lifecycle_shipped_count) || 0), 0);
|
|
975
|
+
if (!shipped) { root.hidden = true; return; }
|
|
976
|
+
const awarded = worksSummary.cost ? Number(worksSummary.cost.total_value_credits) || 0 : null;
|
|
977
|
+
const latest = Array.isArray(historyRows) && historyRows.length ? historyRows[0] : null;
|
|
978
|
+
|
|
979
|
+
const tiles = [summaryTile('Works shipped', fmtNum(shipped))];
|
|
980
|
+
if (awarded != null) tiles.push(summaryTile(`${curLabel(true)} awarded`, fmtNum(awarded)));
|
|
981
|
+
if (latest) {
|
|
982
|
+
tiles.push(summaryTile('Latest ship', latest.shipped_at ? fmtAgo(latest.shipped_at) : '—',
|
|
983
|
+
latest.title || `#${latest.id}`, latest.title || ''));
|
|
984
|
+
}
|
|
985
|
+
root.innerHTML = tiles.join('');
|
|
986
|
+
root.hidden = false;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function renderHistory(tasks) {
|
|
990
|
+
if (Array.isArray(tasks)) historyRows = tasks;
|
|
991
|
+
const root = $('#history-list');
|
|
992
|
+
if (!root) return;
|
|
993
|
+
// task 1754: reveal the Completed group only when there are shipped tasks.
|
|
994
|
+
const scroll = $('#history-scroll');
|
|
995
|
+
if (scroll) scroll.hidden = !historyRows.length;
|
|
996
|
+
const rows = historyRows;
|
|
997
|
+
setCountPill('#done-count', rows.length);
|
|
998
|
+
if (!rows.length) {
|
|
999
|
+
root.innerHTML = `<div class="profile__placeholder">Nothing shipped yet.</div>`;
|
|
1000
|
+
pagers.done.page = 1;
|
|
1001
|
+
const dp = $('#done-pager');
|
|
1002
|
+
if (dp) dp.hidden = true;
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const shown = paginate(rows, pagers.done);
|
|
1006
|
+
root.innerHTML = shown.map((t) => {
|
|
1007
|
+
const ver = t.version_id ? `<span class="task__tag">${escapeHtml(t.version_id)}</span>` : '';
|
|
1008
|
+
const when = t.shipped_at ? fmtAgo(t.shipped_at) : '';
|
|
1009
|
+
return `
|
|
1010
|
+
<div class="history__row">
|
|
1011
|
+
<span class="history__id">#${escapeHtml(String(t.id))}</span>
|
|
1012
|
+
<span class="history__title">${escapeHtml(t.title || '(untitled)')} ${ver}</span>
|
|
1013
|
+
<span class="history__when" title="${escapeHtml(fmtDate(t.shipped_at))}">${escapeHtml(when)}</span>
|
|
1014
|
+
</div>
|
|
1015
|
+
`;
|
|
1016
|
+
}).join('');
|
|
1017
|
+
// task 1469: now that historyRows is populated, (re)render the momentum card so
|
|
1018
|
+
// its "Latest ship" tile reflects the newest shipped task.
|
|
1019
|
+
renderWorksSummary();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// ---- backlog (not-yet-ready tasks) ---------------------------------------
|
|
1023
|
+
|
|
1024
|
+
// task 1754: the collapsed "Backlog" group — status=backlog tasks, read-only
|
|
1025
|
+
// (they can't be claimed until their blockers ship). Reuses rankTagHtml (1752)
|
|
1026
|
+
// and opens the shared task-detail overlay (1753) on click.
|
|
1027
|
+
// task 1955: module-level store so the Backlog pager re-renders without a param.
|
|
1028
|
+
let backlogRows = [];
|
|
1029
|
+
|
|
1030
|
+
function renderBacklog(tasks) {
|
|
1031
|
+
if (Array.isArray(tasks)) backlogRows = tasks;
|
|
1032
|
+
const scroll = $('#backlog-scroll');
|
|
1033
|
+
const root = $('#backlog-list');
|
|
1034
|
+
if (!root) return;
|
|
1035
|
+
// Hide the whole section only when there's genuinely no backlog; when a
|
|
1036
|
+
// search is active but filters everything out, keep the section visible so
|
|
1037
|
+
// the "0 tasks" count explains the empty list (task 1829).
|
|
1038
|
+
if (!backlogRows.length) { if (scroll) scroll.hidden = true; return; }
|
|
1039
|
+
const q = searchQuery();
|
|
1040
|
+
const list = q ? backlogRows.filter((t) => matchesSearch(t, q)) : backlogRows;
|
|
1041
|
+
if (scroll) scroll.hidden = false;
|
|
1042
|
+
const countEl = $('#backlog-count');
|
|
1043
|
+
if (countEl) countEl.textContent = `${list.length} ${list.length === 1 ? 'task' : 'tasks'}`;
|
|
1044
|
+
const shown = paginate(list, pagers.backlog);
|
|
1045
|
+
root.innerHTML = shown.map((t) => {
|
|
1046
|
+
const ver = t.version_id ? `<span class="task__tag">${escapeHtml(t.version_id)}</span>` : '';
|
|
1047
|
+
return `
|
|
1048
|
+
<div class="history__row" data-task-id="${escapeHtml(String(t.id))}" title="Open task #${escapeHtml(String(t.id))}">
|
|
1049
|
+
<span class="history__id">#${escapeHtml(String(t.id))}</span>
|
|
1050
|
+
<span class="history__title">${escapeHtml(t.title || '(untitled)')} ${ver} ${rankTagHtml(t.requires_rank)}</span>
|
|
1051
|
+
<span class="history__when">${escapeHtml(t.kind || '')}</span>
|
|
1052
|
+
</div>`;
|
|
1053
|
+
}).join('');
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// ---- live refresh + polling ----------------------------------------------
|
|
1057
|
+
|
|
1058
|
+
// Pull the claimable feed + active claims and re-render both. Shared by the
|
|
1059
|
+
// post-action refresh and the poll timer. Swallows the gate/transport errors
|
|
1060
|
+
// the same way loadAll does so a transient failure doesn't blank the board.
|
|
1061
|
+
async function refreshLive() {
|
|
1062
|
+
try {
|
|
1063
|
+
const claimResp = await getJSON(`${API}/tasks/claimable`);
|
|
1064
|
+
renderInFlight(claimResp.active_claims || []);
|
|
1065
|
+
taskState.all = claimResp.claimable || [];
|
|
1066
|
+
renderClaimable();
|
|
1067
|
+
pruneSelection();
|
|
1068
|
+
renderBulkToolbar();
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
if (err && err.status === 403) { showClaimGate(); renderInFlight([]); }
|
|
1071
|
+
// Other failures: keep the last good render rather than blanking.
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function startPolling() {
|
|
1076
|
+
// Re-render the in-flight ages every tick even without new data, and pull
|
|
1077
|
+
// fresh truth from the server. Paused while the tab is hidden to avoid
|
|
1078
|
+
// pointless background traffic.
|
|
1079
|
+
const POLL_MS = 20000;
|
|
1080
|
+
setInterval(() => {
|
|
1081
|
+
if (document.hidden) { return; }
|
|
1082
|
+
refreshLive();
|
|
1083
|
+
}, POLL_MS);
|
|
1084
|
+
// Cheap local re-render of claim ages between polls.
|
|
1085
|
+
setInterval(() => {
|
|
1086
|
+
if (!document.hidden && liveState.active.length) renderInFlight();
|
|
1087
|
+
}, 60000);
|
|
1088
|
+
// Snap to fresh data the moment the tab regains focus.
|
|
1089
|
+
document.addEventListener('visibilitychange', () => {
|
|
1090
|
+
if (!document.hidden) refreshLive();
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// ---- gate notice (signed in, primer not yet read) ------------------------
|
|
1095
|
+
|
|
1096
|
+
function showClaimGate() {
|
|
1097
|
+
const gate = $('#gate-scroll');
|
|
1098
|
+
const claim = $('#claim-scroll');
|
|
1099
|
+
if (gate) gate.hidden = false;
|
|
1100
|
+
if (claim) claim.hidden = true;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// ---- bootstrap -----------------------------------------------------------
|
|
1104
|
+
|
|
1105
|
+
async function loadAll() {
|
|
1106
|
+
// /me confirms the viewer + personalises the header. softAuth so a stale
|
|
1107
|
+
// session renders the page chrome rather than a hard bounce — though the
|
|
1108
|
+
// server gate means an unauth visitor never gets here in the first place.
|
|
1109
|
+
let me = null;
|
|
1110
|
+
try {
|
|
1111
|
+
me = await getJSON(`${API}/me`, { softAuth: true });
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
if (err && err.code === AUTH_NEEDED) { toAuth(); return; }
|
|
1114
|
+
throw err;
|
|
1115
|
+
}
|
|
1116
|
+
viewer.id = me?.builder?.id ?? null;
|
|
1117
|
+
const sub = $('#work-sub');
|
|
1118
|
+
if (sub) {
|
|
1119
|
+
const name = me?.builder?.display_name || me?.builder?.github_login || 'builder';
|
|
1120
|
+
sub.textContent = `Welcome, ${name}. Claim and release tasks and follow work in progress — no terminal needed.`;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Recent history — public read surface, never gated. Independent of the
|
|
1124
|
+
// claimable feed so it paints even if that's gated/late.
|
|
1125
|
+
getJSON(`${API}/public/recent-shipped`)
|
|
1126
|
+
.then((r) => renderHistory(r.tasks || []))
|
|
1127
|
+
.catch(() => {
|
|
1128
|
+
const root = $('#history-list');
|
|
1129
|
+
if (root) root.innerHTML = `<div class="profile__placeholder">Recent history could not be loaded right now.</div>`;
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// task 1469: the project-wide "works completed" momentum summary above the
|
|
1133
|
+
// Completed list. Public reads, independent of the claimable feed so it paints
|
|
1134
|
+
// regardless; the card stays hidden if either read fails.
|
|
1135
|
+
Promise.all([
|
|
1136
|
+
getJSON(`${API}/public/progress`).catch(() => null),
|
|
1137
|
+
getJSON(`${API}/public/cost-summary`).catch(() => null),
|
|
1138
|
+
]).then(([progress, cost]) => {
|
|
1139
|
+
worksSummary = { progress, cost };
|
|
1140
|
+
renderWorksSummary();
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// task 1754: the Backlog group — not-yet-ready tasks. Best-effort + independent
|
|
1144
|
+
// of the claimable feed; leaves the section hidden on failure.
|
|
1145
|
+
getJSON(`${API}/tasks?status=backlog`)
|
|
1146
|
+
.then((r) => { taskState.backlog = r.tasks || []; renderBacklog(taskState.backlog); })
|
|
1147
|
+
.catch(() => { /* backlog stays hidden */ });
|
|
1148
|
+
|
|
1149
|
+
// task 1755: goal id → title map for the Goal filter (best-effort).
|
|
1150
|
+
taskState.goalTitles = {};
|
|
1151
|
+
try {
|
|
1152
|
+
const gr = await getJSON(`${API}/goals`);
|
|
1153
|
+
(gr.goals || []).forEach((g) => { taskState.goalTitles[g.id] = g.title; });
|
|
1154
|
+
} catch (_) { /* filter labels fall back to 'Goal #id' */ }
|
|
1155
|
+
|
|
1156
|
+
// Claimable + active claims in one call. A 403 means the primer gate — show
|
|
1157
|
+
// the friendly notice and hide the claim queue, but keep the in-flight list
|
|
1158
|
+
// empty-but-honest (the gate also blocks active_claims, so say so).
|
|
1159
|
+
try {
|
|
1160
|
+
const [claimResp, versionsResp] = await Promise.all([
|
|
1161
|
+
getJSON(`${API}/tasks/claimable`),
|
|
1162
|
+
getJSON(`${API}/versions`).catch(() => ({ versions: [] })),
|
|
1163
|
+
]);
|
|
1164
|
+
taskState.versions = versionsResp.versions || [];
|
|
1165
|
+
populateVersionFilter(taskState.versions);
|
|
1166
|
+
renderInFlight(claimResp.active_claims || []);
|
|
1167
|
+
taskState.all = claimResp.claimable || [];
|
|
1168
|
+
renderClaimable();
|
|
1169
|
+
populateGoalFilter(); // task 1755
|
|
1170
|
+
pruneSelection();
|
|
1171
|
+
renderBulkToolbar();
|
|
1172
|
+
} catch (err) {
|
|
1173
|
+
if (err && err.status === 403) {
|
|
1174
|
+
showClaimGate();
|
|
1175
|
+
renderInFlight([]);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const root = $('#task-list');
|
|
1179
|
+
if (root) root.innerHTML = `<div class="profile__placeholder">The task list could not be loaded right now.</div>`;
|
|
1180
|
+
renderInFlight([]);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function wireEvents() {
|
|
1185
|
+
// task 1931: any filter/sort change re-paginates the Ready list from page 1.
|
|
1186
|
+
const onReadyFilterChange = () => { pagers.ready.page = 1; renderClaimable(); };
|
|
1187
|
+
$('#f-version')?.addEventListener('change', onReadyFilterChange);
|
|
1188
|
+
$('#f-goal')?.addEventListener('change', onReadyFilterChange);
|
|
1189
|
+
$('#f-kind')?.addEventListener('change', onReadyFilterChange);
|
|
1190
|
+
$('#f-discipline')?.addEventListener('change', onReadyFilterChange);
|
|
1191
|
+
$('#f-newcomer')?.addEventListener('change', onReadyFilterChange);
|
|
1192
|
+
$('#f-fit')?.addEventListener('change', onReadyFilterChange);
|
|
1193
|
+
$('#f-sort')?.addEventListener('change', onReadyFilterChange);
|
|
1194
|
+
// task 1829: free-text search — 'input' fires on every keystroke and on the
|
|
1195
|
+
// type=search clear (✕). Re-render BOTH the claimable and backlog lists,
|
|
1196
|
+
// resetting each to page 1 so matches aren't hidden behind pagination.
|
|
1197
|
+
$('#f-search')?.addEventListener('input', () => {
|
|
1198
|
+
pagers.ready.page = 1;
|
|
1199
|
+
pagers.backlog.page = 1;
|
|
1200
|
+
renderClaimable();
|
|
1201
|
+
renderBacklog();
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
// task 1931/1955: pagination controls for all four Work-page lists.
|
|
1205
|
+
wirePager(pagers.ready, renderClaimable);
|
|
1206
|
+
wirePager(pagers.wip, renderInFlight);
|
|
1207
|
+
wirePager(pagers.done, renderHistory);
|
|
1208
|
+
wirePager(pagers.backlog, renderBacklog);
|
|
1209
|
+
|
|
1210
|
+
$('#task-list')?.addEventListener('click', (e) => {
|
|
1211
|
+
// task 1759: the multi-select checkbox. Toggle the Set + the card's
|
|
1212
|
+
// highlight class directly (cheaper than a full renderClaimable) and
|
|
1213
|
+
// stop here — it must never fall through to the card-select handler below.
|
|
1214
|
+
const selectCb = e.target.closest('.task__select');
|
|
1215
|
+
if (selectCb) {
|
|
1216
|
+
const card = selectCb.closest('.task');
|
|
1217
|
+
if (card) toggleTaskSelection(card, selectCb.checked);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const claimBtn = e.target.closest('.task__claim');
|
|
1221
|
+
if (claimBtn) { doClaim(claimBtn.dataset.taskId, claimBtn); return; }
|
|
1222
|
+
const copyBtn = e.target.closest('.task__copy');
|
|
1223
|
+
if (copyBtn) { copyPromptForTask(copyBtn.dataset.taskId, copyBtn); return; }
|
|
1224
|
+
// task 1869: the explicit open-detail hitbox — now the ONLY way to open
|
|
1225
|
+
// the shared task-detail overlay from a claimable card (the card body
|
|
1226
|
+
// itself toggles selection below, since multi-select is the primary card
|
|
1227
|
+
// interaction now that 1759 shipped).
|
|
1228
|
+
const openBtn = e.target.closest('.task__open');
|
|
1229
|
+
if (openBtn && openBtn.dataset.taskId && window.OTBTaskDetail) {
|
|
1230
|
+
window.OTBTaskDetail.open(openBtn.dataset.taskId);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
// task 1869: clicking the card anywhere else toggles its selection
|
|
1234
|
+
// checkbox (matches the affordance a checkbox already implies).
|
|
1235
|
+
const card = e.target.closest('.task');
|
|
1236
|
+
if (card && card.dataset.taskId) toggleTaskSelection(card);
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
// task 1754: a Backlog row opens the shared task-detail overlay (read-only).
|
|
1240
|
+
$('#backlog-list')?.addEventListener('click', (e) => {
|
|
1241
|
+
const row = e.target.closest('.history__row');
|
|
1242
|
+
if (row && row.dataset.taskId && window.OTBTaskDetail) window.OTBTaskDetail.open(row.dataset.taskId);
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
// Release lives in the in-flight table (the viewer's own claims only).
|
|
1246
|
+
$('#wip-table')?.addEventListener('click', (e) => {
|
|
1247
|
+
// task 1759: the bulk-release checkbox — mirrors the task-list one above.
|
|
1248
|
+
const selectCb = e.target.closest('.wip__select');
|
|
1249
|
+
if (selectCb) {
|
|
1250
|
+
const claimId = String(selectCb.dataset.claimId);
|
|
1251
|
+
if (selectCb.checked) selection.release.set(claimId, selectCb.dataset.taskId);
|
|
1252
|
+
else selection.release.delete(claimId);
|
|
1253
|
+
renderBulkToolbar();
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const rel = e.target.closest('.wip__release');
|
|
1257
|
+
if (!rel) return;
|
|
1258
|
+
doRelease(rel.dataset.claimId, rel.dataset.taskId, rel);
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
// task 1759: the bottom bulk-action toolbar.
|
|
1262
|
+
$('#bulk-claim-btn')?.addEventListener('click', doBulkClaim);
|
|
1263
|
+
$('#bulk-release-btn')?.addEventListener('click', doBulkRelease);
|
|
1264
|
+
$('#bulk-clear-btn')?.addEventListener('click', clearSelection);
|
|
1265
|
+
$('#bulk-parallel-btn')?.addEventListener('click', async () => {
|
|
1266
|
+
const ids = selectedClaimIds();
|
|
1267
|
+
const tasks = ids.map((id) => taskState.all.find((t) => Number(t.id) === id)).filter(Boolean);
|
|
1268
|
+
if (tasks.length < 2) return;
|
|
1269
|
+
const ok = await copyText(buildBulkParallelPrompt(tasks));
|
|
1270
|
+
toast(ok ? `Parallel-session prompt for ${tasks.length} tasks copied.` : 'Could not copy to the clipboard.');
|
|
1271
|
+
});
|
|
1272
|
+
$('#bulk-sequential-btn')?.addEventListener('click', async () => {
|
|
1273
|
+
const ids = selectedClaimIds();
|
|
1274
|
+
const key = selectionKey(ids);
|
|
1275
|
+
if (sequentialCache.key !== key || sequentialCache.order == null) return;
|
|
1276
|
+
const tasks = sequentialCache.order.map((id) => taskState.all.find((t) => Number(t.id) === id)).filter(Boolean);
|
|
1277
|
+
if (tasks.length < 2) return;
|
|
1278
|
+
const ok = await copyText(buildBulkSequentialPrompt(tasks));
|
|
1279
|
+
toast(ok ? `Sequential prompt for ${tasks.length} tasks copied.` : 'Could not copy to the clipboard.');
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1284
|
+
wireEvents();
|
|
1285
|
+
loadAll()
|
|
1286
|
+
.then(() => startPolling())
|
|
1287
|
+
.catch((err) => {
|
|
1288
|
+
console.error('[work] load failed:', err);
|
|
1289
|
+
toast('The work board failed to load. Reload to try again.');
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
})();
|