@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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// craft-mode.js — the hall's craft mode (task 1004492, goal 1000095 WA6).
|
|
2
2
|
//
|
|
3
3
|
// On entering the hall (once per browser session) a picker asks "What are you
|
|
4
|
-
// doing now?":
|
|
4
|
+
// doing now?": Artisan, Artist or Patron (task 1005141; old saved modes are
|
|
5
|
+
// read as today's — LEGACY_MODES). The
|
|
5
6
|
// answer is ONE attribute, html[data-craft="<mode>"], that every hall page can
|
|
6
7
|
// read, and a switch in the top bar changes it at any time without a reload.
|
|
7
8
|
// Each change also fires `otb:craft` on document ({ detail: { mode } }).
|
|
@@ -28,10 +29,10 @@
|
|
|
28
29
|
'use strict';
|
|
29
30
|
|
|
30
31
|
var ICONS = {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
artisan: '<path d="M2 13.5h12M3.5 13.5V8.5h4v5M7.5 13.5V4.5h5v9"/>',
|
|
33
|
+
artist: '<path d="M8 2a6 6 0 1 0 0 12c.9 0 1.4-.6 1.4-1.3 0-.8-.6-1-.6-1.7 0-.6.5-1 1.2-1h1.6A2.4 2.4 0 0 0 14 7.6C14 4.5 11.3 2 8 2Z"/><path d="M5 6.2h.01M7.6 4.6h.01M10.4 5.2h.01M4.6 9h.01" stroke-width="2.2"/>',
|
|
33
34
|
ideating: '<path d="M6 12.5h4M6.6 14.5h2.8M8 1.8a4.2 4.2 0 0 0-2.6 7.5c.6.5 1 1.2 1 2h3.2c0-.8.4-1.5 1-2A4.2 4.2 0 0 0 8 1.8Z"/>',
|
|
34
|
-
|
|
35
|
+
patron: '<path d="M2 6 8 2.5 14 6M3.5 6.5v6M6.5 6.5v6M9.5 6.5v6M12.5 6.5v6M2 13.5h12"/>',
|
|
35
36
|
brush: '<path d="M13.6 2.4 7.4 8.6"/><path d="M7.4 8.6c-1.3-.7-3-.1-3.4 1.5-.3 1.3-.8 2.2-1.9 2.9 2.4.7 4.6.1 5.6-1.4.6-.9.5-2.1-.3-3Z"/>',
|
|
36
37
|
note: '<path d="M9.5 2.5h-6v11h9v-6"/><path d="M5.5 8h5M5.5 10.5h3.5"/><path d="M12.5 1.5v4M10.5 3.5h4"/>',
|
|
37
38
|
modes: '<rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1"/><rect x="9" y="9" width="4.5" height="4.5" rx="1"/>',
|
|
@@ -39,11 +40,15 @@
|
|
|
39
40
|
// The four crafts. `short` is the switch's label; `line` is the
|
|
40
41
|
// picker's one sentence — what you do in the mode, never a feature not built yet.
|
|
41
42
|
var MODES = [
|
|
42
|
-
{ id: '
|
|
43
|
-
{ id: '
|
|
44
|
-
{ id: '
|
|
45
|
-
{ id: 'governing', label: 'Governing', short: 'Governing', line: 'Decide, approve and keep it healthy.' },
|
|
43
|
+
{ id: 'artisan', label: 'Artisan', short: 'Artisan', line: 'Claim tasks and ship code.' },
|
|
44
|
+
{ id: 'artist', label: 'Artist', short: 'Artist', line: 'Shape how pages look and read, and turn thoughts into ideas.' },
|
|
45
|
+
{ id: 'patron', label: 'Patron', short: 'Patron', line: 'Watch, propose, and with a seat decide.' },
|
|
46
46
|
];
|
|
47
|
+
// The game's three characters (task 1005141, BONGOS-V3): Building became Artisan,
|
|
48
|
+
// Artistry and Ideating became Artist, Governing became Patron. A mode stored
|
|
49
|
+
// under its old id is read as the new one (LEGACY_MODES in valid()), so nobody
|
|
50
|
+
// loses their choice to the rename.
|
|
51
|
+
var LEGACY_MODES = { building: 'artisan', artistry: 'artist', ideating: 'artist', governing: 'patron' };
|
|
47
52
|
var IDS = MODES.map(function (m) { return m.id; });
|
|
48
53
|
|
|
49
54
|
// ---------- GOVERNING IS A SEAT (task 1004685, the living-city prototype) ----------
|
|
@@ -55,15 +60,21 @@
|
|
|
55
60
|
// so a hall that cannot read the seats is unchanged. Cosmetic, like the mode itself.
|
|
56
61
|
var seat;
|
|
57
62
|
function seatName() { return typeof seat === 'string' ? seat : null; }
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
// A builder who holds a character (task 1005147) is offered that one alone: the
|
|
64
|
+
// switch, the session picker and the welcome's role choices all read offered(), so
|
|
65
|
+
// none of them can wear another. Changing character is signing out (CHARACTER below).
|
|
66
|
+
function offered(mode) {
|
|
67
|
+
if (typeof account === 'string' && mode !== account) return false;
|
|
68
|
+
return mode !== 'patron' || seat !== null;
|
|
69
|
+
}
|
|
70
|
+
function labelOf(m) { return m.id === 'patron' && seatName() ? seatName() : m.label; }
|
|
71
|
+
function lineOf(m) { return m.id === 'patron' && seatName() ? 'Your seat. ' + m.line : m.line; }
|
|
61
72
|
function paintSeat() {
|
|
62
73
|
var q = document.querySelector ? function (s) { return document.querySelector(s); } : function () { return null; };
|
|
63
|
-
var opt = q('#craft-switch [data-craft-opt="
|
|
64
|
-
var gov = MODES[IDS.indexOf('
|
|
74
|
+
var opt = q('#craft-switch [data-craft-opt="patron"]');
|
|
75
|
+
var gov = MODES[IDS.indexOf('patron')];
|
|
65
76
|
if (opt) {
|
|
66
|
-
if (offered('
|
|
77
|
+
if (offered('patron')) opt.removeAttribute('hidden'); else opt.setAttribute('hidden', '');
|
|
67
78
|
opt.setAttribute('title', labelOf(gov) + ' — ' + lineOf(gov));
|
|
68
79
|
var lab = opt.querySelector && opt.querySelector('.craft-switch__label');
|
|
69
80
|
if (lab) lab.textContent = labelOf(gov);
|
|
@@ -73,7 +84,7 @@
|
|
|
73
84
|
var keep = picker.querySelector('[data-craft-again]');
|
|
74
85
|
var again = !keep || keep.checked;
|
|
75
86
|
var inside = picker.contains && picker.contains(document.activeElement);
|
|
76
|
-
picker.innerHTML = pickerHtml(read());
|
|
87
|
+
picker.innerHTML = creating ? createHtml(creating) : pickerHtml(read());
|
|
77
88
|
var box = picker.querySelector('[data-craft-again]');
|
|
78
89
|
if (box) box.checked = again;
|
|
79
90
|
var pre = inside && (picker.querySelector('.craft-door--pre') || picker.querySelector('[data-craft-close]'));
|
|
@@ -81,11 +92,11 @@
|
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
function setPositions(p) {
|
|
84
|
-
seat = !p || p.known !== true ? undefined : (p.has_position ? (p.title || '
|
|
85
|
-
if (seat === null && stored() === '
|
|
95
|
+
seat = !p || p.known !== true ? undefined : (p.has_position ? (p.title || 'Patron') : null);
|
|
96
|
+
if (seat === null && stored() === 'patron') {
|
|
86
97
|
drop('sessionStorage', MODE_KEY);
|
|
87
|
-
if (get('localStorage', KEEP_KEY) === '
|
|
88
|
-
if (document.documentElement && document.documentElement.dataset.craft === '
|
|
98
|
+
if (get('localStorage', KEEP_KEY) === 'patron') drop('localStorage', KEEP_KEY);
|
|
99
|
+
if (document.documentElement && document.documentElement.dataset.craft === 'patron') apply(null);
|
|
89
100
|
}
|
|
90
101
|
paintSeat();
|
|
91
102
|
}
|
|
@@ -101,25 +112,25 @@
|
|
|
101
112
|
// every craft has agents, and board 6 pairs it with the city in every mode.
|
|
102
113
|
// task 1005071 (owner, 2026-11-04): Help (the old Docs group) joins ALWAYS — the
|
|
103
114
|
// Primer is the newcomer's way in, and only Building could see it.
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
// task 1005144: My agents and Modules are options menu entries now, not tabs.
|
|
116
|
+
var ALWAYS_TABS = ['roster', 'collab', 'profile',
|
|
117
|
+
'primer', 'commands', 'atlas', 'apidocs'];
|
|
106
118
|
// No mode lists 'home': the craft plate under the brand is the Home button in
|
|
107
119
|
// every mode (task 1005065), so a Home tab beside it is a second home icon
|
|
108
120
|
// (task 1005070). With no mode the plate is absent and the Home tab shows.
|
|
109
121
|
var MODE_TABS = {
|
|
110
|
-
|
|
122
|
+
artisan: ['goals', 'goal-map', 'work', 'blockers', 'thinking', 'studio',
|
|
111
123
|
'market-watch'],
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
'watch', 'project-settings', 'money', 'deploy', 'government'],
|
|
124
|
+
artist: ['studio', 'goals', 'thinking', 'goal-map', 'market-watch'],
|
|
125
|
+
patron: ['city', 'blockers', 'board-room',
|
|
126
|
+
'watch', 'money', 'deploy'],
|
|
116
127
|
};
|
|
117
128
|
// A mode can lead with ONE tab and fold the rest of its own under a dropdown
|
|
118
129
|
// (owner, 2026-10-08: the city is Governing's tab, everything else under it).
|
|
119
130
|
// The folded items are MOVED into the dropdown, never copied, so the ids, the
|
|
120
131
|
// gate attributes and the active mark the shell set all travel with them; a
|
|
121
132
|
// placeholder keeps each one's place so leaving the mode puts it back exactly.
|
|
122
|
-
var MODE_LEAD = {
|
|
133
|
+
var MODE_LEAD = { patron: { lead: 'city', label: 'More' } };
|
|
123
134
|
var FOLD_ID = 'craft-fold';
|
|
124
135
|
// The tab ids a mode shows, or null for every tab.
|
|
125
136
|
function tabsFor(mode) {
|
|
@@ -224,7 +235,7 @@
|
|
|
224
235
|
// in both crafts' lists) and a page no craft owns (a task, an idea) stay put; so
|
|
225
236
|
// does Home, which redraws itself in place. The street has no nav item, so it is
|
|
226
237
|
// named here as Governing's. Tests swap the hop for a recorder (OTBCraftHop).
|
|
227
|
-
var OWN_PAGES = {
|
|
238
|
+
var OWN_PAGES = { patron: ['street'] };
|
|
228
239
|
function pageKeys() {
|
|
229
240
|
var keys = [];
|
|
230
241
|
var nav = document.getElementById('app-sidebar');
|
|
@@ -254,7 +265,7 @@
|
|
|
254
265
|
return false;
|
|
255
266
|
}
|
|
256
267
|
function homeHref(mode) {
|
|
257
|
-
var street = mode === '
|
|
268
|
+
var street = mode === 'patron' && window.GovCity && window.GovCity.STREET_HREF;
|
|
258
269
|
return street || '/';
|
|
259
270
|
}
|
|
260
271
|
function goHome(mode) {
|
|
@@ -269,11 +280,11 @@
|
|
|
269
280
|
// draw — the mode's words beside the page title, and its door's scene framed
|
|
270
281
|
// under the brand (artHtml, the picker's own art, so the two never drift).
|
|
271
282
|
// Building and no mode have no entry here and wear nothing.
|
|
272
|
-
var LOOKS = {
|
|
283
|
+
var LOOKS = { artist: 'the studio · night', patron: 'the city' };
|
|
273
284
|
// The plate is the Home button in every mode (owner, 2026-11-04, task 1005065): the
|
|
274
285
|
// mode's scene with a home mark over it. Building wears no look, so its plate shows
|
|
275
286
|
// the city, the scene its Home is.
|
|
276
|
-
var PLATE_ART = {
|
|
287
|
+
var PLATE_ART = { artisan: 'patron', artist: 'artist', patron: 'patron' };
|
|
277
288
|
var HOME_MARK = '<span class="craft-plate__home" aria-hidden="true"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round">' +
|
|
278
289
|
'<path d="M3 8.5 9 3l6 5.5V15a1 1 0 0 1-1 1h-3.5v-4h-3v4H4a1 1 0 0 1-1-1V8.5Z"/></svg></span>';
|
|
279
290
|
function remove(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
|
|
@@ -373,7 +384,7 @@
|
|
|
373
384
|
}
|
|
374
385
|
function applyTallies(mode) {
|
|
375
386
|
var box = document.getElementById('craft-tallies');
|
|
376
|
-
if (mode !== '
|
|
387
|
+
if (mode !== 'patron' || !tallyPage()) { remove(box); return; }
|
|
377
388
|
var mark = document.getElementById('craft-eyebrow');
|
|
378
389
|
if (!box && mark && mark.parentNode) {
|
|
379
390
|
box = document.createElement('span');
|
|
@@ -385,7 +396,7 @@
|
|
|
385
396
|
if (!box) return;
|
|
386
397
|
askTallies().then(function (t) {
|
|
387
398
|
var live = document.getElementById('craft-tallies');
|
|
388
|
-
if (!live || document.documentElement.dataset.craft !== '
|
|
399
|
+
if (!live || document.documentElement.dataset.craft !== 'patron') return;
|
|
389
400
|
var html = tallyHtml(t);
|
|
390
401
|
live.innerHTML = html;
|
|
391
402
|
if (html) live.removeAttribute('hidden'); else live.setAttribute('hidden', '');
|
|
@@ -401,17 +412,16 @@
|
|
|
401
412
|
// (same ?v as this file) and calls its open(). Cosmetic, like the mode: the
|
|
402
413
|
// routes behind a tool hold its walls (ADR 0016).
|
|
403
414
|
var TOOLS = {
|
|
404
|
-
|
|
415
|
+
artist: [
|
|
405
416
|
// Named "Artist review" since task 1004499: the public site's button
|
|
406
417
|
// (public-landing assets/artist-review.js) opens this same panel.
|
|
407
418
|
// `docked` (task 1004771): no top-bar button. The panel itself sits on
|
|
408
419
|
// the right edge behind a small tab, and carries the review-mode choice.
|
|
409
|
-
{ id: 'palette', icon: '
|
|
420
|
+
{ id: 'palette', icon: 'artist', short: 'Review', label: 'Artist review: write feedback on this page', script: 'page-notes.js', api: 'OTBPageNotes', docked: true },
|
|
410
421
|
// Task 1004497: this page's words, edited where they stand as a proposed wording change.
|
|
411
422
|
{ id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' },
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
ideating: [
|
|
423
|
+
// Task 1004498: notes about this page, split into ideas (lightbulb.js) — the
|
|
424
|
+
// ideator's tool, the artist's since the two crafts folded (task 1005141).
|
|
415
425
|
{ id: 'lightbulb', icon: 'note', short: 'Ideas', label: 'Write ideas about this page', script: 'lightbulb.js', api: 'OTBLightbulb' },
|
|
416
426
|
],
|
|
417
427
|
};
|
|
@@ -441,8 +451,8 @@
|
|
|
441
451
|
// artist keeps Artistry's tools and gains that experience's own after them.
|
|
442
452
|
function toolsFor(mode) {
|
|
443
453
|
var own = Object.prototype.hasOwnProperty.call(TOOLS, mode) ? TOOLS[mode] : [];
|
|
444
|
-
if (!review || mode === '
|
|
445
|
-
return TOOLS.
|
|
454
|
+
if (!review || mode === 'artist') return own;
|
|
455
|
+
return TOOLS.artist.concat(own);
|
|
446
456
|
}
|
|
447
457
|
function openTool(t, b) {
|
|
448
458
|
loadTool(t).then(function (api) { api.open(); }, function () { b.setAttribute('aria-disabled', 'true'); });
|
|
@@ -493,7 +503,7 @@
|
|
|
493
503
|
// 360px the bar has no room for three, so craft-review.css folds it away.
|
|
494
504
|
own.forEach(function (t) {
|
|
495
505
|
var el = document.getElementById('craft-tool-' + t.id);
|
|
496
|
-
if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.
|
|
506
|
+
if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.artist.indexOf(t) === -1);
|
|
497
507
|
});
|
|
498
508
|
}
|
|
499
509
|
|
|
@@ -509,23 +519,22 @@
|
|
|
509
519
|
// picker ends it. Cosmetic like the mode: a tab the gate hides stays hidden.
|
|
510
520
|
var REVIEW_KEY = 'hall-craft-review';
|
|
511
521
|
var EXPERIENCES = [
|
|
512
|
-
{ id: '
|
|
513
|
-
{ id: '
|
|
514
|
-
{ id: '
|
|
515
|
-
{ id: 'artistry', label: 'Artistry experience' },
|
|
522
|
+
{ id: 'patron', label: 'Patron experience' },
|
|
523
|
+
{ id: 'artisan', label: 'Artisan experience' },
|
|
524
|
+
{ id: 'artist', label: 'Artist experience' },
|
|
516
525
|
];
|
|
517
526
|
// The experience being reviewed, or null. Read from the session once, then
|
|
518
527
|
// held here, so a browser whose storage throws still reviews this page.
|
|
519
528
|
var review = null;
|
|
520
529
|
function storedReview() {
|
|
521
|
-
return read() === '
|
|
530
|
+
return read() === 'artist' ? valid(get('sessionStorage', REVIEW_KEY)) : null;
|
|
522
531
|
}
|
|
523
532
|
// The mode the hall wears: the experience under review, else the chosen mode.
|
|
524
533
|
function wearing() { return review || read(); }
|
|
525
534
|
function startReview(mode) {
|
|
526
535
|
mode = valid(mode);
|
|
527
536
|
if (!mode) return;
|
|
528
|
-
set('sessionStorage', MODE_KEY, '
|
|
537
|
+
set('sessionStorage', MODE_KEY, 'artist');
|
|
529
538
|
set('sessionStorage', ASKED_KEY, '1');
|
|
530
539
|
set('sessionStorage', REVIEW_KEY, mode);
|
|
531
540
|
review = mode;
|
|
@@ -534,7 +543,7 @@
|
|
|
534
543
|
function stopReview() {
|
|
535
544
|
drop('sessionStorage', REVIEW_KEY);
|
|
536
545
|
review = null;
|
|
537
|
-
apply('
|
|
546
|
+
apply('artist');
|
|
538
547
|
}
|
|
539
548
|
|
|
540
549
|
// The scribble wall: the top bar's empty space, doodled on while reviewing.
|
|
@@ -576,7 +585,10 @@
|
|
|
576
585
|
function icon(id, cls) {
|
|
577
586
|
return '<svg class="' + cls + '" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + ICONS[id] + '</svg>';
|
|
578
587
|
}
|
|
579
|
-
function valid(v) {
|
|
588
|
+
function valid(v) {
|
|
589
|
+
if (v && Object.prototype.hasOwnProperty.call(LEGACY_MODES, v)) v = LEGACY_MODES[v];
|
|
590
|
+
return IDS.indexOf(v) !== -1 ? v : null;
|
|
591
|
+
}
|
|
580
592
|
function get(store, key) { try { return window[store].getItem(key); } catch (_) { return null; } }
|
|
581
593
|
function set(store, key, v) { try { window[store].setItem(key, v); } catch (_) { /* private mode */ } }
|
|
582
594
|
function drop(store, key) { try { window[store].removeItem(key); } catch (_) { /* private mode */ } }
|
|
@@ -590,6 +602,7 @@
|
|
|
590
602
|
// earlier session. null = no mode, which is the hall as it always was. A stored
|
|
591
603
|
// Governing reads as no mode for a builder known to hold no seat (task 1004685).
|
|
592
604
|
function stored() {
|
|
605
|
+
if (typeof account === 'string') return account;
|
|
593
606
|
return valid(get('sessionStorage', MODE_KEY)) || valid(get('localStorage', KEEP_KEY));
|
|
594
607
|
}
|
|
595
608
|
function read() {
|
|
@@ -640,13 +653,15 @@
|
|
|
640
653
|
function switchHtml() {
|
|
641
654
|
if (pageOptsOut()) return '';
|
|
642
655
|
review = storedReview();
|
|
656
|
+
var held = heldCharacter();
|
|
657
|
+
if (held) return '<div class="craft-switch craft-switch--character" id="craft-switch" role="group" aria-label="Your character">' + chipHtml(held) + '</div>';
|
|
643
658
|
var current = review || read();
|
|
644
659
|
return '<div class="craft-switch' + (current ? '' : ' craft-switch--unset') + '" id="craft-switch" role="group" aria-label="Mode">' +
|
|
645
660
|
MODES.map(function (m) {
|
|
646
661
|
return '<button class="craft-switch__opt craft-switch__opt--' + m.id + '" type="button" data-craft-opt="' + m.id + '"' +
|
|
647
662
|
' aria-pressed="' + (m.id === current ? 'true' : 'false') + '" title="' + esc(labelOf(m) + ' — ' + lineOf(m)) + '"' +
|
|
648
663
|
(offered(m.id) ? '' : ' hidden') + '>' +
|
|
649
|
-
icon(m.id, 'craft-switch__icon') + '<span class="craft-switch__label">' + esc(m.id === '
|
|
664
|
+
icon(m.id, 'craft-switch__icon') + '<span class="craft-switch__label">' + esc(m.id === 'patron' && seatName() ? seatName() : m.short) + '</span></button>';
|
|
650
665
|
}).join('') +
|
|
651
666
|
// At phone width the options fold away and this one button opens the picker.
|
|
652
667
|
'<button class="craft-switch__open" type="button" data-craft-open aria-haspopup="dialog" aria-label="Choose your mode" title="Choose your mode">' +
|
|
@@ -659,6 +674,7 @@
|
|
|
659
674
|
function onSwitchClick(e) {
|
|
660
675
|
var btn = e.target.closest && e.target.closest('button');
|
|
661
676
|
if (!btn) return;
|
|
677
|
+
if (btn.hasAttribute('data-craft-me')) { changeCharacter(); return; }
|
|
662
678
|
if (btn.hasAttribute('data-craft-open')) { openPicker(); return; }
|
|
663
679
|
var mode = btn.getAttribute('data-craft-opt');
|
|
664
680
|
// At phone width only the current mode shows; tapping it opens the full picker.
|
|
@@ -698,24 +714,17 @@
|
|
|
698
714
|
}
|
|
699
715
|
function artHtml(id) {
|
|
700
716
|
var html = '', rnd, i;
|
|
701
|
-
if (id === '
|
|
717
|
+
if (id === 'artisan') {
|
|
702
718
|
html = '<span class="craft-art__bar" style="left:20px;top:22px;width:110px"></span>' +
|
|
703
719
|
'<span class="craft-art__bar craft-art__bar--dim" style="left:20px;top:42px;width:150px"></span>' +
|
|
704
720
|
'<span class="craft-art__pill" style="left:20px;top:68px"></span><span class="craft-art__card"></span>';
|
|
705
|
-
} else if (id === '
|
|
721
|
+
} else if (id === 'artist') {
|
|
706
722
|
rnd = seeded(7);
|
|
707
723
|
for (i = 0; i < 14; i++) html += dot(Math.round(rnd() * 250), Math.round(rnd() * 66), rnd() > 0.8 ? 2 : 1, 'opacity:' + (0.4 + rnd() * 0.6).toFixed(2), 'star');
|
|
708
724
|
html += '<span class="craft-art__moon"></span><span class="craft-art__ground"></span>' +
|
|
709
725
|
'<span class="craft-art__house" style="left:24px;height:54px;width:26px"></span><span class="craft-art__house" style="left:60px;height:44px;width:34px"></span>' +
|
|
710
726
|
dot(32, 66, 3, '', 'lit') + dot(70, 72, 3, '', 'lit');
|
|
711
|
-
} else if (id === '
|
|
712
|
-
rnd = seeded(11);
|
|
713
|
-
html = '<span class="craft-art__ring"></span>';
|
|
714
|
-
for (i = 0; i < 18; i++) {
|
|
715
|
-
var proto = rnd() > 0.7, r = proto ? 6 : (rnd() > 0.75 ? 5 : 2);
|
|
716
|
-
html += dot(Math.round(rnd() * 260), Math.round(6 + rnd() * 90), r, '', proto ? 'proto' : (r > 3 ? 'glow' : 'gold'));
|
|
717
|
-
}
|
|
718
|
-
} else if (id === 'governing') {
|
|
727
|
+
} else if (id === 'patron') {
|
|
719
728
|
html = '<svg class="craft-art__city" viewBox="0 0 300 110" aria-hidden="true">' + cityPaths() + '</svg>';
|
|
720
729
|
}
|
|
721
730
|
return '<span class="craft-art craft-art--' + id + '" aria-hidden="true">' + html + '</span>';
|
|
@@ -737,7 +746,7 @@
|
|
|
737
746
|
'<div class="craft-picker__doors" data-doors="' + shown.length + '">' + doors + '</div>' +
|
|
738
747
|
'<div class="craft-picker__foot">' +
|
|
739
748
|
'<label class="craft-picker__again"><input type="checkbox" data-craft-again checked> Ask me again next session</label>' +
|
|
740
|
-
(preselect === '
|
|
749
|
+
(preselect === 'artist' ? '<span class="craft-picker__hint">Artists start here.</span>' : '') +
|
|
741
750
|
'</div>' +
|
|
742
751
|
'</div>';
|
|
743
752
|
}
|
|
@@ -746,7 +755,8 @@
|
|
|
746
755
|
}
|
|
747
756
|
function onPickerKey(e) {
|
|
748
757
|
if (!picker) return;
|
|
749
|
-
|
|
758
|
+
// Character creation is a choice to make, not a box to dismiss: Escape stays put.
|
|
759
|
+
if (e.key === 'Escape') { e.preventDefault(); if (!creating) closePicker(); return; }
|
|
750
760
|
if (e.key !== 'Tab') return;
|
|
751
761
|
var f = focusables();
|
|
752
762
|
if (!f.length) return;
|
|
@@ -755,6 +765,11 @@
|
|
|
755
765
|
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
|
|
756
766
|
}
|
|
757
767
|
function onPickerClick(e) {
|
|
768
|
+
if (creating) {
|
|
769
|
+
var c = e.target.closest && e.target.closest('button[data-char-pick]');
|
|
770
|
+
if (c) pickCharacter(c.getAttribute('data-char-pick'));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
758
773
|
if (e.target === picker) { closePicker(); return; }
|
|
759
774
|
var btn = e.target.closest && e.target.closest('button');
|
|
760
775
|
if (!btn) return;
|
|
@@ -787,6 +802,7 @@
|
|
|
787
802
|
document.removeEventListener('keydown', onPickerKey, true);
|
|
788
803
|
if (picker.parentNode) picker.parentNode.removeChild(picker);
|
|
789
804
|
picker = null;
|
|
805
|
+
creating = false;
|
|
790
806
|
document.documentElement.classList.remove('craft-picker-open');
|
|
791
807
|
if (returnFocus && returnFocus.focus) { try { returnFocus.focus(); } catch (_) { /* gone */ } }
|
|
792
808
|
returnFocus = null;
|
|
@@ -797,7 +813,173 @@
|
|
|
797
813
|
if (!builder) return null;
|
|
798
814
|
var main = builder.main_discipline;
|
|
799
815
|
var crafts = Array.isArray(builder.preferred_disciplines) ? builder.preferred_disciplines : [];
|
|
800
|
-
return (main === 'artist' || (!main && crafts[0] === 'artist')) ? '
|
|
816
|
+
return (main === 'artist' || (!main && crafts[0] === 'artist')) ? 'artist' : null;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ---------- character creation (task 1005147, BONGOS-V3 G2) ----------
|
|
820
|
+
// A builder picks one of three characters when they join, and the pick is kept on
|
|
821
|
+
// their ACCOUNT (PUT /me/character, core_297), so it follows them to every device.
|
|
822
|
+
// /me says which one they hold; null means not picked yet, and the hall then shows
|
|
823
|
+
// character creation before anything else (the welcome and the session picker wait).
|
|
824
|
+
// Holding a character replaces the per-session mode: the top-bar switch becomes the
|
|
825
|
+
// character's sprite, and offered() names that character alone. Switching is signing
|
|
826
|
+
// out and back in (the spec's settled point 2): the sprite's "Change character"
|
|
827
|
+
// clears the pick and signs out, and the next sign-in finds none and asks again.
|
|
828
|
+
// Identity and view only — rank still gates every verb on the server (ADR 0016).
|
|
829
|
+
//
|
|
830
|
+
// The sprites are the owner's art (Builder → Artisan, Artist → Artist, Ruler →
|
|
831
|
+
// Patron; the Governor is the spare), one file per character under characters/.
|
|
832
|
+
var account; // undefined: not told; null: none picked; else the character
|
|
833
|
+
var creating = false; // the preselected door while character creation is open
|
|
834
|
+
var CHAR_KEY = 'hall-character'; // localStorage: the last held character, so the first paint is right
|
|
835
|
+
var CHARACTER_LINES = {
|
|
836
|
+
artisan: 'Claim work and build it. Your record is the leaderboard.',
|
|
837
|
+
artist: 'Shape how things look and read, and turn thoughts into ideas. Your work hangs in the museum.',
|
|
838
|
+
patron: 'Watch the project, propose, and with a seat decide.',
|
|
839
|
+
};
|
|
840
|
+
// Only ever one of the three names reaches the URL (valid() is the allowlist).
|
|
841
|
+
function spriteSrc(id) { return '/builders/characters/' + (valid(id) || 'artisan') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
|
|
842
|
+
function spriteHtml(id, cls) {
|
|
843
|
+
return '<img class="craft-sprite ' + cls + '" src="' + spriteSrc(id) + '" alt="" width="250" height="290" decoding="async">';
|
|
844
|
+
}
|
|
845
|
+
function heldCharacter() {
|
|
846
|
+
if (typeof account === 'string') return account;
|
|
847
|
+
if (account === null) return null;
|
|
848
|
+
return valid(get('localStorage', CHAR_KEY));
|
|
849
|
+
}
|
|
850
|
+
function modeOf(id) { return MODES[IDS.indexOf(id)]; }
|
|
851
|
+
function chipHtml(id) {
|
|
852
|
+
var m = modeOf(id);
|
|
853
|
+
var name = m.label; // a character's name, never the seat's title
|
|
854
|
+
return '<button class="craft-switch__me craft-switch__opt--' + id + '" type="button" data-craft-me aria-haspopup="dialog"' +
|
|
855
|
+
' title="' + esc('You are the ' + name + '. Change character') + '">' +
|
|
856
|
+
spriteHtml(id, 'craft-sprite--chip') + '<span class="craft-switch__label">' + esc(name) + '</span></button>';
|
|
857
|
+
}
|
|
858
|
+
function paintCharacter() {
|
|
859
|
+
var sw = document.getElementById('craft-switch');
|
|
860
|
+
if (!sw) return;
|
|
861
|
+
var held = heldCharacter();
|
|
862
|
+
if (held) {
|
|
863
|
+
sw.className = 'craft-switch craft-switch--character';
|
|
864
|
+
sw.setAttribute('aria-label', 'Your character');
|
|
865
|
+
sw.innerHTML = chipHtml(held);
|
|
866
|
+
} else if (sw.classList && sw.classList.contains('craft-switch--character')) {
|
|
867
|
+
// A character dropped (signed out mid-change): back to the plain switch.
|
|
868
|
+
var tmp = document.createElement('div');
|
|
869
|
+
tmp.innerHTML = switchHtml();
|
|
870
|
+
var fresh = tmp.firstChild;
|
|
871
|
+
if (fresh && sw.parentNode) { sw.parentNode.replaceChild(fresh, sw); fresh.addEventListener('click', onSwitchClick); }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// What /me said. Called by characterGate; also exported for a page that re-reads /me.
|
|
875
|
+
function setCharacter(c) {
|
|
876
|
+
account = c === null ? null : (valid(c) || undefined);
|
|
877
|
+
if (typeof account === 'string') set('localStorage', CHAR_KEY, account);
|
|
878
|
+
else if (account === null) drop('localStorage', CHAR_KEY);
|
|
879
|
+
paintCharacter();
|
|
880
|
+
if (typeof account === 'string' && offered(account)) {
|
|
881
|
+
if (document.documentElement.dataset.craft !== account && !review) apply(account);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function createHtml(preselect) {
|
|
885
|
+
var shown = MODES.filter(function (m) { return offered(m.id); });
|
|
886
|
+
var doors = shown.map(function (m) {
|
|
887
|
+
return '<button class="craft-door craft-door--character craft-door--' + m.id + (m.id === preselect ? ' craft-door--pre' : '') + '" type="button" data-char-pick="' + m.id + '">' +
|
|
888
|
+
'<span class="craft-plinth" aria-hidden="true">' + spriteHtml(m.id, 'craft-sprite--door') + '</span>' +
|
|
889
|
+
'<span class="craft-door__head"><span class="craft-door__name">' + esc(m.label) + '</span></span>' +
|
|
890
|
+
'<span class="craft-door__line">' + esc(CHARACTER_LINES[m.id] || m.line) + '</span></button>';
|
|
891
|
+
}).join('');
|
|
892
|
+
return '<div class="craft-picker__panel craft-picker__panel--create" role="dialog" aria-modal="true" aria-labelledby="craft-picker-title" aria-describedby="craft-picker-lede">' +
|
|
893
|
+
'<p class="craft-picker__eyebrow">Character creation</p>' +
|
|
894
|
+
'<h2 class="craft-picker__title" id="craft-picker-title">Choose your character</h2>' +
|
|
895
|
+
'<p class="craft-picker__lede" id="craft-picker-lede">This is who you are in the project. To change it later, sign out and pick again.</p>' +
|
|
896
|
+
'<div class="craft-picker__doors" data-doors="' + shown.length + '">' + doors + '</div>' +
|
|
897
|
+
'<p class="craft-picker__error" data-char-error role="alert" hidden></p>' +
|
|
898
|
+
'</div>';
|
|
899
|
+
}
|
|
900
|
+
function openCreate(preselect) {
|
|
901
|
+
if (picker || pageOptsOut() || !document.body) return;
|
|
902
|
+
creating = valid(preselect) || 'artisan';
|
|
903
|
+
returnFocus = document.activeElement;
|
|
904
|
+
picker = document.createElement('div');
|
|
905
|
+
picker.className = 'craft-picker craft-picker--create';
|
|
906
|
+
picker.id = 'craft-picker';
|
|
907
|
+
picker.innerHTML = createHtml(creating);
|
|
908
|
+
picker.addEventListener('click', onPickerClick);
|
|
909
|
+
document.addEventListener('keydown', onPickerKey, true);
|
|
910
|
+
document.body.appendChild(picker);
|
|
911
|
+
document.documentElement.classList.add('craft-picker-open');
|
|
912
|
+
var pre = picker.querySelector('.craft-door--pre') || picker.querySelector('[data-char-pick]');
|
|
913
|
+
if (pre && pre.focus) pre.focus();
|
|
914
|
+
}
|
|
915
|
+
function client() {
|
|
916
|
+
var C = window.BongosClient;
|
|
917
|
+
return C && C.createClient ? C.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false }) : null;
|
|
918
|
+
}
|
|
919
|
+
var saving = false;
|
|
920
|
+
function pickCharacter(mode) {
|
|
921
|
+
mode = valid(mode);
|
|
922
|
+
if (!mode || !offered(mode) || saving) return;
|
|
923
|
+
var api = client();
|
|
924
|
+
var err = picker && picker.querySelector('[data-char-error]');
|
|
925
|
+
if (!api) { if (err) { err.textContent = 'Your character could not be saved. Reload and try again.'; err.removeAttribute('hidden'); } return; }
|
|
926
|
+
saving = true;
|
|
927
|
+
if (err) err.setAttribute('hidden', '');
|
|
928
|
+
api.request('PUT', '/api/bongos/me/character', { body: { character: mode } }).then(function (res) {
|
|
929
|
+
saving = false;
|
|
930
|
+
if (!res || !res.ok) throw new Error('character not saved');
|
|
931
|
+
closePicker();
|
|
932
|
+
choose(mode, true);
|
|
933
|
+
setCharacter(res.data && res.data.character ? res.data.character : mode);
|
|
934
|
+
}).then(null, function () {
|
|
935
|
+
saving = false;
|
|
936
|
+
var e2 = picker && picker.querySelector('[data-char-error]');
|
|
937
|
+
if (e2) { e2.textContent = 'Your character could not be saved. Try again.'; e2.removeAttribute('hidden'); }
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
// The sprite's one action: change character, which is signing out (spec point 2).
|
|
941
|
+
function signOut() {
|
|
942
|
+
var link = document.getElementById('logout-link');
|
|
943
|
+
if (link && link.click) { link.click(); return; }
|
|
944
|
+
var api = client();
|
|
945
|
+
var home = function () { if (window.location) window.location.assign('/'); };
|
|
946
|
+
if (api) api.request('POST', '/api/bongos/auth/logout').then(home, home); else home();
|
|
947
|
+
}
|
|
948
|
+
var changing = false; // one dialog at a time, however many clicks
|
|
949
|
+
function changeCharacter() {
|
|
950
|
+
if (changing) return;
|
|
951
|
+
changing = true;
|
|
952
|
+
var held = heldCharacter();
|
|
953
|
+
var name = held ? modeOf(held).label : 'your character';
|
|
954
|
+
var D = window.OTBDialog;
|
|
955
|
+
var ask = D && D.confirm ? D.confirm({
|
|
956
|
+
title: 'Change character?',
|
|
957
|
+
body: 'You are the ' + name + '. To change, you sign out, and you pick a character again when you sign back in.',
|
|
958
|
+
confirmLabel: 'Sign out and change',
|
|
959
|
+
}) : Promise.resolve(false);
|
|
960
|
+
ask.then(function (yes) {
|
|
961
|
+
changing = false;
|
|
962
|
+
if (!yes) return;
|
|
963
|
+
var api = client();
|
|
964
|
+
var done = function () { drop('localStorage', CHAR_KEY); drop('localStorage', KEEP_KEY); drop('sessionStorage', MODE_KEY); signOut(); };
|
|
965
|
+
if (api) api.request('PUT', '/api/bongos/me/character', { body: { character: null } }).then(done, done); else done();
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
// The shell's first call once /me has answered, ahead of the welcome and the
|
|
969
|
+
// session picker: true when character creation is up (so nothing else stacks on it).
|
|
970
|
+
// The last /me the gate was handed (task 1005148): the visor reads the credits off it.
|
|
971
|
+
var lastMe = null;
|
|
972
|
+
function characterGate(access, me) {
|
|
973
|
+
if (!access || access.authed !== true || !me || !me.builder || pageOptsOut()) return false;
|
|
974
|
+
lastMe = me;
|
|
975
|
+
try { document.dispatchEvent(new CustomEvent('otbcraft:me')); } catch (_) { /* old engine */ }
|
|
976
|
+
if (me.character === undefined) return false; // an older core, or not told: no characters yet
|
|
977
|
+
setCharacter(me.character);
|
|
978
|
+
if (account !== null) return false;
|
|
979
|
+
if (creating) return true;
|
|
980
|
+
var main = me.builder.main_discipline;
|
|
981
|
+
openCreate(main === 'artist' ? 'artist' : 'artisan');
|
|
982
|
+
return true;
|
|
801
983
|
}
|
|
802
984
|
|
|
803
985
|
// ---------- the shell's three calls ----------
|
|
@@ -820,9 +1002,11 @@
|
|
|
820
1002
|
if (note && notes && !onStudio) loadTool(notes).then(function (api) { api.openNote(note[1]); }, function () { /* the page still works */ });
|
|
821
1003
|
}
|
|
822
1004
|
// Once a signed-in builder is known, ask — unless this session already has a
|
|
823
|
-
// mode, already saw the picker, or the page opts out.
|
|
1005
|
+
// mode, already saw the picker, or the page opts out. A screen inside the options menu's
|
|
1006
|
+
// frame never asks (task 1005144): the menu page around it does, once.
|
|
824
1007
|
function onAccess(access, builder) {
|
|
825
1008
|
if (!access || access.authed !== true || !builder || pageOptsOut() || !sessionOk()) return;
|
|
1009
|
+
if (window.OTBShellGame && window.OTBShellGame.embedded && window.OTBShellGame.embedded()) return;
|
|
826
1010
|
if (read() || get('sessionStorage', ASKED_KEY)) return;
|
|
827
1011
|
openPicker(preselectFor(builder));
|
|
828
1012
|
}
|
|
@@ -867,5 +1051,15 @@
|
|
|
867
1051
|
// task 1004685 (prototype): the seats the viewer holds, and the Governing seat's name.
|
|
868
1052
|
setPositions: setPositions,
|
|
869
1053
|
seatName: seatName,
|
|
1054
|
+
// task 1005147: character creation. characterGate is the shell's first call once
|
|
1055
|
+
// /me answers; character() is the one held (null: none, undefined: not told yet).
|
|
1056
|
+
characterGate: characterGate,
|
|
1057
|
+
setCharacter: setCharacter,
|
|
1058
|
+
character: function () { return account; },
|
|
1059
|
+
changeCharacter: changeCharacter,
|
|
1060
|
+
spriteSrc: spriteSrc,
|
|
1061
|
+
// task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
|
|
1062
|
+
me: function () { return lastMe; },
|
|
1063
|
+
openTool: function (id) { var t = toolFor(id); if (t) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
|
|
870
1064
|
};
|
|
871
1065
|
})();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm">
|
|
12
12
|
<link rel="stylesheet" href="/builders/oversight.css">
|
|
13
13
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
14
14
|
<link rel="stylesheet" href="/builders/project-settings.css">
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
80
80
|
|
|
81
81
|
<script src="/builders/dom-utils.js?v=2026-11-05-numbers"></script>
|
|
82
|
-
<script src="/builders/shell-game.js?v=2026-11-
|
|
82
|
+
<script src="/builders/shell-game.js?v=2026-11-11-helm"></script>
|
|
83
83
|
<script src="/builders/shell-trail.js?v=2026-11-05-numbers"></script>
|
|
84
|
-
<script src="/builders/craft-mode.js?v=2026-11-
|
|
85
|
-
<script src="/builders/shell.js?v=2026-11-
|
|
84
|
+
<script src="/builders/craft-mode.js?v=2026-11-10-visor"></script>
|
|
85
|
+
<script src="/builders/shell.js?v=2026-11-11-helm"></script>
|
|
86
86
|
<script src="/builders/hall-dialog.js"></script>
|
|
87
87
|
<script src="/builders/palette.js" defer></script>
|
|
88
88
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_": "DEPLOY (task 1003159, ADR 0293; the npm-release module's sections, task 1002622) through modules/ui-design/kit/render.js. The page reads authenticated routes gated on core.pin.move, so against the kit's own stub it renders its restricted or failed-read state. To look at the populated page — the deploy door AND the module's 'where the work is' + version history, from a fixture taken off the live registry and ledger — run the hall-preview harness with the module on:\n\n BONGOS_MODULE_NPM_RELEASE=1 node scripts/hall-preview/server.js --port 4602 --rank archon --fixture-me --modules-on npm-release,provisioning\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/deploy.states.json --base-in http://localhost:4602 --base-out http://localhost:4602",
|
|
3
3
|
"page": "/builders/deploy",
|
|
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",
|
|
@@ -318,7 +318,8 @@
|
|
|
318
318
|
}
|
|
319
319
|
// Esc exits fullscreen from anywhere.
|
|
320
320
|
document.addEventListener('keydown', (e) => {
|
|
321
|
-
|
|
321
|
+
// Spent once it left the viewer's full screen (ADR 0399 §2.4 row 5, task 1005144).
|
|
322
|
+
if ((e.key === 'Escape' || e.key === 'Esc') && fullscreen) { e.preventDefault(); toggleFullscreen(fullscreen); }
|
|
322
323
|
});
|
|
323
324
|
// Re-fit on resize: the fullscreen viewer always, and any inline viewer the
|
|
324
325
|
// reader hasn't manually zoomed/panned (so they stay responsive).
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
function disciplines() {
|
|
186
186
|
const m = window.__MODULES__;
|
|
187
187
|
const list = m && Array.isArray(m.disciplines) ? m.disciplines : null;
|
|
188
|
-
return list && list.length ? list.slice() : ['
|
|
188
|
+
return list && list.length ? list.slice() : ['artisan', 'artist'];
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// Fill a Kind <select> from the vocabulary the SERVER serves (GET /me
|