@bongos/core 1.23.21 → 1.24.1
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 +983 -428
- package/.claude/skills/blocker-review/SKILL.md +9 -1
- package/.claude/skills/blocker-solve/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/collab-review/SKILL.md +5 -2
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +6 -3
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/owner-review/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/read-session-export/SKILL.md +1 -1
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +1 -1
- package/.claude/skills/session-handoff/SKILL.md +1 -1
- package/.claude/skills/strand-fix/SKILL.md +13 -0
- package/.claude/skills/worktree-clean/SKILL.md +1 -1
- package/AGENTS.md +15 -0
- package/GEMINI.md +5 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +94 -0
- package/clients/bongos-client/index.cjs +94 -0
- package/clients/bongos-client/index.d.ts +154 -4
- package/clients/bongos-client/index.mjs +94 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +3 -1
- package/docs/adr/0055-server-mediated-branch-publish.md +1 -1
- package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +87 -0
- package/docs/adr/0386-the-funding-wall-is-a-per-project-rule-claim-time-hold-by-default.md +37 -0
- package/docs/adr/0387-two-ledgers-credits-measure-work-money-stays-with-the-processor.md +39 -0
- package/docs/adr/0388-a-module-sale-is-split-by-the-project-the-buyer-pays-the-platform-fee.md +42 -0
- package/docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md +33 -0
- package/docs/adr/0393-a-platform-account-is-a-person-and-github-is-one-way-in.md +37 -0
- package/docs/adr/0394-market-watch-searches-through-the-messages-api-on-the-projects-key-and-runs-from-its-own-poller.md +102 -0
- package/docs/adr/0395-a-task-says-what-it-needs-and-lists-show-only-what-you-can-do.md +36 -0
- package/docs/adr/0396-google-sign-in-a-stand-in-login-and-a-browser-approved-cli-sign-in.md +41 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +8619 -5885
- package/docs/api-reference.md +68 -9
- package/docs/architecture.md +37 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/copy-inventory.md +420 -337
- package/docs/copy-registry.json +1321 -500
- package/docs/file-map.md +4 -0
- package/docs/module-api-changelog.md +7 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +3 -3
- package/docs/onboarding/diagrams/README.md +1 -1
- package/docs/onboarding/drachmae-and-karma.md +15 -15
- package/docs/onboarding/primer.md +2 -2
- package/docs/page-inventory.json +55 -6
- package/docs/page-readings.json +1185 -1011
- package/docs/recipes/google-sign-in.md +60 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/migrations/core_282_builders_lower_login_idx.sql +33 -0
- package/migrations/core_283_claimable_ignores_page_markers.sql +53 -0
- package/migrations/core_283_payout_approvals.sql +183 -0
- package/migrations/core_284_payout_gates.sql +179 -0
- package/migrations/core_285_task_needs.sql +74 -0
- package/modules/ai-studio/CLAUDE.md +15 -0
- package/modules/ai-studio/billing.js +67 -0
- package/modules/ai-studio/context.js +29 -0
- package/modules/ai-studio/db.js +172 -0
- package/modules/ai-studio/gemini.js +75 -0
- package/modules/ai-studio/generate.js +119 -0
- package/modules/ai-studio/keys.js +73 -0
- package/modules/ai-studio/migrations/ai_studio_001_keys.sql +22 -0
- package/modules/ai-studio/migrations/ai_studio_002_ledger.sql +48 -0
- package/modules/ai-studio/migrations/ai_studio_003_requests.sql +55 -0
- package/modules/ai-studio/module.json +26 -0
- package/modules/ai-studio/openrouter.js +90 -0
- package/modules/ai-studio/payments.js +105 -0
- package/modules/ai-studio/pricing.js +92 -0
- package/modules/ai-studio/routes/ai-studio.js +252 -0
- package/modules/ai-studio/studio.js +121 -0
- package/modules/autonomy/fence.js +64 -1
- package/modules/builder-settings/builder-needs.js +27 -1
- package/modules/copy-desk/routes/copy-desk.js +5 -0
- package/modules/economy/achievements.js +22 -10
- package/modules/economy/cost-classify.js +9 -0
- package/modules/economy/cost-write-guard.js +52 -0
- package/modules/economy/cost.js +2 -1
- package/modules/economy/credits.js +18 -15
- package/modules/economy/goal-reward.js +161 -0
- package/modules/economy/reward.js +4 -0
- package/modules/economy/routes/cost.js +33 -7
- package/modules/government/catalog.js +6 -0
- package/modules/government/db.js +108 -0
- package/modules/government/docket.js +36 -0
- package/modules/government/migrations/government_035_payout_approve.sql +29 -0
- package/modules/government/pollers/board-expiry.js +108 -20
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/government/routes/government.js +50 -18
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- package/modules/grading/grader-subagent.js +30 -11
- package/modules/hall-ui/public/ai-studio-lib.js +268 -0
- package/modules/hall-ui/public/ai-studio.css +193 -0
- package/modules/hall-ui/public/ai-studio.html +98 -0
- package/modules/hall-ui/public/ai-studio.js +556 -0
- package/modules/hall-ui/public/ai-studio.states.json +94 -0
- package/modules/hall-ui/public/blockers-by-person.js +156 -0
- package/modules/hall-ui/public/blockers-lib.js +15 -1
- package/modules/hall-ui/public/blockers.html +8 -0
- package/modules/hall-ui/public/blockers.js +1 -1
- package/modules/hall-ui/public/city.html +15 -15
- package/modules/hall-ui/public/claim-action.js +3 -0
- package/modules/hall-ui/public/collab.html +12 -0
- package/modules/hall-ui/public/craft-mode.js +4 -1
- package/modules/hall-ui/public/criterion-uat-ui.js +4 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
- package/modules/hall-ui/public/goal-inbox.js +1 -1
- package/modules/hall-ui/public/goals-render.js +4 -0
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/gov-living.js +6 -2
- package/modules/hall-ui/public/hall-ask.css +5 -0
- package/modules/hall-ui/public/hall-ask.js +39 -3
- package/modules/hall-ui/public/notes-panel.css +20 -0
- package/modules/hall-ui/public/notes-panel.js +146 -0
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/project-settings.js +7 -5
- package/modules/hall-ui/public/settings-ai-keys.js +124 -0
- package/modules/hall-ui/public/settings.html +15 -0
- package/modules/hall-ui/public/street-deck.js +12 -3
- package/modules/hall-ui/public/studio.css +90 -8
- package/modules/hall-ui/public/studio.html +39 -10
- package/modules/hall-ui/public/studio.js +148 -18
- package/modules/hall-ui/public/task.html +2 -0
- package/modules/hall-ui/public/task.js +4 -0
- package/modules/hall-ui/public/tweak-editor.js +12 -3
- package/modules/hall-ui/public/welcome-boot.css +42 -6
- package/modules/hall-ui/public/welcome-boot.js +20 -6
- package/modules/hall-ui/public/work.js +20 -0
- package/modules/hall-ui/records/ai-studio.md +36 -0
- package/modules/hall-ui/records/blockers-people.md +24 -0
- package/modules/lifecycle/db-analytics.js +10 -3
- package/modules/lifecycle/db-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +48 -18
- package/modules/lifecycle/db-shared.js +9 -0
- package/modules/lifecycle/db-tasks.js +15 -6
- package/modules/lifecycle/db-versions.js +21 -8
- package/modules/lifecycle/done-when.js +23 -1
- package/modules/lifecycle/lifecycle.js +5 -0
- package/modules/lifecycle/migrations/lifecycle_017_notes.sql +46 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/notes.js +219 -0
- package/modules/lifecycle/page-notice.js +124 -0
- package/modules/lifecycle/page-tweak-artist.js +18 -1
- package/modules/lifecycle/publish-reconciler.js +67 -20
- package/modules/lifecycle/routes/claims.js +5 -2
- package/modules/lifecycle/routes/goals.js +3 -1
- package/modules/lifecycle/routes/notes.js +91 -0
- package/modules/lifecycle/routes/project-settings.js +43 -35
- package/modules/lifecycle/routes/task-route-helpers.js +21 -0
- package/modules/lifecycle/routes/task-write-routes.js +21 -3
- package/modules/lifecycle/routes/tasks.js +12 -12
- package/modules/lifecycle/ship-assurance.js +4 -1
- package/modules/lifecycle/ship-card.js +8 -2
- package/modules/lifecycle/task-needs-gate.js +70 -0
- package/modules/market-watch/CLAUDE.md +15 -0
- package/modules/market-watch/db.js +340 -0
- package/modules/market-watch/key.js +58 -0
- package/modules/market-watch/migrations/market_watch_001_tables.sql +249 -0
- package/modules/market-watch/migrations/market_watch_002_run.sql +46 -0
- package/modules/market-watch/module.json +26 -0
- package/modules/market-watch/pollers/hub-seed.js +54 -0
- package/modules/market-watch/pollers/weekly-run.js +100 -0
- package/modules/market-watch/research.js +350 -0
- package/modules/market-watch/run.js +245 -0
- package/modules/market-watch/seed/market-data.json +38 -0
- package/modules/market-watch/seed/partners-data.json +497 -0
- package/modules/market-watch/seed.js +252 -0
- package/modules/market-watch/store.js +230 -0
- package/modules/memory/routes/memory.js +14 -2
- package/modules/memory/routes/search.js +8 -2
- package/modules/onboarding/howto-moments.js +8 -1
- package/modules/platform-identity/account-logins.js +192 -0
- package/modules/platform-identity/migrations/platform_identity_034_account_logins.sql +96 -0
- package/modules/platform-identity/platform-identity.js +5 -4
- package/modules/platform-identity/routes/my-projects.js +6 -0
- package/modules/platform-identity/routes/profile.js +18 -0
- package/modules/platform-identity/routes/public-profile.js +10 -1
- package/modules/platform-identity/routes/sso.js +26 -1
- package/modules/platform-identity/tests/platform-identity.mjs +2 -1
- package/modules/provisioning/credential-preflight.js +13 -3
- package/modules/provisioning/pollers/app-liveness.js +25 -5
- package/modules/provisioning/routes/provisioning.js +1 -1
- package/modules/provisioning/starter-bundles.js +8 -2
- package/modules/public-landing/public/account.html +10 -2
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-bead-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-frost-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-gold-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-blob-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r3-obsidian-pair-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r3-space-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-chrome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg +0 -0
- package/modules/public-landing/public/assets/world/r4-card-nebula.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-1-primordial-sea.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-2-first-cells.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-3-genome.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-4-membrane.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system-cut.webp.json +1 -1
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg +0 -0
- package/modules/public-landing/public/assets/world/r5-life-5-nervous-system.jpg.json +1 -1
- package/modules/public-landing/public/community.html +1 -1
- package/modules/public-landing/public/connections.html +2 -2
- package/modules/public-landing/public/guild.html +1 -1
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/projects.html +5 -5
- package/modules/raise/views/nda.html +2 -0
- package/modules/specialities/db.js +32 -1
- package/modules/specialities/routes/specialities.js +10 -6
- package/modules/status-ui/public/index.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +286 -0
- package/scripts/bongos/autobongos-tweak-lane.js +46 -1
- package/scripts/bongos/backfill-subagent-rewards.js +2 -2
- package/scripts/bongos/claim.js +22 -1
- package/scripts/bongos/db-backup-nightly.sh +29 -0
- package/scripts/bongos/deploy-divergence.js +7 -0
- package/scripts/bongos/doc-cli-guard.js +6 -0
- package/scripts/bongos/fitness.js +3 -2
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/lib.sh +4 -5
- package/scripts/bongos/migration-namespace.js +13 -1
- package/scripts/bongos/plain-cards.js +4 -0
- package/scripts/bongos/provision-github-setup.js +3 -1
- package/scripts/bongos/provision-units.js +6 -1
- package/scripts/bongos/publish-manifest.js +4 -0
- package/scripts/bongos/release.js +2 -0
- package/scripts/bongos/run-unit-tests.js +268 -82
- package/scripts/bongos/seed-tasks.example.json +56 -0
- package/scripts/bongos/seed-tasks.js +222 -0
- package/scripts/bongos/seed-verify-criteria.js +1 -2
- package/scripts/bongos/ship-finish.js +2 -0
- package/scripts/bongos/ship-resume.js +2 -0
- package/scripts/bongos/strand-lease.js +250 -0
- package/scripts/bongos/strand-watch.js +129 -12
- package/scripts/bongos/version-literal-guard.js +2 -2
- package/scripts/discord/post.js +4 -3
- package/scripts/gds/seed-tasks.js +7 -0
- package/scripts/gds/{build-bongos.js → strand-lease.js} +3 -3
- package/scripts/hall-preview/server.js +21 -0
- package/src/bongos/auth-config.js +29 -1
- package/src/bongos/auth-discord.js +3 -3
- package/src/bongos/auth-github.js +3 -6
- package/src/bongos/auth-google.js +217 -0
- package/src/bongos/device-approval.js +140 -0
- package/src/bongos/llm-pricing.js +17 -1
- package/src/bongos/metrics.js +48 -2
- package/src/bongos/middleware/rate-limit.js +30 -11
- package/src/bongos/module-scope-map.js +16 -1
- package/src/bongos/optimizer.js +15 -1
- package/src/bongos/payout-approvals.js +342 -0
- package/src/bongos/payout-gates.js +364 -0
- package/src/bongos/prelaunch-gate.js +7 -9
- package/src/bongos/prelaunch-switch.js +20 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/auth-google.js +246 -0
- package/src/bongos/routes/auth.js +38 -1
- package/src/bongos/routes/backup.js +33 -9
- package/src/bongos/routes/builders.js +57 -30
- package/src/bongos/routes/github.js +9 -0
- package/src/bongos/routes/me.js +18 -0
- package/src/bongos/routes/my-payouts.js +133 -0
- package/src/bongos/routes/payout-approvals.js +312 -0
- package/src/bongos/routes/payout-gates.js +179 -0
- package/src/bongos/routes.js +15 -1
- package/src/bongos/secret-box.js +18 -3
- package/src/bongos/serve-internal.js +47 -19
- package/src/bongos/sign-in-pages.js +138 -0
- package/src/bongos/task-needs.js +146 -0
- package/src/bongos-downloads.js +18 -1
- package/src/instance-config.js +28 -0
- package/src/module-api.js +14 -1
- package/src/platform-server.js +27 -1
- package/tests/account_logins.mjs +326 -0
- package/tests/account_logins_db.mjs +161 -0
- package/tests/ai_studio_billing.mjs +253 -0
- package/tests/ai_studio_generate.mjs +282 -0
- package/tests/ai_studio_keys.mjs +159 -0
- package/tests/ai_studio_requests.mjs +319 -0
- package/tests/auth_backchannel_logout_route.mjs +201 -0
- package/tests/auto_satisfy_criteria.mjs +10 -1
- package/tests/autobongos_tweak_lane.mjs +144 -0
- package/tests/autobongos_tweak_pin_identity.mjs +137 -0
- package/tests/backfill_subagent_rewards.mjs +17 -1
- package/tests/backup_trigger_lock.mjs +119 -0
- package/tests/batched_writes_parity.mjs +340 -0
- package/tests/blockers_people.mjs +198 -0
- package/tests/bounded_rate_maps.mjs +184 -0
- package/tests/claimable_blocker_status.mjs +1 -1
- package/tests/claims_batch_routes.mjs +421 -0
- package/tests/conductor_main_e2e.mjs +4 -0
- package/tests/config_read_cache.mjs +171 -0
- package/tests/copy_desk_page_editor.mjs +12 -0
- package/tests/cost_write_guard.mjs +200 -0
- package/tests/craft_achievements.mjs +33 -10
- package/tests/criterion_goal_attach.mjs +1 -1
- package/tests/db_backup_nightly.mjs +73 -1
- package/tests/deploy_divergence_line.mjs +7 -0
- package/tests/effective_visibility_predicate.mjs +12 -0
- package/tests/fitness.mjs +19 -0
- package/tests/fixtures/per-row-loops-legacy.cjs +245 -0
- package/tests/fixtures/seed-tasks/bongos-v1-idea-routing.json +256 -0
- package/tests/fixtures/seed-tasks/bv1-profiles.json +592 -0
- package/tests/fixtures/seed-tasks/bv2-ideator-surface.json +373 -0
- package/tests/fixtures/seed-tasks/fake-api.mjs +80 -0
- package/tests/github_one_trip.mjs +34 -0
- package/tests/github_repo_visibility.mjs +20 -0
- package/tests/goal_achievement.mjs +30 -1
- package/tests/goal_achievement_reward.mjs +205 -0
- package/tests/goal_archive_disposition.mjs +5 -5
- package/tests/goal_sort_staleness.mjs +2 -1
- package/tests/goal_tier.mjs +11 -1
- package/tests/google_fonts_nonblocking.mjs +84 -0
- package/tests/google_sign_in.mjs +401 -0
- package/tests/government_board_expiry.mjs +94 -0
- package/tests/government_protected_surfaces.mjs +1 -1
- package/tests/government_routes.mjs +146 -23
- package/tests/grader.mjs +31 -0
- package/tests/hall_ai_studio.mjs +347 -0
- package/tests/hall_city.mjs +8 -0
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_studio_home.mjs +61 -2
- package/tests/home_attention_cards.mjs +1 -1
- package/tests/hook_shell_tools.mjs +8 -0
- package/tests/hooks_drain.mjs +1 -1
- package/tests/host_topology_skips.mjs +2 -0
- package/tests/howto_moments.mjs +17 -0
- package/tests/human_signal.mjs +141 -0
- package/tests/install_script_reader.mjs +51 -0
- package/tests/integration_fixture_ids.mjs +82 -0
- package/tests/invite_expiry_db.mjs +13 -1
- package/tests/lib_sh_resolution.mjs +0 -4
- package/tests/market_watch_data.mjs +244 -0
- package/tests/market_watch_data_db.mjs +308 -0
- package/tests/market_watch_module.mjs +114 -0
- package/tests/market_watch_run.mjs +503 -0
- package/tests/metrics.mjs +124 -0
- package/tests/migration_namespace.mjs +14 -0
- package/tests/module_api.mjs +2 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/module_skills_hook.mjs +69 -0
- package/tests/notes.mjs +182 -0
- package/tests/own_scope_guard.mjs +5 -0
- package/tests/page_tweak_marker_sql.mjs +76 -0
- package/tests/page_tweak_no_block.mjs +135 -0
- package/tests/page_tweak_notice.mjs +103 -0
- package/tests/payout_approvals.mjs +253 -0
- package/tests/payout_approvals_db.mjs +106 -0
- package/tests/payout_gates.mjs +322 -0
- package/tests/payout_gates_db.mjs +113 -0
- package/tests/planning_session_skill.mjs +13 -3
- package/tests/project_settings_page.mjs +29 -5
- package/tests/project_settings_spend_cap_route.mjs +235 -0
- package/tests/promotion_sweep.mjs +29 -1
- package/tests/provision.mjs +3 -2
- package/tests/provisioning_app_status.mjs +18 -0
- package/tests/provisioning_credential_preflight.mjs +53 -0
- package/tests/provisioning_env_manifest_edit_delete.mjs +223 -0
- package/tests/public_landing_projects.mjs +37 -0
- package/tests/publish_reconciler_overlap.mjs +60 -0
- package/tests/rank_change_broadcast_port.mjs +151 -0
- package/tests/seed_tasks.mjs +176 -0
- package/tests/seed_verify_criteria.mjs +2 -6
- package/tests/session_start_freshness.mjs +66 -1
- package/tests/shell_hooks.mjs +209 -0
- package/tests/ship_assurance.mjs +28 -0
- package/tests/ship_card.mjs +20 -5
- package/tests/site_classified_once.mjs +142 -0
- package/tests/speciality_db_shape.mjs +23 -0
- package/tests/speciality_session_skills.mjs +48 -2
- package/tests/strand_lease.mjs +249 -0
- package/tests/strand_watch.mjs +58 -0
- package/tests/task_criteria_routes.mjs +275 -0
- package/tests/task_criteria_unlink_db.mjs +123 -0
- package/tests/task_needs.mjs +233 -0
- package/tests/task_needs_db.mjs +110 -0
- package/tests/unit_runner_serial.mjs +266 -43
- package/tests/version_close_route.mjs +1 -1
- package/tests/version_literals.mjs +5 -7
- package/tests/welcome_boot.mjs +16 -5
- package/tests/welcome_boot_browser.mjs +36 -4
- package/docs/recipes/bongos-cli-release.md +0 -66
- package/scripts/bongos/backfill-disciplines.js +0 -144
- package/scripts/bongos/backfill-session-earnings.js +0 -123
- package/scripts/bongos/backfill-session-types.js +0 -87
- package/scripts/bongos/build-bongos.js +0 -94
- package/scripts/bongos/cleanup-orphans.js +0 -74
- package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +0 -152
- package/scripts/bongos/seed-bv1-profiles-tasks.js +0 -135
- package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +0 -390
- package/scripts/bongos/smoke-batch-claim.sh +0 -238
- package/scripts/bongos/smoke-batch-release.sh +0 -316
- package/scripts/bongos/smoke-claim-consistency.sh +0 -222
- package/scripts/bongos/smoke-dependencies.sh +0 -143
- package/scripts/gds/backfill-disciplines.js +0 -7
- package/scripts/gds/backfill-session-earnings.js +0 -7
- package/scripts/gds/backfill-session-types.js +0 -7
- package/scripts/gds/cleanup-orphans.js +0 -7
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profiles-tasks.js +0 -6
- package/scripts/gds/seed-bv2-ideator-surface-tasks.js +0 -6
- package/scripts/gds/smoke-batch-claim.sh +0 -8
- package/scripts/gds/smoke-batch-release.sh +0 -8
- package/scripts/gds/smoke-claim-consistency.sh +0 -8
- package/scripts/gds/smoke-dependencies.sh +0 -8
- package/tests/smoke_dependencies_witness.mjs +0 -46
|
@@ -687,6 +687,32 @@
|
|
|
687
687
|
setText('st-status-detail', lib.statusDetail(home.pages));
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
+
// The AI door and the AI balance line (task 1004921, goal 1000136). Only where
|
|
691
|
+
// the ai-studio module is on: it is off by default, so a missing flag means
|
|
692
|
+
// off here (not moduleOn's show-when-unsure). The door's line follows the
|
|
693
|
+
// newest request (ai-studio-lib.js homeLine); the balance line shows only
|
|
694
|
+
// where paying from a balance is set up.
|
|
695
|
+
async function loadAi() {
|
|
696
|
+
const mods = window.__MODULES__;
|
|
697
|
+
const aiLib = window.OTBAiStudioLib;
|
|
698
|
+
if (!aiLib || !mods || !mods.enabled || mods.enabled['ai-studio'] !== true) return;
|
|
699
|
+
const [reqs, bal] = await Promise.all([
|
|
700
|
+
api.request('GET', `${API}/ai-studio/requests`),
|
|
701
|
+
api.request('GET', `${API}/ai-studio/balance`),
|
|
702
|
+
]);
|
|
703
|
+
if (!reqs.ok && !bal.ok) return;
|
|
704
|
+
const door = byId('st-ai');
|
|
705
|
+
const line = aiLib.homeLine(reqs.ok && reqs.data ? reqs.data.requests : [], new Date());
|
|
706
|
+
if (door) { door.href = line.href; door.hidden = false; }
|
|
707
|
+
setText('st-ai-detail', line.detail);
|
|
708
|
+
const b = bal.ok && bal.data ? bal.data.balance : null;
|
|
709
|
+
const row = byId('st-ai-balance');
|
|
710
|
+
if (row && b) {
|
|
711
|
+
setText('st-ai-balance-detail', `${aiLib.balanceUsd(b.available_micro)} left`);
|
|
712
|
+
row.hidden = false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
690
716
|
// The public site's reviews (task 1004499): docs written with its Artist
|
|
691
717
|
// review button, newest first. Each row is the doc's own link, which opens it
|
|
692
718
|
// in the palette's panel right here (craft-mode.js reads ?note=).
|
|
@@ -760,25 +786,33 @@
|
|
|
760
786
|
return `<ul class="st-surfaces">${rows}</ul>${asked ? `<p class="st-asked">${escapeHtml(asked)}</p>` : ''}${inFlight}${worstList}`;
|
|
761
787
|
}
|
|
762
788
|
|
|
789
|
+
// What the two views of the live site's reviews (the panel and the full page)
|
|
790
|
+
// say in common, so they cannot drift apart.
|
|
791
|
+
const SITE_FAILED = 'The reviews could not be read just now. Try again in a moment.';
|
|
792
|
+
const SITE_NONE = 'None yet. A signed-in builder on the live site sees a Review button in its top bar; what they write lands here.';
|
|
793
|
+
const siteWhen = (iso, withYear) => {
|
|
794
|
+
const d = iso ? new Date(iso) : null;
|
|
795
|
+
return d && !isNaN(d) ? d.toLocaleDateString(undefined, withYear ? { month: 'short', day: 'numeric', year: 'numeric' } : { month: 'short', day: 'numeric' }) : '';
|
|
796
|
+
};
|
|
797
|
+
const siteIdeas = (r) => (r.status === 'filed' ? `${r.idea_count} ${r.idea_count === 1 ? 'idea' : 'ideas'}` : 'splitting');
|
|
798
|
+
const siteExcerpt = (r, max) => {
|
|
799
|
+
const whole = String(r.excerpt || '').replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, '[image]').replace(/\s+/g, ' ').trim();
|
|
800
|
+
return whole.length > max ? `${whole.slice(0, max - 1).replace(/\s+\S*$/, '')}…` : whole;
|
|
801
|
+
};
|
|
802
|
+
// A review's link comes from the server; only a path on this site is followed.
|
|
803
|
+
const siteHref = (link) => (/^\/(?!\/)/.test(String(link || '')) ? String(link) : '#');
|
|
804
|
+
|
|
763
805
|
function siteHtml() {
|
|
764
806
|
if (!home.site) {
|
|
765
|
-
return `<p class="st-panel__line">${home.siteFailed ?
|
|
807
|
+
return `<p class="st-panel__line">${home.siteFailed ? SITE_FAILED : 'Reading the reviews…'}</p>`;
|
|
766
808
|
}
|
|
767
|
-
if (!home.site.length) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const text = whole.length > 140 ? `${whole.slice(0, 139).replace(/\s+\S*$/, '')}…` : whole;
|
|
775
|
-
return `
|
|
776
|
-
<li><a class="st-site" href="${escapeHtml(r.link)}">
|
|
777
|
-
<span class="st-site__head"><span class="st-site__page">${escapeHtml(r.page_title || r.page_path || r.page_id)}</span><span class="st-site__meta">${escapeHtml(r.author_name)} · ${escapeHtml(day(r.submitted_at))} · ${escapeHtml(ideas)}</span></span>
|
|
778
|
-
<span class="st-site__text">${escapeHtml(text)}</span>
|
|
779
|
-
</a></li>`;
|
|
780
|
-
}).join('');
|
|
781
|
-
return `<ul class="st-sites">${rows}</ul>`;
|
|
809
|
+
if (!home.site.length) return `<p class="st-panel__line">${SITE_NONE}</p>`;
|
|
810
|
+
const rows = home.site.map((r) => `
|
|
811
|
+
<li><a class="st-site" href="${escapeHtml(siteHref(r.link))}">
|
|
812
|
+
<span class="st-site__head"><span class="st-site__page">${escapeHtml(r.page_title || r.page_path || r.page_id)}</span><span class="st-site__meta">${escapeHtml(r.author_name)} · ${escapeHtml(siteWhen(r.submitted_at))} · ${escapeHtml(siteIdeas(r))}</span></span>
|
|
813
|
+
<span class="st-site__text">${escapeHtml(siteExcerpt(r, 140))}</span>
|
|
814
|
+
</a></li>`).join('');
|
|
815
|
+
return `<ul class="st-sites">${rows}</ul><p class="st-panel__line"><a href="/builders/studio?view=site">Read all the live-site reviews</a></p>`;
|
|
782
816
|
}
|
|
783
817
|
|
|
784
818
|
function paintPanel() {
|
|
@@ -1092,11 +1126,107 @@
|
|
|
1092
1126
|
window.addEventListener('pagehide', () => { if (wedit.editing) saveWords(); });
|
|
1093
1127
|
}
|
|
1094
1128
|
|
|
1129
|
+
// ---- the full-page reader --------------------------------------------------
|
|
1130
|
+
// The live site's reviews as a page of their own, not a panel over the room:
|
|
1131
|
+
// /studio?view=site lists them, /studio?note=N reads one review. Both replace the
|
|
1132
|
+
// room's greeting and doors in place (html.st-reading) and nothing is modal.
|
|
1133
|
+
|
|
1134
|
+
const IMAGE_RE = /!\[([^\]\n]{0,120})\]\(\/api\/bongos\/page-notes\/images\/(\d{1,18})\)/g;
|
|
1135
|
+
const HEADING_RE = /^(#{1,3})\s+(.*)$/;
|
|
1136
|
+
const ITEM_RE = /^\s*[-*]\s+(.*)$/;
|
|
1137
|
+
const NOTE_RE = /[?&]note=([1-9]\d{0,17})(?:&|$)/;
|
|
1138
|
+
const SITE_VIEW_RE = /[?&]view=site(?:&|$)/;
|
|
1139
|
+
|
|
1140
|
+
// A review's markdown as headings, lists and paragraphs. Deliberately not
|
|
1141
|
+
// md-reader.js: that one follows links and draws tables and code, and a review
|
|
1142
|
+
// may show only this module's own pictures; anything else stays escaped text.
|
|
1143
|
+
// Same subset as the side sheet's reader (page-notes.js), which this replaces
|
|
1144
|
+
// on the Studio.
|
|
1145
|
+
function docBodyHtml(md) {
|
|
1146
|
+
const out = [];
|
|
1147
|
+
let para = [];
|
|
1148
|
+
let list = [];
|
|
1149
|
+
const inline = (line) => escapeHtml(line).replace(IMAGE_RE, (_m, alt, id) => `<img class="st-doc__img" src="${API}/page-notes/images/${id}" alt="${alt}" loading="lazy">`);
|
|
1150
|
+
const flush = () => {
|
|
1151
|
+
if (para.length) out.push(`<p>${para.join('<br>')}</p>`);
|
|
1152
|
+
if (list.length) out.push(`<ul>${list.map((x) => `<li>${x}</li>`).join('')}</ul>`);
|
|
1153
|
+
para = []; list = [];
|
|
1154
|
+
};
|
|
1155
|
+
for (const raw of String(md || '').split('\n')) {
|
|
1156
|
+
const line = raw.replace(/\s+$/, '');
|
|
1157
|
+
const h = HEADING_RE.exec(line);
|
|
1158
|
+
const li = ITEM_RE.exec(line);
|
|
1159
|
+
if (!line.trim()) flush();
|
|
1160
|
+
else if (h) { flush(); out.push(`<h3>${inline(h[2])}</h3>`); }
|
|
1161
|
+
else if (li) { if (para.length) flush(); list.push(inline(li[1])); }
|
|
1162
|
+
else { if (list.length) flush(); para.push(inline(line)); }
|
|
1163
|
+
}
|
|
1164
|
+
flush();
|
|
1165
|
+
return out.join('');
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function showReader(html, title) {
|
|
1169
|
+
const box = byId('st-doc');
|
|
1170
|
+
if (!box) return;
|
|
1171
|
+
setText('st-doc-title', title);
|
|
1172
|
+
const body = byId('st-doc-body');
|
|
1173
|
+
if (body) body.innerHTML = html;
|
|
1174
|
+
box.hidden = false;
|
|
1175
|
+
document.documentElement.classList.add('st-reading');
|
|
1176
|
+
const t = byId('st-doc-title');
|
|
1177
|
+
if (t) t.focus();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function siteListPage() {
|
|
1181
|
+
if (!home.site) return `<p class="st-panel__line">${home.siteFailed ? SITE_FAILED : 'Reading the reviews…'}</p>`;
|
|
1182
|
+
if (!home.site.length) return `<p class="st-panel__line">${SITE_NONE}</p>`;
|
|
1183
|
+
return `<ul class="st-doc-list">${home.site.map((r) => `<li><a class="st-doc-list__row" href="${escapeHtml(siteHref(r.link))}">
|
|
1184
|
+
<span class="st-doc-list__page">${escapeHtml(r.page_title || r.page_path || r.page_id)}</span>
|
|
1185
|
+
<span class="st-doc-list__meta">${escapeHtml(r.author_name)} · ${escapeHtml(siteWhen(r.submitted_at, true))} · ${escapeHtml(siteIdeas(r))}</span>
|
|
1186
|
+
<span class="st-doc-list__text">${escapeHtml(siteExcerpt(r, 260))}</span>
|
|
1187
|
+
</a></li>`).join('')}</ul>`;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
async function openDocPage(id) {
|
|
1191
|
+
showReader('<p class="st-panel__line">Opening the review…</p>', 'Review');
|
|
1192
|
+
const res = await api.request('GET', `${API}/page-notes/${id}`);
|
|
1193
|
+
if (!res.ok || !res.data || !res.data.note) {
|
|
1194
|
+
showReader('<p class="st-panel__err" role="alert">That review could not be opened. It may have been removed, or you may not have access.</p>', 'Review');
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
const n = res.data.note;
|
|
1198
|
+
const ideas = res.data.ideas || [];
|
|
1199
|
+
const filed = n.status === 'filed';
|
|
1200
|
+
const became = filed
|
|
1201
|
+
? `<h3 class="st-doc__h">It became ${ideas.length} ${ideas.length === 1 ? 'idea' : 'ideas'}</h3><ul class="st-doc__ideas">${ideas.map((i) => `<li><a href="/#/idea/${escapeHtml(String(i.id))}">${escapeHtml(i.title)}</a></li>`).join('')}</ul>`
|
|
1202
|
+
: '<p class="st-panel__line">Being split into ideas…</p>';
|
|
1203
|
+
showReader(
|
|
1204
|
+
`<p class="st-doc__meta">${escapeHtml(n.author_name)} · ${escapeHtml(siteWhen(n.submitted_at, true))}</p><div class="st-doc__text">${docBodyHtml(n.body_md)}</div>${became}`,
|
|
1205
|
+
n.page_title || n.page_id || 'Review',
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Which reader the address asks for: one review (?note=N) wins over the list
|
|
1210
|
+
// (?view=site). Read once, so the page makes one request for it.
|
|
1211
|
+
function readerRoute() {
|
|
1212
|
+
const q = (window.location && window.location.search) || '';
|
|
1213
|
+
const note = NOTE_RE.exec(q);
|
|
1214
|
+
if (note) return { note: note[1] };
|
|
1215
|
+
return SITE_VIEW_RE.test(q) ? { site: true } : null;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1095
1218
|
async function bootHome(signedIn) {
|
|
1096
1219
|
if (!hasDom || !lib) return;
|
|
1097
1220
|
wireHome();
|
|
1098
|
-
|
|
1099
|
-
|
|
1221
|
+
const route = readerRoute();
|
|
1222
|
+
if (!signedIn) {
|
|
1223
|
+
// The side sheet stays shut on this page (craft-mode.js), so say why here.
|
|
1224
|
+
if (route) showReader('<p class="st-panel__line">Sign in to read page reviews.</p>', 'Review');
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (route && route.note) openDocPage(route.note);
|
|
1228
|
+
await Promise.all([loadTally(), loadNext(), loadPages(), loadSite(), loadAi()]);
|
|
1229
|
+
if (route && route.site) showReader(siteListPage(), 'From the live site');
|
|
1100
1230
|
if (home.panel) paintPanel();
|
|
1101
1231
|
}
|
|
1102
1232
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<link rel="stylesheet" href="/builders/record.css">
|
|
16
16
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
17
17
|
<link rel="stylesheet" href="/builders/task.css">
|
|
18
|
+
<link rel="stylesheet" href="/builders/notes-panel.css">
|
|
18
19
|
</head>
|
|
19
20
|
<body data-page="task" data-page-title="Task" data-back-fallback="/work">
|
|
20
21
|
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
page's Claim it button consumes it rather than a second hand-rolled POST. -->
|
|
73
74
|
<script src="/builders/claim-action.js?v=2026-09-26-claimaction1" defer></script>
|
|
74
75
|
<script src="/builders/hall-ask.js" defer></script>
|
|
76
|
+
<script src="/builders/notes-panel.js" defer></script>
|
|
75
77
|
<script src="/builders/task.js?v=2026-09-28-taskclaim" defer></script>
|
|
76
78
|
</body>
|
|
77
79
|
</html>
|
|
@@ -815,6 +815,9 @@
|
|
|
815
815
|
: '<button class="td-btn" type="button" id="abandon-open-btn">Retire this task…</button>';
|
|
816
816
|
}
|
|
817
817
|
|
|
818
|
+
// Notes (task 1004745): grouped by purpose, filled by notes-panel.js after the body is set.
|
|
819
|
+
parts.push('<section class="rec-sec"><p class="rec-sec__h">Notes</p><div id="task-notes"></div></section>');
|
|
820
|
+
|
|
818
821
|
// Copy prompt (the "ship link") — the same claim/build/ship prompt the Work
|
|
819
822
|
// Board hands an agent. Purely client-side (clipboard write, no server call).
|
|
820
823
|
parts.push(`
|
|
@@ -833,6 +836,7 @@
|
|
|
833
836
|
if (body) body.innerHTML = parts.join('');
|
|
834
837
|
fillExits(body);
|
|
835
838
|
announceShown(t);
|
|
839
|
+
if (window.OTBNotes) window.OTBNotes.mount(document.getElementById('task-notes'), { task_id: t.id });
|
|
836
840
|
|
|
837
841
|
const copyBtn = $('#copy-prompt-btn');
|
|
838
842
|
if (copyBtn) {
|
|
@@ -296,10 +296,19 @@
|
|
|
296
296
|
$('tw-submit').disabled = !st.writable;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// Information only (task 1004948): engineering work is open on this page's files.
|
|
300
|
+
function engineeringLine() {
|
|
301
|
+
const list = st.ed && Array.isArray(st.ed.engineering_active) ? st.ed.engineering_active : [];
|
|
302
|
+
if (!list.length) return '';
|
|
303
|
+
const who = list.slice(0, 2).map((e) => escapeHtml(e.holder)).join(' and ');
|
|
304
|
+
return `<p>Engineering work is open on this page (${who}), so code may change around your words. You can keep writing.</p>`;
|
|
305
|
+
}
|
|
306
|
+
|
|
299
307
|
function notice(html) {
|
|
300
308
|
const n = $('tw-notice');
|
|
301
|
-
|
|
302
|
-
n.innerHTML =
|
|
309
|
+
const all = (html || '') + engineeringLine();
|
|
310
|
+
if (!all) { n.hidden = true; n.innerHTML = ''; return; }
|
|
311
|
+
n.innerHTML = all;
|
|
303
312
|
n.hidden = false;
|
|
304
313
|
}
|
|
305
314
|
|
|
@@ -309,7 +318,7 @@
|
|
|
309
318
|
if (st.ed.draft_on_older_reading) {
|
|
310
319
|
notice('<p>The page changed after your draft was saved, so the draft is not shown over its new words. Rewrite the lines again here.</p>');
|
|
311
320
|
}
|
|
312
|
-
if (st.writable) return;
|
|
321
|
+
if (st.writable) { if (!st.ed.draft_on_older_reading) notice(''); return; }
|
|
313
322
|
if (!r || r.state === 'draft' || (r.state === 'writing' && !r.holder)) {
|
|
314
323
|
notice('<p>Nobody is writing this page. Take it, and every line here is yours to type over.</p>'
|
|
315
324
|
+ '<button class="tw-notice__act" id="tw-claim" type="button">take this page</button>');
|
|
@@ -56,20 +56,56 @@ html.wb-open { overflow: hidden; }
|
|
|
56
56
|
.wb__skip:hover, .wb__skip:focus-visible { border-color: rgba(255, 255, 255, .6); outline: none; }
|
|
57
57
|
.wb--day .wb__skip { background: rgba(255, 255, 255, .55); border-color: rgba(20, 22, 30, .2); color: #14161c; }
|
|
58
58
|
|
|
59
|
-
/* the banner:
|
|
59
|
+
/* the banner: the logo, animated (task 1004953). The rest state below IS the finished logo; the
|
|
60
|
+
motion lives only in keyframes that run once the veil lifts, so reduced motion (animation: none)
|
|
61
|
+
and a page that never lights both show the whole mark. */
|
|
60
62
|
.wb__banner {
|
|
61
63
|
position: absolute; top: 20px; left: 50%; transform: translateX(-50%); z-index: 4;
|
|
62
|
-
display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 10px
|
|
64
|
+
display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 10px 24px 10px 16px;
|
|
63
65
|
max-width: calc(100% - 140px); text-align: center;
|
|
64
|
-
background: rgba(10, 14, 32, .
|
|
66
|
+
background: rgba(10, 14, 32, .5); border: 1px solid rgba(255, 255, 255, .14); border-radius: 999px;
|
|
65
67
|
backdrop-filter: blur(6px);
|
|
66
68
|
transition: transform 2.2s cubic-bezier(.45, 0, .25, 1);
|
|
67
69
|
}
|
|
68
|
-
.
|
|
70
|
+
.wb__lockup { display: flex; align-items: center; gap: 12px; }
|
|
71
|
+
.wb__mark, .wb__logo { width: clamp(34px, 4.4vw, 46px); height: clamp(34px, 4.4vw, 46px); flex: none; overflow: visible; }
|
|
72
|
+
.wb__logo { object-fit: contain; }
|
|
73
|
+
.wb__name {
|
|
74
|
+
font-family: var(--font-display); font-size: clamp(18px, 2.6vw, 26px); font-weight: 300;
|
|
75
|
+
letter-spacing: -.01em; text-transform: lowercase; white-space: nowrap;
|
|
76
|
+
}
|
|
77
|
+
/* the mark's ink follows the painting: pale by night, dark by day; the moon stays salmon */
|
|
78
|
+
.wb__ring { stroke: #e2e1e4; }
|
|
79
|
+
.wb__planet { fill: #e2e1e4; transform-box: fill-box; transform-origin: center; }
|
|
80
|
+
.wb__moonlet { transform-box: view-box; transform-origin: 100px 100px; }
|
|
81
|
+
.wb--day .wb__ring { stroke: #17181a; }
|
|
82
|
+
.wb--day .wb__planet { fill: #17181a; }
|
|
83
|
+
.wb--lit .wb__ring { stroke-dasharray: 100; animation: wb-ring 1.5s cubic-bezier(.6, 0, .2, 1) .5s both; }
|
|
84
|
+
.wb--lit .wb__planet { animation: wb-planet 1s cubic-bezier(.3, 1.4, .5, 1) .3s both; }
|
|
85
|
+
.wb--lit .wb__moonlet { animation: wb-orbit 1.6s cubic-bezier(.25, .8, .3, 1) 1.1s both, wb-glow 3.2s ease-in-out 2.7s infinite alternate; }
|
|
86
|
+
.wb--lit .wb__name { animation: wb-name 1.2s ease-out 1.5s both; }
|
|
87
|
+
@keyframes wb-ring { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
|
|
88
|
+
@keyframes wb-planet { from { transform: scale(.35); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
89
|
+
@keyframes wb-orbit { from { transform: rotate(-200deg); opacity: 0; } 25% { opacity: 1; } to { transform: none; opacity: 1; } }
|
|
90
|
+
@keyframes wb-glow { from { filter: drop-shadow(0 0 0 rgba(251, 149, 105, 0)); } to { filter: drop-shadow(0 0 7px rgba(251, 149, 105, .95)); } }
|
|
91
|
+
@keyframes wb-name { from { opacity: 0; letter-spacing: .18em; } to { opacity: 1; letter-spacing: -.01em; } }
|
|
69
92
|
.wb__version { font-family: var(--font-mono); font-size: 12px; color: #a5b4cf; }
|
|
70
|
-
.wb--day .wb__banner { background: rgba(255, 255, 255, .7); border-color: rgba(
|
|
93
|
+
.wb--day .wb__banner { background: rgba(255, 255, 255, .7); border-color: rgba(20, 22, 30, .14); }
|
|
71
94
|
.wb--day .wb__version { color: #475569; }
|
|
72
95
|
|
|
96
|
+
/* the credit, bottom right (task 1004953): under the veil, so it arrives with the scene */
|
|
97
|
+
.wb__built {
|
|
98
|
+
position: absolute; right: 16px; bottom: 14px; z-index: 5;
|
|
99
|
+
display: inline-flex; align-items: center; min-height: 24px; padding: 2px 10px; border-radius: 999px;
|
|
100
|
+
font-size: 12px; color: rgba(244, 244, 246, .78); text-decoration: none;
|
|
101
|
+
background: rgba(10, 14, 32, .4); backdrop-filter: blur(4px);
|
|
102
|
+
}
|
|
103
|
+
.wb__built b { font-weight: 600; margin-left: .3em; color: #f4f4f6; }
|
|
104
|
+
.wb__built:hover, .wb__built:focus-visible { color: #fff; outline: 1px solid rgba(255, 255, 255, .5); text-decoration: none; }
|
|
105
|
+
.wb--day .wb__built { color: rgba(20, 22, 28, .7); background: rgba(255, 255, 255, .6); }
|
|
106
|
+
.wb--day .wb__built b { color: #14161c; }
|
|
107
|
+
.wb--leaving .wb__built { opacity: 0; transition: opacity .4s; }
|
|
108
|
+
|
|
73
109
|
/* the greeting and the choice */
|
|
74
110
|
.wb__after {
|
|
75
111
|
position: absolute; left: 0; right: 0; bottom: 8vh; z-index: 5;
|
|
@@ -124,7 +160,7 @@ html.wb-open { overflow: hidden; }
|
|
|
124
160
|
}
|
|
125
161
|
@media (max-width: 600px) {
|
|
126
162
|
.wb__banner { top: 16px; max-width: calc(100% - 110px); border-radius: 18px; }
|
|
127
|
-
.
|
|
163
|
+
.wb__name { font-size: 17px; }
|
|
128
164
|
.wb__choices { grid-template-columns: 1fr; }
|
|
129
165
|
.wb__roles, .wb__roles:has(> :nth-child(3):last-child) { grid-template-columns: 1fr 1fr; }
|
|
130
166
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Home only. The scene is the owner's own painting of a circuit city (welcome-day.webp and
|
|
4
4
|
// welcome-night.webp, 2026-10-23), brought to life: a slow drift across the skyline, data pulses
|
|
5
5
|
// climbing the circuit towers, hover cars crossing, and by night blinking aircraft lights, a breathing
|
|
6
|
-
// moon and twinkling stars.
|
|
7
|
-
//
|
|
6
|
+
// moon and twinkling stars. The banner at the top is the logo, animated, and a "Built on
|
|
7
|
+
// cloudbongos.com" credit sits in the bottom-right corner (task 1004953). The picture follows the local time of day: the day painting from 06:00 to 17:59, the
|
|
8
8
|
// night one otherwise. It opens from a white (day) or black (night) veil, then "Good morning, <name>"
|
|
9
9
|
// comes up with two choices. The way out is slow: the view sinks past the rooftops and fades into the
|
|
10
10
|
// hall underneath.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// - A painting that will not load leaves the sky gradient behind it, and the welcome works the same.
|
|
23
23
|
// - prefers-reduced-motion: no motion; the still painting and the greeting at once, leaving is immediate.
|
|
24
24
|
//
|
|
25
|
-
// The pure parts (the day rule, the
|
|
25
|
+
// The pure parts (the day rule, the logo, the roles, the motion layout) are exported for
|
|
26
26
|
// tests/welcome_boot.mjs; tests/welcome_boot_browser.mjs runs the overlay itself in a real browser.
|
|
27
27
|
(function () {
|
|
28
28
|
'use strict';
|
|
@@ -54,7 +54,20 @@
|
|
|
54
54
|
var id = branding && branding.identity;
|
|
55
55
|
return String((id && (id.worldName || id.productName)) || 'this project');
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
// The banner is the logo, animated (task 1004953; it replaced "Working on the core of <project>"):
|
|
58
|
+
// the ring draws in, the planet settles, the moon glides into the gap and glows. A project with its
|
|
59
|
+
// own logo (theme.logo, ADR 0358) shows that instead, as the sidebar's brand mark does.
|
|
60
|
+
var MARK_SVG = '<svg class="wb__mark" viewBox="0 0 200 200" aria-hidden="true">' +
|
|
61
|
+
'<path class="wb__ring" d="M137 31 A78 78 0 1 0 170 66" pathLength="100" fill="none" stroke-width="15" stroke-linecap="round"/>' +
|
|
62
|
+
'<circle class="wb__planet" cx="100" cy="100" r="55"/>' +
|
|
63
|
+
'<circle class="wb__moonlet" cx="156" cy="46" r="12" fill="#fb9569"/></svg>';
|
|
64
|
+
function logoHtml(branding) {
|
|
65
|
+
var logo = branding && branding.theme && branding.theme.logo;
|
|
66
|
+
var mark = logo ? '<img class="wb__logo" src="' + esc(logo) + '" alt="" width="40" height="40" decoding="async">' : MARK_SVG;
|
|
67
|
+
return '<span class="wb__lockup">' + mark + '<span class="wb__name">' + esc(projectName(branding)) + '</span></span>';
|
|
68
|
+
}
|
|
69
|
+
// The credit in the bottom-right corner (task 1004953).
|
|
70
|
+
var BUILT_ON = { href: 'https://cloudbongos.com', label: 'cloudbongos.com' };
|
|
58
71
|
|
|
59
72
|
// ---------- the roles (the owner's mapping) ----------
|
|
60
73
|
// `mode` is the craft mode the pick sets; `href` null means stay on Home.
|
|
@@ -159,10 +172,11 @@
|
|
|
159
172
|
'<div class="wb__fx" id="wb-fx"></div>' +
|
|
160
173
|
'</div></div>' +
|
|
161
174
|
'<div class="wb__cloud wb__cloud--a" aria-hidden="true"></div><div class="wb__cloud wb__cloud--b" aria-hidden="true"></div>' +
|
|
162
|
-
'<div class="wb__banner"
|
|
175
|
+
'<div class="wb__banner">' + logoHtml(window.__BRANDING__) +
|
|
163
176
|
'<span class="wb__version" id="wb-version" hidden></span></div>' +
|
|
164
177
|
'<div class="wb__veil" aria-hidden="true"></div>' +
|
|
165
178
|
'<button class="wb__skip" type="button" data-wb-skip>Skip</button>' +
|
|
179
|
+
'<a class="wb__built" href="' + BUILT_ON.href + '" target="_blank" rel="noopener">Built on <b>' + BUILT_ON.label + '</b></a>' +
|
|
166
180
|
'<div class="wb__after">' + (builder ? signedInHtml(builder, now) : signedOutHtml()) + '</div>';
|
|
167
181
|
}
|
|
168
182
|
function signedInHtml(builder, now) {
|
|
@@ -374,7 +388,7 @@
|
|
|
374
388
|
var api = {
|
|
375
389
|
DAY_KEY: DAY_KEY, SIGNIN_KEY: SIGNIN_KEY, ROLES: ROLES, ART: ART, TOWERS: TOWERS, BEACONS: BEACONS, LANES: LANES, AW: AW, AH: AH,
|
|
376
390
|
localDay: localDay, firstVisitToday: firstVisitToday, greetingFor: greetingFor, nameOf: nameOf, timeOfDay: timeOfDay,
|
|
377
|
-
artFor: artFor, projectName: projectName,
|
|
391
|
+
artFor: artFor, projectName: projectName, logoHtml: logoHtml, BUILT_ON: BUILT_ON, rolesFor: rolesFor, pulseRuns: pulseRuns,
|
|
378
392
|
onAccess: onAccess, finish: finish, close: close,
|
|
379
393
|
};
|
|
380
394
|
if (typeof window !== 'undefined') { window.OTBWelcome = api; watchSignInLinks(); }
|
|
@@ -88,6 +88,8 @@
|
|
|
88
88
|
const parts = [];
|
|
89
89
|
const creator = $('#f-creator') ? $('#f-creator').value : '';
|
|
90
90
|
const stale = $('#f-stale') ? $('#f-stale').value : '';
|
|
91
|
+
const needs = $('#f-needs') ? $('#f-needs').value : '';
|
|
92
|
+
if (needs) parts.push(`needs=${encodeURIComponent(needs)}`); // task 1004950
|
|
91
93
|
if (creator) parts.push(`created_by=${encodeURIComponent(creator === '__none__' ? 'none' : creator)}`);
|
|
92
94
|
if (stale) parts.push(`stale_days=${encodeURIComponent(stale)}`);
|
|
93
95
|
return parts.length ? `?${parts.join('&')}` : '';
|
|
@@ -115,6 +117,7 @@
|
|
|
115
117
|
const parts = [];
|
|
116
118
|
if (f.creator) parts.push(`created_by=${encodeURIComponent(f.creator === '__none__' ? 'none' : f.creator)}`);
|
|
117
119
|
if (f.stale) parts.push(`stale_days=${encodeURIComponent(f.stale)}`);
|
|
120
|
+
if (f.needs) parts.push(`needs=${encodeURIComponent(f.needs)}`);
|
|
118
121
|
if (f.version) parts.push(`version=${encodeURIComponent(f.version)}`);
|
|
119
122
|
if (f.kind) parts.push(`kind=${encodeURIComponent(f.kind)}`);
|
|
120
123
|
if (f.discipline) parts.push(`discipline=${encodeURIComponent(f.discipline)}`);
|
|
@@ -347,6 +350,7 @@
|
|
|
347
350
|
newcomer: val('#f-newcomer'),
|
|
348
351
|
creator: val('#f-creator'),
|
|
349
352
|
stale: val('#f-stale'),
|
|
353
|
+
needs: val('#f-needs'),
|
|
350
354
|
};
|
|
351
355
|
}
|
|
352
356
|
|
|
@@ -368,6 +372,9 @@
|
|
|
368
372
|
if (f.creator === '__none__' && t.created_by != null) return false;
|
|
369
373
|
if (f.creator && f.creator !== '__none__' && String(t.created_by) !== f.creator) return false;
|
|
370
374
|
if (f.stale && !isStale(taskActivityMs(t), f.stale)) return false;
|
|
375
|
+
const tNeeds = Array.isArray(t.needs) ? t.needs : [];
|
|
376
|
+
if (f.needs === 'none' && tNeeds.length > 0) return false;
|
|
377
|
+
if (f.needs && f.needs !== 'none' && !tNeeds.includes(f.needs)) return false;
|
|
371
378
|
return true;
|
|
372
379
|
}
|
|
373
380
|
|
|
@@ -456,6 +463,8 @@
|
|
|
456
463
|
: `<span class="task__tag">${escapeHtml(window.OTB.roleLabel(t.discipline))}</span>`;
|
|
457
464
|
// A kind nobody set is no tag at all, like the role above (task 1004038).
|
|
458
465
|
const kindTag = window.OTB.kindIsUnset(t.kind) ? '' : `<span class="task__tag">${escapeHtml(t.kind)}</span>`;
|
|
466
|
+
// task 1004950 — what the task needs beyond the build loop, said in words.
|
|
467
|
+
const needsTags = (Array.isArray(t.needs) ? t.needs : []).map((n) => `<span class="task__tag task__tag--needs">${n === 'owner' ? 'needs the project owner' : n === 'server' ? 'needs server access' : escapeHtml(n)}</span>`).join('');
|
|
459
468
|
const newcomerTag = t.newcomer_friendly
|
|
460
469
|
? '<span class="task__tag task__tag--newcomer">newcomer</span>'
|
|
461
470
|
: '';
|
|
@@ -489,6 +498,7 @@
|
|
|
489
498
|
${kindTag}
|
|
490
499
|
${disciplineTag}
|
|
491
500
|
${newcomerTag}
|
|
501
|
+
${needsTags}
|
|
492
502
|
${rankTag}
|
|
493
503
|
${t.priority ? `<span class="task__tag">${escapeHtml(t.priority)}</span>` : ''}
|
|
494
504
|
<span>${minutesText}</span>
|
|
@@ -1162,6 +1172,16 @@
|
|
|
1162
1172
|
// shared STALE_CHOICES so both tiers offer the same lenses.
|
|
1163
1173
|
{ id: 'f-creator', label: 'Creator', options: [{ value: '', label: '— any —' }] },
|
|
1164
1174
|
{ id: 'f-stale', label: 'Age', options: STALE_CHOICES },
|
|
1175
|
+
// task 1004950 — what a task needs beyond the build loop. Answered by the
|
|
1176
|
+
// server (both feeds), like Creator and Age.
|
|
1177
|
+
{
|
|
1178
|
+
id: 'f-needs',
|
|
1179
|
+
label: 'Needs',
|
|
1180
|
+
options: [{ value: '', label: '— any —' },
|
|
1181
|
+
{ value: 'owner', label: 'needs the project owner' },
|
|
1182
|
+
{ value: 'server', label: 'needs server access' },
|
|
1183
|
+
{ value: 'none', label: 'needs neither' }],
|
|
1184
|
+
},
|
|
1165
1185
|
{
|
|
1166
1186
|
id: 'f-fit',
|
|
1167
1187
|
label: 'Run mode',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# The studio's AI pages — /builders/ai-studio (task 1004921)
|
|
2
|
+
|
|
3
|
+
**Goal 1000136, [ADR 0392](../../../docs/adr/0392-ai-generation-runs-on-a-processor-held-balance-and-the-builders-own-keys.md).** Built 1:1 on the owner's pick, option C of the five-option "AI Studio options" canvas ("Three steps: pick, describe, make"), plus the owner's addition (2026-10-29): *"when the image is made it needs the option to edit/update the prompt"*. The server half is the `ai-studio` module (`modules/ai-studio/CLAUDE.md`); this page is its only face besides the key panel in Settings.
|
|
4
|
+
|
|
5
|
+
Files: `ai-studio.html` (the room, drawn as studio.html draws it, and one view mount), `ai-studio.css` (spends only studio.css's `--st-*` tokens), `ai-studio-lib.js` (the pure rules, `window.OTBAiStudioLib`, node-require-safe) and `ai-studio.js` (the reads, the writes, the painting). The page's own id is `data-page="ai-studio"` (page-inventory.js keys pages on it, so it cannot share `studio`); studio.css names `ai-studio` beside `studio` on the room's rules (the tokens, the day sky, the full-width stage, the full-screen button), so the room and its two skies apply unchanged.
|
|
6
|
+
|
|
7
|
+
## The views (one page, the address names the view)
|
|
8
|
+
|
|
9
|
+
- `?step=pick` — "What are you starting from?" Each file uploads as it is picked, one per call; the page says no to a wrong type or size before the upload starts, in the server's words.
|
|
10
|
+
- `?step=describe` — "What should it make?"
|
|
11
|
+
- `?step=make` — "How do you want to pay?" Two cards: *My own key* (only when one is saved; OpenRouter first, as `keys.chooseKey` prefers it) and *My balance* (only where payments are set up; disabled under the run ceiling, with a link to add money). A card press sends the request.
|
|
12
|
+
- `?request=N` — the waiting page (a clock, three lines, the lamp on), polled every 2.5 s, then the result: the picture big with thumbnails and *Save picture*, the reply, **Your request** in an editable box with *Make it again* / *Change files* / *Start something new*, and the receipt.
|
|
13
|
+
- `?view=balance` — what is left, *Add how much?* with the card charge shown before paying, this month's summary, and every charge and refund (`&all=1`).
|
|
14
|
+
|
|
15
|
+
The draft (files and words) lives in sessionStorage, so the steps survive a reload and the back button.
|
|
16
|
+
|
|
17
|
+
## The rules worth knowing
|
|
18
|
+
|
|
19
|
+
- **A file goes with one request only** (the server's `createRequest`). So *Make it again* and *Change files* carry the earlier request's UPLOADS by address and upload a fresh copy of each when the new request is sent; outputs are never copied. *Make it again* pays the same way the first run did (`key_source`).
|
|
20
|
+
- **No price is spelled in the browser.** The card charge comes from `GET /ai-studio/topup/quote` and the "up to $0.50 is set aside" from `max_cost_micro` on `GET /ai-studio/balance`, both added in this task, so the fee and the ceiling stay named only in `pricing.js` / `studio.js`. The file limits ARE repeated in the lib (named, with a pointer), only to spare a doomed 4 MB upload; the server still checks.
|
|
21
|
+
- **Which own key ran is inferred**: the stored request says `own`, not which provider. A reported cost means OpenRouter, none means Gemini (Google reports no price). A failed run shows no receipt; its error line already says what was charged.
|
|
22
|
+
- **A key is never asked for here.** The "add your own key" line links to `/builders/settings#ai-keys`. `tests/hall_ai_studio.mjs` pins the three writes (`upload`, `request`, `topup`) and the absence of a key field.
|
|
23
|
+
- **The studio home's door** ("Make something with AI") and its **AI balance** line show only where `window.__MODULES__.enabled['ai-studio'] === true`: the module is off by default, so a missing flag means off (not `moduleOn`'s show-when-unsure). The door's line follows the newest request while it runs or for a day after it finished.
|
|
24
|
+
|
|
25
|
+
## Where it differs from the canvas, and why
|
|
26
|
+
|
|
27
|
+
- **The column is 580 px**, not the canvas's 680: the studio's window never slides left of 680 px, and a wider column ran under it on a narrower window.
|
|
28
|
+
- **The waiting checklist is paced by the clock** ("Wrote the answer" ticks at 25 s): the server reports queued or running, not which call it is on.
|
|
29
|
+
- **By day the lamp lights while a request runs** (by day the room's lamp is otherwise off), so "the lamp stays on until it's done" holds at noon.
|
|
30
|
+
- **On a phone** the words start below the scaled window and end above the plant (`padding-bottom: 240px`), so no line is read over the drawing.
|
|
31
|
+
- **The PDF badge reads 10 px**, not 8: the kit flags 8 px as tiny type.
|
|
32
|
+
- **Not on the canvas at all:** the no-key and no-payments lines on step 3, the short-balance line, the failed-run result, *Test mode: no real money moves.*, the other-amount box, the module-off and signed-out lines.
|
|
33
|
+
|
|
34
|
+
## The proof
|
|
35
|
+
|
|
36
|
+
`ai-studio.states.json` renders ten states (pick, describe, make, waiting, result, result-own, failed, balance, balance-all, out) against the hall-preview harness with the module on (`node scripts/hall-preview/server.js --port 4637 --fixture-me --modules-on ai-studio`, the `hall-preview-ai-studio` launch entry). All clean at 1440, 390 and 320, dark and light. The harness answers from `ai-studio__*.json` (request 1002 running, 1003 failed, 1004 own Gemini key, any other id the finished result on the balance) and serves every file as a stand-in picture. Behaviour: `tests/hall_ai_studio.mjs` (the lib, and the real page in a vm over those fixtures: the steps, the request sent on the card pressed, *Make it again* with the changed words on fresh copies, *Change files*, a failed run, the balance and top-up, the module off).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Blockers under people — Blockers, Collab and the City say WHO a blocker waits on (task 1004999)
|
|
2
|
+
|
|
3
|
+
Goal 1000111 (working area 7, Government). A blocker has no assignee: who it waits on is the seat its ask
|
|
4
|
+
names (the owner, a building's Lead, a board), and the person in that seat changes. Every surface used to
|
|
5
|
+
show the seat alone — or, on /blockers, every blocker under "Requires project owner fix".
|
|
6
|
+
|
|
7
|
+
- **One rule, the server's.** `GET /government/blockers/people` and `/government/blockers/for-me` group
|
|
8
|
+
open blockers with `modules/government/waiting.js`, which resolves each one through `routing.js` — the
|
|
9
|
+
City's own rule. A page never decides whose a blocker is; it draws the answer.
|
|
10
|
+
- **/blockers** gains a *Waiting on people* section above the queue (`blockers-by-person.js`): the viewer's
|
|
11
|
+
own group first as **You**, then each person with their count, then **Nobody yet** for a seat nobody
|
|
12
|
+
sits in. Each row says the seat in words ("as the owner, since Planning has no Lead"). Hidden until the
|
|
13
|
+
read answers, so a signed-out visitor still gets the public queue and nothing else.
|
|
14
|
+
- **Collab** gains *Blockers waiting on you* beside *Asked of you*, from the same file. Collab loads no
|
|
15
|
+
`hall-kit.js`, so the file falls back to the shell's `OTB.escapeHtml` and a local date format.
|
|
16
|
+
- **The City** names the people on every routed card: `route.people` rides the docket, `gov-living.js`
|
|
17
|
+
keeps it on `waits_on.people`, and `street-deck.js` `whoWords` says "It waits on Ana, the Planning Lead."
|
|
18
|
+
- **The heading stays "Requires project owner fix"** — Home names its summary that way and
|
|
19
|
+
`tests/blockers_page_ui.mjs` pins the match; renaming both is a separate copy call.
|
|
20
|
+
- **Harness fixtures are DERIVED, not hand-written**: `government__docket.json`'s routes and the two
|
|
21
|
+
`government__blockers__*.json` reads are computed with `routing.js` + `waiting.js` over the leads
|
|
22
|
+
fixture, because `tests/hall_preview_governor_fixtures.mjs` re-derives every route and fails on a guess.
|
|
23
|
+
|
|
24
|
+
Tests: `tests/blockers_people.mjs`.
|
|
@@ -860,12 +860,19 @@ async function tallyPeerVotes() {
|
|
|
860
860
|
const karmaRows = [];
|
|
861
861
|
for (const [builderId, net] of netByBuilder) {
|
|
862
862
|
if (net === 0) continue; // no net signal → no karma row, but still mark tallied
|
|
863
|
+
karmaRows.push({ builder_id: builderId, delta: net });
|
|
864
|
+
}
|
|
865
|
+
// task 1004929: ONE insert for every credited builder, not one per builder.
|
|
866
|
+
// WITH ORDINALITY + ORDER BY keeps the karma_log ids in the order the loop
|
|
867
|
+
// inserted them (first vote seen first).
|
|
868
|
+
if (karmaRows.length) {
|
|
863
869
|
await client.query(
|
|
864
870
|
`INSERT INTO karma_log (builder_id, delta, reason, granted_by)
|
|
865
|
-
|
|
866
|
-
|
|
871
|
+
SELECT k.builder_id, k.delta, 'peer_vote_weekly', NULL::bigint
|
|
872
|
+
FROM unnest($1::bigint[], $2::int[]) WITH ORDINALITY AS k(builder_id, delta, ord)
|
|
873
|
+
ORDER BY k.ord`,
|
|
874
|
+
[karmaRows.map((r) => r.builder_id), karmaRows.map((r) => r.delta)]
|
|
867
875
|
);
|
|
868
|
-
karmaRows.push({ builder_id: builderId, delta: net });
|
|
869
876
|
}
|
|
870
877
|
if (voteIds.length) {
|
|
871
878
|
await client.query(
|