@bongos/core 1.24.79 → 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 +495 -350
- 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 +2 -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 +70 -0
- package/scripts/bongos/autobongos-loop.js +1 -1
- package/scripts/bongos/discipline-modes.json +7 -12
- package/scripts/bongos/fitness.js +1 -1
- package/scripts/bongos/gen-hall-sitemap.js +7 -7
- package/scripts/bongos/grade-smoke.js +2 -2
- package/scripts/bongos/init.js +2 -2
- package/scripts/bongos/newcomer-floor.config.json +1 -2
- package/scripts/bongos/newcomer-floor.js +4 -3
- package/scripts/bongos/newcomer-floor.templates.json +10 -12
- package/scripts/bongos/newcomer-floor.test.js +47 -38
- package/scripts/bongos/role-pack-guard.js +5 -4
- package/scripts/bongos/seed-tasks.example.json +3 -3
- package/scripts/bongos/setup.js +20 -107
- package/scripts/bongos/start.js +2 -2
- package/scripts/bongos/task.js +5 -4
- package/scripts/bongos/triage.js +1 -1
- package/scripts/bongos/worktree.js +16 -4
- package/scripts/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/blocker_hall_live_proof.mjs +1 -1
- package/tests/blocker_tasks_named_db.mjs +2 -2
- package/tests/canonical_profile_url.mjs +1 -1
- package/tests/cap_newcomer_tasks.mjs +13 -13
- package/tests/character_creation.mjs +294 -0
- package/tests/claim_gate_open_blocker.mjs +2 -2
- package/tests/claim_gate_rebase.mjs +6 -6
- package/tests/claude_materialize.mjs +1 -1
- package/tests/clawback_confirmed_backlog.mjs +3 -3
- package/tests/collab_page.mjs +8 -9
- package/tests/collab_self_settling.mjs +2 -2
- package/tests/community_leaderboard_route.mjs +13 -5
- package/tests/community_proof_db.mjs +3 -0
- package/tests/community_search_route.mjs +7 -6
- package/tests/conductor.mjs +1 -1
- package/tests/conductor_main_e2e.mjs +8 -8
- package/tests/copy_desk_flags_db.mjs +1 -1
- package/tests/copy_desk_page_approve.mjs +2 -2
- package/tests/copy_desk_page_claim.mjs +2 -2
- package/tests/copy_desk_page_next.mjs +1 -1
- package/tests/copy_desk_page_release.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +2 -2
- package/tests/core_258_box_session_claims_db.mjs +1 -1
- package/tests/craft_mode.mjs +48 -34
- package/tests/craft_mode_home.mjs +20 -20
- package/tests/craft_mode_looks.mjs +16 -15
- package/tests/craft_mode_nav.mjs +31 -29
- package/tests/craft_review_mode.mjs +46 -41
- package/tests/crafts_vocabulary.mjs +70 -0
- package/tests/cross_discipline_priority_direction.mjs +5 -5
- package/tests/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
|
@@ -428,3 +428,32 @@ test('category: every bundled module declares one (core modules get categories t
|
|
|
428
428
|
.filter((k) => !JSON.parse(fs.readFileSync(path.join(dir, k, 'module.json'), 'utf8')).category);
|
|
429
429
|
assert.deepEqual(missing, [], `declare "category": { "parent": … } in: ${missing.join(', ')}`);
|
|
430
430
|
});
|
|
431
|
+
|
|
432
|
+
// --- language (task 1004794) ---
|
|
433
|
+
|
|
434
|
+
test('language: absent defaults to javascript; "javascript" is accepted', () => {
|
|
435
|
+
const plain = baseManifest();
|
|
436
|
+
assert.equal(validateManifest(plain).valid, true);
|
|
437
|
+
const js = { ...baseManifest(), language: 'javascript' };
|
|
438
|
+
assert.equal(validateManifest(js).valid, true, validateManifest(js).errors.join('; '));
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('language: a value off the closed list is refused with the allowed list', () => {
|
|
442
|
+
for (const bad of ['python', 'JavaScript', '', 42, null]) {
|
|
443
|
+
const r = validateManifest({ ...baseManifest(), language: bad });
|
|
444
|
+
assert.equal(r.valid, false, `language ${JSON.stringify(bad)} should be refused`);
|
|
445
|
+
assert.ok(r.errors.some((e) => e.startsWith('language:') && e.includes('allowed: javascript')), r.errors.join('; '));
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('language: every module.json in the repo still validates (existing modules load unchanged)', async () => {
|
|
450
|
+
const fs = await import('node:fs');
|
|
451
|
+
const dir = path.join(ROOT, 'modules');
|
|
452
|
+
const keys = fs.readdirSync(dir).filter((k) => fs.existsSync(path.join(dir, k, 'module.json')));
|
|
453
|
+
assert.ok(keys.length > 10, 'expected the bundled modules, saw ' + keys.length);
|
|
454
|
+
for (const k of keys) {
|
|
455
|
+
const m = JSON.parse(fs.readFileSync(path.join(dir, k, 'module.json'), 'utf8'));
|
|
456
|
+
const r = validateManifest(m);
|
|
457
|
+
assert.equal(r.valid, true, `${k}: ${r.errors.join('; ')}`);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
@@ -23,11 +23,11 @@ function t(name, fn) {
|
|
|
23
23
|
|
|
24
24
|
t('an artist-craft builder gets the skills', () => {
|
|
25
25
|
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artist'] } }), true);
|
|
26
|
-
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['
|
|
26
|
+
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artisan', 'artist'] } }), true);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
t('anyone else gets nothing (the 2026-09-02 rule holds)', () => {
|
|
30
|
-
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['
|
|
30
|
+
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: ['artisan'] } }), false);
|
|
31
31
|
assert.equal(wantsModuleSkills({ builder: { preferred_disciplines: [] } }), false);
|
|
32
32
|
assert.equal(wantsModuleSkills({ builder: {} }), false);
|
|
33
33
|
});
|
package/tests/modules.mjs
CHANGED
|
@@ -130,11 +130,15 @@ test('isModuleEnabled throws on an unknown module name (programmer error)', () =
|
|
|
130
130
|
assert.throws(() => isModuleEnabled('nope', resolveModules({})), /unknown module/);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
test('disciplines:
|
|
133
|
+
test('disciplines: artisan + artist are CORE — offered with no modules on', () => {
|
|
134
134
|
// Ideation is methodology core (idea_inbox / capture / triage ship to every
|
|
135
135
|
// instance). 'artist' joined them at task 1003719 / ADR 0271 — see the
|
|
136
136
|
// CORE_DISCIPLINES comment for why the R59 module gate had stopped existing.
|
|
137
|
-
|
|
137
|
+
// Since task 1005141 (BONGOS-V3) engineer is 'artisan' and ideator folded into
|
|
138
|
+
// 'artist', so the core crafts are two, not three.
|
|
139
|
+
assert.deepEqual(enabledDisciplines(resolveModules({})), ['artisan', 'artist']);
|
|
140
|
+
assert.ok(!enabledDisciplines(resolveModules({})).includes('engineer'), 'the old spelling is not offered');
|
|
141
|
+
assert.ok(!enabledDisciplines(resolveModules({})).includes('ideator'), 'ideator is not a separate craft');
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
test('THE REGRESSION: every craft the DB CHECK accepts is offered by SOME instance shape', () => {
|
|
@@ -153,8 +157,10 @@ test('THE REGRESSION: every craft the DB CHECK accepts is offered by SOME instan
|
|
|
153
157
|
const widest = new Set(enabledDisciplines(resolveModules({ instance: { modules: Object.fromEntries(
|
|
154
158
|
Object.keys(EFFECTIVE).map((k) => [k, true])
|
|
155
159
|
) }, registry: EFFECTIVE })));
|
|
156
|
-
// Mirrors the CHECK on builders.preferred_disciplines
|
|
157
|
-
|
|
160
|
+
// Mirrors the CURRENT-spelling crafts in the CHECK on builders.preferred_disciplines
|
|
161
|
+
// (migrations 086 + 176, renamed by core_296). The CHECK also still admits the old
|
|
162
|
+
// 'engineer'/'ideator' for rollback — those are normalized on input, never offered.
|
|
163
|
+
for (const craft of ['artisan', 'artist', 'ui']) {
|
|
158
164
|
assert.ok(
|
|
159
165
|
widest.has(craft),
|
|
160
166
|
`the DB accepts preferred_disciplines='${craft}' but no instance shape offers it — ` +
|
|
@@ -182,7 +188,7 @@ test('clientModules is a safe boolean projection covering every known module + d
|
|
|
182
188
|
const c = clientModules(resolveModules({ instance: OTB, registry: SAMPLE_REG }));
|
|
183
189
|
for (const k of KNOWN_MODULES) assert.equal(typeof c.enabled[k], 'boolean');
|
|
184
190
|
assert.ok(Array.isArray(c.disciplines));
|
|
185
|
-
assert.ok(c.disciplines.includes('
|
|
191
|
+
assert.ok(c.disciplines.includes('artisan'));
|
|
186
192
|
});
|
|
187
193
|
|
|
188
194
|
test('the resolved set is frozen (callers cannot mutate the singleton)', () => {
|
|
@@ -249,11 +255,12 @@ test('a branded sibling (GDS_BRANDING_FILE set, no GDS_MODULES_FILE) defaults to
|
|
|
249
255
|
// A non-default brand must fall through to the neutral (all-off) module set.
|
|
250
256
|
const m = loadModules({ env: { GDS_BRANDING_FILE: 'config/branding.cloudbongos.json' } });
|
|
251
257
|
for (const k of KNOWN_MODULES) assert.equal(m[k], false, `${k} must be off for a branded sibling`);
|
|
252
|
-
// The
|
|
258
|
+
// The two core crafts (artisan + artist — task 1003719 / ADR 0271, renamed and
|
|
259
|
+
// folded by task 1005141),
|
|
253
260
|
// plus 'ui' from ui-design, which is default-on for EVERY instance ("every
|
|
254
261
|
// instance has a face") and is not one of KNOWN_MODULES, so the all-off
|
|
255
262
|
// assertion above never reaches it (task 1003487).
|
|
256
|
-
assert.deepEqual(enabledDisciplines(m), ['
|
|
263
|
+
assert.deepEqual(enabledDisciplines(m), ['artisan', 'artist', 'ui']);
|
|
257
264
|
});
|
|
258
265
|
|
|
259
266
|
// ── the DB override layer (task 1002857, migration core_248) ────────────────────
|
|
@@ -102,7 +102,8 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
102
102
|
// task 1005051: Roadmap left it (folded into Goals; /roadmap redirects).
|
|
103
103
|
// task 1005070: the remembered nav (the rail paints before /me).
|
|
104
104
|
// task 1005088: the sidebar mark is drawn by reference to the one brand mark.
|
|
105
|
-
|
|
105
|
+
// task 1005144: My agents, Project settings, Permissions and Modules left for the options menu.
|
|
106
|
+
const SHELL_SHA = 'fb82186606e781b5';
|
|
106
107
|
|
|
107
108
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
108
109
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
package/tests/needs_rebase.mjs
CHANGED
|
@@ -52,7 +52,7 @@ try {
|
|
|
52
52
|
// core_233) and the seeds already hold that slot, so a 'planning' row here fails setup (task 1004861).
|
|
53
53
|
await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'nr','building','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
|
|
54
54
|
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,'nr-w','nr-worker','NR Worker','thetes')`, [WORKER]);
|
|
55
|
-
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'nr fixture','confirmed',10,'feature',$3,'
|
|
55
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'nr fixture','confirmed',10,'feature',$3,'artisan')`, [TASK, VER, WORKER]);
|
|
56
56
|
await q(`INSERT INTO claims (task_id,builder_id,claimed_at) VALUES ($1,$2,now())`, [TASK, WORKER]);
|
|
57
57
|
|
|
58
58
|
// 1) flag a CONFIRMED task → flagged, reason + at set, returns true.
|
|
@@ -23,11 +23,11 @@ function t(name, fn) {
|
|
|
23
23
|
|
|
24
24
|
// ---- parseSeq ----
|
|
25
25
|
t('parseSeq: matches discipline+template, rejects others', () => {
|
|
26
|
-
assert.equal(r.parseSeq('tmpl:
|
|
27
|
-
assert.equal(r.parseSeq('tmpl:
|
|
28
|
-
assert.equal(r.parseSeq('tmpl:
|
|
29
|
-
assert.equal(r.parseSeq('manual:
|
|
30
|
-
assert.equal(r.parseSeq('tmpl:
|
|
26
|
+
assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:5', 'artisan', 'docs-hygiene-pass'), 5);
|
|
27
|
+
assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:5', 'artist', 'docs-hygiene-pass'), null);
|
|
28
|
+
assert.equal(r.parseSeq('tmpl:artisan:other:5', 'artisan', 'docs-hygiene-pass'), null);
|
|
29
|
+
assert.equal(r.parseSeq('manual:artisan:x', 'artisan', 'docs-hygiene-pass'), null);
|
|
30
|
+
assert.equal(r.parseSeq('tmpl:artisan:docs-hygiene-pass:nope', 'artisan', 'docs-hygiene-pass'), null);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
// ---- buildDescription ----
|
|
@@ -40,22 +40,22 @@ t('buildDescription: folds done-when + "always one waiting" footer', () => {
|
|
|
40
40
|
|
|
41
41
|
// ---- templateIdFromRef ----
|
|
42
42
|
t('templateIdFromRef: extracts the template_id, honouring the discipline', () => {
|
|
43
|
-
assert.equal(r.templateIdFromRef('tmpl:
|
|
44
|
-
assert.equal(r.templateIdFromRef('tmpl:
|
|
45
|
-
assert.equal(r.templateIdFromRef('manual:
|
|
46
|
-
assert.equal(r.templateIdFromRef('tmpl:
|
|
43
|
+
assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass:3', 'artisan'), 'unit-test-pass');
|
|
44
|
+
assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass:3', 'artist'), null, 'discipline mismatch');
|
|
45
|
+
assert.equal(r.templateIdFromRef('manual:artisan:x', 'artisan'), null, 'non-chore ref');
|
|
46
|
+
assert.equal(r.templateIdFromRef('tmpl:artisan:unit-test-pass', 'artisan'), null, 'too few parts');
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
// ---- loadTemplates: real templates.json (per-discipline LIST since task 1488) ----
|
|
50
50
|
t('loadTemplates: real file is a per-discipline list of valid chores', () => {
|
|
51
51
|
const tmpl = r.loadTemplates();
|
|
52
|
-
for (const disc of ['
|
|
52
|
+
for (const disc of ['artisan', 'artist', 'artist']) {
|
|
53
53
|
assert.ok(Array.isArray(tmpl[disc]) && tmpl[disc].length >= 1, `${disc} is a non-empty list`);
|
|
54
54
|
for (const c of tmpl[disc]) {
|
|
55
55
|
assert.ok(c.template_id && c.version_id && c.title, `${disc}/${c.template_id || '?'} has id/version/title`);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
const engIds = tmpl.
|
|
58
|
+
const engIds = tmpl.artisan.map((c) => c.template_id);
|
|
59
59
|
assert.ok(engIds.includes('unit-test-pass') && engIds.includes('docs-hygiene-pass'),
|
|
60
60
|
`engineer offers the unit-test chore + the docs sweep (got ${engIds.join(', ')})`);
|
|
61
61
|
});
|
|
@@ -63,7 +63,7 @@ t('loadTemplates: real file is a per-discipline list of valid chores', () => {
|
|
|
63
63
|
// ---- loadFloors: real config, floor >= 2 so one is always left on claim ----
|
|
64
64
|
t('loadFloors: real config keeps floor >= 2 per discipline (a spare on every claim)', () => {
|
|
65
65
|
const f = r.loadFloors();
|
|
66
|
-
for (const disc of ['
|
|
66
|
+
for (const disc of ['artisan', 'artist', 'artist']) {
|
|
67
67
|
assert.ok(f[disc] >= 2, `${disc} floor ${f[disc]} must be >= 2 so one remains the instant one is claimed`);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
@@ -147,7 +147,7 @@ t('restockForClaim threads its loaded map into the no-match fallback', () => {
|
|
|
147
147
|
// ---- restockForClaim: skip path is DB-free (awaited) ----
|
|
148
148
|
// (returns an array of per-template results; the skip path is a 1-element array.)
|
|
149
149
|
assert.deepEqual(await r.restockForClaim(null), [{ skipped: true }]);
|
|
150
|
-
assert.deepEqual(await r.restockForClaim({ source: 'auto-newcomer-floor-ai', discipline: '
|
|
150
|
+
assert.deepEqual(await r.restockForClaim({ source: 'auto-newcomer-floor-ai', discipline: 'artisan' }), [{ skipped: true }]);
|
|
151
151
|
assert.deepEqual(await r.restockForClaim({ source: 'newcomer-floor-manual', discipline: 'artist' }), [{ skipped: true }]);
|
|
152
152
|
assert.deepEqual(await r.restockForClaim({ source: 'newcomer-template', discipline: 'bogus' }), [{ skipped: true }]);
|
|
153
153
|
passed += 1;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// tests/page_notes_panel.mjs — the hall side of the artist's page feedback doc
|
|
2
2
|
// (task 1004496): the docked review panel craft-mode.js puts at the right edge
|
|
3
|
-
// in Artistry
|
|
3
|
+
// in Artist (Artistry until task 1005141; task 1004771: no top-bar button), and the reader in page-notes.js. The server half is tests/page_notes.mjs.
|
|
4
4
|
//
|
|
5
|
-
// Runs the REAL craft-mode.js over a small DOM: the panel docks in
|
|
5
|
+
// Runs the REAL craft-mode.js over a small DOM: the panel docks in Artist
|
|
6
6
|
// alone and closes when the mode changes, page-notes.js loads once (same ?v as
|
|
7
7
|
// craft-mode.js) and a ?note= link opens the doc in any mode. Then
|
|
8
8
|
// the REAL page-notes.js reader: it escapes the doc and shows only this module's
|
|
@@ -75,19 +75,19 @@ function boot({ session = {}, search = '' } = {}) {
|
|
|
75
75
|
}
|
|
76
76
|
const flush = () => new Promise((r) => setTimeout(r, 0));
|
|
77
77
|
|
|
78
|
-
test('THE PANEL: docked at the edge in
|
|
79
|
-
const p = boot({ session: { 'hall-craft': '
|
|
78
|
+
test('THE PANEL: docked at the edge in Artist alone, with no top-bar button, and closed when the mode changes', async () => {
|
|
79
|
+
const p = boot({ session: { 'hall-craft': 'artist' } });
|
|
80
80
|
await flush();
|
|
81
|
-
assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-paintbrush'], 'the top bar keeps Edit words
|
|
82
|
-
assert.equal(p.tools()[0].nextSibling, p.
|
|
81
|
+
assert.deepEqual(p.tools().map((t) => t.id), ['craft-tool-paintbrush', 'craft-tool-lightbulb'], 'the top bar keeps Edit words, and the lightbulb that joined from Ideating (task 1005141) — no Review button (task 1004771)');
|
|
82
|
+
assert.equal(p.tools()[0].nextSibling, p.tools()[1], 'in TOOLS order');
|
|
83
|
+
assert.equal(p.tools()[1].nextSibling, p.sw, 'right before the mode switch');
|
|
83
84
|
assert.deepEqual(p.opened, ['dock'], 'the review panel docks instead');
|
|
84
|
-
for (const m of ['
|
|
85
|
-
assert.deepEqual(p.opened, ['dock', 'close', 'close'], 'and leaves with
|
|
86
|
-
p.
|
|
87
|
-
|
|
88
|
-
p.craft.choose('artistry');
|
|
85
|
+
for (const m of ['artisan', 'patron']) p.craft.choose(m);
|
|
86
|
+
assert.deepEqual(p.opened, ['dock', 'close', 'close'], 'and leaves with Artist');
|
|
87
|
+
assert.deepEqual(p.tools().map((t) => t.id), [], 'Artisan and Patron carry no tool, the lightbulb included');
|
|
88
|
+
p.craft.choose('artist');
|
|
89
89
|
await flush();
|
|
90
|
-
assert.equal(p.opened.filter((x) => x === 'dock').length, 2, 'back in
|
|
90
|
+
assert.equal(p.opened.filter((x) => x === 'dock').length, 2, 'back in Artist it docks again');
|
|
91
91
|
const none = boot();
|
|
92
92
|
await flush();
|
|
93
93
|
assert.equal(none.tools().length, 0, 'no tools with no mode at all');
|
|
@@ -95,9 +95,9 @@ test('THE PANEL: docked at the edge in Artistry alone, with no top-bar button, a
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
test('the panel script loads once, at the same version as craft-mode.js', async () => {
|
|
98
|
-
const p = boot({ session: { 'hall-craft': '
|
|
98
|
+
const p = boot({ session: { 'hall-craft': 'artist' } });
|
|
99
99
|
await flush();
|
|
100
|
-
p.craft.choose('
|
|
100
|
+
p.craft.choose('artist');
|
|
101
101
|
await flush();
|
|
102
102
|
assert.deepEqual(p.loaded, ['/builders/page-notes.js?v=2026-10-04-test']);
|
|
103
103
|
});
|
|
@@ -106,7 +106,7 @@ test('a ?note= link opens that doc on arrival, in any mode', async () => {
|
|
|
106
106
|
const p = boot({ search: '?note=42' });
|
|
107
107
|
await flush();
|
|
108
108
|
assert.deepEqual(p.opened, ['note 42']);
|
|
109
|
-
const q = boot({ session: { 'hall-craft': '
|
|
109
|
+
const q = boot({ session: { 'hall-craft': 'patron' }, search: '?x=1¬e=7' });
|
|
110
110
|
await flush();
|
|
111
111
|
assert.deepEqual(q.opened, ['note 7']);
|
|
112
112
|
const bad = boot({ search: '?note=7x' });
|
|
@@ -204,7 +204,7 @@ function dockedPanel({ reviewing = null, opened = false } = {}) {
|
|
|
204
204
|
reviewing: () => reviewing,
|
|
205
205
|
review: (id) => { reviewing = id; calls.push('review ' + id); },
|
|
206
206
|
stopReview: () => { reviewing = null; calls.push('stop'); },
|
|
207
|
-
experiences: () => [{ id: '
|
|
207
|
+
experiences: () => [{ id: 'patron', label: 'Patron experience', short: 'Patron' }, { id: 'artisan', label: 'Artisan experience', short: 'Artisan' }],
|
|
208
208
|
iconHtml: () => '<svg></svg>',
|
|
209
209
|
};
|
|
210
210
|
const window = { OTB: realOTB(), location: { pathname: '/studio' }, OTBCraftMode: craft };
|
|
@@ -256,17 +256,17 @@ test('DOCKED: a touch never peeks, the tab click toggles, Escape and the X tuck
|
|
|
256
256
|
assert.ok(p.collapsed(), 'docked, the X tucks it away too');
|
|
257
257
|
assert.equal(p.sheet.parentNode, p.body, 'and leaves the tab on the page, the only way back in');
|
|
258
258
|
p.api.close();
|
|
259
|
-
assert.equal(p.sheet.parentNode, null, 'leaving
|
|
259
|
+
assert.equal(p.sheet.parentNode, null, 'leaving Artist (close) is what removes it');
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
test('THE MODE ROW names each mode in the mode switch word and starts a review on a press', () => {
|
|
263
263
|
const p = dockedPanel();
|
|
264
|
-
assert.match(p.sheet.innerHTML, /<span>
|
|
265
|
-
assert.doesNotMatch(p.sheet.innerHTML,
|
|
264
|
+
assert.match(p.sheet.innerHTML, /<span>Patron<\/span>/, 'the switch says Patron, so the row does');
|
|
265
|
+
assert.doesNotMatch(p.sheet.innerHTML, /Patron experience/, 'the switch word, never the long label');
|
|
266
266
|
assert.doesNotMatch(p.sheet.innerHTML, /Stop reviewing/, 'no way out of a review that has not started');
|
|
267
|
-
p.press('data-pn-mode', '
|
|
268
|
-
assert.ok(p.calls.includes('review
|
|
269
|
-
assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /data-pn-mode="
|
|
267
|
+
p.press('data-pn-mode', 'artisan');
|
|
268
|
+
assert.ok(p.calls.includes('review artisan'));
|
|
269
|
+
assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /data-pn-mode="artisan" aria-pressed="true"/, 'the row repaints with the live one pressed');
|
|
270
270
|
assert.match(p.sheet.querySelector('#pn-modes').innerHTML, /Stop reviewing/);
|
|
271
271
|
p.press('data-pn-mode', '');
|
|
272
272
|
assert.ok(p.calls.includes('stop'), 'Stop reviewing ends it');
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,$2,$2,$2,'archon')`, [id, login]);
|
|
59
59
|
}
|
|
60
60
|
const seedTask = (id, title, status, touches, source = null) => q(
|
|
61
|
-
`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,touches,source) VALUES ($1,$2,$3,$4,10,'feature','
|
|
61
|
+
`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,touches,source) VALUES ($1,$2,$3,$4,10,'feature','artisan',$5,$6)`,
|
|
62
62
|
[id, VER, title, status, touches, source]
|
|
63
63
|
);
|
|
64
64
|
|
package/tests/paintbrush.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// tests/paintbrush.mjs — the
|
|
1
|
+
// tests/paintbrush.mjs — the Artist paintbrush (task 1004497): a page's own
|
|
2
2
|
// words, edited where they stand, as a proposed wording change.
|
|
3
3
|
//
|
|
4
4
|
// Runs the REAL paintbrush.js, with the REAL tweak-editor-lib.js and
|
|
@@ -192,12 +192,12 @@ function boot({ round = null, draft = null, claim = { status: 200, body: { ok: t
|
|
|
192
192
|
}
|
|
193
193
|
const settle = () => new Promise((r) => setTimeout(r, 5));
|
|
194
194
|
|
|
195
|
-
test('craft-mode.js puts the paintbrush beside the palette in
|
|
195
|
+
test('craft-mode.js puts the paintbrush beside the palette in Artist, loading its own script', () => {
|
|
196
196
|
assert.match(CRAFT, /\{ id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' \}/);
|
|
197
197
|
const tools = /var TOOLS = \{([\s\S]*?)\n \};/.exec(CRAFT)[1];
|
|
198
|
-
assert.match(tools, /^\s*
|
|
199
|
-
assert.doesNotMatch(tools, /
|
|
200
|
-
assert.doesNotMatch(
|
|
198
|
+
assert.match(tools, /^\s*artist: \[/m, 'it is an Artist tool (Artistry until task 1005141)');
|
|
199
|
+
assert.doesNotMatch(tools, /artisan:|patron:/, 'Artisan and Patron have none');
|
|
200
|
+
assert.doesNotMatch(tools, /building:|artistry:|ideating:|governing:/, 'no tool list survives under a pre-rename mode id');
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
test('pressing it on a page nobody holds OFFERS the page — nothing is editable and nothing is written until the artist takes it', async () => {
|
|
@@ -398,7 +398,7 @@ test('emptying a line\'s words and leaving it marks it to be removed, instead of
|
|
|
398
398
|
assert.deepEqual(JSON.parse(JSON.stringify(put.body.lines)), [{ key: 'L0002', remove: true }]);
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
-
test('REVIEW lists a removal as its own row, says it goes to an
|
|
401
|
+
test('REVIEW lists a removal as its own row, says it goes to an artisan, and SEND submits it with the rewrites', async () => {
|
|
402
402
|
const p = held();
|
|
403
403
|
p.brush.open();
|
|
404
404
|
await settle();
|
|
@@ -409,7 +409,7 @@ test('REVIEW lists a removal as its own row, says it goes to an engineer, and SE
|
|
|
409
409
|
await settle();
|
|
410
410
|
const html = p.bar().innerHTML;
|
|
411
411
|
assert.match(html, /pb__change--remove[\s\S]*Remove<\/span> See every goal/);
|
|
412
|
-
assert.match(html, /A line you remove goes to an
|
|
412
|
+
assert.match(html, /A line you remove goes to an artisan to take out\./);
|
|
413
413
|
p.press('data-pb-send');
|
|
414
414
|
await settle();
|
|
415
415
|
const lastPut = p.writes().filter((c) => c.method === 'PUT').pop();
|
package/tests/privacy_matrix.mjs
CHANGED
|
@@ -491,6 +491,7 @@ require.cache[moduleApiPath] = {
|
|
|
491
491
|
loaded: true,
|
|
492
492
|
exports: {
|
|
493
493
|
pool,
|
|
494
|
+
crafts: require('../src/crafts.js'),
|
|
494
495
|
parsePagination: helpers.parsePagination,
|
|
495
496
|
validateOrRespond: helpers.validateOrRespond,
|
|
496
497
|
corsPublicGet: () => (_req, _res, next) => next(),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// you did, by role", including the idea trail.
|
|
5
5
|
//
|
|
6
6
|
// The done-when, as assertions: the owner opens their profile, switches to the
|
|
7
|
-
//
|
|
7
|
+
// artist view (the ideas trail), sees EVERY idea they ever captured with what became of it, and
|
|
8
8
|
// can click through to the task a promotion produced.
|
|
9
9
|
//
|
|
10
10
|
// Five things are pinned here, and each one is a bug that is invisible in review:
|
|
@@ -212,7 +212,7 @@ await test('goals: a private goal is walled off in SQL, before the LIMIT', async
|
|
|
212
212
|
|
|
213
213
|
await test("goals: a goal's craft is what its TASKS carry — no discipline column on goals", async () => {
|
|
214
214
|
resetSql();
|
|
215
|
-
await activity.activityGoalsByBuilder(95, { role: '
|
|
215
|
+
await activity.activityGoalsByBuilder(95, { role: 'artisan' });
|
|
216
216
|
const sql = listSql().sql;
|
|
217
217
|
assert.ok(/array_agg\(DISTINCT COALESCE\(t\.discipline/.test(sql), 'the craft set is derived from the tasks');
|
|
218
218
|
assert.ok(/= ANY\(c\.disciplines\)/.test(sql), 'the craft filter tests that derived set');
|
|
@@ -304,7 +304,7 @@ seams.registerProvider('lifecycle', {
|
|
|
304
304
|
recentShippedTasksByBuilder: async () => [],
|
|
305
305
|
activityTasksByBuilder: async (id, opts) => {
|
|
306
306
|
portCalls.push(['tasks', id, opts]);
|
|
307
|
-
return { rows: [{ id: 42, title: 'A work', discipline: '
|
|
307
|
+
return { rows: [{ id: 42, title: 'A work', discipline: 'artisan' }], total: 1 };
|
|
308
308
|
},
|
|
309
309
|
activityGoalsByBuilder: async (id, opts) => {
|
|
310
310
|
portCalls.push(['goals', id, opts]);
|
|
@@ -440,7 +440,7 @@ const TRAIL = [
|
|
|
440
440
|
|
|
441
441
|
|
|
442
442
|
|
|
443
|
-
await test(
|
|
443
|
+
await test("the artist tab's idea trail shows EVERY idea filed, whatever became of it", async () => {
|
|
444
444
|
const p = bootProfile();
|
|
445
445
|
await settle(); await settle(); await settle();
|
|
446
446
|
const html = p.htmlFor('activity-ideas');
|
|
@@ -469,33 +469,30 @@ await test('the other three outcomes each say what happened', async () => {
|
|
|
469
469
|
});
|
|
470
470
|
|
|
471
471
|
// Task 1004435 (WA6.RP05): one tab per role the person has, each its craft's
|
|
472
|
-
// own record. The
|
|
473
|
-
// most of the corpus is unclassified); the
|
|
474
|
-
//
|
|
472
|
+
// own record. The artisan tab is the works and goals, WHOLE (no craft filter:
|
|
473
|
+
// most of the corpus is unclassified); the artist tab (task 1005141 — the old
|
|
474
|
+
// ideator folded into it) is the approved pages AND the idea trail. No tab is
|
|
475
|
+
// "tasks carrying that label" any more.
|
|
475
476
|
await test('each role tab asks the server for that craft\'s OWN record', async () => {
|
|
476
|
-
const p = bootProfile({ roles: ['
|
|
477
|
+
const p = bootProfile({ roles: ['artist', 'artisan'] });
|
|
477
478
|
await settle(); await settle(); await settle();
|
|
478
|
-
assert.ok(p.requests.some((u) => u.includes('/inbox/by-builder/')), 'the first tab (the
|
|
479
|
-
assert.ok(
|
|
480
|
-
|
|
481
|
-
p.
|
|
479
|
+
assert.ok(p.requests.some((u) => u.includes('/inbox/by-builder/')), 'the first tab (the artist) reads the idea trail');
|
|
480
|
+
assert.ok(p.requests.some((u) => /\/copy-desk\/artists\/95\/pages\?limit=\d+&offset=0/.test(u)), 'and their approved pages, a few at a time');
|
|
481
|
+
assert.ok(!p.requests.some((u) => u.includes('role=artist')), 'never "tasks labelled artist"');
|
|
482
|
+
assert.ok(!p.requests.some((u) => u.includes('section=goals')), 'and no goals ledger until the artisan tab opens');
|
|
483
|
+
const before = p.requests.length;
|
|
484
|
+
p.switchCraft('artisan');
|
|
482
485
|
await settle(); await settle();
|
|
483
|
-
|
|
484
|
-
assert.ok(after.some((u) => u.includes('section=tasks') && !u.includes('role=')), 'the
|
|
486
|
+
const after = p.requests.slice(before);
|
|
487
|
+
assert.ok(after.some((u) => u.includes('section=tasks') && !u.includes('role=')), 'the artisan tab reads the whole works ledger');
|
|
485
488
|
assert.ok(after.some((u) => u.includes('section=goals') && !u.includes('role=')), 'and the whole goals ledger');
|
|
486
|
-
before = p.requests.length;
|
|
487
|
-
p.switchCraft('artist');
|
|
488
|
-
await settle(); await settle();
|
|
489
|
-
after = p.requests.slice(before);
|
|
490
|
-
assert.ok(after.some((u) => /\/copy-desk\/artists\/95\/pages\?limit=\d+&offset=0/.test(u)), 'the artist tab reads their approved pages, a few at a time');
|
|
491
|
-
assert.ok(!after.some((u) => u.includes('role=artist')), 'never "tasks labelled artist"');
|
|
492
489
|
});
|
|
493
490
|
|
|
494
491
|
await test('a role the person does not have gets no tab and no read', async () => {
|
|
495
|
-
const p = bootProfile({ roles: ['
|
|
492
|
+
const p = bootProfile({ roles: ['artisan'] });
|
|
496
493
|
await settle(); await settle(); await settle();
|
|
497
|
-
assert.ok(p.requests.some((u) => u.includes('section=tasks')), 'the only tab is the
|
|
498
|
-
assert.ok(!p.requests.some((u) => u.includes('/inbox/by-builder/')), 'no trail read for a person with no
|
|
494
|
+
assert.ok(p.requests.some((u) => u.includes('section=tasks')), 'the only tab is the artisan\'s');
|
|
495
|
+
assert.ok(!p.requests.some((u) => u.includes('/inbox/by-builder/')), 'no trail read for a person with no artist role');
|
|
499
496
|
assert.ok(!p.requests.some((u) => u.includes('/copy-desk/artists/')), 'no pages read for a person with no artist role');
|
|
500
497
|
});
|
|
501
498
|
|
|
@@ -183,7 +183,7 @@ const PEOPLE = [
|
|
|
183
183
|
end: '412 credits',
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
|
-
who: '
|
|
186
|
+
who: 'artist', id: 2002, name: 'Builder 9',
|
|
187
187
|
lede: 'Builder 9 has never shipped a task. Every credit here came from ideas.',
|
|
188
188
|
kinds: [['Ideas that became shipped work', '96'], ['Work split from their ideas', '38'], ['Ratified by the board', '30'], ['Full Ideas passed', '22']],
|
|
189
189
|
end: '186 credits',
|
|
@@ -25,12 +25,12 @@ test('missingProfileFields: empty, whitespace, and absent all count as missing',
|
|
|
25
25
|
|
|
26
26
|
test('missingProfileFields: a real value on either side drops only that entry', () => {
|
|
27
27
|
assert.deepEqual(p.missingProfileFields({ bio: 'I build things', preferredDisciplines: [] }), ['disciplines']);
|
|
28
|
-
assert.deepEqual(p.missingProfileFields({ bio: '', preferredDisciplines: ['
|
|
29
|
-
assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: ['
|
|
28
|
+
assert.deepEqual(p.missingProfileFields({ bio: '', preferredDisciplines: ['artisan'] }), ['bio']);
|
|
29
|
+
assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: ['artisan'] }), []);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test('missingProfileFields: a non-array disciplines value is missing, not a crash', () => {
|
|
33
|
-
assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: '
|
|
33
|
+
assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: 'artisan' }), ['disciplines']);
|
|
34
34
|
assert.deepEqual(p.missingProfileFields({ bio: 'x', preferredDisciplines: {} }), ['disciplines']);
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -47,7 +47,7 @@ test('nudgeDismissed: defaults to NOT dismissed; only a real boolean overrides',
|
|
|
47
47
|
|
|
48
48
|
test('shouldNudge: shows only when something is missing AND not dismissed', () => {
|
|
49
49
|
const missing = { bio: '', preferredDisciplines: [] };
|
|
50
|
-
const complete = { bio: 'x', preferredDisciplines: ['
|
|
50
|
+
const complete = { bio: 'x', preferredDisciplines: ['artisan'] };
|
|
51
51
|
assert.equal(p.shouldNudge({ behaviorPrefs: {}, ...missing }), true);
|
|
52
52
|
assert.equal(p.shouldNudge({ behaviorPrefs: { profile_nudge_dismissed: true }, ...missing }), false);
|
|
53
53
|
assert.equal(p.shouldNudge({ behaviorPrefs: {}, ...complete }), false);
|