@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/help_requests.mjs
CHANGED
|
@@ -43,11 +43,15 @@ test('an unknown craft is refused', () => {
|
|
|
43
43
|
assert.ok(reasons({ needs_craft: 'wizard', what_is_stuck: 'help' }).includes('unknown_craft'));
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test('the crafts are
|
|
46
|
+
test('the crafts are the two working characters, plus the ui craft the ui-design module contributes', () => {
|
|
47
47
|
// 'ui' joined the vocabulary with task 1003487, which made the discipline
|
|
48
|
-
// selectable everywhere a builder chooses work.
|
|
49
|
-
//
|
|
50
|
-
|
|
48
|
+
// selectable everywhere a builder chooses work. Since task 1005141 (BONGOS-V3)
|
|
49
|
+
// the help craft IS the discipline word: artisan (was 'builder'/'engineer') and
|
|
50
|
+
// artist (which absorbed 'ideator'). The DB CHECK is widened in lockstep by
|
|
51
|
+
// modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql.
|
|
52
|
+
assert.deepEqual(help.helpCrafts().sort(), ['artisan', 'artist', 'ui']);
|
|
53
|
+
assert.ok(!help.helpCrafts().includes('builder'), "the old 'builder' help spelling is not offered");
|
|
54
|
+
assert.ok(!help.helpCrafts().includes('ideator'), 'ideator folded into artist');
|
|
51
55
|
assert.equal(help.disciplineForCraft('ui'), 'ui', 'ui is spelled the same on both sides of the seam');
|
|
52
56
|
assert.equal(help.craftForDiscipline('ui'), 'ui');
|
|
53
57
|
});
|
|
@@ -57,23 +61,46 @@ test('every craft the app offers is permitted by the help_requests CHECK', () =>
|
|
|
57
61
|
// a compiled-in CHECK, and widening one without the other is invisible until an
|
|
58
62
|
// INSERT fails in production as a 500 on a feature that reads as supported.
|
|
59
63
|
const sql = fs.readFileSync(
|
|
60
|
-
path.join(ROOT, 'modules', 'lifecycle', 'migrations', '
|
|
61
|
-
|
|
64
|
+
path.join(ROOT, 'modules', 'lifecycle', 'migrations', 'lifecycle_021_crafts_artisan_artist.sql'), 'utf8');
|
|
65
|
+
// The CHECK's list, not the UPDATE's WHERE that renames the old rows above it.
|
|
66
|
+
const m = sql.match(/CHECK \(needs_craft IS NULL OR needs_craft IN \(([^)]*)\)/);
|
|
62
67
|
assert.ok(m, 'the migration states the permitted craft list');
|
|
63
68
|
const allowed = m[1].split(',').map((x) => x.trim().replace(/^'|'$/g, ''));
|
|
64
69
|
for (const c of help.helpCrafts()) {
|
|
65
70
|
assert.ok(allowed.includes(c), `craft '${c}' is offered by helpCrafts() but the CHECK forbids it`);
|
|
66
71
|
}
|
|
72
|
+
// A rolled-back release still writes the old spellings, so the CHECK keeps them.
|
|
73
|
+
for (const old of ['builder', 'ideator']) {
|
|
74
|
+
assert.ok(allowed.includes(old), `the CHECK still admits the pre-1005141 spelling '${old}'`);
|
|
75
|
+
}
|
|
67
76
|
});
|
|
68
77
|
|
|
69
|
-
test('THE VOCABULARY SEAM:
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
assert.equal(help.disciplineForCraft('
|
|
78
|
+
test('THE VOCABULARY SEAM is closed: the help craft and the discipline are one word', () => {
|
|
79
|
+
// Before task 1005141 the engineering craft was 'builder' here and 'engineer' in
|
|
80
|
+
// builders.preferred_disciplines. Now both say 'artisan'. If C02 re-derives the
|
|
81
|
+
// mapping instead of consuming disciplineForCraft, a builder silently matches nothing.
|
|
82
|
+
assert.equal(help.disciplineForCraft('artisan'), 'artisan');
|
|
74
83
|
assert.equal(help.disciplineForCraft('artist'), 'artist');
|
|
75
|
-
assert.equal(help.disciplineForCraft('ideator'), 'ideator');
|
|
76
84
|
assert.equal(help.disciplineForCraft('wizard'), null);
|
|
85
|
+
// Old spellings are read as today's craft, on both sides of the seam.
|
|
86
|
+
assert.equal(help.disciplineForCraft('builder'), 'artisan', "the old help spelling 'builder' still resolves");
|
|
87
|
+
assert.equal(help.disciplineForCraft('ideator'), 'artist', 'ideator folded into artist');
|
|
88
|
+
assert.equal(help.craftForDiscipline('engineer'), 'artisan', 'a stored engineer discipline is the artisan craft');
|
|
89
|
+
assert.equal(help.craftForDiscipline('ideator'), 'artist', 'a stored ideator discipline is the artist craft');
|
|
90
|
+
assert.equal(help.craftForDiscipline('artisan'), 'artisan');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("an OLD craft spelling validates and is stored as today's", async () => {
|
|
94
|
+
// An old CLI or a bookmarked form still posts needs_craft 'builder'/'ideator'.
|
|
95
|
+
assert.deepEqual(reasons({ needs_craft: 'builder', what_is_stuck: 'help' }), []);
|
|
96
|
+
assert.deepEqual(reasons({ needs_craft: 'ideator', what_is_stuck: 'help' }), []);
|
|
97
|
+
assert.deepEqual(reasons({ needs_craft: 'engineer', what_is_stuck: 'help' }), []);
|
|
98
|
+
const calls = [];
|
|
99
|
+
const pool = { query: async (sql, params) => { calls.push(params); return { rows: [{ id: 1 }] }; } };
|
|
100
|
+
await help.createHelpRequest({ requestedBy: 1, needsCraft: 'builder', whatIsStuck: 'x' }, { pool });
|
|
101
|
+
await help.createHelpRequest({ requestedBy: 1, needsCraft: 'ideator', whatIsStuck: 'x' }, { pool });
|
|
102
|
+
assert.equal(calls[0][1], 'artisan', "'builder' is stored as 'artisan'");
|
|
103
|
+
assert.equal(calls[1][1], 'artist', "'ideator' is stored as 'artist'");
|
|
77
104
|
});
|
|
78
105
|
|
|
79
106
|
// ---- what is stuck --------------------------------------------------------
|
|
@@ -98,7 +125,7 @@ test('a request may name a task OR a goal, never both', () => {
|
|
|
98
125
|
});
|
|
99
126
|
|
|
100
127
|
test('context is optional', () => {
|
|
101
|
-
assert.deepEqual(reasons({ needs_craft: '
|
|
128
|
+
assert.deepEqual(reasons({ needs_craft: 'artist', what_is_stuck: 'a shape question' }), []);
|
|
102
129
|
});
|
|
103
130
|
|
|
104
131
|
// ---- the schema keeps the same promises -----------------------------------
|
|
@@ -106,7 +133,10 @@ test('the CHECK constraints mirror the validator, so neither is the only guard',
|
|
|
106
133
|
// The validator exists so a refusal names the field; the constraints are the
|
|
107
134
|
// authority. Both must exist or one of the two promises is unkept.
|
|
108
135
|
const sql = read('modules', 'lifecycle', 'migrations', 'lifecycle_004_help_requests.sql');
|
|
136
|
+
// lifecycle_004 is history (its vocabulary was ADR 0174's); the live CHECK is lifecycle_021's.
|
|
109
137
|
assert.match(sql, /needs_craft IN \('artist', 'builder', 'ideator'\)/);
|
|
138
|
+
assert.match(read('modules', 'lifecycle', 'migrations', 'lifecycle_021_crafts_artisan_artist.sql'),
|
|
139
|
+
/needs_craft IN \('artisan', 'artist'/, 'the renamed CHECK names the new crafts');
|
|
110
140
|
assert.match(sql, /\(needs_craft IS NOT NULL\) <> \(needs_builder_id IS NOT NULL\)/, 'exactly-one-addressee');
|
|
111
141
|
assert.match(sql, /NOT \(task_id IS NOT NULL AND goal_id IS NOT NULL\)/, 'at most one context');
|
|
112
142
|
assert.match(sql, /length\(btrim\(what_is_stuck\)\) > 0/, 'non-empty description');
|
|
@@ -350,9 +380,11 @@ test('the /me read is IN the fan-out, not a serialized await before it', () => {
|
|
|
350
380
|
test('the count is ONE query, and its craft map is generated from the JS map', () => {
|
|
351
381
|
// Two sequential queries on a hot path is a cost every builder pays on every
|
|
352
382
|
// open. The SQL CASE is generated from DISCIPLINE_TO_CRAFT so the two spellings
|
|
353
|
-
// of
|
|
383
|
+
// of a craft cannot drift apart — and a stored old discipline still maps.
|
|
354
384
|
const sql = help.craftMapSql('d');
|
|
355
|
-
assert.match(sql, /WHEN '
|
|
385
|
+
assert.match(sql, /WHEN 'artisan' THEN 'artisan'/);
|
|
386
|
+
assert.match(sql, /WHEN 'engineer' THEN 'artisan'/, 'a legacy engineer discipline still counts');
|
|
387
|
+
assert.match(sql, /WHEN 'ideator' THEN 'artist'/, 'a legacy ideator discipline still counts');
|
|
356
388
|
for (const craft of help.helpCrafts()) {
|
|
357
389
|
const disc = help.disciplineForCraft(craft);
|
|
358
390
|
assert.ok(sql.includes(`WHEN '${disc}' THEN '${craft}'`), `${disc}->${craft} is in the SQL map`);
|
package/tests/helpers.mjs
CHANGED
|
@@ -540,11 +540,11 @@ const PROFILE_CRAFTS_JS = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/publi
|
|
|
540
540
|
//
|
|
541
541
|
// `roles` is the profile's roles.shown, main first (task 1004435): one Activity
|
|
542
542
|
// tab each. The default leads with the ideator, so the trail is the first tab
|
|
543
|
-
// and a suite that reads it needs no switch; switchCraft('
|
|
543
|
+
// and a suite that reads it needs no switch; switchCraft('artisan') opens the
|
|
544
544
|
// works and goals, switchCraft('artist') the approved pages.
|
|
545
545
|
// `search` is the page's ?query (task 1005050: ?id= decides whose profile it is), and
|
|
546
546
|
// `withOwn` also loads profile-own.js, the "Just you" screen drawn on your own profile.
|
|
547
|
-
export function bootProfilePage({ signedIn = true, trail = [], roles = ['
|
|
547
|
+
export function bootProfilePage({ signedIn = true, trail = [], roles = ['artist', 'artisan'], artistPages = [], search = '', withOwn = false } = {}) {
|
|
548
548
|
const els = new Map();
|
|
549
549
|
const makeEl = () => ({
|
|
550
550
|
dataset: {}, style: {}, textContent: '', hidden: false, _html: '',
|
|
@@ -579,7 +579,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
579
579
|
}
|
|
580
580
|
if (url.includes('/activity?')) {
|
|
581
581
|
const isGoals = url.includes('section=goals');
|
|
582
|
-
return { status: 200, ok: true, data: { rows: isGoals ? [{ id: 7, title: 'A goal', disciplines: ['artist'], task_shipped: 1, task_total: 2 }] : [{ id: 42, title: 'A work', discipline: '
|
|
582
|
+
return { status: 200, ok: true, data: { rows: isGoals ? [{ id: 7, title: 'A goal', disciplines: ['artist'], task_shipped: 1, task_total: 2 }] : [{ id: 42, title: 'A work', discipline: 'artisan', shipped_at: '2026-08-01T00:00:00Z' }], total: 1 } };
|
|
583
583
|
}
|
|
584
584
|
if (url.includes('/copy-desk/artists/')) {
|
|
585
585
|
return { status: 200, ok: true, data: { rows: artistPages, total: artistPages.length } };
|
|
@@ -632,7 +632,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
632
632
|
// the same reason categoryChip is: an undefined helper throws mid-render.
|
|
633
633
|
roleAxisLabel: () => 'Role',
|
|
634
634
|
roleLabel: (d) => (!d ? '' : d === 'unclassified' ? 'No role yet' : d === 'ui' ? 'UI' : d.charAt(0).toUpperCase() + d.slice(1)),
|
|
635
|
-
roleOptions: () => [{ value: '', label: '— any —' }, { value: '
|
|
635
|
+
roleOptions: () => [{ value: '', label: '— any —' }, { value: 'artisan', label: 'Artisan' }, { value: 'artist', label: 'Artist' }],
|
|
636
636
|
roleIsUnset: (d) => !d || d === 'unclassified',
|
|
637
637
|
ROLE_UNSET: 'unclassified',
|
|
638
638
|
escapeHtml: realOTB().escapeHtml, // the real one (task 1004876)
|
package/tests/home_city.mjs
CHANGED
|
@@ -419,16 +419,18 @@ await test('on the builder\'s Home the core and the museum are faded; a craft\'s
|
|
|
419
419
|
assert.match(css, /\.hc-quiet:hover,\s*\.hc-svg \.hc-quiet:focus-visible\s*\{[^}]*filter:\s*none/, 'back to colour under the pointer or the keyboard');
|
|
420
420
|
});
|
|
421
421
|
|
|
422
|
-
await test('a building opens its goal; the museum and the band of stars are doors to the artist\'s
|
|
422
|
+
await test('a building opens its goal; the museum and the band of stars are both doors to the artist\'s Home (the ideator folded in, task 1005141)', () => {
|
|
423
423
|
const m = Draw.cityModel(FIXTURE);
|
|
424
424
|
const ls = linksOf(all(Draw.citySvg(m, m.W - 1, { mode: 'dark', gov: Gov }).nodes));
|
|
425
425
|
const towers = ls.filter((n) => n.attrs.class === 'hc-tower');
|
|
426
426
|
assert.equal(towers.length, m.areas.length);
|
|
427
427
|
towers.forEach((n) => assert.match(n.attrs.href, /^\/goals#\/goal\/\d+$/));
|
|
428
428
|
assert.ok(m.areas.every((a) => towers.some((n) => n.attrs.href === `/goals#/goal/${a.id}`)), 'each area links to its own goal');
|
|
429
|
-
assert.equal(ls.find((n) => /hc-museum/.test(n.attrs.class)).attrs['data-craft-door'], '
|
|
429
|
+
assert.equal(ls.find((n) => /hc-museum/.test(n.attrs.class)).attrs['data-craft-door'], 'artist');
|
|
430
430
|
const sky = ls.find((n) => /hc-sky/.test(n.attrs.class));
|
|
431
|
-
assert.equal(sky.attrs['data-craft-door'], '
|
|
431
|
+
assert.equal(sky.attrs['data-craft-door'], undefined, 'the stars switch no mode');
|
|
432
|
+
assert.equal(sky.attrs.href, '/thinking', 'they open the sky itself, the old ideator room');
|
|
433
|
+
assert.ok(ls.every((n) => !/^(artistry|ideating|governing|building)$/.test(n.attrs['data-craft-door'] || '')), 'no door names a pre-rename mode');
|
|
432
434
|
assert.ok(ls.every((n) => n.attrs['aria-label']), 'every door is named for a screen reader');
|
|
433
435
|
const stars = all([sky]).filter((n) => n.tag === 'circle');
|
|
434
436
|
const box = Draw.viewBoxOf(m);
|
|
@@ -536,7 +538,7 @@ await test('outlines never show through a nearer building, and the three doors l
|
|
|
536
538
|
const group = (k) => hits.filter((h) => h.attrs['data-group'] === k);
|
|
537
539
|
assert.ok(group('area').every((h) => h.attrs.href === '/goals'), 'Building opens the goals');
|
|
538
540
|
assert.ok(group('civic').every((h) => h.attrs.href === Gov.STREET_HREF), 'Government opens the street');
|
|
539
|
-
assert.ok(group('museum').every((h) => h.attrs['data-craft-door'] === '
|
|
541
|
+
assert.ok(group('museum').every((h) => h.attrs['data-craft-door'] === 'artist'), 'Art switches to Artist');
|
|
540
542
|
['area', 'civic', 'museum'].forEach((k) => {
|
|
541
543
|
const g = group(k);
|
|
542
544
|
assert.ok(g.length > 0);
|
|
@@ -154,31 +154,33 @@ test('with reduced motion nothing glides: every control jumps straight to where
|
|
|
154
154
|
assert.equal(s.t, 0);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
test('the museum
|
|
157
|
+
test('the museum switches the craft to Artist; the band of stars, the core and a building are plain links (the stars open Your thinking since task 1005141)', { skip }, async () => {
|
|
158
158
|
const { ctx, page } = await boot();
|
|
159
159
|
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
160
|
-
await page.evaluate(() => { document.querySelector('.hc-svg a.hc-museum').dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
161
|
-
const r = await page.evaluate(() => ({ chosen: window.__chosen, core: document.querySelector('.hc-svg a.hc-core').getAttribute('href'), tower: document.querySelector('.hc-svg a.hc-tower').getAttribute('href') }));
|
|
160
|
+
await page.evaluate(() => { document.querySelector('.hc-svg a.hc-museum').dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); });
|
|
161
|
+
const r = await page.evaluate(() => ({ chosen: window.__chosen, sky: document.querySelector('.hc-svg a.hc-sky').getAttribute('href'), skyDoor: document.querySelector('.hc-svg a.hc-sky').getAttribute('data-craft-door'), core: document.querySelector('.hc-svg a.hc-core').getAttribute('href'), tower: document.querySelector('.hc-svg a.hc-tower').getAttribute('href') }));
|
|
162
162
|
await ctx.close();
|
|
163
|
-
assert.deepEqual(r.chosen, ['
|
|
163
|
+
assert.deepEqual(r.chosen, ['artist'], 'the museum opens the artist home');
|
|
164
|
+
assert.equal(r.sky, '/thinking', 'the stars open the sky itself');
|
|
165
|
+
assert.equal(r.skyDoor, null, 'and switch no craft');
|
|
164
166
|
assert.equal(r.core, '/builders/street');
|
|
165
167
|
assert.match(r.tower, /^\/goals#\/goal\/\d+$/);
|
|
166
168
|
});
|
|
167
169
|
|
|
168
|
-
test('the governor\'s Home is the street:
|
|
169
|
-
const opened = await boot({ craft: '
|
|
170
|
+
test('the governor\'s Home is the street: Patron on Home goes there, opened or chosen; other crafts stay', { skip }, async () => {
|
|
171
|
+
const opened = await boot({ craft: 'patron' });
|
|
170
172
|
const onOpen = await opened.page.evaluate(() => ({ hops: window.__hops, asked: window.__asked, built: !!document.querySelector('.hc-svg') }));
|
|
171
173
|
await opened.ctx.close();
|
|
172
|
-
assert.deepEqual(onOpen.hops, ['/builders/street'], 'Home opened with
|
|
174
|
+
assert.deepEqual(onOpen.hops, ['/builders/street'], 'Home opened with Patron chosen goes to the street');
|
|
173
175
|
assert.deepEqual(onOpen.asked, [], 'and asks the server for nothing on the way');
|
|
174
176
|
assert.equal(onOpen.built, false, 'and builds no city it is leaving');
|
|
175
177
|
|
|
176
178
|
const { ctx, page } = await boot();
|
|
177
179
|
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
178
180
|
const flip = (m) => page.evaluate((mode) => { document.documentElement.dataset.craft = mode; window.__craft = mode; document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode } })); return window.__hops.slice(); }, m);
|
|
179
|
-
assert.deepEqual(await flip('
|
|
180
|
-
assert.deepEqual(await flip('
|
|
181
|
-
assert.deepEqual(await flip('
|
|
181
|
+
assert.deepEqual(await flip('artist'), [], 'Artist stays on Home');
|
|
182
|
+
assert.deepEqual(await flip('artisan'), [], 'Artisan stays on Home');
|
|
183
|
+
assert.deepEqual(await flip('patron'), ['/builders/street'], 'choosing Patron on Home goes to the street');
|
|
182
184
|
await ctx.close();
|
|
183
185
|
});
|
|
184
186
|
|
|
@@ -201,6 +203,23 @@ test('a phone width scrolls nothing sideways', { skip }, async () => {
|
|
|
201
203
|
});
|
|
202
204
|
|
|
203
205
|
// ---- the ideator's sky (task 1005059) ----
|
|
206
|
+
// Since task 1005141 no craft lands on the sky: Ideating folded into Artist, and craft-mode.js
|
|
207
|
+
// reads a stored 'ideating' as 'artist' (tests/craft_mode.mjs). The sky view stays in
|
|
208
|
+
// home-city.js behind one switch so it can come back as its own view, so the cases below
|
|
209
|
+
// still drive it by handing the widget a raw 'ideating' — a craft the real switch never
|
|
210
|
+
// returns any more. The first case pins the new truth: every real craft draws the city.
|
|
211
|
+
|
|
212
|
+
test('NO CRAFT LANDS ON THE SKY (task 1005141): Artist, Artisan and no mode all draw the city', { skip }, async () => {
|
|
213
|
+
for (const craft of ['artist', 'artisan', null]) {
|
|
214
|
+
const { ctx, page } = await boot({ craft });
|
|
215
|
+
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
216
|
+
const r = await page.evaluate(() => ({ sky: document.getElementById('home-city').classList.contains('hc-is-sky'), towers: document.querySelectorAll('.hc-tower').length, hits: document.querySelectorAll('.hc-hit').length }));
|
|
217
|
+
await ctx.close();
|
|
218
|
+
assert.equal(r.sky, false, `${craft}: no sky`);
|
|
219
|
+
assert.ok(r.towers > 0, `${craft}: the city's towers`);
|
|
220
|
+
assert.equal(r.hits, 0, `${craft}: no skyline hit groups`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
204
223
|
|
|
205
224
|
// a point inside the hit group that the group itself takes (a nearer building may cover part of it)
|
|
206
225
|
const pointOf = (page, group) => page.evaluate((g) => {
|
|
@@ -215,7 +234,7 @@ const pointOf = (page, group) => page.evaluate((g) => {
|
|
|
215
234
|
}, group);
|
|
216
235
|
const lit = (page) => page.evaluate(() => Object.fromEntries(['area', 'civic', 'museum'].map((g) => [g, [...document.querySelectorAll('.hc-ol-' + g)].every((o) => Number(getComputedStyle(o).strokeOpacity) > 0.5)])));
|
|
217
236
|
|
|
218
|
-
test('
|
|
237
|
+
test('the dormant sky view, handed a raw ideating, draws the sky over the skyline from the same one read, and the ladder ranks ideators', { skip }, async () => {
|
|
219
238
|
const { ctx, page } = await boot({ craft: 'ideating' });
|
|
220
239
|
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
221
240
|
const r = await page.evaluate(() => ({
|
|
@@ -251,7 +270,7 @@ test('hovering the skyline outlines exactly one whole group: Building, Governmen
|
|
|
251
270
|
assert.deepEqual(rest, { area: false, civic: false, museum: false });
|
|
252
271
|
});
|
|
253
272
|
|
|
254
|
-
test('each group is one door: the goals, the street, and the artist\'s Home', { skip }, async () => {
|
|
273
|
+
test('in the dormant sky, each group is one door: the goals, the street, and the artist\'s Home', { skip }, async () => {
|
|
255
274
|
const { ctx, page } = await boot({ craft: 'ideating' });
|
|
256
275
|
await page.waitForFunction(() => document.querySelectorAll('.hc-hit').length > 0);
|
|
257
276
|
await page.evaluate(() => document.querySelector('.hc-hit[data-group="museum"]').dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })));
|
|
@@ -263,7 +282,7 @@ test('each group is one door: the goals, the street, and the artist\'s Home', {
|
|
|
263
282
|
small: [...document.querySelectorAll('.hc-hit')].map((a) => a.getBoundingClientRect()).filter((b) => b.height < 24).length,
|
|
264
283
|
}));
|
|
265
284
|
await ctx.close();
|
|
266
|
-
assert.deepEqual(r.chosen, ['
|
|
285
|
+
assert.deepEqual(r.chosen, ['artist']);
|
|
267
286
|
assert.deepEqual(r.area, ['/goals']);
|
|
268
287
|
assert.deepEqual(r.civic, ['/builders/street']);
|
|
269
288
|
assert.deepEqual(r.stops.sort(), ['area', 'civic', 'museum']);
|
|
@@ -273,7 +292,7 @@ test('each group is one door: the goals, the street, and the artist\'s Home', {
|
|
|
273
292
|
test('switching the craft swaps the sky and the city in place, with no second read', { skip }, async () => {
|
|
274
293
|
const { ctx, page } = await boot({ craft: 'ideating' });
|
|
275
294
|
await page.waitForFunction(() => document.querySelectorAll('.hc-hit').length > 0);
|
|
276
|
-
await page.evaluate(() => { window.__craft = '
|
|
295
|
+
await page.evaluate(() => { window.__craft = 'artisan'; document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode: 'artisan' } })); });
|
|
277
296
|
const r = await page.evaluate(() => ({ asked: window.__asked.length, sky: document.getElementById('home-city').classList.contains('hc-is-sky'), towers: document.querySelectorAll('.hc-tower').length, hits: document.querySelectorAll('.hc-hit').length }));
|
|
278
297
|
await ctx.close();
|
|
279
298
|
assert.deepEqual(r, { asked: 1, sky: false, towers: 10, hits: 0 });
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Runs modules/hall-ui/public/home-gallery.js (and, for the craft switch, the city's
|
|
5
5
|
// three files) in a real page with the reads stubbed by the hall-preview fixture: the
|
|
6
|
-
// wall is asked for once, only in Artistry, Most popular first; the arrows and keys walk
|
|
6
|
+
// wall is asked for once, only in Artist (Artistry until task 1005141), Most popular first; the arrows and keys walk
|
|
7
7
|
// it; the label opens closer on a click; the heart is the viewer's own, given and taken
|
|
8
8
|
// back, and never changes size; Latest asks again in that order; full screen keeps the
|
|
9
9
|
// signature and heart and drops the label; the full gallery hangs every work and a click
|
|
@@ -199,17 +199,17 @@ test('at phone width the gallery fits: nothing scrolls sideways and every contro
|
|
|
199
199
|
assert.deepEqual(m.small, []);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
test('Home is the gallery only in
|
|
202
|
+
test('Home is the gallery only in Artist: the city\'s widget switches on the craft and asks for the wall only then', { skip }, async () => {
|
|
203
203
|
const { ctx, page } = await boot({ city: true });
|
|
204
204
|
await page.evaluate(() => window.__widget.mount(document.getElementById('home-city')));
|
|
205
205
|
await page.waitForFunction(() => document.querySelectorAll('.hc-rung').length > 0);
|
|
206
206
|
const building = await page.evaluate(() => ({ asked: window.__asked.slice(), gallery: getComputedStyle(document.querySelector('.hg')).display, stage: getComputedStyle(document.querySelector('.hc-stage')).display }));
|
|
207
207
|
assert.deepEqual(building.asked, ['GET /api/bongos/home/city?weeks=10'], 'the builder\'s Home never asks for the wall');
|
|
208
208
|
assert.equal(building.gallery, 'none');
|
|
209
|
-
await page.evaluate(() => { document.documentElement.dataset.craft = '
|
|
209
|
+
await page.evaluate(() => { document.documentElement.dataset.craft = 'artist'; document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode: 'artist' } })); });
|
|
210
210
|
await page.waitForFunction(() => document.querySelectorAll('.hg-hang').length > 0);
|
|
211
211
|
const art = await page.evaluate(() => ({ asked: window.__asked.slice(), gallery: getComputedStyle(document.querySelector('.hg')).display, stage: getComputedStyle(document.querySelector('.hc-stage')).display, hidden: document.getElementById('home-city').hidden }));
|
|
212
|
-
await page.evaluate(() => { document.documentElement.dataset.craft = '
|
|
212
|
+
await page.evaluate(() => { document.documentElement.dataset.craft = 'artisan'; document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode: 'artisan' } })); });
|
|
213
213
|
const back = await page.evaluate(() => getComputedStyle(document.querySelector('.hc-stage')).display);
|
|
214
214
|
await ctx.close();
|
|
215
215
|
assert.deepEqual(art.asked.slice(1), ['GET /api/bongos/home/gallery?order=popular']);
|
package/tests/home_idea_box.mjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// goal 1000095, the owner's work-first hall).
|
|
3
3
|
//
|
|
4
4
|
// Runs modules/hall-ui/public/home-idea-box.js with the city's three files in a real
|
|
5
|
-
// page, the reads stubbed by the hall-preview fixture: the box shows only in
|
|
5
|
+
// page, the reads stubbed by the hall-preview fixture: the box shows only in the artist's Home
|
|
6
6
|
// and sits above the sky; it is ONE box and a button (owner, 2026-11-05); filing is
|
|
7
7
|
// POST /inbox with { title, body_md: '', kind: 'feature' }; an empty line files
|
|
8
8
|
// nothing; a refused filing keeps the words and says why; a filed one clears and links
|
|
9
|
-
// to Your thinking; the box shows in
|
|
9
|
+
// to Your thinking; the box shows in the artist's Home even when the sky could not be read; at
|
|
10
10
|
// phone width nothing scrolls sideways. The pure halves run in node. Skips the browser
|
|
11
11
|
// cases where no system Edge or Chrome is installed.
|
|
12
12
|
//
|
|
@@ -45,7 +45,7 @@ const stubs = (craft, { city = true, refuse = false } = {}) => `
|
|
|
45
45
|
window.OTBCraftMode = { iconHtml: () => '', choose: () => {}, current: () => document.documentElement.dataset.craft || null };
|
|
46
46
|
window.OTB = { contributeHallWidget(w) { window.__widget = w; } };`;
|
|
47
47
|
|
|
48
|
-
async function boot({ craft = '
|
|
48
|
+
async function boot({ craft = 'artist', width = 1440, ...opts } = {}) {
|
|
49
49
|
const ctx = await browser.newContext({ viewport: { width, height: 900 }, reducedMotion: 'reduce' });
|
|
50
50
|
const page = await ctx.newPage();
|
|
51
51
|
await page.setContent(`<!doctype html><html data-theme="dark"${craft ? ` data-craft="${craft}"` : ''}><head><style>${CSS}</style></head><body><section id="welcome-scroll" class="scroll">setup guide</section><section id="home-city" class="scroll home-city" hidden></section></body></html>`);
|
|
@@ -65,7 +65,7 @@ test('the pure halves: the first line is the title and the rest the details; a r
|
|
|
65
65
|
assert.match(Box.explain(null), /Nothing was saved/);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
test('in
|
|
68
|
+
test('in the Artist Home the box is the first thing in the section, ONE box and a button, and the setup guide is hidden', { skip }, async () => {
|
|
69
69
|
const { ctx, page } = await boot();
|
|
70
70
|
await page.waitForFunction(() => document.querySelector('.hc-svg').childElementCount > 0);
|
|
71
71
|
const s = await page.evaluate(() => {
|
|
@@ -92,7 +92,7 @@ test('in Ideating the box is the first thing in the section, ONE box and a butto
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
test('in any other craft, or none, there is no box and the setup guide shows', { skip }, async () => {
|
|
95
|
-
for (const craft of ['
|
|
95
|
+
for (const craft of ['artisan', null]) {
|
|
96
96
|
const { ctx, page } = await boot({ craft });
|
|
97
97
|
const s = await page.evaluate(() => ({ hidden: document.querySelector('.hib').hidden, guide: getComputedStyle(document.getElementById('welcome-scroll')).display }));
|
|
98
98
|
await ctx.close();
|
|
@@ -159,7 +159,7 @@ test('a refused filing keeps the words and says why', { skip }, async () => {
|
|
|
159
159
|
assert.equal(s.link, false);
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
test('the box shows in
|
|
162
|
+
test('the box shows in the Artist Home even when the sky could not be read', { skip }, async () => {
|
|
163
163
|
const { ctx, page } = await boot({ city: false });
|
|
164
164
|
const s = await page.evaluate(() => ({ host: document.getElementById('home-city').hidden, box: document.querySelector('.hib').hidden }));
|
|
165
165
|
await ctx.close();
|
|
@@ -205,7 +205,7 @@ const find = (n, cls) => {
|
|
|
205
205
|
return null;
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
-
const ROW = (place, handle, credits, extra = {}) => ({ place, handle, github_login: handle, display_name: handle.toUpperCase(), avatar_url: `https://avatars.example/${handle}`, credits, tasks_shipped: 2, karma: 1, disciplines: ['
|
|
208
|
+
const ROW = (place, handle, credits, extra = {}) => ({ place, handle, github_login: handle, display_name: handle.toUpperCase(), avatar_url: `https://avatars.example/${handle}`, credits, tasks_shipped: 2, karma: 1, disciplines: ['artisan'], ...extra });
|
|
209
209
|
const WEEK = () => ({ week: { start: '2026-10-19', end: '2026-10-26' },
|
|
210
210
|
winner: { week_start: '2026-10-19', handle: 'ana', github_login: 'ana', display_name: 'Ana <i>L</i>', avatar_url: 'https://avatars.example/ana', credits: 1240, tasks_shipped: 1 },
|
|
211
211
|
past: [{ week_start: '2026-10-12', handle: 'bo', github_login: 'bo', display_name: null, avatar_url: 'data:x', credits: 300, tasks_shipped: 2 },
|
|
@@ -229,7 +229,7 @@ const READS = () => ({
|
|
|
229
229
|
'/community/leaderboard?window=all': { status: 200, body: { window: 'all', count: 99, results: [ROW(26, 'ana', 1200), ROW(27, 'bo', 1500, { tasks_shipped: 1, karma: 0, avatar_url: 'javascript:alert(1)', display_name: null })] } },
|
|
230
230
|
'/community/leaderboard?window=30d': { status: 200, body: { window: '30d', results: [ROW(1, 'cy', 40)] } },
|
|
231
231
|
'/community/leaderboard?window=all&discipline=artist': { status: 200, body: { window: 'all', results: [] } },
|
|
232
|
-
'/community/search?q=pri': { status: 200, body: { results: [{ handle: 'priya', github_login: 'priyan', display_name: 'Priya', avatar_url: null, disciplines: ['
|
|
232
|
+
'/community/search?q=pri': { status: 200, body: { results: [{ handle: 'priya', github_login: 'priyan', display_name: 'Priya', avatar_url: null, disciplines: ['artisan', 'ui'] }, { handle: 'pri-hidden', github_login: 'ph', display_name: 'Pri <b>H</b>', avatar_url: null, disciplines: null }] } },
|
|
233
233
|
'/community/search?q=pri&discipline=artist': { status: 200, body: { results: [] } },
|
|
234
234
|
'/scouting': { status: 403, body: { error: { code: 'forbidden' } } },
|
|
235
235
|
});
|
|
@@ -465,7 +465,7 @@ test('find: nothing read on arrival; too short is refused before any read; a hid
|
|
|
465
465
|
assert.equal(ui.gets().pop(), '/api/bongos/community/search?q=pri');
|
|
466
466
|
assert.equal(ui.$('findStatus').textContent, '2 builders found.');
|
|
467
467
|
const [priya, hidden] = kids(ui.$('findList'));
|
|
468
|
-
assert.equal(find(priya, 'mineRow__hint').textContent, '
|
|
468
|
+
assert.equal(find(priya, 'mineRow__hint').textContent, 'artisan · designer');
|
|
469
469
|
assert.equal(find(hidden, 'mineRow__name').textContent, 'Pri <b>H</b>', 'textContent: a name is never markup');
|
|
470
470
|
assert.equal(find(hidden, 'mineRow__hint'), null, 'disciplines null (hidden numbers): nothing said about their craft');
|
|
471
471
|
assert.equal(ui.$('findBtn').disabled, false);
|