@bongos/core 1.24.79 → 1.24.81
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 +554 -394
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +11 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/adr/0274-one-kernel-three-role-packs.md +2 -2
- package/docs/adr/0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md +91 -0
- package/docs/adr/0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md +109 -0
- package/docs/adr/0401-one-options-menu-opens-every-settings-screen-in-a-frame.md +53 -0
- package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +142 -0
- package/docs/adr/README.md +4 -0
- package/docs/api/openapi.json +195 -3
- package/docs/api-reference.md +8 -4
- package/docs/architecture.md +7 -3
- package/docs/copy-inventory.md +393 -378
- package/docs/copy-registry.json +722 -578
- package/docs/design/characters/character-sprites-source.webp +0 -0
- package/docs/file-map.md +7 -8
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +5 -0
- package/docs/onboarding/slash-commands.md +1 -1
- package/docs/packs/{engineer.md → artisan.md} +5 -5
- package/docs/packs/artist.md +50 -4
- package/docs/page-inventory.json +16 -7
- package/docs/page-readings.json +3184 -3338
- package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
- package/migrations/core_296_crafts_artisan_artist.sql +72 -0
- package/migrations/core_297_builder_character.sql +30 -0
- package/modules/copy-desk/proposals.js +1 -1
- package/modules/economy/credits-by-craft.js +29 -15
- package/modules/hall-ui/public/ai-studio.html +6 -4
- package/modules/hall-ui/public/ai-studio.states.json +1 -0
- package/modules/hall-ui/public/atlas.html +5 -5
- package/modules/hall-ui/public/atlas.js +3 -1
- package/modules/hall-ui/public/atlas.states.json +1 -0
- package/modules/hall-ui/public/blockers.html +5 -5
- package/modules/hall-ui/public/board-room.html +4 -4
- package/modules/hall-ui/public/board-room.states.json +1 -0
- package/modules/hall-ui/public/characters/artisan.png +0 -0
- package/modules/hall-ui/public/characters/artist.png +0 -0
- package/modules/hall-ui/public/characters/patron.png +0 -0
- package/modules/hall-ui/public/city.css +3 -4
- package/modules/hall-ui/public/city.html +9 -9
- package/modules/hall-ui/public/city.js +17 -56
- package/modules/hall-ui/public/city.states.json +7 -32
- package/modules/hall-ui/public/collab.html +4 -4
- package/modules/hall-ui/public/collab.states.json +1 -0
- package/modules/hall-ui/public/commands.html +4 -4
- package/modules/hall-ui/public/commands.states.json +1 -0
- package/modules/hall-ui/public/controls.css +63 -11
- package/modules/hall-ui/public/copy-desk.html +4 -4
- package/modules/hall-ui/public/copy-desk.states.json +1 -0
- package/modules/hall-ui/public/craft-mode.js +258 -64
- package/modules/hall-ui/public/deploy.html +4 -4
- package/modules/hall-ui/public/deploy.states.json +1 -0
- package/modules/hall-ui/public/diagram-viewer.js +2 -1
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/goal-map.html +9 -6
- package/modules/hall-ui/public/goals.html +5 -5
- package/modules/hall-ui/public/goals.states.json +1 -0
- package/modules/hall-ui/public/government.html +4 -4
- package/modules/hall-ui/public/government.states.json +1 -0
- package/modules/hall-ui/public/hall-render.js +3 -1
- package/modules/hall-ui/public/home-city-draw.js +3 -3
- package/modules/hall-ui/public/home-city.css +4 -0
- package/modules/hall-ui/public/home-city.js +17 -9
- package/modules/hall-ui/public/home-gallery.js +8 -1
- package/modules/hall-ui/public/home-idea-box.css +2 -2
- package/modules/hall-ui/public/home-idea-box.js +3 -3
- package/modules/hall-ui/public/idea.html +5 -5
- package/modules/hall-ui/public/idea.states.json +1 -0
- package/modules/hall-ui/public/ideas.html +5 -5
- package/modules/hall-ui/public/ideas.states.json +1 -0
- package/modules/hall-ui/public/index-founding.states.json +1 -0
- package/modules/hall-ui/public/index-genesis-decide.states.json +1 -0
- package/modules/hall-ui/public/index-genesis-demo.states.json +1 -0
- package/modules/hall-ui/public/index-genesis.states.json +1 -0
- package/modules/hall-ui/public/index.html +11 -7
- package/modules/hall-ui/public/index.states.json +1 -0
- package/modules/hall-ui/public/market-watch-item.html +4 -4
- package/modules/hall-ui/public/market-watch-item.states.json +1 -0
- package/modules/hall-ui/public/market-watch-rules.html +4 -4
- package/modules/hall-ui/public/market-watch-rules.states.json +1 -0
- package/modules/hall-ui/public/market-watch.html +6 -9
- package/modules/hall-ui/public/market-watch.js +22 -45
- package/modules/hall-ui/public/market-watch.states.json +1 -0
- package/modules/hall-ui/public/modules.html +4 -4
- package/modules/hall-ui/public/modules.states.json +1 -0
- package/modules/hall-ui/public/money.html +4 -4
- package/modules/hall-ui/public/money.states.json +1 -0
- package/modules/hall-ui/public/my-agents.html +4 -4
- package/modules/hall-ui/public/my-agents.states.json +1 -0
- package/modules/hall-ui/public/options-menu.js +409 -0
- package/modules/hall-ui/public/paintbrush.js +3 -3
- package/modules/hall-ui/public/palette.js +11 -13
- package/modules/hall-ui/public/primer.html +5 -5
- package/modules/hall-ui/public/primer.states.json +1 -0
- package/modules/hall-ui/public/profile-crafts.js +1 -1
- package/modules/hall-ui/public/profile-roles.js +23 -19
- package/modules/hall-ui/public/profile.css +1 -1
- package/modules/hall-ui/public/profile.html +19 -18
- package/modules/hall-ui/public/profile.js +19 -18
- package/modules/hall-ui/public/profile.states.json +5 -4
- package/modules/hall-ui/public/project-settings.html +4 -4
- package/modules/hall-ui/public/roster.html +4 -4
- package/modules/hall-ui/public/roster.js +3 -3
- package/modules/hall-ui/public/roster.states.json +1 -0
- package/modules/hall-ui/public/settings.css +24 -4
- package/modules/hall-ui/public/settings.html +21 -17
- package/modules/hall-ui/public/settings.js +44 -44
- package/modules/hall-ui/public/settings.states.json +1 -0
- package/modules/hall-ui/public/shell-game.css +110 -0
- package/modules/hall-ui/public/shell-game.js +316 -3
- package/modules/hall-ui/public/shell.js +15 -66
- package/modules/hall-ui/public/sky-interaction.js +7 -0
- package/modules/hall-ui/public/speciality-tools.js +1 -2
- package/modules/hall-ui/public/street-card.states.json +1 -0
- package/modules/hall-ui/public/street-inside.js +3 -4
- package/modules/hall-ui/public/street.css +1 -5
- package/modules/hall-ui/public/street.html +6 -10
- package/modules/hall-ui/public/street.js +8 -59
- package/modules/hall-ui/public/street.states.json +7 -32
- package/modules/hall-ui/public/studio.html +5 -5
- package/modules/hall-ui/public/studio.states.json +1 -0
- package/modules/hall-ui/public/style.css +197 -112
- package/modules/hall-ui/public/task-detail.js +5 -4
- package/modules/hall-ui/public/task.html +5 -5
- package/modules/hall-ui/public/task.states.json +1 -0
- package/modules/hall-ui/public/thinking.css +10 -0
- package/modules/hall-ui/public/thinking.html +9 -6
- package/modules/hall-ui/public/thinking.states.json +1 -0
- package/modules/hall-ui/public/tweak-editor.html +5 -5
- package/modules/hall-ui/public/tweak-editor.js +5 -1
- package/modules/hall-ui/public/tweak-editor.states.json +1 -0
- package/modules/hall-ui/public/update-pill.js +91 -2
- package/modules/hall-ui/public/visor.css +80 -0
- package/modules/hall-ui/public/visor.js +316 -0
- package/modules/hall-ui/public/watch-repo-invite.js +159 -0
- package/modules/hall-ui/public/watch.html +6 -5
- package/modules/hall-ui/public/watch.js +17 -2
- package/modules/hall-ui/public/watch.states.json +1 -0
- package/modules/hall-ui/public/welcome-boot.js +15 -6
- package/modules/hall-ui/public/work-founding.states.json +1 -0
- package/modules/hall-ui/public/work.html +5 -5
- package/modules/hall-ui/public/work.states.json +1 -0
- package/modules/hall-ui/records/builders-roster.md +2 -2
- package/modules/hall-ui/records/craft-mode.md +13 -11
- package/modules/hall-ui/records/game-mode.md +2 -2
- package/modules/hall-ui/records/governor-street.md +1 -1
- package/modules/hall-ui/records/home-city.md +3 -1
- package/modules/hall-ui/records/market-watch-settings.md +2 -0
- package/modules/hall-ui/records/options-menu.md +15 -0
- package/modules/hall-ui/records/profile-roles.md +11 -11
- package/modules/hall-ui/records/sky.md +3 -3
- package/modules/hall-ui/records/start-screen.md +25 -0
- package/modules/hall-ui/records/tweak-editor.md +1 -1
- package/modules/hall-ui/records/update-pill.md +12 -0
- package/modules/hall-ui/records/visor.md +23 -0
- package/modules/hall-ui/records/welcome-tower.md +5 -5
- package/modules/hall-ui/specimens/components.states.json +2 -1
- package/modules/ideas/ideator-stats.js +3 -1
- package/modules/ideas/inbox.js +2 -2
- package/modules/lifecycle/activity.js +1 -1
- package/modules/lifecycle/cascade.js +4 -4
- package/modules/lifecycle/goal-spec-task.js +1 -1
- package/modules/lifecycle/help-requests.js +19 -15
- package/modules/lifecycle/kickoff-checklist.js +3 -3
- package/modules/lifecycle/kickoff-seed.js +1 -1
- package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
- package/modules/lifecycle/mingle.js +4 -4
- package/modules/lifecycle/page-tweak-write.js +1 -1
- package/modules/lifecycle/routes/task-write-routes.js +3 -1
- package/modules/lifecycle/routes/tasks.js +3 -3
- package/modules/lifecycle/ship-card.js +2 -3
- package/modules/lifecycle/task-classifier.js +39 -29
- package/modules/lifecycle/tests/cascade.mjs +7 -7
- package/modules/onboarding/founding-mode.js +3 -0
- package/modules/onboarding/genesis-home.js +21 -1
- package/modules/onboarding/routes/onboarding.js +2 -0
- package/modules/page-notes/notes.js +3 -2
- package/modules/page-notes/split.js +1 -1
- package/modules/platform-identity/craft-rollup.js +35 -7
- package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
- package/modules/platform-identity/platform-identity.js +2 -2
- package/modules/platform-identity/routes/community-leaderboard.js +2 -1
- package/modules/platform-identity/routes/community-search.js +2 -1
- package/modules/platform-identity/routes/sso.js +2 -1
- package/modules/platform-identity/tests/platform-identity.mjs +6 -6
- package/modules/public-landing/public/community.html +3 -5
- package/modules/public-landing/public/index.html +1 -1
- package/modules/public-landing/public/projects.html +1 -1
- package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
- package/modules/specialities/routes/specialities.js +2 -2
- package/modules/status-ui/public/status.js +1 -1
- package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
- package/modules/ui-design/kit/fixtures/community-search.json +2 -2
- package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
- package/modules/ui-design/public/brand.html +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +76 -0
- package/scripts/bongos/autobongos-loop.js +1 -1
- package/scripts/bongos/discipline-modes.json +7 -12
- package/scripts/bongos/fitness.js +1 -1
- package/scripts/bongos/gen-hall-sitemap.js +7 -7
- package/scripts/bongos/grade-smoke.js +2 -2
- package/scripts/bongos/init.js +2 -2
- package/scripts/bongos/newcomer-floor.config.json +1 -2
- package/scripts/bongos/newcomer-floor.js +4 -3
- package/scripts/bongos/newcomer-floor.templates.json +10 -12
- package/scripts/bongos/newcomer-floor.test.js +47 -38
- package/scripts/bongos/role-pack-guard.js +5 -4
- package/scripts/bongos/seed-tasks.example.json +3 -3
- package/scripts/bongos/setup.js +20 -107
- package/scripts/bongos/start.js +2 -2
- package/scripts/bongos/task.js +5 -4
- package/scripts/bongos/triage.js +1 -1
- package/scripts/bongos/worktree.js +16 -4
- package/scripts/hall-preview/README.md +2 -3
- package/scripts/setup-builder.js +6 -46
- package/src/bongos/auth-admission.js +8 -8
- package/src/bongos/craft-mode.js +2 -0
- package/src/bongos/db.js +39 -1
- package/src/bongos/github-repos.js +78 -0
- package/src/bongos/module-scope-map.js +22 -0
- package/src/bongos/role-stats.js +27 -11
- package/src/bongos/routes/github.js +100 -2
- package/src/bongos/routes/me.js +33 -0
- package/src/bongos/serve-internal.js +33 -11
- package/src/crafts.js +66 -0
- package/src/hall-helm.js +79 -0
- package/src/module-api.js +6 -1
- package/src/module-loader/manifest-schema.js +16 -0
- package/src/modules.js +2 -2
- package/src/role-responsibilities.js +6 -5
- package/tests/acclaim_feed_evidence_db.mjs +1 -1
- package/tests/activity_rollup_as_of.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +3 -3
- package/tests/application_lifecycle.mjs +1 -1
- package/tests/artist_review.mjs +1 -1
- package/tests/artist_review_goal.mjs +1 -1
- package/tests/blocker_hall_live_proof.mjs +1 -1
- package/tests/blocker_tasks_named_db.mjs +2 -2
- package/tests/canonical_profile_url.mjs +1 -1
- package/tests/cap_newcomer_tasks.mjs +13 -13
- package/tests/character_creation.mjs +294 -0
- package/tests/claim_gate_open_blocker.mjs +2 -2
- package/tests/claim_gate_rebase.mjs +6 -6
- package/tests/claude_materialize.mjs +1 -1
- package/tests/clawback_confirmed_backlog.mjs +3 -3
- package/tests/collab_page.mjs +8 -9
- package/tests/collab_self_settling.mjs +2 -2
- package/tests/community_leaderboard_route.mjs +13 -5
- package/tests/community_proof_db.mjs +3 -0
- package/tests/community_search_route.mjs +7 -6
- package/tests/conductor.mjs +1 -1
- package/tests/conductor_main_e2e.mjs +8 -8
- package/tests/copy_desk_flags_db.mjs +1 -1
- package/tests/copy_desk_page_approve.mjs +2 -2
- package/tests/copy_desk_page_claim.mjs +2 -2
- package/tests/copy_desk_page_next.mjs +1 -1
- package/tests/copy_desk_page_release.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +2 -2
- package/tests/core_258_box_session_claims_db.mjs +1 -1
- package/tests/craft_mode.mjs +48 -34
- package/tests/craft_mode_home.mjs +20 -20
- package/tests/craft_mode_looks.mjs +16 -15
- package/tests/craft_mode_nav.mjs +31 -29
- package/tests/craft_review_mode.mjs +46 -41
- package/tests/crafts_vocabulary.mjs +70 -0
- package/tests/cross_discipline_priority_direction.mjs +5 -5
- package/tests/design_reference.mjs +1 -1
- package/tests/fitness.mjs +7 -11
- package/tests/funded_project_pays_proof_db.mjs +1 -1
- package/tests/genesis_home.mjs +21 -1
- package/tests/github_chip.mjs +3 -3
- package/tests/github_repo_invite.mjs +379 -0
- package/tests/goal_criterion_confirm_ui.mjs +1 -1
- package/tests/goal_invite_consent.mjs +1 -1
- package/tests/goal_map_page.mjs +2 -2
- package/tests/goal_sort_staleness.mjs +1 -1
- package/tests/goal_spec_task.mjs +1 -1
- package/tests/goal_spec_task_db.mjs +3 -3
- package/tests/grade_engine.mjs +2 -2
- package/tests/grader_no_artifact.mjs +1 -1
- package/tests/grader_prompt_fencing.mjs +1 -1
- package/tests/grader_prompt_window_bound.mjs +1 -1
- package/tests/grader_reader_lens.mjs +2 -2
- package/tests/grader_severity_gate.mjs +1 -1
- package/tests/grader_unbacked_score.mjs +1 -1
- package/tests/hall_approval_queue.mjs +1 -1
- package/tests/hall_board_world.mjs +2 -2
- package/tests/hall_builders_page_pass.mjs +3 -2
- package/tests/hall_city.mjs +16 -30
- package/tests/hall_controls.mjs +52 -4
- package/tests/hall_fullscreen.mjs +27 -3
- package/tests/hall_helm.mjs +187 -0
- package/tests/hall_kit_charts.mjs +1 -1
- package/tests/hall_landing_boot.mjs +1 -1
- package/tests/hall_my_agents.mjs +16 -16
- package/tests/hall_nav.mjs +22 -15
- package/tests/hall_options_menu.mjs +247 -0
- package/tests/hall_oversight_world.mjs +1 -1
- package/tests/hall_palette.mjs +8 -9
- package/tests/hall_profile_world.mjs +3 -3
- package/tests/hall_record_world.mjs +6 -4
- package/tests/hall_settings_world.mjs +2 -1
- package/tests/hall_sitemap.mjs +6 -5
- package/tests/hall_start_screen.mjs +307 -0
- package/tests/hall_street.mjs +15 -28
- package/tests/hall_street_inside.mjs +17 -9
- package/tests/hall_studio_world.mjs +3 -3
- package/tests/hall_topbar_320.mjs +4 -2
- package/tests/hall_visor.mjs +224 -0
- package/tests/help_requests.mjs +47 -15
- package/tests/helpers.mjs +4 -4
- package/tests/home_city.mjs +6 -4
- package/tests/home_city_widget.mjs +33 -14
- package/tests/home_gallery_widget.mjs +4 -4
- package/tests/home_idea_box.mjs +6 -6
- package/tests/hub_community_page.mjs +3 -3
- package/tests/hub_craft_rollup.mjs +75 -36
- package/tests/hub_recent_ships.mjs +3 -2
- package/tests/idea_cascade.mjs +2 -2
- package/tests/idea_credit_streams_db.mjs +1 -1
- package/tests/idea_detail_page.mjs +1 -1
- package/tests/idea_ideate_full.mjs +5 -2
- package/tests/idea_spark_hall.mjs +1 -1
- package/tests/init.mjs +3 -3
- package/tests/instance_manifest.mjs +2 -2
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/leaderboard_roles.mjs +34 -25
- package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
- package/tests/lightbulb.mjs +14 -10
- package/tests/main_discipline.mjs +8 -8
- package/tests/markdown_line_endings.mjs +1 -1
- package/tests/market_watch_pages.mjs +52 -74
- package/tests/me_streak_leaderboard_queries.mjs +3 -3
- package/tests/migration_allocation.mjs +1 -1
- package/tests/mingle.mjs +36 -21
- package/tests/module-scope-map.mjs +22 -2
- package/tests/module_api.mjs +1 -0
- package/tests/module_contributions.mjs +13 -13
- package/tests/module_manifest.mjs +29 -0
- package/tests/module_skills_hook.mjs +2 -2
- package/tests/modules.mjs +14 -7
- package/tests/nav_permission_atoms.mjs +2 -1
- package/tests/needs_rebase.mjs +1 -1
- package/tests/newcomer_restock.mjs +13 -13
- package/tests/page_notes_panel.mjs +22 -22
- package/tests/page_tweak_no_block.mjs +1 -1
- package/tests/paintbrush.mjs +7 -7
- package/tests/privacy_matrix.mjs +1 -0
- package/tests/profile_activity.mjs +20 -23
- package/tests/profile_earnings.mjs +1 -1
- package/tests/profile_nudge_prefs.mjs +4 -4
- package/tests/profile_role_headline.mjs +73 -46
- package/tests/profile_role_stats.mjs +55 -45
- package/tests/profile_rollup_consent.mjs +19 -6
- package/tests/profile_route.mjs +12 -12
- package/tests/profile_ui_cross_project.mjs +4 -4
- package/tests/project_invite_suggestions.mjs +2 -2
- package/tests/promote_goal_id.mjs +1 -1
- package/tests/publish_manifest.mjs +1 -1
- package/tests/rank_floor_wiring.mjs +1 -1
- package/tests/regrade_actual_minutes.mjs +1 -1
- package/tests/removal_proposal.mjs +2 -2
- package/tests/reward_gate.mjs +4 -4
- package/tests/reward_mode_cost_plus_only.mjs +1 -1
- package/tests/role_responsibilities.mjs +18 -12
- package/tests/role_stats_db.mjs +17 -16
- package/tests/scouting_rollup_fanout.mjs +5 -5
- package/tests/seed_tasks.mjs +5 -2
- package/tests/session_earnings_mirror_db.mjs +1 -1
- package/tests/session_reward_on_land_db.mjs +1 -1
- package/tests/session_reward_reconcile_db.mjs +2 -2
- package/tests/sessions_kit_pager.mjs +1 -1
- package/tests/settings_art_key_absent.mjs +2 -2
- package/tests/settings_main_role.mjs +21 -20
- package/tests/setup_flow.mjs +3 -3
- package/tests/setup_smoke.mjs +20 -0
- package/tests/ship_card.mjs +6 -6
- package/tests/specialities.mjs +16 -4
- package/tests/speciality_db_shape.mjs +6 -6
- package/tests/speciality_injection.mjs +17 -17
- package/tests/speciality_offer_ui.mjs +5 -5
- package/tests/speciality_offered.mjs +6 -5
- package/tests/speciality_session_skills.mjs +7 -7
- package/tests/speciality_skills.mjs +11 -11
- package/tests/speciality_walkthrough_ui.mjs +2 -2
- package/tests/sso_checkin_identity_spoof.mjs +2 -1
- package/tests/task_classifier_vocab.mjs +10 -0
- package/tests/task_cli.mjs +11 -3
- package/tests/task_criteria_unlink_db.mjs +2 -2
- package/tests/task_discipline_required.mjs +38 -16
- package/tests/task_goal_required.mjs +4 -4
- package/tests/task_needs_db.mjs +1 -1
- package/tests/tweak_replaces_artist_review.mjs +1 -1
- package/tests/update_pill.mjs +153 -2
- package/tests/upkeep_home.mjs +1 -1
- package/tests/v3_c1_options_proof.mjs +145 -0
- package/tests/visual_review.mjs +3 -3
- package/tests/watch_applications_queue.mjs +8 -5
- package/tests/watch_sealed_floor.mjs +3 -1
- package/tests/welcome_boot.mjs +12 -9
- package/tests/welcome_boot_browser.mjs +13 -13
- package/tests/worktree_helper.mjs +16 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
11
11
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
12
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-
|
|
13
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
|
|
14
14
|
<link rel="stylesheet" href="/builders/tweak-editor.css?v=2026-09-29-tw27-aqfix">
|
|
15
15
|
</head>
|
|
16
16
|
<!-- THE TWEAK EDITOR (task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and
|
|
@@ -143,14 +143,14 @@
|
|
|
143
143
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
144
144
|
|
|
145
145
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
146
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
146
|
+
<script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
|
|
147
147
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
148
|
-
<script src="/builders/craft-mode.js?v=2026-11-
|
|
149
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
148
|
+
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
149
|
+
<script src="/builders/shell.js?v=2026-11-11-helm"></script>
|
|
150
150
|
<script src="/builders/hall-dialog.js"></script>
|
|
151
151
|
<script src="/builders/palette.js" defer></script>
|
|
152
152
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
153
153
|
<script src="/builders/tweak-editor-lib.js?v=2026-09-29-tw27"></script>
|
|
154
|
-
<script src="/builders/tweak-editor.js?v=2026-
|
|
154
|
+
<script src="/builders/tweak-editor.js?v=2026-11-10-options"></script>
|
|
155
155
|
</body>
|
|
156
156
|
</html>
|
|
@@ -647,7 +647,11 @@
|
|
|
647
647
|
if (ev.target && ev.target.classList && ev.target.classList.contains('tw-line')) flush();
|
|
648
648
|
});
|
|
649
649
|
document.addEventListener('keydown', (ev) => {
|
|
650
|
-
if (ev.key === 'Escape') {
|
|
650
|
+
if (ev.key === 'Escape') {
|
|
651
|
+
// Spent only when it closed something (ADR 0399 §2.4 row 5, task 1005144).
|
|
652
|
+
if (!$('tw-refused').hidden || $('tw-openwrap').classList.contains('is-open')) ev.preventDefault();
|
|
653
|
+
closeRefused(); setMenu(false);
|
|
654
|
+
}
|
|
651
655
|
if (ev.target && ev.target.id === 'tw-divider') {
|
|
652
656
|
const next = lib.splitFromKey(st.splitSize, ev.key);
|
|
653
657
|
if (next !== null) { ev.preventDefault(); setSize(next); remember(SIZE_KEY, String(st.splitSize)); }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "The tweak editor (task 1004321 / BV2.TW10), built 1:1 on docs/design/mocks/tweak-mode/Editor.dc.html. Against the kit's own stub the page renders its could-not-read state (no /copy-desk feed), a state the floors must hold in. TO SEE IT POPULATED, point the kit at the hall-preview harness: `node scripts/hall-preview/server.js --port 4631 --fixture-me`, then `render.js --page '/builders/tweak-editor?page=landing:index' --base-in http://builders.localhost:4631 --base-out http://builders.localhost:4631`. The harness answers the read from fixtures/copy-desk__pages__draft.json (the real landing:index reading with the artboard's two rewrites as the saved draft), next up from copy-desk__next.json, and the autosave, the submit and the claim from its canned writes.",
|
|
3
3
|
"page": "/builders/tweak-editor?page=landing:index",
|
|
4
|
+
"modes": ["dark"],
|
|
4
5
|
"_storage": "The hall asks a first-visit question (the session mode picker, craft-mode.js) over every page; seeding \"already asked\" renders the page a returning builder sees (task 1005080).",
|
|
5
6
|
"storage": { "session": { "hall-craft-asked": "1" } },
|
|
6
7
|
"surface": "hall-ui",
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
'use strict';
|
|
39
39
|
|
|
40
40
|
var RUN_KEY = 'bongos.update.run';
|
|
41
|
+
var LATER_KEY = 'bongos.update.later';
|
|
42
|
+
var LATER_MS = 24 * 60 * 60 * 1000;
|
|
41
43
|
var MSG = 'bongos-update'; // the tag deploy-handoff.js answers to; both sides check it
|
|
42
44
|
var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
43
45
|
// A hub door as the server words it (core-update.js updateDoor): an origin and
|
|
@@ -152,12 +154,34 @@
|
|
|
152
154
|
return slow ? 'slow' : 'starting';
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
// THE POP-UP (task 1005191). The owner, 2026-11-10: the pill alone was not seen. Game mode
|
|
158
|
+
// opens every page without its bars, and a project sat 3 minor versions behind. So an
|
|
159
|
+
// update that is ready also ASKS, once, in the hall's own dialog. Any way of closing it
|
|
160
|
+
// other than Update now is "later": that version is not asked about again in this browser
|
|
161
|
+
// for a day. The pill stays, so later never means lost.
|
|
162
|
+
function readLater(storage) {
|
|
163
|
+
var l = null;
|
|
164
|
+
try { l = JSON.parse((storage && storage.getItem(LATER_KEY)) || 'null'); } catch (e) { l = null; }
|
|
165
|
+
return l && isVersion(l.v) && isFinite(l.at) ? l : null;
|
|
166
|
+
}
|
|
167
|
+
function keepLater(storage, version, now) {
|
|
168
|
+
try { if (storage && isVersion(version)) storage.setItem(LATER_KEY, JSON.stringify({ v: version, at: now })); } catch (e) { /* it asks again next page */ }
|
|
169
|
+
}
|
|
170
|
+
/** Whether the pop-up opens: only for an update that is ready, and not for a version the
|
|
171
|
+
* owner already said "later" to inside the last day. A newer version asks again at once. */
|
|
172
|
+
function askDue(o) {
|
|
173
|
+
if (!o || o.face !== 'ready' || !isVersion(o.offered)) return false;
|
|
174
|
+
var l = o.later;
|
|
175
|
+
if (l && l.v === o.offered && o.now >= l.at && o.now - l.at < LATER_MS) return false;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
// ---------- the page half ----------
|
|
156
180
|
|
|
157
181
|
var state = {
|
|
158
182
|
api: null, update: null, held: null, sw: null, swAsked: null, swError: null, ended: null, note: null,
|
|
159
183
|
face: 'none', stage: null, run: null, error: null, pill: null, card: null,
|
|
160
|
-
popup: null, plan: null, offered: null, looks: 0, timer: null, popupTimer: null, listening: false,
|
|
184
|
+
popup: null, plan: null, offered: null, asked: false, looks: 0, timer: null, popupTimer: null, listening: false,
|
|
161
185
|
};
|
|
162
186
|
|
|
163
187
|
function doc() { return root && root.document; }
|
|
@@ -316,7 +340,7 @@
|
|
|
316
340
|
d.body.appendChild(c);
|
|
317
341
|
state.card = c;
|
|
318
342
|
if (root.addEventListener) root.addEventListener('resize', function () { if (state.card && !state.card.hidden) placeCard(); });
|
|
319
|
-
d.addEventListener('keydown', function (e) { if (e.key === 'Escape' && state.card && !state.card.hidden) { closeCard(); if (state.pill) state.pill.focus(); } });
|
|
343
|
+
d.addEventListener('keydown', function (e) { if (e.key === 'Escape' && state.card && !state.card.hidden) { e.preventDefault(); closeCard(); if (state.pill) state.pill.focus(); } }); // spent: ADR 0399 §2.4, task 1005144
|
|
320
344
|
d.addEventListener('click', function (e) {
|
|
321
345
|
if (!state.card || state.card.hidden) return;
|
|
322
346
|
var t = e.target;
|
|
@@ -556,6 +580,69 @@
|
|
|
556
580
|
if (root.location && root.location.reload) root.location.reload();
|
|
557
581
|
}
|
|
558
582
|
|
|
583
|
+
// ---------- the pop-up ----------
|
|
584
|
+
|
|
585
|
+
// Not in the hub's handoff window, not on the update door itself, and not over the setup
|
|
586
|
+
// screen: each of those is already the thing the owner is doing.
|
|
587
|
+
function askAllowed() {
|
|
588
|
+
var d = doc();
|
|
589
|
+
if (!d || !root.OTBDialog || typeof root.OTBDialog.open !== 'function' || state.asked) return false;
|
|
590
|
+
var el = d.documentElement;
|
|
591
|
+
if (el && el.dataset && el.dataset.handoff === 'on') return false;
|
|
592
|
+
if (root.OTBSetupGate) return false;
|
|
593
|
+
return !(root.location && root.location.pathname === '/deploy');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function askBodyHtml(plan) {
|
|
597
|
+
var u = state.update || {};
|
|
598
|
+
var h = highlights((state.sw.update || {}).whats_new, HIGHLIGHTS);
|
|
599
|
+
var list = h.lines.length ? '<ul class="upd-card__list">' + h.lines.map(function (l) { return '<li>' + esc(l) + '</li>'; }).join('') + '</ul>' : '';
|
|
600
|
+
var note = plan.kind === 'cli'
|
|
601
|
+
? 'This project is updated on the server that runs it: <code>bongos upgrade</code> there takes this version, and puts the old one back if it fails.'
|
|
602
|
+
: 'Takes a few minutes. The project restarts once, and goes back to the version it runs now on its own if the new one is not healthy.';
|
|
603
|
+
var versions = isVersion(u.running) && isVersion(state.offered) ? '<p class="upd-card__ver">' + mono(u.running) + ' → ' + mono(state.offered) + '</p>' : '';
|
|
604
|
+
return '<p>A newer version of Bongos is ready, with the latest changes and bug fixes.</p>' + list +
|
|
605
|
+
'<p class="upd-card__note">' + note + '</p>' + versions;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Asked from the same reading the card uses, so the pop-up and the card name one version
|
|
609
|
+
// and the two-step rule holds: the pop-up shows the version, Update now sends that one.
|
|
610
|
+
function maybeAsk() {
|
|
611
|
+
if (!askAllowed() || !askDue({ face: state.face, offered: state.offered, later: readLater(store()), now: now() })) return;
|
|
612
|
+
var read = readSoftwareUpdate();
|
|
613
|
+
if (!read) { ask(); return; }
|
|
614
|
+
read.then(ask);
|
|
615
|
+
}
|
|
616
|
+
function ask() {
|
|
617
|
+
if (!state.sw || !askAllowed() || !askDue({ face: state.face, offered: state.offered, later: readLater(store()), now: now() })) return;
|
|
618
|
+
var plan = doorPlan(state.sw);
|
|
619
|
+
if (plan.kind === 'none') return; // a viewer the door would refuse is not asked
|
|
620
|
+
var d = doc();
|
|
621
|
+
var body = d.createElement('div');
|
|
622
|
+
body.className = 'upd-ask';
|
|
623
|
+
body.innerHTML = askBodyHtml(plan);
|
|
624
|
+
var canGo = plan.kind === 'local' || plan.kind === 'hub';
|
|
625
|
+
var offered = state.offered;
|
|
626
|
+
var actions = canGo
|
|
627
|
+
? [{ label: 'Later', value: 'later', kind: 'ghost' }, { label: 'Update now', value: 'go' }]
|
|
628
|
+
: [{ label: 'Close', value: 'later', kind: 'ghost' }];
|
|
629
|
+
state.asked = true;
|
|
630
|
+
var dlg = root.OTBDialog.open({ title: 'Update the builders hall?', body: body, actions: actions, initialFocus: '[data-dialog-value="' + (actions.length - 1) + '"]' });
|
|
631
|
+
// Update now runs inside the press itself: a hosted project opens the hub's window, and
|
|
632
|
+
// a browser allows that only from the click, not from the dialog's settled promise.
|
|
633
|
+
if (canGo && dlg && dlg.el && dlg.el.addEventListener) {
|
|
634
|
+
dlg.el.addEventListener('click', function (e) {
|
|
635
|
+
var t = e && e.target;
|
|
636
|
+
var b = t && t.closest ? t.closest('[data-dialog-value]') : null;
|
|
637
|
+
if (!b || b.getAttribute('data-dialog-value') !== '1') return;
|
|
638
|
+
if (updateNow()) openCard();
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
if (dlg && dlg.result && dlg.result.then) {
|
|
642
|
+
dlg.result.then(function (v) { if (v !== 'go') keepLater(store(), offered, now()); });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
559
646
|
/** shell.js hands over GET /core-update's answer; the pill decides what, if anything, to show. */
|
|
560
647
|
function mount(data, opts) {
|
|
561
648
|
state.api = opts && opts.api;
|
|
@@ -575,6 +662,7 @@
|
|
|
575
662
|
state.face = face;
|
|
576
663
|
drawPill();
|
|
577
664
|
if (face === 'running') schedule();
|
|
665
|
+
if (face === 'ready') maybeAsk();
|
|
578
666
|
return face;
|
|
579
667
|
}
|
|
580
668
|
|
|
@@ -590,6 +678,7 @@
|
|
|
590
678
|
// the pure half, for the tests
|
|
591
679
|
highlights: highlights, doorPlan: doorPlan, pillState: pillState, stageOf: stageOf,
|
|
592
680
|
readRun: readRun, keepRun: keepRun, forgetRun: forgetRun, isVersion: isVersion,
|
|
681
|
+
askDue: askDue, readLater: readLater, keepLater: keepLater, LATER_KEY: LATER_KEY, LATER_MS: LATER_MS,
|
|
593
682
|
RUN_KEY: RUN_KEY, MSG: MSG, SLOW_MS: SLOW_MS, GIVE_UP_MS: GIVE_UP_MS,
|
|
594
683
|
_state: state,
|
|
595
684
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* visor.css — the first-person visor (task 1005148, visor.js; ADR 0399). Glass over the scene:
|
|
2
|
+
the frame lines, the place, three pills and the actions sheet. It shows in game mode only, and
|
|
3
|
+
it is an overlay, never layout, so the stage under it never changes size (ADR 0399 §2.1).
|
|
4
|
+
Colours are the hall's tokens; the frame is hairline ink, never a glow (DESIGN.md: no
|
|
5
|
+
near-black-plus-neon HUD). Record: modules/hall-ui/records/visor.md. */
|
|
6
|
+
.visor { display: none; }
|
|
7
|
+
html[data-game="on"] .visor:not([hidden]) {
|
|
8
|
+
display: block; position: fixed; inset: 0; z-index: 4; pointer-events: none;
|
|
9
|
+
/* where the glass sits, so a scene's pop-up can keep clear of it (ADR 0399 §2.3) */
|
|
10
|
+
--visor-inset-top: 64px; --visor-inset-right: 40px; --visor-inset-bottom: 76px; --visor-inset-left: 40px;
|
|
11
|
+
color: var(--ink); font-family: var(--font-body);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* the frame: stretched over the stage, every stroke one hairline at any size */
|
|
15
|
+
.visor__frame { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
|
16
|
+
.visor__frame path { vector-effect: non-scaling-stroke; }
|
|
17
|
+
.visor__line { fill: none; stroke: var(--rule); stroke-width: var(--ds-line); }
|
|
18
|
+
.visor__line--side { stroke: var(--rule-soft); }
|
|
19
|
+
.visor__cell { fill: color-mix(in srgb, var(--ink) 12%, transparent); stroke: var(--rule); stroke-width: var(--ds-line); }
|
|
20
|
+
.visor__mark { fill: var(--accent); stroke: none; }
|
|
21
|
+
|
|
22
|
+
/* the place, under the compass */
|
|
23
|
+
.visor__place {
|
|
24
|
+
position: absolute; left: 50%; top: max(9.5%, 84px); transform: translateX(-50%); margin: 0;
|
|
25
|
+
font-family: var(--font-mono); font-size: var(--ds-text-2xs); letter-spacing: .14em; text-transform: uppercase;
|
|
26
|
+
color: var(--ink-soft); white-space: nowrap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* the pills: the only parts of the glass that take a pointer */
|
|
30
|
+
.visor__pill, .visor__me {
|
|
31
|
+
position: absolute; pointer-events: auto; box-sizing: border-box;
|
|
32
|
+
display: inline-flex; align-items: center; gap: var(--ds-space-xs);
|
|
33
|
+
min-height: 46px; padding: 0 var(--ds-space-sm);
|
|
34
|
+
background: var(--bg-card); color: var(--ink); border: var(--ds-line) solid var(--rule);
|
|
35
|
+
border-radius: var(--ds-radius-pill); font: inherit; font-size: var(--ds-text-sm); text-decoration: none;
|
|
36
|
+
}
|
|
37
|
+
.visor__pill { cursor: pointer; }
|
|
38
|
+
.visor__pill:hover { border-color: var(--ink-faint); }
|
|
39
|
+
.visor__pill:focus-visible, .visor__row:focus-visible, .visor__close:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
40
|
+
.visor__pill--main { left: var(--visor-inset-left); bottom: var(--visor-inset-bottom); background: var(--accent); color: var(--accent-on); border-color: var(--accent); font-weight: 500; }
|
|
41
|
+
.visor__pill--main:hover { border-color: var(--accent-deep); }
|
|
42
|
+
.visor__pill--actions { right: var(--visor-inset-right); top: var(--visor-inset-top); }
|
|
43
|
+
.visor__pill[hidden] { display: none; }
|
|
44
|
+
.visor__dots { width: 14px; height: 4px; background: radial-gradient(circle, currentColor 1.5px, transparent 2px) 0 0 / 5px 4px repeat-x; }
|
|
45
|
+
.visor__me { right: var(--visor-inset-right); bottom: var(--visor-inset-bottom); padding: 4px var(--ds-space-sm) 4px 4px; pointer-events: none; }
|
|
46
|
+
.visor__sprite { width: 38px; height: 38px; border-radius: var(--ds-radius-pill); object-fit: cover; object-position: 50% 12%; background: var(--bg-deep); }
|
|
47
|
+
.visor__who { display: flex; flex-direction: column; line-height: 1.2; }
|
|
48
|
+
.visor__name { font-weight: 500; }
|
|
49
|
+
.visor__credits { font-size: var(--ds-text-2xs); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
|
|
50
|
+
.visor__key {
|
|
51
|
+
font-family: var(--font-mono); font-size: var(--ds-text-2xs); line-height: 1; padding: 3px 5px;
|
|
52
|
+
border: var(--ds-line) solid currentColor; border-radius: var(--ds-radius-pill); opacity: .75;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* the sheet: above the scene's pop-ups, under the modals (ADR 0399 §2.3 layer 4) */
|
|
56
|
+
.visor__sheet {
|
|
57
|
+
position: absolute; right: var(--visor-inset-right); top: var(--visor-inset-top); z-index: 1;
|
|
58
|
+
pointer-events: auto; box-sizing: border-box; width: min(18rem, calc(100vw - 2 * var(--ds-space-gutter-narrow)));
|
|
59
|
+
max-height: calc(100% - var(--visor-inset-top) - var(--visor-inset-bottom)); overflow-y: auto;
|
|
60
|
+
padding: var(--ds-space-sm); background: var(--bg-card); border: var(--ds-line) solid var(--rule);
|
|
61
|
+
border-radius: var(--ds-radius-panel); box-shadow: var(--ds-shadow-pop);
|
|
62
|
+
}
|
|
63
|
+
.visor__sheet-head { display: flex; align-items: center; justify-content: space-between; }
|
|
64
|
+
.visor__sheet-title { margin: 0; font-size: var(--ds-text-md); font-weight: 500; }
|
|
65
|
+
.visor__close { min-width: 32px; min-height: 32px; background: none; border: 0; color: var(--ink-soft); cursor: pointer; }
|
|
66
|
+
.visor__cap { margin: var(--ds-space-xs) 0 2px; font-family: var(--font-mono); font-size: var(--ds-text-2xs); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }
|
|
67
|
+
.visor__rows { list-style: none; margin: 0; padding: 0; }
|
|
68
|
+
.visor__rows + .visor__rows { margin-top: var(--ds-space-xs); }
|
|
69
|
+
.visor__row {
|
|
70
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-xs);
|
|
71
|
+
box-sizing: border-box; width: 100%; min-height: 36px; padding: 0 2px;
|
|
72
|
+
background: none; border: 0; border-top: var(--ds-line) solid var(--rule-soft);
|
|
73
|
+
color: var(--ink); font: inherit; font-size: var(--ds-text-sm); text-align: left; text-decoration: none; cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.visor__row:hover { color: var(--accent-deep); }
|
|
76
|
+
.visor__row--hint { cursor: default; color: var(--ink-soft); }
|
|
77
|
+
.visor__row--hint:hover { color: var(--ink-soft); }
|
|
78
|
+
|
|
79
|
+
/* a phone (640px and narrower): the rules live in style.css’s shared phone block, so the
|
|
80
|
+
visor adds no breakpoint of its own (the design-count ratchet). */
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// visor.js — the first-person visor: glass over the scene (task 1005148, BONGOS-V3 R08,
|
|
2
|
+
// goal 1000142, criterion `you-are-a-character-in-first-person`). Built to ADR 0399.
|
|
3
|
+
//
|
|
4
|
+
// What is on the glass (the design the owner agreed on 2026-11-10, task 1005148):
|
|
5
|
+
// - the frame: thin curved visor lines at the top, the bottom and both sides, a small
|
|
6
|
+
// centre cross, and a compass of nine slanted cells along the top line with the one
|
|
7
|
+
// salmon marker under its centre. Drawn once as SVG; it is ornament, never a control.
|
|
8
|
+
// - the place, in a mono line under the compass ("THE SKY", "GOVERNOR CITY", …).
|
|
9
|
+
// - bottom-left: your character's main action, the one accent pill, with its key.
|
|
10
|
+
// - bottom-right: your sprite (third person) with your character's name and your credits
|
|
11
|
+
// (builders.total_credits — credits only, never a payout; the spec's "does not exist").
|
|
12
|
+
// - top-right: "Actions" (key A), which opens the sheet: your character's actions, then
|
|
13
|
+
// the keys of the place you are in, then Options on Escape.
|
|
14
|
+
//
|
|
15
|
+
// The rules it keeps (ADR 0399):
|
|
16
|
+
// - one overlay, #visor, with pointer-events: none; only its pills take a pointer, so a
|
|
17
|
+
// drag, a wheel or a click on the world reaches the scene unchanged (§2.1).
|
|
18
|
+
// - no camera: a scene registers itself once with OTBVisor.scene({ name, place, actions,
|
|
19
|
+
// keys }) and a movement action calls the scene's own function (§2.2). A page with no
|
|
20
|
+
// scene registered shows no visor.
|
|
21
|
+
// - Escape (§2.4): an OPEN SHEET takes the press in capture and stops it (row 4), so the
|
|
22
|
+
// star under it stays picked; the ambient glass never takes one, so the last press
|
|
23
|
+
// still reaches the options menu (shell-game.js onEscape).
|
|
24
|
+
// - it hides nothing and gates nothing (§2.5): every action is a link or tool a page
|
|
25
|
+
// already offers, and the server's rank checks answer exactly as before.
|
|
26
|
+
// - game mode only: html[data-game="on"] shows it (visor.css). Outside it, the hall is
|
|
27
|
+
// exactly as it was.
|
|
28
|
+
//
|
|
29
|
+
// The character comes from craft-mode.js (task 1005147): the held character, or this
|
|
30
|
+
// session's mode on a core that has no characters yet. With neither, no visor.
|
|
31
|
+
// R09 (task 1005149) turns the keys below into the one rebindable registry.
|
|
32
|
+
(function () {
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
// ---------- each character's actions ----------
|
|
36
|
+
// `href` opens a page the hall already has; `tool` opens one of craft-mode's tools on
|
|
37
|
+
// this page. The first is the character's main action (the accent pill).
|
|
38
|
+
var ACTIONS = {
|
|
39
|
+
artisan: [
|
|
40
|
+
{ id: 'claim', label: 'Claim a task', key: 'c', href: '/work' },
|
|
41
|
+
{ id: 'blockers', label: 'Blockers', key: 'b', href: '/blockers' },
|
|
42
|
+
{ id: 'sky', label: 'Goal sky', key: 'g', href: '/goal-map' },
|
|
43
|
+
{ id: 'thinking', label: 'Your thinking', key: 't', href: '/thinking' },
|
|
44
|
+
],
|
|
45
|
+
artist: [
|
|
46
|
+
{ id: 'review', label: 'Review this page', key: 'r', tool: 'palette' },
|
|
47
|
+
{ id: 'words', label: 'Edit its words', key: 'e', tool: 'paintbrush' },
|
|
48
|
+
{ id: 'idea', label: 'Write an idea', key: 'i', tool: 'lightbulb' },
|
|
49
|
+
{ id: 'studio', label: 'Studio', key: 's', href: '/studio' },
|
|
50
|
+
],
|
|
51
|
+
patron: [
|
|
52
|
+
{ id: 'board', label: 'Board room', key: 'o', href: '/board-room' },
|
|
53
|
+
{ id: 'city', label: 'Governor city', key: 'v', href: '/city' },
|
|
54
|
+
{ id: 'blockers', label: 'Blockers', key: 'b', href: '/blockers' },
|
|
55
|
+
{ id: 'watch', label: 'Watch', key: 'w', href: '/watch' },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
var SHEET_KEY = 'a';
|
|
59
|
+
var NAMES = { artisan: 'Artisan', artist: 'Artist', patron: 'Patron' };
|
|
60
|
+
|
|
61
|
+
function esc(v) { return window.OTB && window.OTB.escapeHtml ? window.OTB.escapeHtml(v) : String(v == null ? '' : v).replace(/[^\w .,:'-]/g, ''); }
|
|
62
|
+
function craft() { return window.OTBCraftMode || null; }
|
|
63
|
+
// The character on the glass: the held one, else this session's mode, else none.
|
|
64
|
+
function character() {
|
|
65
|
+
var C = craft();
|
|
66
|
+
if (!C) return null;
|
|
67
|
+
var held = typeof C.character === 'function' ? C.character() : undefined;
|
|
68
|
+
if (typeof held === 'string' && ACTIONS[held]) return held;
|
|
69
|
+
var mode = typeof C.current === 'function' ? C.current() : null;
|
|
70
|
+
return ACTIONS[mode] ? mode : null;
|
|
71
|
+
}
|
|
72
|
+
function actionsFor(id) { return ACTIONS[id] ? ACTIONS[id].slice() : []; }
|
|
73
|
+
function credits() {
|
|
74
|
+
var C = craft();
|
|
75
|
+
var me = C && typeof C.me === 'function' ? C.me() : null;
|
|
76
|
+
var n = me && me.builder ? Number(me.builder.total_credits) : NaN;
|
|
77
|
+
return isFinite(n) ? n : null;
|
|
78
|
+
}
|
|
79
|
+
function creditsText(n) {
|
|
80
|
+
if (n == null) return '';
|
|
81
|
+
var v = Math.round(n);
|
|
82
|
+
var s = String(Math.abs(v)).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
83
|
+
return (v < 0 ? '−' : '') + s + (Math.abs(v) === 1 ? ' credit' : ' credits');
|
|
84
|
+
}
|
|
85
|
+
// A link on the hall's own host: '/work' on the builders host, '/builders/work' on the apex.
|
|
86
|
+
function hallHref(p) {
|
|
87
|
+
var here = String((window.location && window.location.pathname) || '');
|
|
88
|
+
return (here.indexOf('/builders/') === 0 || here === '/builders' ? '/builders' : '') + p;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------- the scene ----------
|
|
92
|
+
var scene = null;
|
|
93
|
+
// A scene says what it is and, if it has a camera, the actions that move it (ADR 0399
|
|
94
|
+
// §2.2). `keys` are lines for the sheet's "this place" list: keys the scene already binds.
|
|
95
|
+
function registerScene(s) {
|
|
96
|
+
if (!s || typeof s !== 'object') return;
|
|
97
|
+
scene = {
|
|
98
|
+
name: String(s.name || ''),
|
|
99
|
+
place: String(s.place || ''),
|
|
100
|
+
actions: (Array.isArray(s.actions) ? s.actions : []).filter(function (a) { return a && a.label && typeof a.run === 'function'; }),
|
|
101
|
+
keys: (Array.isArray(s.keys) ? s.keys : []).filter(function (k) { return k && k.label && k.key; }),
|
|
102
|
+
};
|
|
103
|
+
render();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------- drawing ----------
|
|
107
|
+
var root = null, sheet = null, returnFocus = null;
|
|
108
|
+
var NS = 'http://www.w3.org/2000/svg';
|
|
109
|
+
// The frame is drawn in a 1000×1000 box stretched over the stage (preserveAspectRatio
|
|
110
|
+
// none), with non-scaling strokes so every line stays one hairline at any size.
|
|
111
|
+
function frameSvg() {
|
|
112
|
+
var cells = '';
|
|
113
|
+
for (var i = -4; i <= 4; i++) {
|
|
114
|
+
var x = 500 + i * 38, y = 30 + Math.pow(i / 4, 2) * 14, s = i * 2;
|
|
115
|
+
cells += '<path class="visor__cell" d="M' + (x - 15) + ' ' + (y + 4 - s / 2) + 'l30 ' + s + 'l-6 15l-30 ' + (-s) + 'z"/>';
|
|
116
|
+
}
|
|
117
|
+
return '<svg class="visor__frame" viewBox="0 0 1000 1000" preserveAspectRatio="none" aria-hidden="true" focusable="false" xmlns="' + NS + '">' +
|
|
118
|
+
'<path class="visor__line" d="M0 120Q500 -10 1000 120"/>' +
|
|
119
|
+
'<path class="visor__line" d="M0 880Q500 1010 1000 880"/>' +
|
|
120
|
+
'<path class="visor__line visor__line--side" d="M18 120Q4 500 18 880M982 120Q996 500 982 880"/>' +
|
|
121
|
+
'<path class="visor__line" d="M490 500h-12M510 500h12M500 490v-14M500 510v14"/>' +
|
|
122
|
+
cells + '<path class="visor__mark" d="M494 76l6 9l6 -9z"/>' +
|
|
123
|
+
'</svg>';
|
|
124
|
+
}
|
|
125
|
+
function keyHtml(k) { k = String(k); return '<kbd class="visor__key">' + esc(k.length === 1 ? k.toUpperCase() : k) + '</kbd>'; }
|
|
126
|
+
function actionAttrs(a) {
|
|
127
|
+
return a.href ? 'href="' + esc(hallHref(a.href)) + '"' : 'href="#" role="button"';
|
|
128
|
+
}
|
|
129
|
+
function glassHtml(id) {
|
|
130
|
+
var acts = actionsFor(id), main = acts[0], C = craft();
|
|
131
|
+
var sprite = C && typeof C.spriteSrc === 'function' ? C.spriteSrc(id) : '';
|
|
132
|
+
var n = credits();
|
|
133
|
+
return frameSvg() +
|
|
134
|
+
(scene.place ? '<p class="visor__place">' + esc(scene.place) + '</p>' : '') +
|
|
135
|
+
'<a class="visor__pill visor__pill--main" data-visor-act="' + esc(main.id) + '" ' + actionAttrs(main) + '>' +
|
|
136
|
+
'<span>' + esc(main.label) + '</span>' + keyHtml(main.key) + '</a>' +
|
|
137
|
+
'<button class="visor__pill visor__pill--actions" type="button" data-visor-sheet aria-expanded="false" aria-controls="visor-sheet">' +
|
|
138
|
+
'<span class="visor__dots" aria-hidden="true"></span><span>Actions</span>' + keyHtml(SHEET_KEY) + '</button>' +
|
|
139
|
+
'<div class="visor__me">' +
|
|
140
|
+
(sprite ? '<img class="visor__sprite" src="' + esc(sprite) + '" alt="" width="250" height="290" decoding="async">' : '') +
|
|
141
|
+
'<span class="visor__who"><span class="visor__name">' + esc(NAMES[id]) + '</span>' +
|
|
142
|
+
'<span class="visor__credits" data-visor-credits>' + esc(creditsText(n)) + '</span></span>' +
|
|
143
|
+
'</div>';
|
|
144
|
+
}
|
|
145
|
+
function sheetHtml(id) {
|
|
146
|
+
var rows = actionsFor(id).map(function (a) {
|
|
147
|
+
return '<li><a class="visor__row" data-visor-act="' + esc(a.id) + '" ' + actionAttrs(a) + '><span>' + esc(a.label) + '</span>' + keyHtml(a.key) + '</a></li>';
|
|
148
|
+
}).join('');
|
|
149
|
+
var place = scene.actions.map(function (a, i) {
|
|
150
|
+
return '<li><button class="visor__row" type="button" data-visor-scene="' + i + '"><span>' + esc(a.label) + '</span>' + (a.key ? keyHtml(a.key) : '') + '</button></li>';
|
|
151
|
+
}).concat(scene.keys.map(function (k) {
|
|
152
|
+
return '<li><span class="visor__row visor__row--hint"><span>' + esc(k.label) + '</span>' + keyHtml(k.key) + '</span></li>';
|
|
153
|
+
})).join('');
|
|
154
|
+
return '<div class="visor__sheet-head"><h2 class="visor__sheet-title" id="visor-sheet-title">Actions</h2>' +
|
|
155
|
+
'<button class="visor__close" type="button" data-visor-close aria-label="Close actions">' + keyHtml('Esc') + '</button></div>' +
|
|
156
|
+
'<p class="visor__cap">' + esc(NAMES[id]) + '</p><ul class="visor__rows">' + rows + '</ul>' +
|
|
157
|
+
(place ? '<p class="visor__cap">This place</p><ul class="visor__rows">' + place + '</ul>' : '') +
|
|
158
|
+
'<ul class="visor__rows"><li><a class="visor__row" href="' + esc(hallHref('/settings')) + '"><span>Options</span>' + keyHtml('Esc') + '</a></li></ul>';
|
|
159
|
+
}
|
|
160
|
+
function shown() { return !!(root && !root.hidden); }
|
|
161
|
+
function render() {
|
|
162
|
+
if (!document.body) return;
|
|
163
|
+
var id = scene && !embedded() ? character() : null;
|
|
164
|
+
var html = document.documentElement;
|
|
165
|
+
if (!id) { if (html.classList) html.classList.remove('visor-on'); if (root) { closeSheet(false); root.hidden = true; } return; }
|
|
166
|
+
// A scene's own layers read this to keep clear of the glass (thinking.css), in game mode only.
|
|
167
|
+
if (html.classList) html.classList.toggle('visor-on', html.dataset.game === 'on');
|
|
168
|
+
if (!root) {
|
|
169
|
+
root = document.createElement('div');
|
|
170
|
+
root.id = 'visor';
|
|
171
|
+
root.className = 'visor';
|
|
172
|
+
root.setAttribute('role', 'region');
|
|
173
|
+
root.setAttribute('aria-label', 'Visor');
|
|
174
|
+
root.addEventListener('click', onClick);
|
|
175
|
+
document.body.appendChild(root);
|
|
176
|
+
}
|
|
177
|
+
var open = !!sheet;
|
|
178
|
+
closeSheet(false);
|
|
179
|
+
root.hidden = false;
|
|
180
|
+
root.setAttribute('data-character', id);
|
|
181
|
+
root.innerHTML = glassHtml(id);
|
|
182
|
+
if (open) openSheet();
|
|
183
|
+
}
|
|
184
|
+
function embedded() {
|
|
185
|
+
var G = window.OTBShellGame;
|
|
186
|
+
return !!(G && typeof G.embedded === 'function' && G.embedded());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------- the sheet ----------
|
|
190
|
+
function openSheet() {
|
|
191
|
+
var id = character();
|
|
192
|
+
if (!shown() || sheet || !id) return;
|
|
193
|
+
returnFocus = document.activeElement;
|
|
194
|
+
sheet = document.createElement('div');
|
|
195
|
+
sheet.id = 'visor-sheet';
|
|
196
|
+
sheet.className = 'visor__sheet';
|
|
197
|
+
sheet.setAttribute('role', 'dialog');
|
|
198
|
+
sheet.setAttribute('aria-labelledby', 'visor-sheet-title');
|
|
199
|
+
sheet.innerHTML = sheetHtml(id);
|
|
200
|
+
sheet.addEventListener('click', onClick);
|
|
201
|
+
root.appendChild(sheet);
|
|
202
|
+
var btn = root.querySelector('[data-visor-sheet]');
|
|
203
|
+
if (btn) { btn.setAttribute('aria-expanded', 'true'); btn.hidden = true; }
|
|
204
|
+
document.addEventListener('keydown', onSheetKey, true);
|
|
205
|
+
var first = sheet.querySelector('.visor__rows a, .visor__rows button');
|
|
206
|
+
if (first && first.focus) first.focus();
|
|
207
|
+
}
|
|
208
|
+
function closeSheet(refocus) {
|
|
209
|
+
if (!sheet) return;
|
|
210
|
+
document.removeEventListener('keydown', onSheetKey, true);
|
|
211
|
+
if (sheet.parentNode) sheet.parentNode.removeChild(sheet);
|
|
212
|
+
sheet = null;
|
|
213
|
+
var btn = root && root.querySelector('[data-visor-sheet]');
|
|
214
|
+
if (btn) { btn.setAttribute('aria-expanded', 'false'); btn.hidden = false; }
|
|
215
|
+
if (refocus !== false) {
|
|
216
|
+
var to = returnFocus && returnFocus.isConnected !== false && returnFocus !== document.body ? returnFocus : btn;
|
|
217
|
+
if (to && to.focus) { try { to.focus(); } catch (_) { /* gone */ } }
|
|
218
|
+
}
|
|
219
|
+
returnFocus = null;
|
|
220
|
+
}
|
|
221
|
+
// ADR 0399 §2.4 row 4: capture on document, and stop the press, so no scene listener
|
|
222
|
+
// below (the sky's unpick) and not the options menu also spends it.
|
|
223
|
+
function onSheetKey(e) {
|
|
224
|
+
if (!sheet || e.key !== 'Escape') return;
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
closeSheet(true);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---------- running an action ----------
|
|
231
|
+
function run(a) {
|
|
232
|
+
if (!a) return;
|
|
233
|
+
if (a.tool) {
|
|
234
|
+
var C = craft();
|
|
235
|
+
if (C && typeof C.openTool === 'function') C.openTool(a.tool);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (a.href) window.location.assign(hallHref(a.href));
|
|
239
|
+
}
|
|
240
|
+
function find(id) {
|
|
241
|
+
var acts = actionsFor(character());
|
|
242
|
+
for (var i = 0; i < acts.length; i++) if (acts[i].id === id) return acts[i];
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
function onClick(e) {
|
|
246
|
+
var t = e.target && e.target.closest ? e.target : null;
|
|
247
|
+
if (!t) return;
|
|
248
|
+
if (t.closest('[data-visor-close]')) { closeSheet(true); return; }
|
|
249
|
+
if (t.closest('[data-visor-sheet]')) { openSheet(); return; }
|
|
250
|
+
var sc = t.closest('[data-visor-scene]');
|
|
251
|
+
if (sc && scene) { var a = scene.actions[Number(sc.getAttribute('data-visor-scene'))]; closeSheet(false); if (a) a.run(); return; }
|
|
252
|
+
var act = t.closest('[data-visor-act]');
|
|
253
|
+
if (!act) return;
|
|
254
|
+
var found = find(act.getAttribute('data-visor-act'));
|
|
255
|
+
if (found && found.tool) { e.preventDefault(); closeSheet(false); run(found); }
|
|
256
|
+
else closeSheet(false); // a link: the browser follows it
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------- the keys ----------
|
|
260
|
+
function inField(el) {
|
|
261
|
+
var tag = el && el.tagName ? String(el.tagName).toLowerCase() : '';
|
|
262
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select' || !!(el && el.isContentEditable);
|
|
263
|
+
}
|
|
264
|
+
// Something above the glass has the keyboard: the character picker, a modal dialog, the
|
|
265
|
+
// palette, the start screen. The visor's keys wait while any of them is up.
|
|
266
|
+
function busy() {
|
|
267
|
+
var html = document.documentElement;
|
|
268
|
+
if (html.classList && (html.classList.contains('craft-picker-open') || html.classList.contains('gs-open'))) return true;
|
|
269
|
+
return !!document.querySelector('dialog[open], [aria-modal="true"]');
|
|
270
|
+
}
|
|
271
|
+
// Which action (or the sheet) a key press means, or null. Pure, for the tests.
|
|
272
|
+
function keyFor(id, e) {
|
|
273
|
+
if (!e || e.defaultPrevented || e.ctrlKey || e.metaKey || e.altKey || typeof e.key !== 'string' || e.key.length !== 1) return null;
|
|
274
|
+
var k = e.key.toLowerCase();
|
|
275
|
+
if (k === SHEET_KEY) return 'sheet';
|
|
276
|
+
var acts = actionsFor(id);
|
|
277
|
+
for (var i = 0; i < acts.length; i++) if (acts[i].key === k) return acts[i];
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
// Bubble, on window: a scene or pop-up that used the key has called preventDefault by now.
|
|
281
|
+
function onKey(e) {
|
|
282
|
+
if (!shown() || inField(e.target) || busy()) return;
|
|
283
|
+
var hit = keyFor(character(), e);
|
|
284
|
+
if (!hit) return;
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
if (hit === 'sheet') { if (sheet) closeSheet(true); else openSheet(); return; }
|
|
287
|
+
closeSheet(false);
|
|
288
|
+
run(hit);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function start() {
|
|
292
|
+
window.addEventListener('keydown', onKey);
|
|
293
|
+
// A scene whose script ran before this file left its registration here.
|
|
294
|
+
if (window.__visorScene && !scene) registerScene(window.__visorScene);
|
|
295
|
+
// The character and the credits settle when /me answers (craft-mode.js), after this file.
|
|
296
|
+
document.addEventListener('otbcraft:me', render);
|
|
297
|
+
document.addEventListener('otb:craft', render);
|
|
298
|
+
render();
|
|
299
|
+
}
|
|
300
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
|
301
|
+
else start();
|
|
302
|
+
|
|
303
|
+
window.OTBVisor = {
|
|
304
|
+
scene: registerScene,
|
|
305
|
+
render: render,
|
|
306
|
+
open: openSheet,
|
|
307
|
+
close: function () { closeSheet(true); },
|
|
308
|
+
isOpen: function () { return !!sheet; },
|
|
309
|
+
// pure parts, for the tests
|
|
310
|
+
ACTIONS: ACTIONS,
|
|
311
|
+
actionsFor: actionsFor,
|
|
312
|
+
keyFor: keyFor,
|
|
313
|
+
creditsText: creditsText,
|
|
314
|
+
character: character,
|
|
315
|
+
};
|
|
316
|
+
})();
|