@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
|
@@ -52,11 +52,11 @@ const account = (id, handle, extra = {}) => ({
|
|
|
52
52
|
|
|
53
53
|
// Deliberately NOT in rank order — the module ranks, the fake does not.
|
|
54
54
|
const BOARD = [
|
|
55
|
-
{ acct: account('205', 'third-builder'), credits: 50, tasks_shipped: 1, karma: 0, disciplines: ['artist', '
|
|
55
|
+
{ acct: account('205', 'third-builder'), credits: 50, tasks_shipped: 1, karma: 0, disciplines: ['artist', 'artisan'] },
|
|
56
56
|
{ acct: account('201', 'hidden-builder', { hide_stats: true }), credits: 250, tasks_shipped: 8, karma: 1, disciplines: ['artist'] },
|
|
57
|
-
{ acct: account('200', 'top-builder'), credits: 300, tasks_shipped: 9, karma: 4, disciplines: ['
|
|
58
|
-
{ acct: account('202', 'private-builder', { account_visibility: 'private' }), credits: 240, tasks_shipped: 7, karma: 1, disciplines: ['
|
|
59
|
-
{ acct: account('203', 'prov-builder', { profile_state: 'provisional', terms_accepted_at: null }), credits: 230, tasks_shipped: 7, karma: 1, disciplines: ['
|
|
57
|
+
{ acct: account('200', 'top-builder'), credits: 300, tasks_shipped: 9, karma: 4, disciplines: ['artisan'] },
|
|
58
|
+
{ acct: account('202', 'private-builder', { account_visibility: 'private' }), credits: 240, tasks_shipped: 7, karma: 1, disciplines: ['artisan'] },
|
|
59
|
+
{ acct: account('203', 'prov-builder', { profile_state: 'provisional', terms_accepted_at: null }), credits: 230, tasks_shipped: 7, karma: 1, disciplines: ['artisan'] },
|
|
60
60
|
// Opted out of RECRUITING only — spec D3: that opt-out does not touch a leaderboard.
|
|
61
61
|
{ acct: account('204', 'optout-builder', { recruiter_discoverable: false }), credits: 120, tasks_shipped: 3, karma: 0, disciplines: ['ui'] },
|
|
62
62
|
// Ties top-builder on every number: the handle decides, so it ranks second.
|
|
@@ -99,6 +99,7 @@ const moduleApiPath = require.resolve('../src/module-api.js');
|
|
|
99
99
|
require.cache[moduleApiPath] = {
|
|
100
100
|
id: moduleApiPath, filename: moduleApiPath, loaded: true,
|
|
101
101
|
exports: {
|
|
102
|
+
crafts: require('../src/crafts.js'), // the real vocabulary (task 1005141)
|
|
102
103
|
pool: engine,
|
|
103
104
|
corsPublicGet: () => (_req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); },
|
|
104
105
|
logger: () => ({ error: () => {}, info: () => {}, warn: () => {} }),
|
|
@@ -168,7 +169,7 @@ await t('all-time binds no window start; a window binds exactly its start', asyn
|
|
|
168
169
|
await t('ranked credits → ships → karma → handle, with a trimmed discipline filter and clamped paging', async () => {
|
|
169
170
|
const all = await lb.listLeaderboard({ window: 'all', pool: engine });
|
|
170
171
|
assert.deepEqual(all.results.map((r) => r.handle), ['top-builder', 'twin-builder', 'optout-builder', 'third-builder']);
|
|
171
|
-
const eng = await lb.listLeaderboard({ window: 'all', discipline: '
|
|
172
|
+
const eng = await lb.listLeaderboard({ window: 'all', discipline: ' artisan ', limit: 5000, offset: 1, pool: engine });
|
|
172
173
|
assert.deepEqual(eng.results.map((r) => [r.place, r.handle]), [[2, 'third-builder']]);
|
|
173
174
|
assert.equal(eng.limit, lb.MAX_LIMIT);
|
|
174
175
|
});
|
|
@@ -280,6 +281,13 @@ await t('a discipline filter narrows the board and is echoed', async () => {
|
|
|
280
281
|
'the hidden builder holds artist too, and still does not appear');
|
|
281
282
|
});
|
|
282
283
|
|
|
284
|
+
await t('an old link\'s ideator reads as today\'s artist (task 1005141)', async () => {
|
|
285
|
+
const r = await get(port, `${PATH}?window=30d&discipline=ideator`);
|
|
286
|
+
assert.equal(r.status, 200);
|
|
287
|
+
assert.equal(r.json.discipline, 'artist');
|
|
288
|
+
assert.deepEqual(r.json.results.map((x) => x.handle), ['third-builder']);
|
|
289
|
+
});
|
|
290
|
+
|
|
283
291
|
await t('place counts from the page offset, and page echoes the clamped values', async () => {
|
|
284
292
|
const r = await get(port, `${PATH}?limit=1&offset=1`);
|
|
285
293
|
assert.equal(r.status, 200);
|
|
@@ -137,6 +137,9 @@ require.cache[moduleApiPath] = {
|
|
|
137
137
|
pool,
|
|
138
138
|
corsPublicGet: () => (_req, _res, next) => next(),
|
|
139
139
|
logger: () => ({ error: (o, m) => console.error(`route error: ${m}`, o), info: () => {}, warn: () => {} }),
|
|
140
|
+
// The routes fold an old craft name in ?discipline= through it (task 1005141); the real
|
|
141
|
+
// doorway's getter, so this stand-in cannot drift from it (task 1005193).
|
|
142
|
+
crafts: require('../src/crafts.js'),
|
|
140
143
|
},
|
|
141
144
|
};
|
|
142
145
|
const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
|
|
@@ -102,7 +102,7 @@ const ACCOUNTS = [
|
|
|
102
102
|
// active + public + NOT hidden — the plain control.
|
|
103
103
|
{ github_id: '100', handle: 'plain-vis', github_login: 'plainvis', display_name: 'Plain Visible',
|
|
104
104
|
avatar_url: null, profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'public', hide_stats: false,
|
|
105
|
-
disciplines: ['
|
|
105
|
+
disciplines: ['artisan'] },
|
|
106
106
|
// active + public + HIDDEN — findable by name, not by discipline.
|
|
107
107
|
{ github_id: '101', handle: 'hidden-builder', github_login: 'hiddenbuilder', display_name: 'Hidden Builder',
|
|
108
108
|
avatar_url: null, profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'public', hide_stats: true,
|
|
@@ -110,11 +110,11 @@ const ACCOUNTS = [
|
|
|
110
110
|
// active + PRIVATE — absent entirely, findable or not.
|
|
111
111
|
{ github_id: '102', handle: 'private-builder', github_login: 'privatebuilder', display_name: 'Private Builder',
|
|
112
112
|
avatar_url: null, profile_state: 'active', terms_accepted_at: ACCEPTED_AT, account_visibility: 'private', hide_stats: false,
|
|
113
|
-
disciplines: ['
|
|
113
|
+
disciplines: ['artisan'] },
|
|
114
114
|
// PROVISIONAL — absent entirely.
|
|
115
115
|
{ github_id: '103', handle: 'provisional-builder', github_login: 'provbuilder', display_name: 'Provisional Builder',
|
|
116
116
|
avatar_url: null, profile_state: 'provisional', terms_accepted_at: null, account_visibility: 'public', hide_stats: false,
|
|
117
|
-
disciplines: ['
|
|
117
|
+
disciplines: ['artisan'] },
|
|
118
118
|
// active + public + NOT hidden, but 'octocat' sits mid-handle — a query for
|
|
119
119
|
// it is NOT a prefix match, so this fixture exercises the FUZZY (`%`) leg
|
|
120
120
|
// specifically, not the LIKE prefix leg.
|
|
@@ -185,6 +185,7 @@ const moduleApiPath = require.resolve('../src/module-api.js');
|
|
|
185
185
|
require.cache[moduleApiPath] = {
|
|
186
186
|
id: moduleApiPath, filename: moduleApiPath, loaded: true,
|
|
187
187
|
exports: {
|
|
188
|
+
crafts: require('../src/crafts.js'), // the real vocabulary (task 1005141)
|
|
188
189
|
pool: engine,
|
|
189
190
|
corsPublicGet: () => (_req, _res, next) => next(),
|
|
190
191
|
logger: () => ({ error: () => {}, info: () => {}, warn: () => {} }),
|
|
@@ -252,7 +253,7 @@ await t('the SAME hide_stats account drops out of a DISCIPLINE-filtered search',
|
|
|
252
253
|
await t('a private account never appears — by name or by its real discipline', async () => {
|
|
253
254
|
const byName = await get(port, '/api/bongos/community/search?q=private-builder');
|
|
254
255
|
assert.ok(!byName.json.results.some((x) => x.github_login === 'privatebuilder'));
|
|
255
|
-
const byDiscipline = await get(port, '/api/bongos/community/search?q=private&discipline=
|
|
256
|
+
const byDiscipline = await get(port, '/api/bongos/community/search?q=private&discipline=artisan');
|
|
256
257
|
assert.ok(!byDiscipline.json.results.some((x) => x.github_login === 'privatebuilder'));
|
|
257
258
|
});
|
|
258
259
|
|
|
@@ -271,8 +272,8 @@ await t('a case-differing, NON-PREFIX query hits the fuzzy leg (not just the LIK
|
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
await t('the plain visible account IS found under its real discipline, and ABSENT under another', async () => {
|
|
274
|
-
const hit = await get(port, '/api/bongos/community/search?q=plain-vis&discipline=
|
|
275
|
-
assert.ok(hit.json.results.some((x) => x.github_login === 'plainvis'), '
|
|
275
|
+
const hit = await get(port, '/api/bongos/community/search?q=plain-vis&discipline=artisan');
|
|
276
|
+
assert.ok(hit.json.results.some((x) => x.github_login === 'plainvis'), 'artisan is its real discipline');
|
|
276
277
|
const miss = await get(port, '/api/bongos/community/search?q=plain-vis&discipline=artist');
|
|
277
278
|
assert.ok(!miss.json.results.some((x) => x.github_login === 'plainvis'), 'artist is not its discipline');
|
|
278
279
|
});
|
package/tests/conductor.mjs
CHANGED
|
@@ -681,7 +681,7 @@ test('selectAutonomousDispatch: GO + feed → picks rows[0] and shapes the claim
|
|
|
681
681
|
const r = dispatch.selectAutonomousDispatch(
|
|
682
682
|
{ decision: 'go', reason: 'signal' },
|
|
683
683
|
[
|
|
684
|
-
{ id: 458, title: 'MAS Phase 4.5', version_id: 'GDS-V4', est_minutes: 120, discipline: '
|
|
684
|
+
{ id: 458, title: 'MAS Phase 4.5', version_id: 'GDS-V4', est_minutes: 120, discipline: 'artisan' },
|
|
685
685
|
{ id: 999, title: 'lower priority' },
|
|
686
686
|
],
|
|
687
687
|
);
|
|
@@ -33,14 +33,14 @@ const SPECIALITY = 'SPECIALITY-CONTRACT: the engineer expert contract.';
|
|
|
33
33
|
const ARTIST_SPECIALITY = 'ARTIST-CONTRACT: must not appear in an engineering session.';
|
|
34
34
|
|
|
35
35
|
// The /me payload the hook consumes, in the route's own shape.
|
|
36
|
-
function mePayload({ discipline = '
|
|
36
|
+
function mePayload({ discipline = 'artisan' } = {}) {
|
|
37
37
|
return {
|
|
38
38
|
builder: { id: 4242, rank: 'metic' },
|
|
39
39
|
active_claims: [{ task_id: 77, title: 'a claimed task', touches: [], worktree_name: 'task-77', discipline }],
|
|
40
40
|
wandering: { effective_level: 'normal', inject: false, contract: '' },
|
|
41
41
|
interaction: { inject: true, contract: INTERACTION },
|
|
42
42
|
specialities: [
|
|
43
|
-
{ discipline: '
|
|
43
|
+
{ discipline: 'artisan', inject: true, contract: SPECIALITY },
|
|
44
44
|
{ discipline: 'artist', inject: true, contract: ARTIST_SPECIALITY },
|
|
45
45
|
],
|
|
46
46
|
};
|
|
@@ -99,7 +99,7 @@ await test('the hook, run as settings.json runs it, injects the interaction prof
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
await test('the speciality contract for the CLAIMED craft reaches the session, and no other craft\'s', async () => {
|
|
102
|
-
const r = await runHook(mePayload({ discipline: '
|
|
102
|
+
const r = await runHook(mePayload({ discipline: 'artisan' }));
|
|
103
103
|
assert.ok(r.out.includes(SPECIALITY), 'the engineer speciality is injected for an engineering claim');
|
|
104
104
|
assert.ok(!r.out.includes(ARTIST_SPECIALITY), 'the artist contract is not');
|
|
105
105
|
});
|
|
@@ -116,17 +116,17 @@ const craftMode = require('../src/bongos/craft-mode.js');
|
|
|
116
116
|
const MODES = craftMode.readModes();
|
|
117
117
|
function twoTreePayload() {
|
|
118
118
|
const claim = (id, discipline) => ({ task_id: id, title: 't', touches: [], worktree_name: `task-${id}`, discipline, craft_mode: craftMode.describeCraftMode(discipline, MODES) });
|
|
119
|
-
return { ...mePayload(), specialities: [], active_claims: [claim(77, '
|
|
119
|
+
return { ...mePayload(), specialities: [], active_claims: [claim(77, 'artisan'), claim(88, 'artist')] };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
await test('a session in a claimed worktree is told its craft\'s pack on the first prompt, and not the other tree\'s', async () => {
|
|
123
123
|
const r = await runHook(twoTreePayload(), { worktree: 'task-77' });
|
|
124
124
|
assert.equal(r.code, 0);
|
|
125
|
-
assert.ok(r.out.includes('docs/packs/
|
|
126
|
-
assert.ok(!r.out.includes('docs/packs/
|
|
125
|
+
assert.ok(r.out.includes('docs/packs/artisan.md'), `the engineer pack pointer reaches the session; stdout was: ${JSON.stringify(r.out.slice(0, 400))}`);
|
|
126
|
+
assert.ok(!r.out.includes('docs/packs/artist.md'), 'worktree task-88\'s ideator craft does not');
|
|
127
127
|
const other = await runHook(twoTreePayload(), { worktree: 'task-88' });
|
|
128
|
-
assert.ok(other.out.includes('docs/packs/
|
|
129
|
-
assert.ok(!other.out.includes('docs/packs/
|
|
128
|
+
assert.ok(other.out.includes('docs/packs/artist.md'));
|
|
129
|
+
assert.ok(!other.out.includes('docs/packs/artisan.md'));
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
await test('outside any claimed worktree no craft is guessed', async () => {
|
|
@@ -72,7 +72,7 @@ await insertTestBuilders(pool, [
|
|
|
72
72
|
// 'building': the seeds own the one 'planning' version the partial index allows.
|
|
73
73
|
const VER = 'ZZZ-CDF';
|
|
74
74
|
await pool.query("INSERT INTO versions (id, name, status) VALUES ($1, 'copy desk fixture', 'building') ON CONFLICT (id) DO NOTHING", [VER]);
|
|
75
|
-
await pool.query("INSERT INTO tasks (id, version_id, title, discipline) VALUES ($1, $2, 'copy desk fixture', '
|
|
75
|
+
await pool.query("INSERT INTO tasks (id, version_id, title, discipline) VALUES ($1, $2, 'copy desk fixture', 'artisan') ON CONFLICT (id) DO NOTHING", [T, VER]);
|
|
76
76
|
|
|
77
77
|
// Apply the migration verbatim — this also proves it is idempotent, since a
|
|
78
78
|
// migrated DB has already run it.
|
|
@@ -33,8 +33,8 @@ const { db } = installPageLedger(api, {
|
|
|
33
33
|
builders: [
|
|
34
34
|
{ id: ALICE, github_login: 'alice', display_name: 'Alice', preferred_disciplines: ['artist'] },
|
|
35
35
|
{ id: BOB, github_login: 'bob', display_name: 'Bob', preferred_disciplines: ['artist'] },
|
|
36
|
-
{ id: CARL, github_login: 'carl', display_name: 'Carl', preferred_disciplines: ['
|
|
37
|
-
{ id: DAN, github_login: 'dan', display_name: 'Dan', preferred_disciplines: ['
|
|
36
|
+
{ id: CARL, github_login: 'carl', display_name: 'Carl', preferred_disciplines: ['artisan'] },
|
|
37
|
+
{ id: DAN, github_login: 'dan', display_name: 'Dan', preferred_disciplines: ['artisan'] },
|
|
38
38
|
],
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -29,8 +29,8 @@ const api = require('../src/module-api.js');
|
|
|
29
29
|
const { db, seen } = installPageLedger(api, {
|
|
30
30
|
builders: [
|
|
31
31
|
{ id: '7', github_login: 'alice', display_name: 'Alice', preferred_disciplines: ['artist'] },
|
|
32
|
-
{ id: '8', github_login: 'bob', display_name: 'Bob', preferred_disciplines: ['
|
|
33
|
-
{ id: '9', github_login: 'carol', display_name: 'Carol', preferred_disciplines: ['
|
|
32
|
+
{ id: '8', github_login: 'bob', display_name: 'Bob', preferred_disciplines: ['artisan', 'artist'] },
|
|
33
|
+
{ id: '9', github_login: 'carol', display_name: 'Carol', preferred_disciplines: ['artisan'] },
|
|
34
34
|
],
|
|
35
35
|
});
|
|
36
36
|
const ALICE = '7';
|
|
@@ -30,7 +30,7 @@ const { db } = installPageLedger(api, {
|
|
|
30
30
|
builders: [
|
|
31
31
|
{ id: '7', github_login: 'alice', display_name: 'Alice', preferred_disciplines: ['artist'] },
|
|
32
32
|
{ id: '8', github_login: 'bob', display_name: 'Bob', preferred_disciplines: ['artist'] },
|
|
33
|
-
{ id: '9', github_login: 'carol', display_name: 'Carol', preferred_disciplines: ['
|
|
33
|
+
{ id: '9', github_login: 'carol', display_name: 'Carol', preferred_disciplines: ['artisan'] },
|
|
34
34
|
],
|
|
35
35
|
});
|
|
36
36
|
const ALICE = '7';
|
|
@@ -29,7 +29,7 @@ const CARL = '9'; // the applier, Metic
|
|
|
29
29
|
const { db } = installPageLedger(api, {
|
|
30
30
|
builders: [
|
|
31
31
|
{ id: ALICE, github_login: 'alice', display_name: 'Alice', preferred_disciplines: ['artist'] },
|
|
32
|
-
{ id: CARL, github_login: 'carl', display_name: 'Carl', preferred_disciplines: ['
|
|
32
|
+
{ id: CARL, github_login: 'carl', display_name: 'Carl', preferred_disciplines: ['artisan'] },
|
|
33
33
|
],
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -208,7 +208,7 @@ try {
|
|
|
208
208
|
`page-tweak-unplaced/${studioTask().id}/L0005`,
|
|
209
209
|
]);
|
|
210
210
|
for (const t of eng) {
|
|
211
|
-
assert.deepEqual([t.status, t.discipline, t.kind, t.version_id, t.goal_id, t.created_by], ['backlog', '
|
|
211
|
+
assert.deepEqual([t.status, t.discipline, t.kind, t.version_id, t.goal_id, t.created_by], ['backlog', 'artisan', 'feature', 'V9', '88', ALICE], 'an unplaced line lands in the artist review goal (task 1004819)');
|
|
212
212
|
assert.match(t.description, /builders:studio/);
|
|
213
213
|
}
|
|
214
214
|
const quiet = eng.find((t) => t.source_ref.endsWith('/L0005'));
|
|
@@ -316,7 +316,7 @@ try {
|
|
|
316
316
|
assert.equal(eng.title, 'Remove "Every project on this instance, newest first" (landing:projects)');
|
|
317
317
|
assert.match(eng.description, /stop showing a line/);
|
|
318
318
|
assert.match(eng.description, /take it out/);
|
|
319
|
-
assert.deepEqual([eng.status, eng.discipline], ['backlog', '
|
|
319
|
+
assert.deepEqual([eng.status, eng.discipline], ['backlog', 'artisan']);
|
|
320
320
|
});
|
|
321
321
|
// Task 1004776: a line that must keep its meaning. The real editor read marks
|
|
322
322
|
// it and says what it must still say; the real submit WARNS (never refuses)
|
|
@@ -102,7 +102,7 @@ async function fixture() {
|
|
|
102
102
|
const claimFrom = async (source) => {
|
|
103
103
|
const session = await insertRow('builder_sessions', { builder_id: builder.id, token_hash: `${tag}-${source}`, source });
|
|
104
104
|
// discipline + kind: core_247's CHECK refuses the column default (task 1004363).
|
|
105
|
-
const task = await insertRow('tasks', { title: `${tag} ${source} task`, status: 'shipped', version_id: version.id, discipline: '
|
|
105
|
+
const task = await insertRow('tasks', { title: `${tag} ${source} task`, status: 'shipped', version_id: version.id, discipline: 'artisan', kind: 'feature' });
|
|
106
106
|
const claim = await insertRow('claims', {
|
|
107
107
|
task_id: task.id, builder_id: builder.id, creator_session_id: session.id,
|
|
108
108
|
worktree_name: `${tag}-${source}`, released_at: new Date(), outcome: 'shipped',
|
package/tests/craft_mode.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/craft_mode.mjs — the hall's craft mode picker (task 1004492, goal 1000095 WA6).
|
|
2
2
|
//
|
|
3
3
|
// On entering the hall a picker asks once per session "What are you doing
|
|
4
|
-
// now?" (
|
|
4
|
+
// now?" (Artisan, Artist or Patron — task 1005141); the answer is
|
|
5
5
|
// html[data-craft], and a switch in the top bar changes it without a reload.
|
|
6
6
|
// These cases boot the REAL shell-trail.js + craft-mode.js + shell.js in a DOM
|
|
7
7
|
// stub, in the order every hall page loads them, and drive them the way a
|
|
@@ -163,7 +163,7 @@ function boot({ craft = true, session = {}, store = {}, throws = false, optOut =
|
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
const MODES = ['
|
|
166
|
+
const MODES = ['artisan', 'artist', 'patron'];
|
|
167
167
|
const noSwitch = (html) => html.replace(/<div class="craft-switch[\s\S]*?<\/div>/, '');
|
|
168
168
|
|
|
169
169
|
test('NO MODE IS THE HALL AS TODAY: with no mode chosen, nothing but the switch differs from a hall without craft modes', () => {
|
|
@@ -185,9 +185,10 @@ test('a signed-in builder is asked ONCE per session, and a reload does not ask a
|
|
|
185
185
|
const html = p.picker().innerHTML;
|
|
186
186
|
assert.match(html, /role="dialog" aria-modal="true"/);
|
|
187
187
|
assert.match(html, /What are you doing now\?/);
|
|
188
|
-
for (const m of
|
|
189
|
-
assert.equal([...html.matchAll(/data-craft-pick="/g)].length,
|
|
190
|
-
assert.doesNotMatch(html, /
|
|
188
|
+
for (const m of MODES) assert.match(html, new RegExp(`data-craft-pick="${m}"`), `a ${m} door`);
|
|
189
|
+
assert.equal([...html.matchAll(/data-craft-pick="/g)].length, 3, 'three doors and nothing else to pick');
|
|
190
|
+
assert.doesNotMatch(html, /data-craft-pick="ideating"/, 'Ideating folded into Artist (task 1005141): no door of its own');
|
|
191
|
+
assert.doesNotMatch(html, /legacy/i, 'Legacy mode is gone (owner, 2026-10-04: Building — now Artisan — has all those tabs anyway)');
|
|
191
192
|
p.escape();
|
|
192
193
|
assert.equal(p.picker(), null, 'Escape closes it without choosing');
|
|
193
194
|
assert.equal(p.mode(), undefined, 'and leaves the hall with no mode');
|
|
@@ -206,13 +207,13 @@ test('choosing a door sets html[data-craft] for every page of the session, and s
|
|
|
206
207
|
const session = {}, store = {};
|
|
207
208
|
const p = boot({ session, store });
|
|
208
209
|
p.signIn();
|
|
209
|
-
p.pick('data-craft-pick', '
|
|
210
|
+
p.pick('data-craft-pick', 'artist');
|
|
210
211
|
assert.equal(p.picker(), null, 'the picker closes');
|
|
211
|
-
assert.equal(p.mode(), '
|
|
212
|
-
assert.ok(p.events.some((e) => e.type === 'otb:craft' && e.detail.mode === '
|
|
213
|
-
assert.deepEqual(p.pressed(), ['
|
|
212
|
+
assert.equal(p.mode(), 'artist');
|
|
213
|
+
assert.ok(p.events.some((e) => e.type === 'otb:craft' && e.detail.mode === 'artist'), 'otb:craft fires with the mode');
|
|
214
|
+
assert.deepEqual(p.pressed(), ['artist'], 'the switch lights the chosen mode');
|
|
214
215
|
const next = boot({ session, store });
|
|
215
|
-
assert.equal(next.mode(), '
|
|
216
|
+
assert.equal(next.mode(), 'artist', 'the next page of the session reads it before /me answers');
|
|
216
217
|
assert.equal(store['hall-craft-keep'], undefined, '"Ask me again next session" was ticked, so nothing outlives the session');
|
|
217
218
|
const tomorrow = boot({ store });
|
|
218
219
|
tomorrow.signIn();
|
|
@@ -224,25 +225,25 @@ test('unticking "Ask me again next session" keeps the mode for later sessions',
|
|
|
224
225
|
const store = {};
|
|
225
226
|
const p = boot({ store });
|
|
226
227
|
p.signIn();
|
|
227
|
-
p.pick('data-craft-pick', '
|
|
228
|
-
assert.equal(store['hall-craft-keep'], '
|
|
228
|
+
p.pick('data-craft-pick', 'patron', { askAgain: false });
|
|
229
|
+
assert.equal(store['hall-craft-keep'], 'patron');
|
|
229
230
|
const tomorrow = boot({ store });
|
|
230
231
|
tomorrow.signIn();
|
|
231
|
-
assert.equal(tomorrow.mode(), '
|
|
232
|
+
assert.equal(tomorrow.mode(), 'patron', 'the kept mode applies');
|
|
232
233
|
assert.equal(tomorrow.picker(), null, 'and nobody is asked');
|
|
233
234
|
});
|
|
234
235
|
|
|
235
|
-
test('THE HEADER SWITCH changes the mode without a reload, and offers the
|
|
236
|
+
test('THE HEADER SWITCH changes the mode without a reload, and offers the three crafts', () => {
|
|
236
237
|
const p = boot();
|
|
237
238
|
const opts = [...p.topbar.innerHTML.matchAll(/data-craft-opt="([a-z]+)"/g)].map((m) => m[1]);
|
|
238
|
-
assert.deepEqual(opts, MODES, 'the
|
|
239
|
-
p.press('
|
|
240
|
-
assert.equal(p.mode(), '
|
|
241
|
-
p.press('
|
|
242
|
-
assert.equal(p.mode(), '
|
|
243
|
-
assert.deepEqual(p.pressed(), ['
|
|
239
|
+
assert.deepEqual(opts, MODES, 'the three crafts, and no Legacy or Ideating');
|
|
240
|
+
p.press('artist');
|
|
241
|
+
assert.equal(p.mode(), 'artist');
|
|
242
|
+
p.press('artisan');
|
|
243
|
+
assert.equal(p.mode(), 'artisan', 'anyone can switch at any time');
|
|
244
|
+
assert.deepEqual(p.pressed(), ['artisan']);
|
|
244
245
|
assert.equal(p.location.reloads, 0, 'no navigation happened');
|
|
245
|
-
assert.equal(p.session['hall-craft'], '
|
|
246
|
+
assert.equal(p.session['hall-craft'], 'artisan', 'and the session remembers it');
|
|
246
247
|
});
|
|
247
248
|
|
|
248
249
|
test('a browser that still has Legacy stored falls back to NO MODE (task 1004567)', () => {
|
|
@@ -256,31 +257,31 @@ test('a browser that still has Legacy stored falls back to NO MODE (task 1004567
|
|
|
256
257
|
const kept = boot({ store });
|
|
257
258
|
kept.signIn();
|
|
258
259
|
assert.equal(kept.mode(), undefined, 'a kept Legacy from an earlier session is no mode too');
|
|
259
|
-
assert.ok(kept.picker(), 'and a new session is asked to pick one of the
|
|
260
|
-
p.press('
|
|
261
|
-
assert.equal(p.mode(), '
|
|
260
|
+
assert.ok(kept.picker(), 'and a new session is asked to pick one of the three');
|
|
261
|
+
p.press('artisan');
|
|
262
|
+
assert.equal(p.mode(), 'artisan', 'and any of the three can be chosen from there');
|
|
262
263
|
});
|
|
263
264
|
|
|
264
265
|
test('on a phone the switch shows only the current mode, and tapping it opens the picker', () => {
|
|
265
|
-
const session = { 'hall-craft': '
|
|
266
|
+
const session = { 'hall-craft': 'artisan' };
|
|
266
267
|
const p = boot({ session, narrow: true });
|
|
267
|
-
p.press('
|
|
268
|
+
p.press('artisan');
|
|
268
269
|
assert.ok(p.picker(), 'the current mode opens the picker');
|
|
269
|
-
assert.equal(p.mode(), '
|
|
270
|
+
assert.equal(p.mode(), 'artisan', 'without changing the mode');
|
|
270
271
|
});
|
|
271
272
|
|
|
272
|
-
test('artists start on
|
|
273
|
+
test('artists start on Artist: the door is pre-selected and the picker says why', () => {
|
|
273
274
|
const p = boot();
|
|
274
|
-
p.signIn({ id: 3, rank: 'thetes', main_discipline: 'artist', preferred_disciplines: ['
|
|
275
|
+
p.signIn({ id: 3, rank: 'thetes', main_discipline: 'artist', preferred_disciplines: ['artisan', 'artist'] });
|
|
275
276
|
const html = p.picker().innerHTML;
|
|
276
|
-
assert.match(html, /craft-door--
|
|
277
|
-
assert.match(html, /Artists start
|
|
277
|
+
assert.match(html, /craft-door--artist craft-door--pre"/);
|
|
278
|
+
assert.match(html, /Artists start here\./);
|
|
278
279
|
const eng = boot();
|
|
279
|
-
eng.signIn({ id: 4, rank: 'thetes', main_discipline: '
|
|
280
|
+
eng.signIn({ id: 4, rank: 'thetes', main_discipline: 'artisan', preferred_disciplines: ['artist'] });
|
|
280
281
|
assert.doesNotMatch(eng.picker().innerHTML, /craft-door--pre/, 'a main role that is not artist pre-selects nothing');
|
|
281
282
|
const unset = boot();
|
|
282
283
|
unset.signIn({ id: 5, rank: 'thetes', main_discipline: null, preferred_disciplines: ['artist'] });
|
|
283
|
-
assert.match(unset.picker().innerHTML, /craft-door--
|
|
284
|
+
assert.match(unset.picker().innerHTML, /craft-door--artist craft-door--pre"/, 'with no main role, the first craft decides');
|
|
284
285
|
});
|
|
285
286
|
|
|
286
287
|
test('storage that throws is never asked and never wears a mode', () => {
|
|
@@ -291,13 +292,26 @@ test('storage that throws is never asked and never wears a mode', () => {
|
|
|
291
292
|
});
|
|
292
293
|
|
|
293
294
|
test('a page can opt out with <body data-craft-mode="off">', () => {
|
|
294
|
-
const p = boot({ optOut: true, session: { 'hall-craft': '
|
|
295
|
+
const p = boot({ optOut: true, session: { 'hall-craft': 'artist' } });
|
|
295
296
|
p.signIn();
|
|
296
297
|
assert.doesNotMatch(p.topbar.innerHTML, /craft-switch/, 'no switch');
|
|
297
298
|
assert.equal(p.picker(), null, 'no picker');
|
|
298
299
|
assert.equal(p.mode(), undefined, 'no mode');
|
|
299
300
|
});
|
|
300
301
|
|
|
302
|
+
test('a mode stored under its pre-rename id reads as the new craft (task 1005141), this session or kept', () => {
|
|
303
|
+
const map = { building: 'artisan', artistry: 'artist', ideating: 'artist', governing: 'patron' };
|
|
304
|
+
for (const [old, now] of Object.entries(map)) {
|
|
305
|
+
const p = boot({ session: { 'hall-craft': old, 'hall-craft-asked': '1' } });
|
|
306
|
+
assert.equal(p.mode(), now, `a session's stored ${old} wears ${now}`);
|
|
307
|
+
p.signIn();
|
|
308
|
+
assert.equal(p.picker(), null, `${old}: nobody loses their choice to the rename`);
|
|
309
|
+
assert.deepEqual(p.pressed(), [now]);
|
|
310
|
+
const kept = boot({ store: { 'hall-craft-keep': old } });
|
|
311
|
+
assert.equal(kept.mode(), now, `a kept ${old} wears ${now}`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
301
315
|
test('a stored value that is not a mode is ignored, not trusted', () => {
|
|
302
316
|
const p = boot({ session: { 'hall-craft': '"><img src=x>' }, store: { 'hall-craft-keep': 'admin' } });
|
|
303
317
|
assert.equal(p.mode(), undefined);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// tests/craft_mode_home.mjs — choosing a craft leaves another craft's page for Home
|
|
2
2
|
// (task 1005067, goal 1000095).
|
|
3
3
|
//
|
|
4
|
-
// Owner, 2026-11-04: a governor on the city who picked Artistry, Ideating or Building
|
|
4
|
+
// Owner, 2026-11-04: a governor on the city who picked Artistry, Ideating or Building (now Artist or Artisan, task 1005141)
|
|
5
5
|
// stayed in the governor's city. Each craft's home is its own dashboard (Home draws it;
|
|
6
|
-
// the street is
|
|
6
|
+
// the street is Patron's, Governing until task 1005141), so a page that belongs ONLY to other crafts is
|
|
7
7
|
// left for Home, while a shared page, a page no craft owns, and Home itself stay put.
|
|
8
8
|
// These cases run the REAL craft-mode.js and drive its own choose(), recording where
|
|
9
9
|
// the hop would go instead of navigating.
|
|
@@ -61,58 +61,58 @@ function boot({ page, active = null, govCity = false }) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
test('leaving the governor\'s city for any other craft goes to Home', () => {
|
|
64
|
-
for (const mode of ['
|
|
64
|
+
for (const mode of ['artisan', 'artist']) {
|
|
65
65
|
const { CM, hops } = boot({ page: 'city', active: 'city' });
|
|
66
66
|
CM.choose(mode);
|
|
67
67
|
assert.deepEqual(hops, ['/'], `${mode} from the city goes Home`);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
test('the street is
|
|
71
|
+
test('the street is Patron\'s own page, so other crafts leave it too', () => {
|
|
72
72
|
const { CM, hops } = boot({ page: 'street' });
|
|
73
|
-
assert.equal(CM.strandedBy('
|
|
74
|
-
CM.choose('
|
|
73
|
+
assert.equal(CM.strandedBy('patron'), false);
|
|
74
|
+
CM.choose('artist');
|
|
75
75
|
assert.deepEqual(hops, ['/']);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
test('choosing
|
|
78
|
+
test('choosing Patron on another craft\'s page lands on the street', () => {
|
|
79
79
|
const { CM, hops } = boot({ page: 'studio', active: 'studio', govCity: true });
|
|
80
|
-
CM.choose('
|
|
80
|
+
CM.choose('patron');
|
|
81
81
|
assert.deepEqual(hops, ['/builders/street']);
|
|
82
82
|
// without the street's script on the page, Home itself hops to the street
|
|
83
|
-
assert.equal(boot({ page: 'studio' }).CM.homeHref('
|
|
83
|
+
assert.equal(boot({ page: 'studio' }).CM.homeHref('patron'), '/');
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
test('a page the chosen craft also shows stays put', () => {
|
|
87
|
-
// goals is in
|
|
87
|
+
// goals is in Artisan and Artist alike
|
|
88
88
|
const { CM, hops } = boot({ page: 'goals', active: 'goals' });
|
|
89
|
-
CM.choose('
|
|
90
|
-
CM.choose('
|
|
89
|
+
CM.choose('artist');
|
|
90
|
+
CM.choose('artisan');
|
|
91
91
|
assert.deepEqual(hops, []);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
test('a page every craft keeps, and a page no craft owns, stay put', () => {
|
|
95
95
|
for (const [page, active] of [['roster', 'roster'], ['my-agents', 'my-agents'], ['task', null], ['idea', null]]) {
|
|
96
96
|
const { CM, hops } = boot({ page, active });
|
|
97
|
-
CM.choose('
|
|
98
|
-
CM.choose('
|
|
97
|
+
CM.choose('patron');
|
|
98
|
+
CM.choose('artist');
|
|
99
99
|
assert.deepEqual(hops, [], `${page} stays`);
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test('Home redraws itself for the craft, so it never hops from here', () => {
|
|
104
104
|
const { CM, hops } = boot({ page: 'home', active: 'home' });
|
|
105
|
-
CM.choose('
|
|
106
|
-
CM.choose('
|
|
105
|
+
CM.choose('artist');
|
|
106
|
+
CM.choose('patron');
|
|
107
107
|
assert.deepEqual(hops, []);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
// The Economy page (drachmae.html, nav item 'economy') was this case's real example
|
|
111
111
|
// until task 1005046 folded it into the Primer; the rule it pins is unchanged, so a
|
|
112
|
-
// made-up pairing keeps it: the marked nav item (Tasks,
|
|
113
|
-
// before the page name (Studio, which
|
|
112
|
+
// made-up pairing keeps it: the marked nav item (Tasks, Artisan's alone) is read
|
|
113
|
+
// before the page name (Studio, which Artist also shows).
|
|
114
114
|
test('the nav item wins over the page name', () => {
|
|
115
115
|
const { CM } = boot({ page: 'studio', active: 'work' });
|
|
116
|
-
assert.equal(CM.strandedBy('
|
|
117
|
-
assert.equal(CM.strandedBy('
|
|
116
|
+
assert.equal(CM.strandedBy('artist'), true);
|
|
117
|
+
assert.equal(CM.strandedBy('artisan'), false);
|
|
118
118
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// tests/craft_mode_looks.mjs — each craft mode's look (task 1004495, goal 1000095
|
|
2
2
|
// WA6; direction docs/design/craft-mode-direction.md).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Artist and Patron each wear a look (task 1005141: Artistry and Ideating became Artist,
|
|
5
|
+
// Governing became Patron, Building became Artisan): --craft / --craft-on tokens
|
|
5
6
|
// (light + dark twins) in style.css, the header eyebrow beside the page title and
|
|
6
|
-
// the door's scene framed under the brand.
|
|
7
|
+
// the door's scene framed under the brand. Artisan and no mode wear
|
|
7
8
|
// NOTHING — the hall as it always was. These cases run the REAL craft-mode.js over
|
|
8
|
-
// a small DOM and read the REAL style.css, so a look that leaks into
|
|
9
|
+
// a small DOM and read the REAL style.css, so a look that leaks into Artisan, a
|
|
9
10
|
// plate that is a second copy of the art, or a colour that loses its contrast reds.
|
|
10
11
|
import assert from 'node:assert/strict';
|
|
11
12
|
import { test } from 'node:test';
|
|
@@ -19,8 +20,8 @@ const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
|
19
20
|
const CRAFT = fs.readFileSync(path.join(HALL, 'craft-mode.js'), 'utf8');
|
|
20
21
|
const CSS = fs.readFileSync(path.join(HALL, 'style.css'), 'utf8');
|
|
21
22
|
|
|
22
|
-
const LOOKED = ['
|
|
23
|
-
const WORDS = {
|
|
23
|
+
const LOOKED = ['artist', 'patron'];
|
|
24
|
+
const WORDS = { artist: 'the studio · night', patron: 'the city' };
|
|
24
25
|
|
|
25
26
|
// A small DOM: enough tree for insertBefore / removeChild / nextSibling to be real.
|
|
26
27
|
function node(tag, id, cls) {
|
|
@@ -97,24 +98,24 @@ test('each looked mode frames its own door scene under the brand, and names itse
|
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
100
|
|
|
100
|
-
test('
|
|
101
|
+
test('ARTISAN AND NO MODE WEAR NO LOOK: no eyebrow; Artisan keeps only the Home plate, showing the city', () => {
|
|
101
102
|
const none = boot();
|
|
102
103
|
assert.equal(none.root.dataset.craft, undefined);
|
|
103
104
|
assert.equal(none.plates().length + none.marks().length, 0, 'no mode draws nothing');
|
|
104
|
-
for (const m of ['
|
|
105
|
+
for (const m of ['artisan']) {
|
|
105
106
|
const p = boot({ 'hall-craft': m });
|
|
106
107
|
assert.equal(p.marks().length, 0, `${m} at boot names no look`);
|
|
107
108
|
assert.equal(p.plates().length, 1, `${m}: the Home plate is there too (task 1005065)`);
|
|
108
109
|
assert.equal(p.plates()[0].getAttribute('data-plate'), m);
|
|
109
|
-
assert.ok(p.plates()[0].innerHTML.startsWith(p.craft.artHtml('
|
|
110
|
-
p.craft.choose('
|
|
110
|
+
assert.ok(p.plates()[0].innerHTML.startsWith(p.craft.artHtml('patron')), `${m}: its plate shows the city`);
|
|
111
|
+
p.craft.choose('patron');
|
|
111
112
|
assert.equal(p.plates().length, 1);
|
|
112
|
-
assert.equal(p.plates()[0].getAttribute('data-plate'), '
|
|
113
|
+
assert.equal(p.plates()[0].getAttribute('data-plate'), 'patron', 'switching mode swaps the plate');
|
|
113
114
|
p.craft.choose(m);
|
|
114
115
|
assert.equal(p.marks().length, 0, `switching to ${m} removes the eyebrow`);
|
|
115
116
|
assert.equal(p.plates()[0].getAttribute('data-plate'), m, 'and swaps the plate back');
|
|
116
117
|
}
|
|
117
|
-
assert.deepEqual(Object.keys(none.craft.LOOKS).sort(), [...LOOKED].sort(), 'only the
|
|
118
|
+
assert.deepEqual(Object.keys(none.craft.LOOKS).sort(), [...LOOKED].sort(), 'only the two looked crafts have a look');
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
test('the art is drawn once: craft-mode.js has one artHtml and exports it', () => {
|
|
@@ -126,11 +127,11 @@ test('the art is drawn once: craft-mode.js has one artHtml and exports it', () =
|
|
|
126
127
|
// Every selector in style.css that names a craft mode.
|
|
127
128
|
const CRAFT_SELECTORS = [...CSS.matchAll(/data-craft="([a-z]+)"/g)].map((m) => m[1]);
|
|
128
129
|
|
|
129
|
-
test('style.css names only the
|
|
130
|
+
test('style.css names only the two looked modes — Artisan has no rule at all', () => {
|
|
130
131
|
assert.ok(CRAFT_SELECTORS.length >= 12, `found ${CRAFT_SELECTORS.length} craft selectors`);
|
|
131
132
|
for (const m of CRAFT_SELECTORS) assert.ok(LOOKED.includes(m), `a rule names data-craft="${m}"`);
|
|
132
|
-
assert.doesNotMatch(CSS, /data-craft="building"
|
|
133
|
-
assert.doesNotMatch(CSS, /data-craft\]/, 'no rule matches ANY mode, which would dress
|
|
133
|
+
assert.doesNotMatch(CSS, /data-craft="(building|artisan|artistry|ideating|governing)"/, 'no rule for Artisan, and none left under a pre-rename id');
|
|
134
|
+
assert.doesNotMatch(CSS, /data-craft\]/, 'no rule matches ANY mode, which would dress Artisan too');
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
// The token values, light and dark, read from the stylesheet itself.
|
|
@@ -165,7 +166,7 @@ test('every mode colour has a light and a dark twin that keep their contrast', (
|
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
test('the shared treatment, the eyebrow and the plate are written as the direction says', () => {
|
|
168
|
-
const looked = 'html:is([data-craft="
|
|
169
|
+
const looked = 'html:is([data-craft="artist"], [data-craft="patron"])';
|
|
169
170
|
for (const target of ['.craft-switch__opt[aria-pressed="true"]', '.nav-item--active', '.nav-item--active::before', '.app-topbar']) {
|
|
170
171
|
assert.ok(CSS.includes(`${looked} ${target}`), `the looked modes restyle ${target}`);
|
|
171
172
|
}
|