@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/craft_mode_nav.mjs
CHANGED
|
@@ -127,15 +127,16 @@ function boot({ session = {}, local = {}, perms = { 'board.vote.cast': true } }
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
const CRAFTS = ['
|
|
130
|
+
const CRAFTS = ['artisan', 'artist', 'patron'];
|
|
131
131
|
|
|
132
132
|
test('THE ALWAYS-PRESENT LIST: one shared list, Builders first, then Collab and Profile, inside every mode', () => {
|
|
133
133
|
const { craft } = boot();
|
|
134
134
|
// task 1004685 (the living-city prototype): My agents joins as a PROPOSAL for the
|
|
135
135
|
// owner — every craft has agents, and round-3 board 6 pairs it with the city.
|
|
136
136
|
// task 1005071 (owner, 2026-11-04): Help — the old Docs group — joins, so every craft has the Primer.
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
// task 1005144 (ADR 0401): My agents and Modules left the sidebar for the one options menu.
|
|
138
|
+
assert.deepEqual([...craft.ALWAYS_TABS], ['roster', 'collab', 'profile', 'primer', 'commands', 'atlas', 'apidocs'],
|
|
139
|
+
'Builders (NAV id roster) heads the list (owner, 2026-10-01); Collab and Profile join it (owner, 2026-10-03); Help (task 1005071)');
|
|
139
140
|
for (const m of CRAFTS) {
|
|
140
141
|
const tabs = craft.tabsFor(m);
|
|
141
142
|
assert.ok(Array.isArray(tabs), `${m} has a named tab list`);
|
|
@@ -147,8 +148,8 @@ test('THE ALWAYS-PRESENT LIST: one shared list, Builders first, then Collab and
|
|
|
147
148
|
|
|
148
149
|
test('every tab a mode names is a real NAV item, and every NAV item lives in some mode', () => {
|
|
149
150
|
const { craft } = boot();
|
|
150
|
-
//
|
|
151
|
-
assert.ok(NAV_IDS.length >=
|
|
151
|
+
// 20 since task 1005144 moved four settings pages into the options menu (a sanity floor, not a count).
|
|
152
|
+
assert.ok(NAV_IDS.length >= 20, `read ${NAV_IDS.length} NAV ids from shell.js`);
|
|
152
153
|
const named = new Set(CRAFTS.flatMap((m) => craft.tabsFor(m)));
|
|
153
154
|
for (const id of named) assert.ok(NAV_IDS.includes(id), `${id} is a NAV id`);
|
|
154
155
|
// Home is the one exception: in a mode the craft plate IS the Home button
|
|
@@ -167,14 +168,14 @@ test('ONE HOME: no mode shows the Home tab, because the plate is Home in every m
|
|
|
167
168
|
assert.ok(none.showing().includes('home'), 'with no mode there is no plate, so the Home tab shows');
|
|
168
169
|
});
|
|
169
170
|
|
|
170
|
-
test('Government shows only in
|
|
171
|
+
test('Government shows only in Patron, and Patron is the shortest list', () => {
|
|
171
172
|
const { craft } = boot();
|
|
172
|
-
assert.ok(GOV_IDS.length >=
|
|
173
|
+
assert.ok(GOV_IDS.length >= 3, `read ${GOV_IDS.length} Government ids`);
|
|
173
174
|
for (const m of CRAFTS) {
|
|
174
|
-
for (const id of GOV_IDS) assert.equal(craft.tabsFor(m).includes(id), m === '
|
|
175
|
+
for (const id of GOV_IDS) assert.equal(craft.tabsFor(m).includes(id), m === 'patron', `${id} in ${m}`);
|
|
175
176
|
}
|
|
176
177
|
const lengths = CRAFTS.map((m) => craft.tabsFor(m).filter((id) => !GOV_IDS.includes(id)).length);
|
|
177
|
-
assert.equal(Math.min(...lengths), lengths[CRAFTS.indexOf('
|
|
178
|
+
assert.equal(Math.min(...lengths), lengths[CRAFTS.indexOf('patron')], 'outside its own Government group, Patron shows the fewest tabs');
|
|
178
179
|
});
|
|
179
180
|
|
|
180
181
|
test('NO MODE IS TODAY\'S NAV: every tab the gate allows, nothing marked — and a stored Legacy is no mode', () => {
|
|
@@ -196,19 +197,19 @@ test('each mode shows its own tabs, and switching updates the nav live', () => {
|
|
|
196
197
|
assert.equal(p.root.dataset.craft, m);
|
|
197
198
|
assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor(m)), `${m} shows exactly its list`);
|
|
198
199
|
for (const id of ['roster', 'collab', 'profile']) assert.ok(p.showing().includes(id), `${id} stays in ${m}`);
|
|
199
|
-
assert.equal(p.headShowing('run-the-project'), m === '
|
|
200
|
+
assert.equal(p.headShowing('run-the-project'), m === 'patron', `the Government head shows only where its tabs do (${m})`);
|
|
200
201
|
}
|
|
201
|
-
p.craft.choose('
|
|
202
|
-
assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor('
|
|
202
|
+
p.craft.choose('artisan');
|
|
203
|
+
assert.deepEqual(new Set(p.showing()), new Set(p.craft.tabsFor('artisan')), 'back to Artisan, its tabs return without a reload');
|
|
203
204
|
});
|
|
204
205
|
|
|
205
206
|
test('a mode never shows a tab the gate hid, and a later gate pass keeps the mode', () => {
|
|
206
|
-
const p = boot({ session: { 'hall-craft': '
|
|
207
|
+
const p = boot({ session: { 'hall-craft': 'patron' } });
|
|
207
208
|
p.signIn('thetes');
|
|
208
|
-
assert.ok(!p.showing().includes('watch'), 'a thetes in
|
|
209
|
+
assert.ok(!p.showing().includes('watch'), 'a thetes in Patron does not get the metic-floored Watch tab');
|
|
209
210
|
assert.ok(!p.headShowing('run-the-project'), 'nor its group head');
|
|
210
211
|
// task 1005071: sidebar order — Work, Community, You, Govern, then Help (every craft).
|
|
211
|
-
assert.deepEqual(p.showing(), ['blockers', 'collab', 'roster', 'profile', '
|
|
212
|
+
assert.deepEqual(p.showing(), ['blockers', 'collab', 'roster', 'profile', 'city', 'board-room', 'primer', 'commands', 'atlas', 'apidocs'], 'what a thetes board member in Patron sees');
|
|
212
213
|
p.signIn('archon');
|
|
213
214
|
assert.ok(p.showing().includes('watch'), 'promoted, the gate lets it through and the mode keeps it');
|
|
214
215
|
assert.ok(p.headShowing('run-the-project'), 'and its group head comes back with it, which only a mode pass after the gate can know');
|
|
@@ -224,8 +225,8 @@ test('shell.js applies the mode AFTER its gate loop, and style.css hides what th
|
|
|
224
225
|
assert.doesNotMatch(CRAFT, /\.hidden\s*=/, 'the mode never writes `hidden`, which the gate owns');
|
|
225
226
|
});
|
|
226
227
|
|
|
227
|
-
// ---------- the
|
|
228
|
-
//
|
|
228
|
+
// ---------- the Patron fold (owner, 2026-10-08; Governing until task 1005141) ----------
|
|
229
|
+
// Patron leads with the City and folds its other own tabs into one dropdown
|
|
229
230
|
// under it. The fold MOVES nodes, which the parsed sidebar above cannot model, so
|
|
230
231
|
// these cases run the real craft-mode.js against a small working tree: nodes with
|
|
231
232
|
// parents, insertBefore and comments, laid out as shell.js's NAV groups are.
|
|
@@ -309,35 +310,36 @@ function bootTree({ mode = null, activeId = null } = {}) {
|
|
|
309
310
|
return { craft, sidebar, fold, ids, order, byId };
|
|
310
311
|
}
|
|
311
312
|
|
|
312
|
-
test('
|
|
313
|
+
test('PATRON LEADS WITH THE CITY: its other tabs fold into one dropdown right under it', () => {
|
|
313
314
|
const before = bootTree().order();
|
|
314
|
-
const p = bootTree({ mode: '
|
|
315
|
+
const p = bootTree({ mode: 'patron' });
|
|
315
316
|
const fold = p.fold();
|
|
316
|
-
assert.ok(fold, 'the dropdown exists in
|
|
317
|
+
assert.ok(fold, 'the dropdown exists in Patron');
|
|
317
318
|
assert.equal(p.byId('nav-city').nextSibling, fold, 'directly under the City tab');
|
|
318
|
-
const own = p.craft.tabsFor('
|
|
319
|
-
assert.deepEqual(JSON.parse(JSON.stringify(p.ids(fold))), JSON.parse(JSON.stringify(own)), 'every other
|
|
319
|
+
const own = p.craft.tabsFor('patron').filter((id) => !p.craft.ALWAYS_TABS.includes(id) && id !== 'city');
|
|
320
|
+
assert.deepEqual(JSON.parse(JSON.stringify(p.ids(fold))), JSON.parse(JSON.stringify(own)), 'every other Patron tab is in it, in the mode\'s order');
|
|
320
321
|
assert.ok(fold.classList.contains('nav-group--collapsed'), 'a dropdown starts closed');
|
|
321
322
|
const head = fold.querySelector('.nav-group__head');
|
|
322
323
|
assert.equal(head.getAttribute('aria-expanded'), 'false');
|
|
323
324
|
assert.equal(head.getAttribute('aria-controls'), 'nav-items-craft-fold', 'the head names the region it opens');
|
|
324
|
-
|
|
325
|
+
// "More governing" until task 1005141; "More patron" does not read, so the label is plain "More".
|
|
326
|
+
assert.match(head.innerHTML, /<span class="nav-group__head-label">More<\/span>/);
|
|
325
327
|
assert.ok(head.hasAttribute('data-craft-off') === false, 'its head shows while a tab inside does');
|
|
326
328
|
assert.ok(p.sidebar.querySelectorAll('.nav-group__head').find((h) => h.dataset.group === 'run-the-project').hasAttribute('data-craft-off'),
|
|
327
329
|
'the emptied Government head hides, so no label sits over nothing');
|
|
328
|
-
p.craft.choose('
|
|
329
|
-
assert.equal(p.fold(), null, 'leaving
|
|
330
|
+
p.craft.choose('artisan');
|
|
331
|
+
assert.equal(p.fold(), null, 'leaving Patron removes the dropdown');
|
|
330
332
|
assert.deepEqual(p.order(), before, 'and puts every tab back exactly where it was');
|
|
331
|
-
p.craft.choose('
|
|
332
|
-
p.craft.choose('
|
|
333
|
+
p.craft.choose('patron');
|
|
334
|
+
p.craft.choose('patron');
|
|
333
335
|
assert.equal(p.sidebar.querySelectorAll('.craft-fold').length, 1, 'choosing it again builds no second dropdown');
|
|
334
336
|
});
|
|
335
337
|
|
|
336
338
|
test('the dropdown opens on its own when the page you are on is inside it', () => {
|
|
337
|
-
const p = bootTree({ mode: '
|
|
339
|
+
const p = bootTree({ mode: 'patron', activeId: 'watch' });
|
|
338
340
|
assert.ok(!p.fold().classList.contains('nav-group--collapsed'), 'on Watch, the dropdown is open');
|
|
339
341
|
assert.equal(p.fold().querySelector('.nav-group__head').getAttribute('aria-expanded'), 'true');
|
|
340
|
-
assert.equal(bootTree({ mode: '
|
|
342
|
+
assert.equal(bootTree({ mode: 'artist' }).fold(), null, 'no other mode folds anything');
|
|
341
343
|
assert.equal(bootTree().fold(), null, 'nor does no mode');
|
|
342
344
|
});
|
|
343
345
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/craft_review_mode.mjs — artist review mode (task 1004686, goal 1000095).
|
|
2
2
|
//
|
|
3
|
-
// Owner ask, 2026-10-09: the artist picks one of the
|
|
4
|
-
//
|
|
3
|
+
// Owner ask, 2026-10-09: the artist picks one of the experiences (since task 1005141
|
|
4
|
+
// the three: Patron, Artisan, Artist — Ideator folded into Artist) and reviews the hall in that mode's view, with
|
|
5
5
|
// graffiti across the top bar's empty space while it lasts. Since task 1004771
|
|
6
6
|
// the choice lives in the docked review panel's own Review mode row
|
|
7
7
|
// (page-notes.js; tests/page_notes_panel.mjs runs that row), not a top-bar menu. These cases run the REAL
|
|
@@ -97,30 +97,30 @@ function boot({ session = {}, store = {}, narrow = false } = {}) {
|
|
|
97
97
|
tools: () => all(side).filter((n) => n.hasAttribute('data-craft-tool')).map((n) => n.id.replace('craft-tool-', '')),
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
const artist = () => ({ 'hall-craft': '
|
|
100
|
+
const artist = () => ({ 'hall-craft': 'artist', 'hall-craft-asked': '1' });
|
|
101
101
|
|
|
102
|
-
test('NO TOP-BAR REVIEW BUTTON: in
|
|
102
|
+
test('NO TOP-BAR REVIEW BUTTON: in Artist the bar holds Edit words and Ideas, and no review-mode menu', async () => {
|
|
103
103
|
const p = boot({ session: artist() });
|
|
104
104
|
await flush();
|
|
105
|
-
assert.deepEqual(p.tools(), ['paintbrush'], 'the review panel docks at the edge instead (task 1004771)');
|
|
105
|
+
assert.deepEqual(p.tools(), ['paintbrush', 'lightbulb'], 'the review panel docks at the edge instead (task 1004771); the lightbulb joined from Ideating (task 1005141)');
|
|
106
106
|
assert.equal(p.byId('craft-review'), null, 'no split pill');
|
|
107
107
|
assert.equal(p.byId('craft-review-menu'), null, 'no menu');
|
|
108
108
|
assert.deepEqual(p.opened, ['dock']);
|
|
109
109
|
assert.equal(p.byId('craft-graffiti'), null, 'no wall until a review starts');
|
|
110
110
|
assert.equal(p.root.getAttribute('data-craft-review'), null);
|
|
111
|
-
assert.deepEqual(JSON.parse(JSON.stringify(p.craft.experiences().map((x) => x.label))), ['
|
|
111
|
+
assert.deepEqual(JSON.parse(JSON.stringify(p.craft.experiences().map((x) => x.label))), ['Patron experience', 'Artisan experience', 'Artist experience'], 'the three, in the owner\'s order (Ideator folded into Artist), for the review panel row');
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
test('PICKING an experience: the hall wears that mode, the artist keeps the Review tools, and the wall goes up', async () => {
|
|
115
115
|
const p = boot({ session: artist() });
|
|
116
|
-
p.craft.review('
|
|
117
|
-
assert.equal(p.root.dataset.craft, '
|
|
118
|
-
assert.equal(p.root.getAttribute('data-craft-review'), '
|
|
119
|
-
assert.equal(p.craft.current(), '
|
|
120
|
-
assert.equal(p.craft.reviewing(), '
|
|
121
|
-
assert.equal(p.session['hall-craft'], '
|
|
122
|
-
assert.equal(p.session['hall-craft-review'], '
|
|
123
|
-
assert.deepEqual(p.tools(), ['paintbrush'], '
|
|
116
|
+
p.craft.review('patron');
|
|
117
|
+
assert.equal(p.root.dataset.craft, 'patron', 'the hall wears Patron: its tabs, look and tallies follow');
|
|
118
|
+
assert.equal(p.root.getAttribute('data-craft-review'), 'patron');
|
|
119
|
+
assert.equal(p.craft.current(), 'patron');
|
|
120
|
+
assert.equal(p.craft.reviewing(), 'patron');
|
|
121
|
+
assert.equal(p.session['hall-craft'], 'artist', 'the artist\'s own mode stays Artist');
|
|
122
|
+
assert.equal(p.session['hall-craft-review'], 'patron', 'the review lasts the session');
|
|
123
|
+
assert.deepEqual(p.tools(), ['paintbrush', 'lightbulb'], 'the artist\'s tools stay in the bar');
|
|
124
124
|
await flush();
|
|
125
125
|
assert.ok(p.opened.filter((x) => x === 'dock').length >= 2, 'the docked panel is told again, so its mode row repaints');
|
|
126
126
|
const wall = p.byId('craft-graffiti');
|
|
@@ -135,50 +135,55 @@ test('PICKING an experience: the hall wears that mode, the artist keeps the Revi
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
test('IT LASTS THE SESSION: a reload keeps the review; a new session starts without one', () => {
|
|
138
|
-
const session = { ...artist(), 'hall-craft-review': '
|
|
138
|
+
const session = { ...artist(), 'hall-craft-review': 'artisan' };
|
|
139
139
|
const p = boot({ session });
|
|
140
|
-
assert.equal(p.root.dataset.craft, '
|
|
141
|
-
assert.match(p.side.innerHTML, /data-craft-opt="
|
|
140
|
+
assert.equal(p.root.dataset.craft, 'artisan', 'a reload wears the experience under review');
|
|
141
|
+
assert.match(p.side.innerHTML, /data-craft-opt="artisan" aria-pressed="true"/, 'the switch shows the experience being worn');
|
|
142
142
|
assert.ok(p.byId('craft-graffiti'), 'the wall goes straight back up');
|
|
143
|
-
const fresh = boot({ session: {}, store: { 'hall-craft-keep': '
|
|
144
|
-
assert.equal(fresh.root.dataset.craft, '
|
|
143
|
+
const fresh = boot({ session: {}, store: { 'hall-craft-keep': 'artist' } });
|
|
144
|
+
assert.equal(fresh.root.dataset.craft, 'artist', 'a new session is the artist\'s own Artist');
|
|
145
145
|
assert.equal(fresh.byId('craft-graffiti'), null);
|
|
146
|
-
const notArtist = boot({ session: { 'hall-craft': '
|
|
147
|
-
assert.equal(notArtist.root.dataset.craft, '
|
|
146
|
+
const notArtist = boot({ session: { 'hall-craft': 'artisan', 'hall-craft-asked': '1', 'hall-craft-review': 'patron' } });
|
|
147
|
+
assert.equal(notArtist.root.dataset.craft, 'artisan', 'a stored review outside Artist is ignored: only an artist reviews');
|
|
148
148
|
assert.equal(notArtist.craft.reviewing(), null);
|
|
149
149
|
const junk = boot({ session: { ...artist(), 'hall-craft-review': 'legacy' } });
|
|
150
|
-
assert.equal(junk.root.dataset.craft, '
|
|
150
|
+
assert.equal(junk.root.dataset.craft, 'artist', 'a review that is not one of the three reads as none');
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
// Ideating folded into Artist (task 1005141): the lightbulb is the artist's OWN tool now,
|
|
154
|
+
// so reviewing another experience brings no guest — Patron and Artisan carry no tool.
|
|
155
|
+
test('REVIEWING another experience keeps the artist\'s own tools, the lightbulb among them, and none is a guest', () => {
|
|
154
156
|
const p = boot({ session: artist() });
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
assert.
|
|
157
|
+
for (const x of ['patron', 'artisan']) {
|
|
158
|
+
p.craft.review(x);
|
|
159
|
+
assert.deepEqual(p.tools(), ['paintbrush', 'lightbulb'], `reviewing ${x}: the artist's tools, in order`);
|
|
160
|
+
for (const t of ['paintbrush', 'lightbulb']) assert.ok(!p.byId('craft-tool-' + t).classList.contains('craft-tool--guest'), `${t} is the artist's own, never a guest`);
|
|
161
|
+
}
|
|
162
|
+
p.craft.choose('artisan');
|
|
163
|
+
assert.deepEqual(p.tools(), [], 'choosing Artisan for real drops the artist\'s tools');
|
|
164
|
+
p.craft.choose('artist');
|
|
165
|
+
assert.deepEqual(p.tools(), ['paintbrush', 'lightbulb'], 'and Artist brings both back');
|
|
166
|
+
assert.ok(!p.byId('craft-tool-lightbulb').classList.contains('craft-tool--guest'));
|
|
162
167
|
});
|
|
163
168
|
|
|
164
169
|
test('ENDING it: Stop reviewing, or any mode from the switch, puts the artist back and takes the wall down', () => {
|
|
165
170
|
const p = boot({ session: artist() });
|
|
166
|
-
p.craft.review('
|
|
167
|
-
assert.equal(p.root.dataset.craft, '
|
|
171
|
+
p.craft.review('artisan');
|
|
172
|
+
assert.equal(p.root.dataset.craft, 'artisan');
|
|
168
173
|
p.craft.stopReview();
|
|
169
|
-
assert.equal(p.root.dataset.craft, '
|
|
174
|
+
assert.equal(p.root.dataset.craft, 'artist', 'Stop reviewing is back in Artist');
|
|
170
175
|
assert.equal(p.root.getAttribute('data-craft-review'), null);
|
|
171
176
|
assert.equal(p.byId('craft-graffiti'), null, 'the wall comes down');
|
|
172
177
|
assert.equal(p.session['hall-craft-review'], undefined, 'and the session forgets it');
|
|
173
|
-
p.craft.review('
|
|
174
|
-
p.craft.choose('
|
|
175
|
-
assert.equal(p.craft.reviewing(), null, 'choosing a mode from the switch, even
|
|
178
|
+
p.craft.review('patron');
|
|
179
|
+
p.craft.choose('artist');
|
|
180
|
+
assert.equal(p.craft.reviewing(), null, 'choosing a mode from the switch, even Artist, ends a review');
|
|
176
181
|
assert.equal(p.byId('craft-graffiti'), null);
|
|
177
|
-
p.craft.review('
|
|
178
|
-
p.craft.choose('
|
|
179
|
-
assert.equal(p.root.dataset.craft, '
|
|
180
|
-
assert.equal(p.root.getAttribute('data-craft-review'), null, '
|
|
181
|
-
assert.ok(p.opened.includes('close'), 'and the review panel leaves with
|
|
182
|
+
p.craft.review('patron');
|
|
183
|
+
p.craft.choose('patron');
|
|
184
|
+
assert.equal(p.root.dataset.craft, 'patron');
|
|
185
|
+
assert.equal(p.root.getAttribute('data-craft-review'), null, 'Patron chosen for real is not a review');
|
|
186
|
+
assert.ok(p.opened.includes('close'), 'and the review panel leaves with Artist');
|
|
182
187
|
});
|
|
183
188
|
|
|
184
189
|
test('THE WALL is scribbled in CSS only, in ink alone, and stands still under reduced motion', () => {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// tests/crafts_vocabulary.mjs
|
|
2
|
+
//
|
|
3
|
+
// Unit test for src/crafts.js (task 1005141, BONGOS-V3 C1) — the ONE place the
|
|
4
|
+
// craft vocabulary lives: artisan (was engineer), artist (absorbed ideator) and
|
|
5
|
+
// patron, plus normalizeCraft() that every input boundary runs an old spelling
|
|
6
|
+
// through. Pure, no database. Run: node tests/crafts_vocabulary.mjs
|
|
7
|
+
|
|
8
|
+
import { strict as assert } from 'node:assert';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const crafts = require('../src/crafts.js');
|
|
13
|
+
const { normalizeCraft, normalizeCrafts } = crafts;
|
|
14
|
+
|
|
15
|
+
let passed = 0;
|
|
16
|
+
let failed = 0;
|
|
17
|
+
function test(name, fn) {
|
|
18
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
19
|
+
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('the module exports only the two functions callers use (knip ratchet)', () => {
|
|
23
|
+
assert.deepEqual(Object.keys(crafts).sort(), ['normalizeCraft', 'normalizeCrafts']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('normalizeCraft maps every old spelling to today\'s', () => {
|
|
27
|
+
assert.equal(normalizeCraft('engineer'), 'artisan');
|
|
28
|
+
assert.equal(normalizeCraft('builder'), 'artisan', 'the old help-request spelling');
|
|
29
|
+
assert.equal(normalizeCraft('building'), 'artisan', 'the old hall craft-mode id');
|
|
30
|
+
assert.equal(normalizeCraft('ideator'), 'artist', 'ideation folded into artist');
|
|
31
|
+
assert.equal(normalizeCraft('ideating'), 'artist');
|
|
32
|
+
assert.equal(normalizeCraft('artistry'), 'artist');
|
|
33
|
+
assert.equal(normalizeCraft('governing'), 'patron');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('normalizeCraft leaves today\'s words alone and is case/space tolerant', () => {
|
|
37
|
+
for (const c of ['artisan', 'artist', 'patron', 'ui']) assert.equal(normalizeCraft(c), c);
|
|
38
|
+
assert.equal(normalizeCraft(' Engineer '), 'artisan');
|
|
39
|
+
assert.equal(normalizeCraft('IDEATOR'), 'artist');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('an unknown word passes through, so a module-contributed craft is never eaten', () => {
|
|
43
|
+
assert.equal(normalizeCraft('wizard'), 'wizard');
|
|
44
|
+
assert.equal(normalizeCraft('sound-design'), 'sound-design');
|
|
45
|
+
// Inherited Object keys are not legacy spellings.
|
|
46
|
+
assert.equal(normalizeCraft('constructor'), 'constructor');
|
|
47
|
+
assert.equal(normalizeCraft('toString'), 'tostring');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('non-strings pass through untouched for the caller to validate', () => {
|
|
51
|
+
assert.equal(normalizeCraft(null), null);
|
|
52
|
+
assert.equal(normalizeCraft(undefined), undefined);
|
|
53
|
+
assert.equal(normalizeCraft(7), 7);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('normalizeCrafts dedupes the fold in first-seen order', () => {
|
|
57
|
+
assert.deepEqual(normalizeCrafts(['artist', 'ideator']), ['artist'], 'one craft now, not two');
|
|
58
|
+
assert.deepEqual(normalizeCrafts(['ideator', 'engineer', 'artist']), ['artist', 'artisan']);
|
|
59
|
+
assert.deepEqual(normalizeCrafts(['engineer', 'ui', 'artisan']), ['artisan', 'ui']);
|
|
60
|
+
assert.deepEqual(normalizeCrafts([]), []);
|
|
61
|
+
assert.equal(normalizeCrafts(null), null, 'a non-array passes through');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('the doorway exposes the same module', () => {
|
|
65
|
+
const api = require('../src/module-api.js');
|
|
66
|
+
assert.equal(api.crafts, crafts);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
70
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -40,13 +40,13 @@ const eachImpl = (rows, prefs, limit, check) => {
|
|
|
40
40
|
for (const [name, out] of bothRank(rows, prefs, limit)) check(out, name);
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
// A claimable row. discipline defaults OUTSIDE the caller's prefs (['
|
|
43
|
+
// A claimable row. discipline defaults OUTSIDE the caller's prefs (['artisan'])
|
|
44
44
|
// so the partition step never eats the fixture; credits/minutes are equal by
|
|
45
45
|
// default so priority is the only live sort key unless a test says otherwise.
|
|
46
46
|
const row = (id, priority, extra = {}) => ({
|
|
47
47
|
id, priority, discipline: 'design', credits_reward: 10, est_minutes: 10, ...extra,
|
|
48
48
|
});
|
|
49
|
-
const PREFS = ['
|
|
49
|
+
const PREFS = ['artisan'];
|
|
50
50
|
const ids = (out) => out.map((t) => t.id);
|
|
51
51
|
|
|
52
52
|
const { test, summary } = makeRunner();
|
|
@@ -129,7 +129,7 @@ await test('shorter est_minutes, then id, break the remaining ties', () => {
|
|
|
129
129
|
|
|
130
130
|
await test('partitioning is unchanged: outside-prefs only, no unclassified, empty prefs → []', () => {
|
|
131
131
|
const rows = [
|
|
132
|
-
row('mine', 1, { discipline: '
|
|
132
|
+
row('mine', 1, { discipline: 'artisan' }),
|
|
133
133
|
row('untriaged', 1, { discipline: 'unclassified' }),
|
|
134
134
|
row('theirs', 4, { discipline: 'design' }),
|
|
135
135
|
];
|
|
@@ -142,7 +142,7 @@ await test('partitioning is unchanged: outside-prefs only, no unclassified, empt
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
await test('the two copies agree row-for-row on a mixed corpus', () => {
|
|
145
|
-
const disciplines = ['design', 'ops', 'research', '
|
|
145
|
+
const disciplines = ['design', 'ops', 'research', 'artisan', 'unclassified'];
|
|
146
146
|
const priorities = [1, 2, 3, 4, 5, null, undefined, '', '2', 'junk'];
|
|
147
147
|
const rows = [];
|
|
148
148
|
for (let i = 0; i < 60; i++) {
|
|
@@ -152,7 +152,7 @@ await test('the two copies agree row-for-row on a mixed corpus', () => {
|
|
|
152
152
|
est_minutes: (i % 4) * 15,
|
|
153
153
|
}));
|
|
154
154
|
}
|
|
155
|
-
const [[, fromCanonical], [, fromMirror]] = bothRank(rows, ['
|
|
155
|
+
const [[, fromCanonical], [, fromMirror]] = bothRank(rows, ['artisan'], 25);
|
|
156
156
|
assert.ok(fromCanonical.length > 5, 'fixture must actually exercise the ranking');
|
|
157
157
|
assert.deepEqual(ids(fromMirror), ids(fromCanonical),
|
|
158
158
|
'start.js is a hand-copied mirror — it must produce the identical order');
|
package/tests/fitness.mjs
CHANGED
|
@@ -150,18 +150,16 @@ test('role pack guard actually CATCHES a missing pack, a pointerless entry, and
|
|
|
150
150
|
|
|
151
151
|
// (1) the silent runtime failure: a pack path that resolves to nothing.
|
|
152
152
|
const missing = rolePacks.checkRolePacks(withRegistry({
|
|
153
|
-
|
|
153
|
+
artisan: { pack: 'docs/packs/__gone__.md', directive: 'x' },
|
|
154
154
|
artist: { pack: 'docs/packs/artist.md', directive: 'x' },
|
|
155
|
-
ideator: { pack: 'docs/packs/ideator.md', directive: 'x' },
|
|
156
155
|
}));
|
|
157
156
|
assert.equal(missing.hardFail, true, 'a pack that is not in the tree must hard-fail');
|
|
158
157
|
assert.ok(missing.violations.some((v) => /__gone__/.test(v) && /not in the tree/.test(v)), 'the violation must name the unresolvable path');
|
|
159
158
|
|
|
160
159
|
// (2) an entry routing a claim to no playbook at all.
|
|
161
160
|
const pointerless = rolePacks.checkRolePacks(withRegistry({
|
|
162
|
-
|
|
161
|
+
artisan: { pack: 'docs/packs/artisan.md', directive: 'x' },
|
|
163
162
|
artist: { pack: 'docs/packs/artist.md', directive: 'x' },
|
|
164
|
-
ideator: { pack: 'docs/packs/ideator.md', directive: 'x' },
|
|
165
163
|
ui: { directive: 'x' },
|
|
166
164
|
}));
|
|
167
165
|
assert.equal(pointerless.hardFail, true, 'an entry with neither pack nor skill must hard-fail');
|
|
@@ -169,21 +167,19 @@ test('role pack guard actually CATCHES a missing pack, a pointerless entry, and
|
|
|
169
167
|
|
|
170
168
|
// (3) a craft quietly dropped back into the kernel — the split's own shape.
|
|
171
169
|
const short = rolePacks.checkRolePacks(withRegistry({
|
|
172
|
-
|
|
173
|
-
artist: {
|
|
174
|
-
ideator: { skill: 'ideate', directive: 'x' },
|
|
170
|
+
artisan: { pack: 'docs/packs/artisan.md', directive: 'x' },
|
|
171
|
+
artist: { skill: 'ideate', directive: 'x' },
|
|
175
172
|
}));
|
|
176
|
-
assert.equal(short.hardFail, true, 'fewer than
|
|
177
|
-
assert.ok(short.violations.some((v) => /only
|
|
173
|
+
assert.equal(short.hardFail, true, 'fewer than two packs must hard-fail (task 1005141: Artisan + Artist)');
|
|
174
|
+
assert.ok(short.violations.some((v) => /only 1 craft/.test(v)));
|
|
178
175
|
|
|
179
176
|
// (4) an over-budget pack — the "CI budgets each pack" half of the criterion.
|
|
180
177
|
const fat = rolePacks.checkRolePacks({
|
|
181
178
|
readFileSync: (p, e) => {
|
|
182
179
|
if (String(p).includes('discipline-modes.json')) {
|
|
183
180
|
return JSON.stringify({ modes: {
|
|
184
|
-
|
|
181
|
+
artisan: { pack: 'docs/packs/artisan.md', directive: 'x' },
|
|
185
182
|
artist: { pack: 'docs/packs/artist.md', directive: 'x' },
|
|
186
|
-
ideator: { pack: 'docs/packs/ideator.md', directive: 'x' },
|
|
187
183
|
} });
|
|
188
184
|
}
|
|
189
185
|
return 'x'.repeat(rolePacks.PACK_CHAR_HARD_MAX + 1);
|
|
@@ -89,7 +89,7 @@ try {
|
|
|
89
89
|
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,$2,$3,'M07 Owner','archon'), ($4,$5,$6,'Ada','archon')`,
|
|
90
90
|
[OWNER, `m07-o-${RUN}`, `m07-owner-${RUN}`, ADA, `m07-a-${RUN}`, `m07-ada-${RUN}`]);
|
|
91
91
|
for (const [id, title] of [[TASK_A, 'Build the paid thing'], [TASK_B, 'Build the second thing'], [TASK_C, 'Build the last thing']]) {
|
|
92
|
-
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline) VALUES ($1,$2,$3,'ready',10,'feature','
|
|
92
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline) VALUES ($1,$2,$3,'ready',10,'feature','artisan')`, [id, VER, title]);
|
|
93
93
|
}
|
|
94
94
|
await cash(TASK_A, '100'); await cash(TASK_B, '80'); await cash(TASK_C, '40');
|
|
95
95
|
await settings.setSetting(WALL_KEY, 'hold_at_claim', OWNER);
|
package/tests/genesis_home.mjs
CHANGED
|
@@ -37,7 +37,7 @@ test('genesis files a task per stage, keyed by a ref that names the step, not it
|
|
|
37
37
|
assert.equal(plain[s].length, 1);
|
|
38
38
|
assert.equal(plain[s][0].ref, `genesis:${s}`);
|
|
39
39
|
assert.equal(plain[s][0].kind, 'decision');
|
|
40
|
-
assert.equal(plain[s][0].discipline, '
|
|
40
|
+
assert.equal(plain[s][0].discipline, 'artist');
|
|
41
41
|
}
|
|
42
42
|
const existing = genesisChecklist({ existing: true });
|
|
43
43
|
assert.deepEqual(existing.scope.map((t) => t.ref), ['genesis:scope:existing', 'genesis:scope'],
|
|
@@ -229,6 +229,26 @@ test('decide → five stages through the Board Room → alive, and the hall open
|
|
|
229
229
|
assert.equal((await gh.startFounding(1, w.deps())).reason, 'already_alive', 'genesis happens once');
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
test('the owner can skip genesis from any stage: no sitting, hall opens, recorded once', async () => {
|
|
233
|
+
const w = world();
|
|
234
|
+
assert.equal((await gh.skipGenesis(1, w.deps())).reason, 'not_founding', 'nothing to skip on a project that is not founding');
|
|
235
|
+
await gh.startFounding(1, w.deps());
|
|
236
|
+
const r = await gh.skipGenesis(1, w.deps());
|
|
237
|
+
assert.deepEqual(r, { ok: true, skipped: true, from_stage: 'decide' }, 'skippable while still deciding, no plan needed');
|
|
238
|
+
assert.equal(await gh.readGenesis(w.deps()), null, 'founding mode is off: the full hall opens');
|
|
239
|
+
assert.ok(w.store.get(fm.KEYS.ENDED_AT), 'the founding window closed');
|
|
240
|
+
assert.equal(JSON.parse(w.store.get(fm.KEYS.SKIPPED_AT)).stage, 'decide', 'the record says it was skipped, and from where');
|
|
241
|
+
assert.equal(w.opened.length, 0, 'the Board Room was never asked');
|
|
242
|
+
assert.equal((await gh.skipGenesis(1, w.deps())).reason, 'already_alive', 'genesis happens once');
|
|
243
|
+
assert.equal((await gh.startFounding(1, w.deps())).reason, 'already_alive');
|
|
244
|
+
|
|
245
|
+
const mid = world();
|
|
246
|
+
await gh.startFounding(1, mid.deps());
|
|
247
|
+
await gh.savePlan({}, 1, mid.deps());
|
|
248
|
+
assert.equal((await gh.skipGenesis(1, mid.deps())).from_stage, 'scope', 'skippable mid-stage with unfinished tasks');
|
|
249
|
+
assert.equal(await gh.readGenesis(mid.deps()), null);
|
|
250
|
+
});
|
|
251
|
+
|
|
232
252
|
test('only the stage in progress can be put to the board', async () => {
|
|
233
253
|
const w = world();
|
|
234
254
|
await gh.startFounding(1, w.deps());
|
package/tests/github_chip.mjs
CHANGED
|
@@ -29,8 +29,8 @@ const BAR = read(path.join(LANDING, 'assets', 'hub-bar.js'));
|
|
|
29
29
|
const HUB = read(path.join(LANDING, 'projects.html'));
|
|
30
30
|
const SHELL = read(path.join(HALL, 'shell.js'));
|
|
31
31
|
const chip = require(path.join(HALL, 'github-chip.js'));
|
|
32
|
-
// the
|
|
33
|
-
const BUILDING = /
|
|
32
|
+
// the artisan's glyph IS the Artisan craft's (craft-mode.js ICONS.artisan; Building until task 1005141)
|
|
33
|
+
const BUILDING = /artisan: '<path d="([^"]+)"\/>'/.exec(read(path.join(HALL, 'craft-mode.js')))[1];
|
|
34
34
|
|
|
35
35
|
// ── a DOM just big enough for the three copies ──────────────────────────────
|
|
36
36
|
class El {
|
|
@@ -104,7 +104,7 @@ test('the hall, executed: Google only draws one small link with the engineer gly
|
|
|
104
104
|
assert.equal(a.href, 'https://example.com/api/bongos/auth/link/github/start');
|
|
105
105
|
assert.equal(a.className, 'gh-chip');
|
|
106
106
|
assert.equal(labelOf(a), 'Link GitHub');
|
|
107
|
-
assert.equal(svgPath(a), BUILDING, 'the
|
|
107
|
+
assert.equal(svgPath(a), BUILDING, 'the artisan craft’s own glyph');
|
|
108
108
|
assert.match(a.attrs['aria-label'], /coding/);
|
|
109
109
|
});
|
|
110
110
|
|