@bongos/core 1.24.78 → 1.24.80
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 +498 -353
- 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/README.md +3 -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 +390 -370
- package/docs/copy-registry.json +719 -518
- 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 +15 -4
- package/docs/page-readings.json +3182 -3336
- 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 +4 -4
- package/modules/hall-ui/public/atlas.html +5 -5
- package/modules/hall-ui/public/atlas.js +3 -1
- package/modules/hall-ui/public/blockers.html +5 -5
- package/modules/hall-ui/public/board-room.html +4 -4
- 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 +2 -1
- package/modules/hall-ui/public/city.html +9 -6
- package/modules/hall-ui/public/city.js +7 -1
- package/modules/hall-ui/public/collab.html +4 -4
- package/modules/hall-ui/public/commands.html +4 -4
- package/modules/hall-ui/public/copy-desk.html +4 -4
- package/modules/hall-ui/public/craft-mode.js +258 -64
- package/modules/hall-ui/public/deploy.html +4 -4
- 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/government.html +4 -4
- 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/ideas.html +5 -5
- package/modules/hall-ui/public/index.html +11 -7
- package/modules/hall-ui/public/market-watch-item.html +4 -4
- package/modules/hall-ui/public/market-watch-rules.html +4 -4
- 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/modules.html +4 -4
- package/modules/hall-ui/public/money.html +4 -4
- package/modules/hall-ui/public/my-agents.html +4 -4
- 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/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 +4 -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/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/shell-game.css +65 -0
- package/modules/hall-ui/public/shell-game.js +283 -3
- package/modules/hall-ui/public/shell.js +7 -26
- 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-inside.js +3 -4
- package/modules/hall-ui/public/street.html +4 -4
- package/modules/hall-ui/public/studio.html +5 -5
- package/modules/hall-ui/public/style.css +73 -47
- package/modules/hall-ui/public/task-detail.js +5 -4
- package/modules/hall-ui/public/task.html +5 -5
- package/modules/hall-ui/public/thinking.css +10 -0
- package/modules/hall-ui/public/thinking.html +9 -6
- 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/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/welcome-boot.js +15 -6
- package/modules/hall-ui/public/work.html +5 -5
- 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 +21 -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/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/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +76 -0
- package/scripts/bongos/autobongos-host.js +19 -4
- package/scripts/bongos/autobongos-loop.js +1 -1
- package/scripts/bongos/claude-materialize.js +20 -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/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 +26 -9
- package/src/crafts.js +66 -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/autobongos_host.mjs +19 -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 +75 -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/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 +1 -1
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +27 -3
- 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 +2 -2
- package/tests/hall_settings_world.mjs +2 -1
- package/tests/hall_sitemap.mjs +6 -5
- package/tests/hall_start_screen.mjs +244 -0
- package/tests/hall_street.mjs +2 -2
- package/tests/hall_street_inside.mjs +17 -9
- package/tests/hall_studio_world.mjs +3 -3
- 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
package/tests/update_pill.mjs
CHANGED
|
@@ -129,7 +129,7 @@ function boot({
|
|
|
129
129
|
permissions = { 'core.pin.move': true }, update = BEHIND, held = null, status = 200, coreFails = false,
|
|
130
130
|
sw = SW({ kind: 'local', url: '/deploy' }), preview = { instance_id: '41', recommended_version: '1.19.871' },
|
|
131
131
|
post = { ok: true, status: 200, data: { ok: true } }, run = null, version = () => '1.19.861',
|
|
132
|
-
pathname = '/goals', hash = '', openResult = 'popup',
|
|
132
|
+
pathname = '/goals', hash = '', openResult = 'popup', dialog = null, later = null, handoff = false,
|
|
133
133
|
} = {}) {
|
|
134
134
|
const ids = new Map();
|
|
135
135
|
const inserted = [];
|
|
@@ -138,6 +138,7 @@ function boot({
|
|
|
138
138
|
const posted = [];
|
|
139
139
|
const store = new Map();
|
|
140
140
|
if (run) store.set('bongos.update.run', JSON.stringify(run));
|
|
141
|
+
if (later) store.set('bongos.update.later', JSON.stringify(later));
|
|
141
142
|
let pillLoads = 0;
|
|
142
143
|
const listeners = {};
|
|
143
144
|
|
|
@@ -181,7 +182,7 @@ function boot({
|
|
|
181
182
|
if (tag.onload) tag.onload();
|
|
182
183
|
},
|
|
183
184
|
},
|
|
184
|
-
documentElement: { dataset: {}, style: {}, setAttribute() {}, classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } } },
|
|
185
|
+
documentElement: { dataset: handoff ? { handoff: 'on' } : {}, style: {}, setAttribute() {}, classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } } },
|
|
185
186
|
getElementById(id) { if (!ids.has(id)) ids.set(id, el('div')); return ids.get(id); },
|
|
186
187
|
querySelector(sel) { return sel === '#app-topbar .topbar-side' ? side : null; },
|
|
187
188
|
querySelectorAll() { return []; },
|
|
@@ -214,6 +215,7 @@ function boot({
|
|
|
214
215
|
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
215
216
|
localStorage: { getItem: (k) => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: (k) => store.delete(k) },
|
|
216
217
|
requestAnimationFrame() {}, scrollTo() {}, location, innerWidth: 1280,
|
|
218
|
+
...(dialog ? { OTBDialog: dialog } : {}),
|
|
217
219
|
...timers,
|
|
218
220
|
};
|
|
219
221
|
windowObj.document = documentObj;
|
|
@@ -433,3 +435,152 @@ test('a run whose version this page already runs has landed: nothing to say', as
|
|
|
433
435
|
assert.equal(b.pill(), undefined);
|
|
434
436
|
assert.equal(b.store.has('bongos.update.run'), false);
|
|
435
437
|
});
|
|
438
|
+
|
|
439
|
+
// ── the pop-up (task 1005191) ─────────────────────────────────────────────────
|
|
440
|
+
// The owner, 2026-11-10: the pill alone was not seen (game mode opens every page without its
|
|
441
|
+
// bars), so an update that is ready also asks, once, in the hall's own dialog.
|
|
442
|
+
|
|
443
|
+
// A stand-in for OTBDialog.open that keeps what it was handed and lets a case press a button
|
|
444
|
+
// or dismiss it. The real one is run by tests/hall_dialog.mjs.
|
|
445
|
+
function fakeDialog() {
|
|
446
|
+
const opens = [];
|
|
447
|
+
return {
|
|
448
|
+
opens,
|
|
449
|
+
open(opts) {
|
|
450
|
+
let resolve;
|
|
451
|
+
const result = new Promise((r) => { resolve = r; });
|
|
452
|
+
const on = {};
|
|
453
|
+
const d = { opts, el: { addEventListener(ev, fn) { (on[ev] ||= []).push(fn); } }, result, close: (v) => resolve(v === undefined ? null : v) };
|
|
454
|
+
d.press = (i) => {
|
|
455
|
+
const btn = { getAttribute: (k) => (k === 'data-dialog-value' ? String(i) : null) };
|
|
456
|
+
for (const fn of on.click || []) fn({ target: { closest: () => btn } });
|
|
457
|
+
resolve(opts.actions[i].value);
|
|
458
|
+
};
|
|
459
|
+
opens.push(d);
|
|
460
|
+
return d;
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
test('the pop-up is due for a ready update, and not for a version said "later" to inside a day', () => {
|
|
466
|
+
const now = 10 * P.LATER_MS;
|
|
467
|
+
assert.equal(P.askDue({ face: 'ready', offered: '1.19.871', later: null, now }), true);
|
|
468
|
+
assert.equal(P.askDue({ face: 'ready', offered: '1.19.871', later: { v: '1.19.871', at: now - 1000 }, now }), false);
|
|
469
|
+
assert.equal(P.askDue({ face: 'ready', offered: '1.19.871', later: { v: '1.19.871', at: now - P.LATER_MS }, now }), true, 'a day later it asks again');
|
|
470
|
+
assert.equal(P.askDue({ face: 'ready', offered: '1.19.872', later: { v: '1.19.871', at: now - 1000 }, now }), true, 'a newer version asks at once');
|
|
471
|
+
assert.equal(P.askDue({ face: 'ready', offered: '1.19.871', later: { v: '1.19.871', at: now + 5000 }, now }), true, 'a clock that went backwards does not silence it');
|
|
472
|
+
for (const face of ['held', 'running', 'done', 'none']) assert.equal(P.askDue({ face, offered: '1.19.871', later: null, now }), false, face);
|
|
473
|
+
assert.equal(P.askDue({ face: 'ready', offered: 'next', later: null, now }), false);
|
|
474
|
+
const box = new Map();
|
|
475
|
+
const storage = { getItem: (k) => (box.has(k) ? box.get(k) : null), setItem: (k, v) => box.set(k, v) };
|
|
476
|
+
P.keepLater(storage, '1.19.871', 5);
|
|
477
|
+
assert.deepEqual(P.readLater(storage), { v: '1.19.871', at: 5 });
|
|
478
|
+
box.set(P.LATER_KEY, '{"v":"soon","at":1}');
|
|
479
|
+
assert.equal(P.readLater(storage), null);
|
|
480
|
+
assert.doesNotThrow(() => P.keepLater({ setItem() { throw new Error('private'); } }, '1.0.0', 1));
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test('an owner who is behind is asked, unprompted, in the hall dialog: what it brings, both versions, two buttons', async () => {
|
|
484
|
+
const dialog = fakeDialog();
|
|
485
|
+
const b = boot({ dialog });
|
|
486
|
+
await settle();
|
|
487
|
+
assert.equal(dialog.opens.length, 1, 'asked once, without a press');
|
|
488
|
+
const { opts } = dialog.opens[0];
|
|
489
|
+
assert.equal(opts.title, 'Update the builders hall?');
|
|
490
|
+
assert.deepEqual(plain(opts.actions.map((a) => a.label)), ['Later', 'Update now']);
|
|
491
|
+
const html = opts.body.innerHTML;
|
|
492
|
+
assert.match(html, /latest changes and bug fixes/);
|
|
493
|
+
assert.match(html, /Faster boards, and fewer clicks/);
|
|
494
|
+
assert.match(html, /1\.19\.861<\/span> → <span class="upd-ver">1\.19\.871/);
|
|
495
|
+
assert.ok(b.pill(), 'the pill is still there');
|
|
496
|
+
assert.equal(b.requests.filter((r) => r.path === '/api/bongos/software-update').length, 1);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('Later is remembered for that version, and the next page does not ask again', async () => {
|
|
500
|
+
const dialog = fakeDialog();
|
|
501
|
+
const b = boot({ dialog });
|
|
502
|
+
await settle();
|
|
503
|
+
dialog.opens[0].press(0);
|
|
504
|
+
await settle();
|
|
505
|
+
const kept = JSON.parse(b.store.get('bongos.update.later'));
|
|
506
|
+
assert.equal(kept.v, '1.19.871');
|
|
507
|
+
assert.equal(b.requests.some((r) => /core-upgrade$/.test(r.path)), false, 'Later moves nothing');
|
|
508
|
+
const again = fakeDialog();
|
|
509
|
+
const next = boot({ dialog: again, later: kept });
|
|
510
|
+
await settle();
|
|
511
|
+
assert.equal(again.opens.length, 0);
|
|
512
|
+
assert.ok(next.pill(), 'the pill still offers it');
|
|
513
|
+
assert.equal(next.requests.some((r) => r.path === '/api/bongos/software-update'), false, 'and nothing is read for a pop-up that will not open');
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test('closing the pop-up any other way is "later" too', async () => {
|
|
517
|
+
const dialog = fakeDialog();
|
|
518
|
+
const b = boot({ dialog });
|
|
519
|
+
await settle();
|
|
520
|
+
dialog.opens[0].close();
|
|
521
|
+
await settle();
|
|
522
|
+
assert.equal(JSON.parse(b.store.get('bongos.update.later')).v, '1.19.871');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test('Update now in the pop-up starts the same update as the card, with the version it showed', async () => {
|
|
526
|
+
const dialog = fakeDialog();
|
|
527
|
+
const b = boot({ dialog });
|
|
528
|
+
await settle();
|
|
529
|
+
dialog.opens[0].press(1);
|
|
530
|
+
await settle(5);
|
|
531
|
+
const post = b.requests.find((r) => /core-upgrade$/.test(r.path));
|
|
532
|
+
assert.ok(post, 'the platform door was called');
|
|
533
|
+
assert.deepEqual(plain(post.body), { to: '1.19.871' });
|
|
534
|
+
assert.equal(b.store.has('bongos.update.later'), false, 'an update taken is not snoozed');
|
|
535
|
+
assert.match(b.card().innerHTML, /Updating Bongos/, 'and the card opens to say where it has got to');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('a hosted project: Update now in the pop-up opens the hub window inside the press', async () => {
|
|
539
|
+
const dialog = fakeDialog();
|
|
540
|
+
const b = boot({ dialog, sw: SW({ kind: 'hub', url: HUB }) });
|
|
541
|
+
await settle();
|
|
542
|
+
assert.equal(b.opened.length, 0);
|
|
543
|
+
dialog.opens[0].press(1);
|
|
544
|
+
assert.equal(b.opened.length, 1, 'opened synchronously, so the browser allows it');
|
|
545
|
+
assert.match(b.opened[0].url, /handoff=1$/);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('who is not asked: a viewer the door would refuse, the handoff window, the door page, a hall with no dialog', async () => {
|
|
549
|
+
const refused = fakeDialog();
|
|
550
|
+
boot({ dialog: refused, sw: SW({ kind: 'local', url: '/deploy' }, { can_update: false }) });
|
|
551
|
+
const handoff = fakeDialog();
|
|
552
|
+
boot({ dialog: handoff, handoff: true });
|
|
553
|
+
const door = fakeDialog();
|
|
554
|
+
boot({ dialog: door, pathname: '/deploy' });
|
|
555
|
+
const b = boot();
|
|
556
|
+
await settle();
|
|
557
|
+
assert.equal(refused.opens.length, 0);
|
|
558
|
+
assert.equal(handoff.opens.length, 0);
|
|
559
|
+
assert.equal(door.opens.length, 0);
|
|
560
|
+
assert.ok(b.pill(), 'no dialog script: the pill alone, as before');
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('a self-hosted project is told the command, with one button and no Update now', async () => {
|
|
564
|
+
const dialog = fakeDialog();
|
|
565
|
+
boot({ dialog, sw: SW({ kind: 'cli' }) });
|
|
566
|
+
await settle();
|
|
567
|
+
assert.equal(dialog.opens.length, 1);
|
|
568
|
+
assert.deepEqual(plain(dialog.opens[0].opts.actions.map((a) => a.label)), ['Close']);
|
|
569
|
+
assert.match(dialog.opens[0].opts.body.innerHTML, /bongos upgrade/);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('a held version, or an update already running, is not asked about', async () => {
|
|
573
|
+
const held = fakeDialog();
|
|
574
|
+
boot({ dialog: held, held: { version: '1.19.871', message: 'rolled back' } });
|
|
575
|
+
const running = fakeDialog();
|
|
576
|
+
boot({ dialog: running, run: { to: '1.19.871', from: '1.19.861', at: Date.now(), via: 'local' } });
|
|
577
|
+
await settle();
|
|
578
|
+
assert.equal(held.opens.length, 0);
|
|
579
|
+
assert.equal(running.opens.length, 0);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('with the bars away the pill stays on screen and can be pressed', () => {
|
|
583
|
+
const css = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/style.css'), 'utf8');
|
|
584
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.topbar-side > :not\(\.fullscreen-toggle\):not\(\.upd-pill\) \{ display: none; \}/);
|
|
585
|
+
assert.match(css, /html\[data-fullscreen="on"\] \.upd-pill \{\s*pointer-events: auto;/);
|
|
586
|
+
});
|
package/tests/upkeep_home.mjs
CHANGED
|
@@ -102,7 +102,7 @@ function loadRoute({ versionStatus = 'building', maintenance = { id: '900', vers
|
|
|
102
102
|
return { call, created, ensured };
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const UPGRADE = { version_id: 'V2', title: 'Upgrade the Bongos core to 1.21.70', description: 'Platform upkeep.', discipline: '
|
|
105
|
+
const UPGRADE = { version_id: 'V2', title: 'Upgrade the Bongos core to 1.21.70', description: 'Platform upkeep.', discipline: 'artisan' };
|
|
106
106
|
|
|
107
107
|
test('an upgrade filed with kind infra and NO goal lands in the maintenance goal', async () => {
|
|
108
108
|
const { call, created, ensured } = loadRoute();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// tests/v3_c1_options_proof.mjs — the proof for BONGOS-V3 criterion C1
|
|
2
|
+
// `game-opens-not-navigates` (task 1005145, R05; docs/specs/bongos-v3-game-conversion.md;
|
|
3
|
+
// ADR 0401).
|
|
4
|
+
//
|
|
5
|
+
// The criterion's proof: "a builder signs in, never sees a sidebar, and changes a sound
|
|
6
|
+
// preference, a CLI token and a module toggle without leaving the options menu. A build
|
|
7
|
+
// that kept any old settings page fails this."
|
|
8
|
+
//
|
|
9
|
+
// The walk itself was done by hand in the hall preview on 2026-11-11 (recorded in task
|
|
10
|
+
// 1005145's ship notes). This file pins the parts of that walk a later change could
|
|
11
|
+
// quietly undo, each against the REAL code that does it:
|
|
12
|
+
// 1. The old settings pages are gone: the real server (serve-internal.js, mounted on a
|
|
13
|
+
// throwaway express app and hit over loopback) answers every old address with a 302
|
|
14
|
+
// into the menu, and only the menu's own frame (?embed=1) still reaches the old file.
|
|
15
|
+
// 2. No sidebar: the start screen's Start turns game mode on, and every hall page loads
|
|
16
|
+
// the game-mode file and none opts out of the full-screen switch.
|
|
17
|
+
// 3. The three settings are inside the one menu, each a control on the menu's own screen
|
|
18
|
+
// or in its frame: the CLI token's button, the module toggle, and the Sound entry.
|
|
19
|
+
// Sounds are switched off (task 1004554), so Sound shows its note, not a toggle;
|
|
20
|
+
// ADR 0401 accepted that for this proof until sounds come back.
|
|
21
|
+
//
|
|
22
|
+
// Run: node --test tests/v3_c1_options_proof.mjs
|
|
23
|
+
|
|
24
|
+
import { test } from 'node:test';
|
|
25
|
+
import assert from 'node:assert/strict';
|
|
26
|
+
import { createRequire } from 'node:module';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import http from 'node:http';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
34
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
35
|
+
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
36
|
+
const Menu = require(path.join(HALL, 'options-menu.js'));
|
|
37
|
+
|
|
38
|
+
// The six screens that were pages of their own, and the menu entry each now opens.
|
|
39
|
+
const OLD_TO_ENTRY = {
|
|
40
|
+
'my-agents': 'my-agents',
|
|
41
|
+
'project-settings': 'project-settings',
|
|
42
|
+
modules: 'modules',
|
|
43
|
+
government: 'permissions',
|
|
44
|
+
'market-watch-rules': 'market-watch-rules',
|
|
45
|
+
'market-watch-settings': 'market-watch',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// GET over loopback as the hall's own host (the tests/hall_asset_cache.mjs pattern).
|
|
49
|
+
function get(base, urlPath) {
|
|
50
|
+
const { port } = new URL(base);
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const req = http.request(
|
|
53
|
+
{ host: '127.0.0.1', port, path: urlPath, method: 'GET', headers: { host: 'builders.example.test' } },
|
|
54
|
+
(res) => { res.resume(); res.on('end', () => resolve({ status: res.statusCode, location: res.headers.location || '' })); },
|
|
55
|
+
);
|
|
56
|
+
req.on('error', reject);
|
|
57
|
+
req.end();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function withServer(fn) {
|
|
62
|
+
const express = require('express');
|
|
63
|
+
const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
|
|
64
|
+
const app = express();
|
|
65
|
+
mountInternalSurfaces(app);
|
|
66
|
+
const server = await new Promise((resolve) => { const s = app.listen(0, () => resolve(s)); });
|
|
67
|
+
try { return await fn(`http://127.0.0.1:${server.address().port}`); }
|
|
68
|
+
finally { await new Promise((resolve) => server.close(resolve)); }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test('PROOF 1 — no old settings page survives: every old address is sent into the menu by the real server', async () => {
|
|
72
|
+
await withServer(async (base) => {
|
|
73
|
+
for (const [old, entry] of Object.entries(OLD_TO_ENTRY)) {
|
|
74
|
+
for (const p of [`/${old}`, `/${old}.html`]) {
|
|
75
|
+
const r = await get(base, p);
|
|
76
|
+
assert.equal(r.status, 302, `${p} must redirect, got ${r.status}`);
|
|
77
|
+
assert.equal(r.location, `/settings?open=${entry}`, `${p} must open the "${entry}" entry of the menu`);
|
|
78
|
+
}
|
|
79
|
+
// the menu's own frame still reaches the screen — except Market Watch settings, which has no page left
|
|
80
|
+
const framed = await get(base, `/${old}?embed=1`);
|
|
81
|
+
if (old === 'market-watch-settings') {
|
|
82
|
+
assert.equal(framed.location, `/settings?open=${entry}`, 'Market Watch settings has no page to frame');
|
|
83
|
+
} else {
|
|
84
|
+
assert.ok(!framed.location.startsWith('/settings?open='), `${old}?embed=1 is the menu's frame and must not bounce back to the menu`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('PROOF 1 — every one of those entries is a line in the menu an Archon sees, opening that same screen in a frame', () => {
|
|
91
|
+
const visible = Menu.visibleMenu({ authed: true, rank: 'archon' }, { moduleOn: () => true })
|
|
92
|
+
.flatMap((g) => g.items);
|
|
93
|
+
for (const [old, entry] of Object.entries(OLD_TO_ENTRY)) {
|
|
94
|
+
const line = visible.find((i) => i.id === entry);
|
|
95
|
+
assert.ok(line, `the menu lists "${entry}" (was /${old})`);
|
|
96
|
+
if (old !== 'market-watch-settings') assert.equal(line.embed, `/${old}`, `"${entry}" frames /${old}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('PROOF 2 — no sidebar: Start turns game mode on, and every hall page is in it with no way to opt out', () => {
|
|
101
|
+
const game = read('shell-game.js');
|
|
102
|
+
assert.match(game, /set\(true\)/, 'the start screen\'s Start turns game mode on');
|
|
103
|
+
const pages = fs.readdirSync(HALL).filter((f) => f.endsWith('.html'));
|
|
104
|
+
const shelled = pages.filter((f) => read(f).includes('id="app-sidebar"') || read(f).includes('id="app-topbar"'));
|
|
105
|
+
assert.ok(shelled.length >= 25, `expected the hall's shelled pages, saw ${shelled.length}`);
|
|
106
|
+
for (const f of shelled) {
|
|
107
|
+
const html = read(f);
|
|
108
|
+
assert.ok(html.includes('shell-game.js'), `${f} must load shell-game.js, or it never enters game mode and keeps its sidebar`);
|
|
109
|
+
assert.ok(!/data-fullscreen-switch="off"/.test(html), `${f} opts out of the full-screen switch, so it would keep its sidebar in game mode`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('PROOF 3 — the CLI token: its button is on the menu\'s own Access and keys screen', () => {
|
|
114
|
+
const menu = Menu.visibleMenu({ authed: true, rank: 'xenos' });
|
|
115
|
+
const [hit] = Menu.search('cli token', menu);
|
|
116
|
+
assert.deepEqual([hit.id, hit.anchor], ['access', 'cli'], 'searching "cli token" lands on Access and keys, CLI access');
|
|
117
|
+
const access = Menu.entries().find((e) => e.id === 'access');
|
|
118
|
+
assert.equal(access.native, true, 'Access and keys is one of the menu\'s own screens, not a frame of another page');
|
|
119
|
+
const html = read('settings.html');
|
|
120
|
+
const section = html.slice(html.indexOf('id="cli-scroll"'), html.indexOf('</section>', html.indexOf('id="cli-scroll"')));
|
|
121
|
+
assert.match(section, /data-group="access"/, 'the CLI section belongs to the Access and keys group');
|
|
122
|
+
assert.match(section, /id="cli-reissue-btn"/, 'the re-issue button is in it');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('PROOF 3 — the module toggle: the Modules entry frames /modules, whose catalogue draws an enable/disable toggle', () => {
|
|
126
|
+
const thetes = Menu.visibleMenu({ authed: true, rank: 'thetes' });
|
|
127
|
+
assert.equal(Menu.search('module toggle', thetes)[0].id, 'modules');
|
|
128
|
+
assert.equal(Menu.entries().find((e) => e.id === 'modules').embed, '/modules');
|
|
129
|
+
const js = read('modules.js');
|
|
130
|
+
assert.match(js, /class="ov-toggle mod-toggle" aria-pressed=/, 'the catalogue draws a pressable toggle per module');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('PROOF 3 — sound: the Sound entry is in the menu, and while sounds are off it says so (ADR 0401)', () => {
|
|
134
|
+
const menu = Menu.visibleMenu({ authed: true, rank: 'xenos' });
|
|
135
|
+
assert.equal(Menu.search('sound', menu)[0].id, 'sound');
|
|
136
|
+
assert.equal(Menu.entries().find((e) => e.id === 'sound').native, true);
|
|
137
|
+
const html = read('settings.html');
|
|
138
|
+
for (const id of ['sounds-off-scroll', 'sounds-scroll']) {
|
|
139
|
+
const at = html.indexOf(`id="${id}"`);
|
|
140
|
+
assert.ok(at > 0, `#${id} is on the Settings page`);
|
|
141
|
+
assert.match(html.slice(at, at + 200), /data-group="sound"/, `#${id} is in the Sound group`);
|
|
142
|
+
}
|
|
143
|
+
// The toggles come back in the same place: when task 1004554 is undone, #sounds-scroll is
|
|
144
|
+
// what shows, and it is already inside the menu's Sound entry.
|
|
145
|
+
});
|
package/tests/visual_review.mjs
CHANGED
|
@@ -64,7 +64,7 @@ await test('the CLI writes the page and prints its path; a missing image is an e
|
|
|
64
64
|
|
|
65
65
|
// ---- the claim-time nudge ----------------------------------------------------
|
|
66
66
|
|
|
67
|
-
const task = (over = {}) => ({ id: '1', discipline: '
|
|
67
|
+
const task = (over = {}) => ({ id: '1', discipline: 'artisan', module_key: null, source: 'manual', touches: [], description: '', ...over });
|
|
68
68
|
|
|
69
69
|
await test('a task that names a page file in its description is told to use /design and get an OK', () => {
|
|
70
70
|
const t = task({ description: 'Where: modules/hall-ui/public/goal-forest-lib.js — NODE_W = 168. Drawing is modules/hall-ui/public/goal-forest-render.js.' });
|
|
@@ -102,12 +102,12 @@ await test('claim.js prints the nudge after the craft role line, and cannot fail
|
|
|
102
102
|
// ---- the rule, where every builder's session reads it ------------------------
|
|
103
103
|
|
|
104
104
|
await test('all three playbooks carry the rule and the way to show a CLI user the shots', () => {
|
|
105
|
-
for (const rel of ['docs/packs/
|
|
105
|
+
for (const rel of ['docs/packs/artisan.md', 'docs/packs/artist.md', '.claude/skills/design/SKILL.md']) {
|
|
106
106
|
const doc = read(rel);
|
|
107
107
|
assert.match(doc, /review-sheet\.js/, `${rel} names the helper`);
|
|
108
108
|
assert.match(doc, /2026-09-23/, `${rel} dates the owner decision`);
|
|
109
109
|
}
|
|
110
|
-
assert.doesNotMatch(read('docs/packs/
|
|
110
|
+
assert.doesNotMatch(read('docs/packs/artisan.md'), /rather than asking a human to look/, 'the old contrary instruction is gone');
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
summary();
|
|
@@ -167,7 +167,7 @@ async function boot(opts = {}) {
|
|
|
167
167
|
// the same reason categoryChip is: an undefined helper throws mid-render.
|
|
168
168
|
roleAxisLabel: () => 'Role',
|
|
169
169
|
roleLabel: (d) => (!d ? '' : d === 'unclassified' ? 'No role yet' : d === 'ui' ? 'UI' : d.charAt(0).toUpperCase() + d.slice(1)),
|
|
170
|
-
roleOptions: () => [{ value: '', label: '— any —' }, { value: '
|
|
170
|
+
roleOptions: () => [{ value: '', label: '— any —' }, { value: 'artisan', label: 'Artisan' }, { value: 'artist', label: 'Artist' }],
|
|
171
171
|
roleIsUnset: (d) => !d || d === 'unclassified',
|
|
172
172
|
ROLE_UNSET: 'unclassified',
|
|
173
173
|
escapeHtml,
|
|
@@ -196,6 +196,9 @@ async function boot(opts = {}) {
|
|
|
196
196
|
};
|
|
197
197
|
sandbox.globalThis = sandbox;
|
|
198
198
|
vm.createContext(sandbox);
|
|
199
|
+
// The GitHub-repository half of the invite box is its own file, loaded before watch.js
|
|
200
|
+
// exactly as watch.html does.
|
|
201
|
+
vm.runInContext(fs.readFileSync(path.join(HALL, 'watch-repo-invite.js'), 'utf8'), sandbox, { filename: 'watch-repo-invite.js' });
|
|
199
202
|
vm.runInContext(SRC, sandbox, { filename: 'watch.js' });
|
|
200
203
|
await settle();
|
|
201
204
|
|
|
@@ -421,13 +424,13 @@ const PUBLIC_PROFILE = {
|
|
|
421
424
|
},
|
|
422
425
|
cross_project: {
|
|
423
426
|
totals: { projects: 3, credits: 4200, works_shipped: 41, karma: 7 },
|
|
424
|
-
disciplines: ['
|
|
427
|
+
disciplines: ['artisan', 'artist'], last_active: '2026-08-30T00:00:00Z',
|
|
425
428
|
projects: [], more_projects: 0,
|
|
426
429
|
},
|
|
427
430
|
};
|
|
428
431
|
const SLIVER_PROFILE = {
|
|
429
432
|
view: 'sliver',
|
|
430
|
-
sliver: { handle: 'quiet', display_name: 'Quiet Person', avatar_url: null, disciplines: ['
|
|
433
|
+
sliver: { handle: 'quiet', display_name: 'Quiet Person', avatar_url: null, disciplines: ['artisan'], active_recently: true },
|
|
431
434
|
};
|
|
432
435
|
const withProfile = (profile, over = {}) => [{ ...WAITING[0], profile, ...over }];
|
|
433
436
|
|
|
@@ -439,7 +442,7 @@ await test('a PUBLIC applicant renders their record beside the ask', async () =>
|
|
|
439
442
|
assert.match(text, /I draw maps\./, 'the bio is part of what the public page already shows');
|
|
440
443
|
assert.match(text, /3 projects/);
|
|
441
444
|
assert.match(text, /41 shipped/);
|
|
442
|
-
assert.match(text, /
|
|
445
|
+
assert.match(text, /artisan/);
|
|
443
446
|
});
|
|
444
447
|
|
|
445
448
|
await test('a PRIVATE applicant renders the sliver AND says why it is thin', async () => {
|
|
@@ -449,7 +452,7 @@ await test('a PRIVATE applicant renders the sliver AND says why it is thin', asy
|
|
|
449
452
|
const text = block_.textContent;
|
|
450
453
|
assert.match(text, /Private account/,
|
|
451
454
|
'a reviewer must not read the thinness as a poor record — it is a boundary, and applying is what opened even this much');
|
|
452
|
-
assert.match(text, /
|
|
455
|
+
assert.match(text, /artisan/);
|
|
453
456
|
assert.match(text, /Active recently/);
|
|
454
457
|
});
|
|
455
458
|
|
|
@@ -119,7 +119,7 @@ async function boot({ answers = {}, fallback = ok({}) } = {}) {
|
|
|
119
119
|
// the same reason categoryChip is: an undefined helper throws mid-render.
|
|
120
120
|
roleAxisLabel: () => 'Role',
|
|
121
121
|
roleLabel: (d) => (!d ? '' : d === 'unclassified' ? 'No role yet' : d === 'ui' ? 'UI' : d.charAt(0).toUpperCase() + d.slice(1)),
|
|
122
|
-
roleOptions: () => [{ value: '', label: '— any —' }, { value: '
|
|
122
|
+
roleOptions: () => [{ value: '', label: '— any —' }, { value: 'artisan', label: 'Artisan' }, { value: 'artist', label: 'Artist' }],
|
|
123
123
|
roleIsUnset: (d) => !d || d === 'unclassified',
|
|
124
124
|
ROLE_UNSET: 'unclassified',
|
|
125
125
|
escapeHtml,
|
|
@@ -147,6 +147,8 @@ async function boot({ answers = {}, fallback = ok({}) } = {}) {
|
|
|
147
147
|
};
|
|
148
148
|
sandbox.globalThis = sandbox;
|
|
149
149
|
vm.createContext(sandbox);
|
|
150
|
+
// watch-repo-invite.js loads before watch.js, as watch.html has it.
|
|
151
|
+
vm.runInContext(fs.readFileSync(path.join(HALL, 'watch-repo-invite.js'), 'utf8'), sandbox, { filename: 'watch-repo-invite.js' });
|
|
150
152
|
vm.runInContext(SRC, sandbox, { filename: 'watch.js' });
|
|
151
153
|
await settle();
|
|
152
154
|
|
package/tests/welcome_boot.mjs
CHANGED
|
@@ -98,23 +98,25 @@ test('the credit links to cloudbongos.com (task 1004953)', () => {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
test('the owner\'s role mapping: destination and craft mode', () => {
|
|
101
|
+
// task 1005141: the game's three characters — Owner became Patron, and the ideator's
|
|
102
|
+
// door folded into the artist's (one Artist door, to the studio).
|
|
103
|
+
assert.equal(W.ROLES.length, 3, 'three doors, no separate ideator door');
|
|
101
104
|
const map = Object.fromEntries(W.ROLES.map((r) => [r.id, [r.label, r.room, r.href, r.mode]]));
|
|
102
105
|
assert.deepEqual(map, {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
engineer: ['Engineer', 'Home', null, 'building'],
|
|
106
|
+
patron: ['Patron', 'City', '/city', 'patron'],
|
|
107
|
+
artist: ['Artist', 'Studio', '/studio', 'artist'],
|
|
108
|
+
artisan: ['Artisan', 'Home', null, 'artisan'],
|
|
107
109
|
});
|
|
108
110
|
// Every mode is one craft-mode.js knows, so OTBCraftMode.choose can never be handed a stranger.
|
|
109
111
|
const craft = fs.readFileSync(path.join(HALL, 'craft-mode.js'), 'utf8');
|
|
110
112
|
for (const r of W.ROLES) assert.match(craft, new RegExp(`id: '${r.mode}'`), r.mode);
|
|
111
113
|
});
|
|
112
114
|
|
|
113
|
-
test('the
|
|
115
|
+
test('the Patron role shows only where the Patron mode is offered', () => {
|
|
114
116
|
const ids = (c) => W.rolesFor(c).map((r) => r.id);
|
|
115
|
-
assert.deepEqual(ids({ offered: (m) => m !== '
|
|
116
|
-
assert.deepEqual(ids({ offered: () => true }), ['
|
|
117
|
-
assert.deepEqual(ids(null), ['
|
|
117
|
+
assert.deepEqual(ids({ offered: (m) => m !== 'patron' }), ['artist', 'artisan'], 'no seat: no Patron');
|
|
118
|
+
assert.deepEqual(ids({ offered: () => true }), ['patron', 'artist', 'artisan']);
|
|
119
|
+
assert.deepEqual(ids(null), ['patron', 'artist', 'artisan'], 'no craft mode at all: the three, the pages gate themselves');
|
|
118
120
|
});
|
|
119
121
|
|
|
120
122
|
test('Home loads the welcome before the shell, and the shell asks it before the craft picker', () => {
|
|
@@ -126,6 +128,7 @@ test('Home loads the welcome before the shell, and the shell asks it before the
|
|
|
126
128
|
assert.ok(at('welcome-boot.css') > 0, 'and its sheet');
|
|
127
129
|
const shell = fs.readFileSync(path.join(HALL, 'shell.js'), 'utf8');
|
|
128
130
|
// task 1005076: and neither is asked while the setup screen holds the builder (tests/setup_flow.mjs).
|
|
129
|
-
|
|
131
|
+
// task 1005147: character creation comes first of all — a builder with no character picks one.
|
|
132
|
+
assert.match(shell, /if \(!holdForSetup\(state && state\.builder\) && !\(CRAFT && CRAFT\.characterGate && CRAFT\.characterGate\(access, state\)\) && !\(window\.OTBWelcome && window\.OTBWelcome\.onAccess\(access, state && state\.builder\)\) && CRAFT\) CRAFT\.onAccess\(/,
|
|
130
133
|
'the picker is asked only when setup is done and the welcome did not show');
|
|
131
134
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// tests/welcome_boot_browser.mjs — the welcome in a real browser (tasks 1004785, 1004818).
|
|
2
2
|
//
|
|
3
|
-
// WHY. "Once a day", "Skip", "the craft picker does not also open", "
|
|
3
|
+
// WHY. "Once a day", "Skip", "the craft picker does not also open", "Patron only with a seat", the day
|
|
4
4
|
// and night paintings and the slow way out are behaviour across the real shell.js, craft-mode.js and
|
|
5
5
|
// welcome-boot.js together — the shell is what decides which of the two greets a builder — so this
|
|
6
6
|
// serves the REAL Home markup, the REAL hall scripts and the REAL paintings from a routed origin
|
|
@@ -44,7 +44,7 @@ async function hall({ art = true, reducedMotion = 'no-preference', time = null }
|
|
|
44
44
|
const p = new URL(route.request().url()).pathname;
|
|
45
45
|
if (p === '/' || p === '/index.html') return route.fulfill({ contentType: 'text/html', body: PAGE });
|
|
46
46
|
if (p === '/api/bongos/auth/web/start') return route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>github</title><p id="auth">sign in</p>' });
|
|
47
|
-
if (p === '/
|
|
47
|
+
if (p === '/studio' || p === '/city') return route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>room</title><p id="room">' + p + '</p>' });
|
|
48
48
|
const ext = path.extname(p);
|
|
49
49
|
if (ext === '.webp' && !art) return route.fulfill({ status: 404, body: '' });
|
|
50
50
|
const f = path.join(HALL, p.replace(/^\/builders\//, ''));
|
|
@@ -70,9 +70,9 @@ const signIn = (page, positions) => page.evaluate((pos) => {
|
|
|
70
70
|
window.OTBShell.applyAccess(me);
|
|
71
71
|
window.OTBShell.applyAccess(me);
|
|
72
72
|
}, positions || null);
|
|
73
|
-
// The seats are settled once the top bar's
|
|
73
|
+
// The seats are settled once the top bar's Patron option (Governing until task 1005141) says so (hidden, or named by the seat).
|
|
74
74
|
const seatsSettled = (page, want) => page.waitForFunction((w) => {
|
|
75
|
-
const opt = document.querySelector('#craft-switch [data-craft-opt="
|
|
75
|
+
const opt = document.querySelector('#craft-switch [data-craft-opt="patron"]');
|
|
76
76
|
return !!opt && (w === null ? opt.hidden : !opt.hidden && opt.textContent.indexOf(w) !== -1);
|
|
77
77
|
}, want);
|
|
78
78
|
const state = (page) => page.evaluate(() => ({
|
|
@@ -190,7 +190,7 @@ test('Escape leaves the welcome at any point', { skip }, async () => {
|
|
|
190
190
|
await ctx.close();
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
test('roles: no seat hides
|
|
193
|
+
test('roles: no seat hides Patron; Artist sets the mode and goes to the Studio', { skip }, async () => {
|
|
194
194
|
const ctx = await hall();
|
|
195
195
|
const { page } = await open(ctx);
|
|
196
196
|
await signIn(page, { held: [] });
|
|
@@ -200,16 +200,16 @@ test('roles: no seat hides Owner; Ideator sets the mode and goes to Your thinkin
|
|
|
200
200
|
await page.click('[data-wb-roles]');
|
|
201
201
|
await page.waitForSelector('#wb-roles:not([hidden]) [data-wb-role]');
|
|
202
202
|
const roles = await page.evaluate(() => [...document.querySelectorAll('[data-wb-role]')].map((b) => b.getAttribute('data-wb-role')));
|
|
203
|
-
assert.deepEqual(roles, ['
|
|
203
|
+
assert.deepEqual(roles, ['artist', 'artisan'], 'the ideator door folded into Artist (task 1005141)');
|
|
204
204
|
const stored = await Promise.all([
|
|
205
|
-
page.waitForURL(ORIGIN + '/
|
|
206
|
-
page.click('[data-wb-role="
|
|
205
|
+
page.waitForURL(ORIGIN + '/studio'),
|
|
206
|
+
page.click('[data-wb-role="artist"]'),
|
|
207
207
|
]).then(() => page.evaluate(() => sessionStorage.getItem('hall-craft')));
|
|
208
|
-
assert.equal(stored, '
|
|
208
|
+
assert.equal(stored, 'artist', 'the hall wears Artist on arrival');
|
|
209
209
|
await ctx.close();
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
-
test('roles: a seat holder sees
|
|
212
|
+
test('roles: a seat holder sees Patron; Artisan stays on Home in Artisan', { skip }, async () => {
|
|
213
213
|
const ctx = await hall();
|
|
214
214
|
const { page } = await open(ctx);
|
|
215
215
|
await signIn(page, { held: [{ kind: 'lead', seat: 'planning', title: 'Planning Lead' }] });
|
|
@@ -219,10 +219,10 @@ test('roles: a seat holder sees Owner; Engineer stays on Home in Building', { sk
|
|
|
219
219
|
await page.click('[data-wb-roles]');
|
|
220
220
|
await page.waitForSelector('#wb-roles:not([hidden]) [data-wb-role]');
|
|
221
221
|
const roles = await page.evaluate(() => [...document.querySelectorAll('[data-wb-role]')].map((b) => b.getAttribute('data-wb-role')));
|
|
222
|
-
assert.deepEqual(roles, ['
|
|
223
|
-
await page.click('[data-wb-role="
|
|
222
|
+
assert.deepEqual(roles, ['patron', 'artist', 'artisan']);
|
|
223
|
+
await page.click('[data-wb-role="artisan"]');
|
|
224
224
|
await page.waitForSelector('#welcome-boot', { state: 'detached' });
|
|
225
|
-
assert.deepEqual(await state(page), { welcome: false, picker: false, craft: '
|
|
225
|
+
assert.deepEqual(await state(page), { welcome: false, picker: false, craft: 'artisan', asked: '1' });
|
|
226
226
|
assert.equal(new URL(page.url()).pathname, '/');
|
|
227
227
|
await ctx.close();
|
|
228
228
|
});
|
|
@@ -27,7 +27,7 @@ import os from 'node:os';
|
|
|
27
27
|
import path from 'node:path';
|
|
28
28
|
|
|
29
29
|
const require = createRequire(import.meta.url);
|
|
30
|
-
const { worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition, unlinkNodeModulesLink } = require('../scripts/bongos/worktree.js');
|
|
30
|
+
const { worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition, unlinkNodeModulesLink, nextStepLines } = require('../scripts/bongos/worktree.js');
|
|
31
31
|
|
|
32
32
|
const HOUR = 60 * 60 * 1000;
|
|
33
33
|
|
|
@@ -285,6 +285,21 @@ test('in-use outranks every other rule', () => {
|
|
|
285
285
|
'in use — this session is inside it');
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
+
// task 1005185 - feedback bug scripts: on a project that installs the core from npm the
|
|
289
|
+
// worktree has no scripts/ of its own (it is a git-ignored link into node_modules), so the
|
|
290
|
+
// "Next:" hint must name `npx bongos claim`, not a node path that points at nothing.
|
|
291
|
+
test('installed core: the Next hint names npx bongos claim, never a node scripts path', () => {
|
|
292
|
+
const out = nextStepLines('/p/.claude/worktrees/task-9', { installed: true }).join('\n');
|
|
293
|
+
eq(/npx bongos claim <task-id>/.test(out), true, 'names npx bongos claim');
|
|
294
|
+
eq(/node scripts\//.test(out), false, 'no node scripts/ path');
|
|
295
|
+
eq(out.includes('cd "/p/.claude/worktrees/task-9"'), true, 'still says where to cd');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('core checkout: the Next hint keeps the node scripts/bongos/claim.js path', () => {
|
|
299
|
+
const out = nextStepLines('/p/.claude/worktrees/task-9', { installed: false }).join('\n');
|
|
300
|
+
eq(/node scripts\/bongos\/claim\.js <task-id>/.test(out), true);
|
|
301
|
+
});
|
|
302
|
+
|
|
288
303
|
console.log('');
|
|
289
304
|
console.log(`worktree_helper: ${passed} passed, ${failed} failed`);
|
|
290
305
|
process.exit(failed > 0 ? 1 : 0);
|