@bongos/core 1.23.22 → 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 +902 -407
- 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 +2 -2
- 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 +90 -0
- package/clients/bongos-client/index.cjs +90 -0
- package/clients/bongos-client/index.d.ts +149 -4
- package/clients/bongos-client/index.mjs +90 -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/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 +5 -1
- package/docs/api/openapi.json +8555 -5876
- package/docs/api-reference.md +63 -10
- 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 +1 -0
- package/docs/module-api-changelog.md +5 -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/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/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 +235 -5
- 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 +34 -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 +22 -0
- package/modules/government/routing.js +39 -5
- package/modules/government/waiting.js +74 -0
- 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/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-claims.js +32 -4
- package/modules/lifecycle/db-goals.js +18 -5
- package/modules/lifecycle/db-tasks.js +15 -6
- 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/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/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 +6 -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 +220 -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 +3 -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 +26 -5
- 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 +12 -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/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/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/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/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_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 +1 -1
- package/tests/hall_ai_studio.mjs +347 -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/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/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/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/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 +10 -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_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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// modules/hall-ui/public/ai-studio-lib.js — the pure rules of the Studio's AI
|
|
2
|
+
// pages, /builders/ai-studio (task 1004921, goal 1000136, ADR 0392).
|
|
3
|
+
//
|
|
4
|
+
// Built 1:1 on the owner's pick, option C of the AI Studio options canvas
|
|
5
|
+
// ("Three steps: pick, describe, make"), with the owner's addition that the
|
|
6
|
+
// results page lets the request be changed and made again. Everything here is
|
|
7
|
+
// pure: money in words, the receipt, the month's summary of the ledger, the
|
|
8
|
+
// waiting checklist, which view a URL names, and the reply's safe markup.
|
|
9
|
+
// ai-studio.js does the reads, the writes and the painting.
|
|
10
|
+
//
|
|
11
|
+
// Money is integer micro-dollars everywhere (1,000,000 = $1), as the server
|
|
12
|
+
// sends it. A PRICE with sub-cent digits shows four decimals ($0.0421); a
|
|
13
|
+
// whole-cent amount and a BALANCE show two ($12.40). A balance rounds DOWN, so
|
|
14
|
+
// the page never promises a cent that is not there.
|
|
15
|
+
//
|
|
16
|
+
// The file limits below are the upload checks' (modules/ai-studio/studio.js),
|
|
17
|
+
// repeated so the page can say no before a 4 MB upload starts. The server
|
|
18
|
+
// still checks every file; these only spare the wait.
|
|
19
|
+
//
|
|
20
|
+
// Node-require-safe: assignment to window is the only side effect, and only
|
|
21
|
+
// when a window exists (tests/hall_ai_studio.mjs requires it directly).
|
|
22
|
+
|
|
23
|
+
(function (root) {
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const MICRO_PER_USD = 1000000;
|
|
27
|
+
const MICRO_PER_CENT = 10000;
|
|
28
|
+
const MAX_FILES = 6;
|
|
29
|
+
const MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
30
|
+
const MAX_PDF_BYTES = 8 * 1024 * 1024;
|
|
31
|
+
const IMAGE_TYPES = Object.freeze(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
|
|
32
|
+
const PDF_TYPE = 'application/pdf';
|
|
33
|
+
const FINISHED = Object.freeze(['done', 'failed', 'refused']);
|
|
34
|
+
const STEPS = Object.freeze(['pick', 'describe', 'make']);
|
|
35
|
+
const TOPUP_CHOICES = Object.freeze([5, 10, 20, 50]);
|
|
36
|
+
// The waiting checklist is paced by the clock: the server reports a request
|
|
37
|
+
// as queued or running, not which call it is on, so "wrote the answer" is
|
|
38
|
+
// ticked once a run has gone on long enough for the reply to be back.
|
|
39
|
+
const ANSWER_AFTER_SEC = 25;
|
|
40
|
+
|
|
41
|
+
function isMicro(v) { return Number.isSafeInteger(v); }
|
|
42
|
+
|
|
43
|
+
// "$0.0421", "$0.50", "$12.40". Sub-cent amounts keep four decimals.
|
|
44
|
+
function usd(micro) {
|
|
45
|
+
if (!isMicro(micro)) return '';
|
|
46
|
+
const neg = micro < 0;
|
|
47
|
+
const a = Math.abs(micro);
|
|
48
|
+
const text = a % MICRO_PER_CENT === 0
|
|
49
|
+
? (a / MICRO_PER_USD).toFixed(2)
|
|
50
|
+
: (Math.round(a / 100) / 10000).toFixed(4);
|
|
51
|
+
return `${neg ? '−' : ''}$${text}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A balance: two decimals, rounded down.
|
|
55
|
+
function balanceUsd(micro) {
|
|
56
|
+
if (!isMicro(micro)) return '';
|
|
57
|
+
const cents = Math.floor(micro / MICRO_PER_CENT);
|
|
58
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sizeText(bytes) {
|
|
62
|
+
const n = Number(bytes) || 0;
|
|
63
|
+
if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
64
|
+
if (n >= 1024) return `${Math.round(n / 1024)} KB`;
|
|
65
|
+
return `${n} B`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Which page the address names. ?request=N is the waiting page or the
|
|
69
|
+
// result; ?view=balance the balance; otherwise one of the three steps.
|
|
70
|
+
function routeOf(search) {
|
|
71
|
+
const q = new URLSearchParams(String(search || ''));
|
|
72
|
+
const request = q.get('request');
|
|
73
|
+
if (request && /^\d{1,18}$/.test(request)) return { view: 'request', id: request };
|
|
74
|
+
if (q.get('view') === 'balance') return { view: 'balance', all: q.get('all') === '1' };
|
|
75
|
+
const step = STEPS.includes(q.get('step')) ? q.get('step') : 'pick';
|
|
76
|
+
const from = q.get('from');
|
|
77
|
+
return { view: 'step', step, from: from && /^\d{1,18}$/.test(from) ? from : null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Says no to a file before it is sent, in the same words the server would.
|
|
81
|
+
function checkFile(file, count) {
|
|
82
|
+
if (count >= MAX_FILES) return `Send up to ${MAX_FILES} files at a time.`;
|
|
83
|
+
const type = String(file && file.type || '');
|
|
84
|
+
if (type !== PDF_TYPE && !IMAGE_TYPES.includes(type)) return 'Pictures (PNG, JPG, GIF, WebP) or a PDF only.';
|
|
85
|
+
const max = type === PDF_TYPE ? MAX_PDF_BYTES : MAX_IMAGE_BYTES;
|
|
86
|
+
if ((Number(file.size) || 0) > max) return type === PDF_TYPE ? 'PDFs up to 8 MB.' : 'Pictures up to 4 MB.';
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isImage(contentType) { return IMAGE_TYPES.includes(String(contentType || '')); }
|
|
91
|
+
|
|
92
|
+
// The step header: "1 Pick ✓", "2 Describe", "3 Make".
|
|
93
|
+
function stepMarks(step) {
|
|
94
|
+
const at = STEPS.indexOf(step);
|
|
95
|
+
return [['1', 'Pick'], ['2', 'Describe'], ['3', 'Make']].map(([n, label], i) => ({
|
|
96
|
+
text: `${n} ${label}${i < at ? ' ✓' : ''}`,
|
|
97
|
+
state: i < at ? 'done' : (i === at ? 'now' : 'later'),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The prompt as one quoted line for step 3's summary row.
|
|
102
|
+
function quoteLine(prompt, max) {
|
|
103
|
+
const t = String(prompt || '').replace(/\s+/g, ' ').trim();
|
|
104
|
+
const cap = max || 64;
|
|
105
|
+
return `“${t.length > cap ? `${t.slice(0, cap - 1).trimEnd()}…` : t}”`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The own key the server will run on: OpenRouter first, as keys.chooseKey
|
|
109
|
+
// prefers it (it reports its cost). null when none is saved.
|
|
110
|
+
function ownKey(keys) {
|
|
111
|
+
const list = (keys && keys.keys) || [];
|
|
112
|
+
const set = (p) => list.find((k) => k && k.provider === p && k.set);
|
|
113
|
+
const k = set('openrouter') || set('gemini');
|
|
114
|
+
if (!k) return null;
|
|
115
|
+
return k.provider === 'openrouter'
|
|
116
|
+
? { provider: 'openrouter', name: 'OpenRouter', billedBy: 'OpenRouter', last4: k.last4 }
|
|
117
|
+
: { provider: 'gemini', name: 'Gemini', billedBy: 'Google', last4: k.last4 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 3's two cards. balance is GET /ai-studio/balance's body.
|
|
121
|
+
function payChoices(keys, balance) {
|
|
122
|
+
const own = ownKey(keys);
|
|
123
|
+
const b = balance || {};
|
|
124
|
+
const avail = b.balance ? b.balance.available_micro : null;
|
|
125
|
+
const max = isMicro(b.max_cost_micro) ? b.max_cost_micro : null;
|
|
126
|
+
const payments = !!b.balance;
|
|
127
|
+
return {
|
|
128
|
+
own,
|
|
129
|
+
payments,
|
|
130
|
+
setAside: max === null ? '' : usd(max),
|
|
131
|
+
left: payments ? `${balanceUsd(avail)} left` : '',
|
|
132
|
+
short: payments && max !== null && avail < max,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The waiting page's three lines, from the request's status and how long it
|
|
137
|
+
// has run.
|
|
138
|
+
function waitSteps(status, elapsedSec, fileCount) {
|
|
139
|
+
const n = Number(fileCount) || 0;
|
|
140
|
+
const files = n === 1 ? 'Read your file' : `Read your ${n} files`;
|
|
141
|
+
const running = status === 'running';
|
|
142
|
+
const answered = running && elapsedSec >= ANSWER_AFTER_SEC;
|
|
143
|
+
return [
|
|
144
|
+
{ text: files, state: running ? 'done' : 'now' },
|
|
145
|
+
{ text: 'Wrote the answer', state: answered ? 'done' : (running ? 'now' : 'later') },
|
|
146
|
+
{ text: 'Drawing the pictures', state: answered ? 'now' : 'later' },
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function clock(sec) {
|
|
151
|
+
const s = Math.max(0, Math.floor(Number(sec) || 0));
|
|
152
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isFinished(status) { return FINISHED.includes(status); }
|
|
156
|
+
|
|
157
|
+
// The receipt on the results page: rows of [label, value] and a note.
|
|
158
|
+
// (The stored request does not name which of the builder's keys ran.)
|
|
159
|
+
// key_source 'ours' is the balance; 'own' with a reported cost is an
|
|
160
|
+
// OpenRouter key; 'own' with none is a Gemini key (Google reports no price).
|
|
161
|
+
// A request that did not finish has no receipt: its error line already says
|
|
162
|
+
// what was (not) charged.
|
|
163
|
+
function receipt(req, multiplier) {
|
|
164
|
+
const r = req || {};
|
|
165
|
+
if (r.status !== 'done') return { rows: [], note: '' };
|
|
166
|
+
if (r.key_source === 'ours' && isMicro(r.price_micro)) {
|
|
167
|
+
const pct = Number(multiplier) > 1 ? Math.round((Number(multiplier) - 1) * 100) : null;
|
|
168
|
+
return {
|
|
169
|
+
rows: [
|
|
170
|
+
['Paid with', 'your balance'],
|
|
171
|
+
['OpenRouter', usd(r.provider_cost_micro)],
|
|
172
|
+
[pct === null ? 'Ours' : `Our ${pct}%`, usd(r.markup_micro)],
|
|
173
|
+
['You paid', usd(r.price_micro)],
|
|
174
|
+
],
|
|
175
|
+
note: '',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (r.key_source === 'own' && isMicro(r.provider_cost_micro)) {
|
|
179
|
+
return {
|
|
180
|
+
rows: [['Paid with', 'your own OpenRouter key'], ['Cost', usd(r.provider_cost_micro)]],
|
|
181
|
+
note: 'OpenRouter bills you for it directly. We charged nothing.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (r.key_source === 'own') {
|
|
185
|
+
return {
|
|
186
|
+
rows: [['Paid with', 'your own Gemini key'], ['Cost', 'not reported']],
|
|
187
|
+
note: "Google doesn't tell us the price on your own key, so check your Google bill. We charged nothing.",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return { rows: [], note: '' };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// "Same 3 files, paid the same way."
|
|
194
|
+
function againLine(uploadCount) {
|
|
195
|
+
const n = Number(uploadCount) || 0;
|
|
196
|
+
return `Change the words and make it again. Same ${n === 1 ? 'file' : `${n} files`}, paid the same way.`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// The month's summary of the ledger rows that fall in `now`'s month.
|
|
200
|
+
function monthSummary(ledger, now) {
|
|
201
|
+
const d = now instanceof Date ? now : new Date();
|
|
202
|
+
const inMonth = (row) => {
|
|
203
|
+
const t = new Date(row.created_at);
|
|
204
|
+
return !Number.isNaN(t.getTime()) && t.getFullYear() === d.getFullYear() && t.getMonth() === d.getMonth();
|
|
205
|
+
};
|
|
206
|
+
const rows = (ledger || []).filter(inMonth);
|
|
207
|
+
const sum = (kind) => rows.filter((r) => r.kind === kind).reduce((a, r) => a + (Number(r.amount_micro) || 0), 0);
|
|
208
|
+
const count = (kind) => rows.filter((r) => r.kind === kind).length;
|
|
209
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
210
|
+
const lines = [];
|
|
211
|
+
if (count('topup')) lines.push({ label: 'Added', value: `+${usd(sum('topup'))}`, tone: 'ok' });
|
|
212
|
+
if (count('capture')) lines.push({ label: `Spent on ${plural(count('capture'), 'request', 'requests')}`, value: usd(-sum('capture')), tone: '' });
|
|
213
|
+
if (count('release')) lines.push({ label: `Money back from ${plural(count('release'), 'failed request', 'failed requests')}`, value: `${usd(sum('release'))} returned`, tone: 'ok' });
|
|
214
|
+
if (count('own_key')) lines.push({ label: `On your own keys, ${plural(count('own_key'), 'request', 'requests')}`, value: 'free', tone: 'dim' });
|
|
215
|
+
return { month: d.toLocaleString('en', { month: 'long' }), lines };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// One line of the full ledger. `prompts` maps request id to its prompt.
|
|
219
|
+
function ledgerLine(row, prompts) {
|
|
220
|
+
const r = row || {};
|
|
221
|
+
const what = (r.request_id && prompts && prompts[String(r.request_id)])
|
|
222
|
+
? quoteLine(prompts[String(r.request_id)], 40).slice(1, -1)
|
|
223
|
+
: 'A request';
|
|
224
|
+
const amt = Number(r.amount_micro) || 0;
|
|
225
|
+
switch (r.kind) {
|
|
226
|
+
case 'topup': return { what: 'Top up', detail: String(r.note || '').replace(/^card charged /, 'card '), value: `+${usd(amt)}`, tone: 'ok' };
|
|
227
|
+
case 'hold': return { what, detail: 'set aside while it ran', value: usd(amt), tone: 'dim' };
|
|
228
|
+
case 'capture': return { what, detail: `${usd(Number(r.provider_cost_micro) || 0)} + ${usd(Number(r.markup_micro) || 0)}`, value: usd(-amt), tone: '' };
|
|
229
|
+
case 'release': return { what, detail: 'failed, all money back', value: `${usd(amt)} returned`, tone: 'ok' };
|
|
230
|
+
case 'own_key': return { what, detail: 'your own key', value: 'free', tone: 'dim' };
|
|
231
|
+
default: return { what, detail: '', value: usd(amt), tone: '' };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// The model's reply as paragraphs, **bold** kept, everything else escaped by
|
|
236
|
+
// the hall's ONE escaper, handed in (window.OTB.escapeHtml in the page).
|
|
237
|
+
function replyHtml(md, escape) {
|
|
238
|
+
const text = String(md || '').replace(/\r\n/g, '\n').trim();
|
|
239
|
+
if (!text || typeof escape !== 'function') return '';
|
|
240
|
+
return text.split(/\n{2,}/).map((para) => {
|
|
241
|
+
const safe = escape(para.trim()).replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br>');
|
|
242
|
+
return `<p>${safe}</p>`;
|
|
243
|
+
}).join('');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The home's AI action line: the newest request, while it is being made or
|
|
247
|
+
// when it finished in the last day, is what the line points at.
|
|
248
|
+
function homeLine(requests, now) {
|
|
249
|
+
const newest = (requests || [])[0];
|
|
250
|
+
const t = now instanceof Date ? now.getTime() : Date.now();
|
|
251
|
+
if (newest && (newest.status === 'queued' || newest.status === 'running')) {
|
|
252
|
+
return { detail: 'one is being made now', href: `/builders/ai-studio?request=${newest.id}` };
|
|
253
|
+
}
|
|
254
|
+
const fin = newest && newest.finished_at ? new Date(newest.finished_at).getTime() : NaN;
|
|
255
|
+
if (newest && newest.status === 'done' && t - fin < 24 * 3600 * 1000) {
|
|
256
|
+
return { detail: 'your newest pictures are ready', href: `/builders/ai-studio?request=${newest.id}` };
|
|
257
|
+
}
|
|
258
|
+
return { detail: 'sketches in, ideas and pictures out', href: '/builders/ai-studio' };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lib = {
|
|
262
|
+
MAX_FILES, MAX_IMAGE_BYTES, MAX_PDF_BYTES, IMAGE_TYPES, PDF_TYPE, STEPS, TOPUP_CHOICES, ANSWER_AFTER_SEC,
|
|
263
|
+
usd, balanceUsd, sizeText, routeOf, checkFile, isImage, stepMarks, quoteLine, ownKey, payChoices,
|
|
264
|
+
waitSteps, clock, isFinished, receipt, againLine, monthSummary, ledgerLine, replyHtml, homeLine,
|
|
265
|
+
};
|
|
266
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = lib;
|
|
267
|
+
if (root) root.OTBAiStudioLib = lib;
|
|
268
|
+
})(typeof window !== 'undefined' ? window : null);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* modules/hall-ui/public/ai-studio.css — the studio's AI pages,
|
|
2
|
+
/builders/ai-studio (task 1004921, goal 1000136).
|
|
3
|
+
|
|
4
|
+
Built 1:1 on option C of the AI Studio options canvas, the owner's pick: the
|
|
5
|
+
three steps, the waiting page, the result with its request to change and
|
|
6
|
+
make again, and the balance. It rides studio.css: the room, its night and
|
|
7
|
+
day tokens (--st-*) and the glass action rows are the studio's own, and this
|
|
8
|
+
sheet spends only those tokens, never a colour of its own. Where it differs
|
|
9
|
+
from the canvas, and why, is in modules/hall-ui/records/ai-studio.md. */
|
|
10
|
+
|
|
11
|
+
.ais-page { position: relative; z-index: 1; width: min(580px, 100%); display: flex; flex-direction: column; gap: 22px; }
|
|
12
|
+
.ais[data-view="result"] .ais-page { width: 100%; }
|
|
13
|
+
.ais-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
|
14
|
+
|
|
15
|
+
.ais-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
16
|
+
.ais-quiet, .ais-note { margin: 0; font-size: 17px; line-height: 1.5; color: var(--st-ink-soft); }
|
|
17
|
+
.ais-soft { color: var(--st-ink-soft); }
|
|
18
|
+
.ais-faint { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--st-ink-faint); }
|
|
19
|
+
.ais-dim { font-size: 13px; color: var(--st-ink-dim); white-space: nowrap; }
|
|
20
|
+
.ais-grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
21
|
+
.ais-wrap { white-space: normal; }
|
|
22
|
+
.ais-head { font-family: var(--st-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--st-ink-faint); }
|
|
23
|
+
.ais-back { align-self: flex-start; display: inline-flex; align-items: center; min-height: 32px; }
|
|
24
|
+
.ais [data-head]:focus { outline: none; }
|
|
25
|
+
.ais [data-head]:focus-visible { outline: 2px solid var(--st-focus); outline-offset: 4px; }
|
|
26
|
+
|
|
27
|
+
/* The step line: back, then "1 Pick ✓ 2 Describe 3 Make". */
|
|
28
|
+
.ais-bar { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
|
29
|
+
.ais-marks { margin-left: auto; display: flex; gap: 18px; font-size: 13px; }
|
|
30
|
+
.ais-mark--done { color: var(--st-link); }
|
|
31
|
+
.ais-mark--now { color: var(--st-ink); font-weight: 500; }
|
|
32
|
+
.ais-mark--later { color: var(--st-ink-dim); }
|
|
33
|
+
|
|
34
|
+
.ais-ask { margin: 20px 0 0; font-size: 44px; font-weight: 200; letter-spacing: -.03em; line-height: 1.1; color: var(--st-ink); }
|
|
35
|
+
.ais-ask label { cursor: text; }
|
|
36
|
+
|
|
37
|
+
/* Step 1: the drop zone and the one-line file rows. */
|
|
38
|
+
.ais-drop {
|
|
39
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
|
|
40
|
+
min-height: 120px; padding: 16px; box-sizing: border-box; text-align: center;
|
|
41
|
+
border-radius: 18px; border: 1.5px dashed var(--st-rule); color: var(--st-ink-soft); font-size: 14px;
|
|
42
|
+
}
|
|
43
|
+
.ais-drop--over { border-color: var(--st-focus); background: var(--st-glass); }
|
|
44
|
+
.ais-link {
|
|
45
|
+
display: inline-flex; align-items: center; min-height: 24px; min-width: 24px; padding: 0; border: 0; background: none;
|
|
46
|
+
font: inherit; color: var(--st-link); text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
|
|
47
|
+
}
|
|
48
|
+
.ais-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
49
|
+
.ais-row {
|
|
50
|
+
display: flex; align-items: center; gap: 14px; min-height: 48px; padding: 0 18px; box-sizing: border-box;
|
|
51
|
+
border-radius: 16px; background: var(--st-glass); border: 1px solid var(--st-glass-edge); font-size: 14px; color: var(--st-ink);
|
|
52
|
+
}
|
|
53
|
+
.ais-row--summary { min-height: 56px; }
|
|
54
|
+
.ais-thumb { flex: none; width: 28px; height: 28px; border-radius: 7px; object-fit: cover; background: var(--st-track); }
|
|
55
|
+
.ais-thumb--pdf { display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: var(--st-link); }
|
|
56
|
+
.ais-remove {
|
|
57
|
+
min-height: 24px; min-width: 24px; padding: 0 2px; border: 0; background: none; cursor: pointer;
|
|
58
|
+
font: inherit; font-size: 13px; color: var(--st-ink-dim);
|
|
59
|
+
}
|
|
60
|
+
.ais-remove:hover { color: var(--st-ink); }
|
|
61
|
+
.ais-err-inline { font-size: 13px; color: var(--st-danger); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
|
|
62
|
+
|
|
63
|
+
/* Buttons: the studio's accent pill and its quiet ghost. */
|
|
64
|
+
.ais-go { display: flex; align-items: center; gap: 16px; }
|
|
65
|
+
.ais-go--wrap { flex-wrap: wrap; gap: 10px; }
|
|
66
|
+
.ais-btn {
|
|
67
|
+
display: inline-flex; align-items: center; justify-content: center; height: 48px; padding: 0 26px; border-radius: 999px;
|
|
68
|
+
border: 0; background: var(--st-accent); color: var(--st-on-accent); font: inherit; font-size: 15px; font-weight: 500; cursor: pointer;
|
|
69
|
+
}
|
|
70
|
+
.ais-btn:disabled { opacity: .5; cursor: default; }
|
|
71
|
+
.ais-ghost {
|
|
72
|
+
display: inline-flex; align-items: center; justify-content: center; height: 44px; padding: 0 18px; border-radius: 999px;
|
|
73
|
+
border: 0; background: var(--st-close); color: var(--st-ink); font: inherit; font-size: 14px; text-decoration: none; cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.ais a.ais-ghost:hover { color: var(--st-ink); border-color: var(--st-focus); }
|
|
76
|
+
.ais-btn:focus-visible, .ais-ghost:focus-visible, .ais-link:focus-visible, .ais-remove:focus-visible,
|
|
77
|
+
.ais-chip:focus-visible, .ais-pick:focus-visible, .ais-back:focus-visible { outline: 2px solid var(--st-focus); outline-offset: 3px; }
|
|
78
|
+
|
|
79
|
+
/* Step 2: the request box (also the result's "Your request"). */
|
|
80
|
+
.ais-field {
|
|
81
|
+
width: 100%; box-sizing: border-box; min-height: 96px; padding: 16px 18px; resize: vertical;
|
|
82
|
+
border-radius: 18px; background: var(--st-glass); border: 1px solid var(--st-panel-edge);
|
|
83
|
+
color: var(--st-ink); font: inherit; font-size: 15px; line-height: 1.5; font-weight: 300;
|
|
84
|
+
}
|
|
85
|
+
.ais-field--tall { min-height: 160px; font-size: 16px; }
|
|
86
|
+
.ais-field::placeholder { color: var(--st-ink-dim); }
|
|
87
|
+
.ais-field:focus-visible { outline: 2px solid var(--st-focus); outline-offset: 2px; }
|
|
88
|
+
|
|
89
|
+
/* Step 3: the two pay cards, the studio's glass action rows made tall. */
|
|
90
|
+
.ais-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
|
91
|
+
.ais .ais-card { min-height: 150px; justify-content: flex-start; padding-top: 22px; font: inherit; }
|
|
92
|
+
.ais .ais-card--ours { background: var(--st-glass); border-color: var(--st-glass-edge); }
|
|
93
|
+
.ais .ais-card:disabled { opacity: .55; cursor: default; }
|
|
94
|
+
.ais .ais-card[aria-busy="true"] { border-color: var(--st-focus); }
|
|
95
|
+
.ais-card__line { font-size: 13.5px; line-height: 1.45; color: var(--st-ink-soft); }
|
|
96
|
+
.ais-card__foot { margin-top: 10px; font-size: 13px; color: var(--st-ink-dim); }
|
|
97
|
+
|
|
98
|
+
.ais-err { margin: 0; font-size: 14px; line-height: 1.5; color: var(--st-danger); }
|
|
99
|
+
.ais-err[hidden] { display: none; }
|
|
100
|
+
|
|
101
|
+
/* The waiting page: the clock, the three lines, and the lamp turned up. */
|
|
102
|
+
.ais-wait { margin-top: 70px; display: flex; flex-direction: column; gap: 18px; }
|
|
103
|
+
.ais-clock { margin: 0; font-family: var(--st-mono); font-size: 56px; font-weight: 200; letter-spacing: -.03em; line-height: 1; color: var(--st-ink); font-variant-numeric: tabular-nums; }
|
|
104
|
+
.ais-wait > .ais-soft { margin: -10px 0 0; }
|
|
105
|
+
.ais-checks { list-style: none; margin: 0; padding: 0; width: min(420px, 100%); }
|
|
106
|
+
.ais-check { display: flex; align-items: center; gap: 12px; min-height: 40px; border-top: 1px solid var(--st-track); font-size: 14px; color: var(--st-ink); }
|
|
107
|
+
.ais-check:first-child { border-top: 0; }
|
|
108
|
+
.ais-check__mark { width: 22px; flex: none; color: var(--st-link); }
|
|
109
|
+
.ais-check--now .ais-check__mark::before {
|
|
110
|
+
content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
|
|
111
|
+
background: var(--st-accent); box-shadow: 0 0 0 4px var(--st-resume);
|
|
112
|
+
}
|
|
113
|
+
.ais-check--later { color: var(--st-ink-dim); }
|
|
114
|
+
/* By day the room's lamp is off; while a request runs it is on in either sky,
|
|
115
|
+
so "the lamp stays on until it's done" holds at noon too. */
|
|
116
|
+
.ais[data-making="on"] .st-glow { opacity: 1; transform: scale(1.35); background: radial-gradient(circle, var(--st-glow) 0%, var(--st-glow) 20%, var(--st-glow-out) 65%); }
|
|
117
|
+
.ais[data-making="on"] .st-lamp-shade { box-shadow: 0 18px 40px var(--st-glow); }
|
|
118
|
+
|
|
119
|
+
/* The result: the picture on the left, the words, the request and the receipt
|
|
120
|
+
on the right. The canvas draws it over the bare floor, so the window steps
|
|
121
|
+
back. */
|
|
122
|
+
.ais[data-view="result"] .st-window,
|
|
123
|
+
.ais[data-view="result"] .st-glow,
|
|
124
|
+
.ais[data-view="result"] .st-lamp-stem,
|
|
125
|
+
.ais[data-view="result"] .st-lamp-shade,
|
|
126
|
+
.ais[data-view="result"] .st-mug,
|
|
127
|
+
.ais[data-view="result"] .st-leaf { display: none; }
|
|
128
|
+
.ais-result { display: flex; gap: 40px; align-items: flex-start; }
|
|
129
|
+
.ais-result__art { width: min(640px, 55%); flex: none; display: flex; flex-direction: column; gap: 12px; }
|
|
130
|
+
.ais-result__words { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; padding-top: 44px; }
|
|
131
|
+
.ais-big { display: block; width: 100%; height: 520px; object-fit: cover; border-radius: 16px; background: var(--st-track); }
|
|
132
|
+
.ais-big--none { display: flex; align-items: center; justify-content: center; height: 240px; background: var(--st-glass); border: 1px solid var(--st-glass-edge); box-sizing: border-box; }
|
|
133
|
+
.ais-thumbs { display: flex; align-items: center; gap: 10px; }
|
|
134
|
+
.ais-pick { width: 96px; height: 64px; padding: 0; border: 0; border-radius: 10px; overflow: hidden; background: none; cursor: pointer; }
|
|
135
|
+
.ais-pick img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
136
|
+
.ais-pick--on { outline: 2px solid var(--st-accent); outline-offset: 0; }
|
|
137
|
+
.ais-reply p { margin: 0 0 12px; font-size: 15px; line-height: 1.6; color: var(--st-ink-sub); font-weight: 300; }
|
|
138
|
+
.ais-reply b { color: var(--st-ink); font-weight: 500; }
|
|
139
|
+
.ais-again { display: flex; flex-direction: column; gap: 8px; }
|
|
140
|
+
.ais-receipt, .ais-topup {
|
|
141
|
+
display: flex; flex-direction: column; padding: 10px 20px; border-radius: 22px;
|
|
142
|
+
background: var(--st-glass); border: 1px solid var(--st-glass-edge);
|
|
143
|
+
}
|
|
144
|
+
.ais-line { display: flex; align-items: center; gap: 14px; min-height: 40px; border-top: 1px solid var(--st-track); font-size: 14px; color: var(--st-ink); }
|
|
145
|
+
.ais-line:first-child, .ais-head + .ais-line { border-top: 0; }
|
|
146
|
+
.ais-line a { color: var(--st-link); display: inline-flex; align-items: center; min-height: 24px; }
|
|
147
|
+
|
|
148
|
+
/* The balance. */
|
|
149
|
+
.ais-balance { display: flex; flex-direction: column; gap: 24px; margin-top: 20px; }
|
|
150
|
+
.ais-figure { margin: 0; display: flex; align-items: baseline; gap: 16px; font-weight: 400; font-size: 17px; }
|
|
151
|
+
.ais-big-num { font-family: var(--st-mono); font-size: 56px; font-weight: 200; letter-spacing: -.03em; line-height: 1; color: var(--st-ink); font-variant-numeric: tabular-nums; }
|
|
152
|
+
.ais-topup { gap: 10px; padding: 18px 20px; background: none; border: 0; padding-left: 0; padding-right: 0; }
|
|
153
|
+
.ais-chips { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
154
|
+
.ais-chip {
|
|
155
|
+
display: inline-flex; align-items: center; height: 36px; padding: 0 16px; border-radius: 999px; cursor: pointer;
|
|
156
|
+
border: 1px solid var(--st-chip-edge); background: none; color: var(--st-ink); font: inherit; font-size: 13.5px;
|
|
157
|
+
}
|
|
158
|
+
.ais-chip[aria-pressed="true"] { background: var(--st-resume); border-color: var(--st-resume-edge); }
|
|
159
|
+
.ais-other { display: flex; align-items: center; gap: 12px; }
|
|
160
|
+
.ais-other input {
|
|
161
|
+
width: 120px; height: 40px; padding: 0 12px; box-sizing: border-box; border-radius: 12px;
|
|
162
|
+
border: 1px solid var(--st-panel-edge); background: var(--st-glass); color: var(--st-ink); font: inherit;
|
|
163
|
+
}
|
|
164
|
+
.ais-ledger { display: flex; flex-direction: column; margin-top: 8px; }
|
|
165
|
+
.ais-ledger > .ais-head { margin-bottom: 4px; }
|
|
166
|
+
.ais-all { display: flex; flex-direction: column; }
|
|
167
|
+
.ais-date { width: 64px; flex: none; font-family: var(--st-mono); font-size: 12px; color: var(--st-ink-dim); }
|
|
168
|
+
.ais-detail { white-space: nowrap; font-size: 12.5px; }
|
|
169
|
+
.ais-num { font-family: var(--st-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
170
|
+
.ais-tone-ok { color: var(--st-link); }
|
|
171
|
+
.ais-tone-dim { color: var(--st-ink-dim); }
|
|
172
|
+
|
|
173
|
+
@media (max-width: 1100px) {
|
|
174
|
+
.ais-result { flex-direction: column; gap: 20px; }
|
|
175
|
+
.ais-result__art { width: 100%; }
|
|
176
|
+
.ais-result__words { padding-top: 0; width: 100%; }
|
|
177
|
+
}
|
|
178
|
+
/* On a phone the studio scales its window into the top right and stands the
|
|
179
|
+
desk on the floor at the bottom; the words start below the window and end
|
|
180
|
+
above the plant, so no line is read over the drawing. */
|
|
181
|
+
@media (max-width: 760px) {
|
|
182
|
+
.studio.ais { padding-bottom: 240px; }
|
|
183
|
+
.ais-page { gap: 18px; }
|
|
184
|
+
.ais-ask { margin-top: 196px; font-size: 32px; }
|
|
185
|
+
.ais-marks { margin-left: 0; width: 100%; gap: 14px; }
|
|
186
|
+
.ais-cards { grid-template-columns: minmax(0, 1fr); }
|
|
187
|
+
.ais .ais-card { min-height: 120px; }
|
|
188
|
+
.ais-wait { margin-top: 210px; }
|
|
189
|
+
.ais-balance { margin-top: 210px; }
|
|
190
|
+
.ais-big { height: auto; aspect-ratio: 4 / 3; }
|
|
191
|
+
.ais-detail { display: none; }
|
|
192
|
+
.ais-err-inline { max-width: 40%; }
|
|
193
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- data-artist="on": the studio's mode marker, as on studio.html. These pages
|
|
3
|
+
are rooms of the studio, so entering them is still entering the mode. -->
|
|
4
|
+
<html lang="en" data-artist="on">
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<title>Studio — {{worldName}}</title>
|
|
9
|
+
<meta name="description" content="Make something with AI in the studio: bring sketches, pictures or a PDF, say what you want, and get an answer and pictures back.">
|
|
10
|
+
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
12
|
+
<link rel="stylesheet" href="/builders/studio.css?v=2026-10-09-day">
|
|
13
|
+
<link rel="stylesheet" href="/builders/ai-studio.css?v=2026-10-29-ais1">
|
|
14
|
+
</head>
|
|
15
|
+
<!-- THE STUDIO'S AI PAGES (task 1004921, goal 1000136; ADR 0392). Built 1:1 on
|
|
16
|
+
the owner's pick, option C of the AI Studio options canvas ("Three steps:
|
|
17
|
+
pick, describe, make"), plus the owner's addition: the results page lets
|
|
18
|
+
the request be changed and made again. One page, four views, each a FULL
|
|
19
|
+
page in the studio's own room (never a dialog):
|
|
20
|
+
?step=pick|describe|make the three steps
|
|
21
|
+
?request=N the waiting page, then the result
|
|
22
|
+
?view=balance what is left, add money, every charge
|
|
23
|
+
data-page="ai-studio" is the page's own id (page-inventory.js); studio.css
|
|
24
|
+
names it beside "studio" on the room's rules, so the room, its tokens and
|
|
25
|
+
its two skies apply unchanged. The words and rules are
|
|
26
|
+
ai-studio-lib.js; the record is modules/hall-ui/records/ai-studio.md. A key
|
|
27
|
+
is NEVER asked for here: it is entered only in Settings. -->
|
|
28
|
+
<body data-page="ai-studio" data-page-title="Studio" data-fullscreen-switch>
|
|
29
|
+
|
|
30
|
+
<div class="app">
|
|
31
|
+
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
32
|
+
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
33
|
+
<div class="app-body">
|
|
34
|
+
<header class="app-topbar" id="app-topbar"></header>
|
|
35
|
+
<div class="app-content">
|
|
36
|
+
|
|
37
|
+
<main id="hall-main" class="studio ais" data-view="step">
|
|
38
|
+
<!-- The studio's room, drawn as studio.html draws it. -->
|
|
39
|
+
<div class="st-room" aria-hidden="true">
|
|
40
|
+
<div class="st-window">
|
|
41
|
+
<span class="st-moon"></span>
|
|
42
|
+
<span class="st-star st-star--1"></span>
|
|
43
|
+
<span class="st-star st-star--2"></span>
|
|
44
|
+
<span class="st-star st-star--3"></span>
|
|
45
|
+
<span class="st-star st-star--4"></span>
|
|
46
|
+
<span class="st-star st-star--5"></span>
|
|
47
|
+
<span class="st-cloud st-cloud--1"></span>
|
|
48
|
+
<span class="st-cloud st-cloud--2"></span>
|
|
49
|
+
<span class="st-tower st-tower--1"></span>
|
|
50
|
+
<span class="st-tower st-tower--2"></span>
|
|
51
|
+
<span class="st-tower st-tower--3"></span>
|
|
52
|
+
<span class="st-tower st-tower--4"></span>
|
|
53
|
+
<span class="st-tower st-tower--5"></span>
|
|
54
|
+
<span class="st-light st-light--1"></span>
|
|
55
|
+
<span class="st-light st-light--2"></span>
|
|
56
|
+
<span class="st-light st-light--3"></span>
|
|
57
|
+
<span class="st-light st-light--4"></span>
|
|
58
|
+
<span class="st-light st-light--5"></span>
|
|
59
|
+
<span class="st-light st-light--6"></span>
|
|
60
|
+
<span class="st-light st-light--7"></span>
|
|
61
|
+
<span class="st-mullion"></span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="st-floor"></div>
|
|
64
|
+
<div class="st-sunpatch"></div>
|
|
65
|
+
<div class="st-glow"></div>
|
|
66
|
+
<div class="st-lamp-stem"></div>
|
|
67
|
+
<div class="st-lamp-shade"></div>
|
|
68
|
+
<div class="st-mug"></div>
|
|
69
|
+
<div class="st-leaf st-leaf--1"></div>
|
|
70
|
+
<div class="st-leaf st-leaf--2"></div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- The view, painted by ai-studio.js. The page's one <h1> is each
|
|
74
|
+
view's own question or figure. -->
|
|
75
|
+
<div class="ais-page" id="ais-page">
|
|
76
|
+
<p class="ais-quiet">Opening…</p>
|
|
77
|
+
</div>
|
|
78
|
+
<input type="file" id="ais-file" class="ais-file" multiple accept="image/png,image/jpeg,image/gif,image/webp,application/pdf" tabindex="-1" aria-hidden="true">
|
|
79
|
+
</main>
|
|
80
|
+
|
|
81
|
+
<footer class="app-foot" id="app-foot"></footer>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
87
|
+
|
|
88
|
+
<script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
|
|
89
|
+
<script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
|
|
90
|
+
<script src="/builders/craft-mode.js?v=2026-10-09-living3"></script>
|
|
91
|
+
<script src="/builders/shell.js?v=2026-10-25-escape"></script>
|
|
92
|
+
<script src="/builders/palette.js" defer></script>
|
|
93
|
+
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
|
|
94
|
+
<script src="/builders/studio-lib.js?v=2026-10-09-day"></script>
|
|
95
|
+
<script src="/builders/ai-studio-lib.js?v=2026-10-29-ais1"></script>
|
|
96
|
+
<script src="/builders/ai-studio.js?v=2026-10-29-ais1"></script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|