@bongos/core 1.21.182 → 1.22.2
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 +273 -248
- package/docs/copy-inventory.md +165 -165
- package/docs/copy-registry.json +184 -184
- package/docs/module-api-changelog.md +12 -0
- package/docs/page-inventory.json +35 -26
- package/docs/page-readings.json +389 -388
- package/modules/agents/lib/answer-hold.js +1 -1
- package/modules/agents/lib/fire-budget.js +1 -1
- package/modules/autonomy/cadence.js +3 -3
- package/modules/autonomy/config-idle.js +1 -1
- package/modules/autonomy/fence.js +1 -1
- package/modules/autonomy/gauge.js +2 -2
- package/modules/autonomy/governor-docket.js +0 -1
- package/modules/autonomy/server-schedule.js +1 -1
- package/modules/builder-settings/builder-needs.js +1 -3
- package/modules/builder-settings/event-sound-prefs.js +0 -1
- package/modules/builder-settings/law-locks.js +0 -1
- package/modules/builder-settings/model-alloc-prefs.js +0 -5
- package/modules/builder-settings/profile-nudge-prefs.js +0 -1
- package/modules/builder-settings/render-prefs.js +0 -1
- package/modules/builder-settings/skill-prefs.js +0 -2
- package/modules/builder-settings/sound-prefs.js +0 -3
- package/modules/builder-settings/wandering-prefs.js +0 -2
- package/modules/copy-desk/artist-stats.js +1 -1
- package/modules/copy-desk/flags.js +1 -1
- package/modules/copy-desk/guarded-lines.js +1 -1
- package/modules/copy-desk/page-status.js +0 -5
- package/modules/copy-desk/proposals.js +0 -2
- package/modules/copy-desk/registry.js +1 -1
- package/modules/copy-desk/stuck-tweaks.js +1 -1
- package/modules/economy/credits-by-craft.js +0 -2
- package/modules/economy/streak.js +0 -3
- package/modules/government/asks-db.js +1 -1
- package/modules/government/asks.js +0 -1
- package/modules/government/decision-board.js +0 -1
- package/modules/government/docket.js +0 -6
- package/modules/government/leads.js +0 -1
- package/modules/government/positions.js +1 -1
- package/modules/government/recommendations.js +0 -3
- package/modules/government/routing.js +1 -1
- package/modules/government/treasury-db.js +1 -1
- package/modules/government/treasury.js +2 -2
- package/modules/grading/grade-engine.js +0 -1
- package/modules/hall-ui/public/approval-queue.js +3 -3
- package/modules/hall-ui/public/atlas.html +2 -1
- package/modules/hall-ui/public/atlas.js +1 -1
- package/modules/hall-ui/public/blockers.html +3 -4
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/brand-holes.js +3 -3
- package/modules/hall-ui/public/city.html +2 -1
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/commands.js +1 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/core-move-notice.js +1 -5
- package/modules/hall-ui/public/craft-mode.js +1 -5
- package/modules/hall-ui/public/criterion-uat-ui.js +3 -3
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagrams.html +2 -1
- package/modules/hall-ui/public/dom-utils.js +15 -6
- package/modules/hall-ui/public/drachmae.html +2 -1
- package/modules/hall-ui/public/fleet-costs.js +3 -2
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/founding-mark.js +1 -6
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -1
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-ask.js +3 -3
- package/modules/hall-ui/public/hall-kit.js +3 -5
- package/modules/hall-ui/public/hall-visual.js +3 -5
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +2 -2
- package/modules/hall-ui/public/lightbulb.js +1 -5
- package/modules/hall-ui/public/md-reader.js +1 -8
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/my-agents.js +3 -1
- package/modules/hall-ui/public/page-notes.js +1 -5
- package/modules/hall-ui/public/paintbrush.js +1 -5
- package/modules/hall-ui/public/palette.js +1 -5
- package/modules/hall-ui/public/primer.html +2 -1
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/profile-crafts.js +3 -5
- package/modules/hall-ui/public/profile.html +2 -3
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/ranks.html +2 -3
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/roster.html +3 -4
- package/modules/hall-ui/public/sessions.html +2 -3
- package/modules/hall-ui/public/settings.html +2 -3
- package/modules/hall-ui/public/shell.js +1 -5
- package/modules/hall-ui/public/sky-data.js +1 -1
- package/modules/hall-ui/public/street.html +2 -1
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/task-detail.js +1 -6
- package/modules/hall-ui/public/task-lib.js +3 -8
- package/modules/hall-ui/public/task.html +2 -3
- package/modules/hall-ui/public/thinking.html +2 -1
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/vote-control.js +1 -1
- package/modules/hall-ui/public/watch.html +2 -3
- package/modules/hall-ui/public/welcome-boot.js +3 -5
- package/modules/hall-ui/public/work.html +2 -3
- package/modules/ideas/completeness.js +1 -1
- package/modules/ideas/descent.js +0 -1
- package/modules/ideas/governor-docket.js +0 -2
- package/modules/ideas/idea-state.js +0 -1
- package/modules/ideas/ideator-stats.js +1 -1
- package/modules/ideas/votes.js +1 -1
- package/modules/lifecycle/autopickup-files.js +0 -3
- package/modules/lifecycle/conflict-resolve.js +0 -1
- package/modules/lifecycle/criterion-uat-db.js +0 -4
- package/modules/lifecycle/dead-deps.js +2 -2
- package/modules/lifecycle/est-advisory.js +1 -1
- package/modules/lifecycle/goal-category.js +0 -3
- package/modules/lifecycle/goal-spec-task.js +1 -1
- package/modules/lifecycle/governor-docket.js +0 -1
- package/modules/lifecycle/kickoff-seed.js +1 -1
- package/modules/lifecycle/mingle.js +3 -3
- package/modules/lifecycle/page-tweak-claim.js +1 -1
- package/modules/lifecycle/rebase-gate.js +1 -1
- package/modules/lifecycle/removal-derivation.js +0 -1
- package/modules/lifecycle/routes/abandon-handler.js +1 -1
- package/modules/lifecycle/routes/version-route-authz.js +0 -1
- package/modules/lifecycle/ship-card.js +2 -5
- package/modules/lifecycle/ship-preflight.js +0 -1
- package/modules/lifecycle/sky.js +1 -1
- package/modules/lifecycle/task-classifier.js +0 -1
- package/modules/lifecycle/task-visuals.js +0 -2
- package/modules/lifecycle/version-close.js +0 -1
- package/modules/memory/memory.js +0 -1
- package/modules/memory/search-chunker.js +1 -1
- package/modules/memory/search.js +0 -3
- package/modules/npm-release/preview/divert.js +1 -1
- package/modules/npm-release/preview/proxy.js +2 -3
- package/modules/npm-release/preview/runtime.js +1 -1
- package/modules/npm-release/public/task-widget.js +1 -1
- package/modules/onboarding/founding-builders.js +1 -1
- package/modules/onboarding/founding-mode.js +1 -1
- package/modules/onboarding/howto-guide.js +1 -1
- package/modules/onboarding/howto-state.js +1 -1
- package/modules/onboarding/onboarding-state.js +0 -4
- package/modules/onboarding/onboarding.js +0 -6
- package/modules/page-notes/notes.js +0 -1
- package/modules/platform-identity/community-leaderboard.js +0 -1
- package/modules/platform-identity/hub-keys.js +0 -2
- package/modules/platform-identity/instance-feedback.js +0 -1
- package/modules/platform-identity/read-rate-limit.js +1 -1
- package/modules/platform-identity/routes/sso.js +1 -3
- package/modules/platform-identity/visibility-pull.js +0 -9
- package/modules/provisioning/app-status.js +1 -1
- package/modules/provisioning/catalog-bridge.js +0 -1
- package/modules/provisioning/core-upgrade.js +1 -1
- package/modules/provisioning/credential-preflight.js +1 -1
- package/modules/provisioning/demo.js +2 -2
- package/modules/provisioning/look.js +1 -1
- package/modules/provisioning/routes/callback-page.js +3 -7
- package/modules/public-landing/public/projects.html +3 -5
- package/modules/raise/raise.js +2 -2
- package/modules/render-deploy/deploys.js +1 -1
- package/modules/security/artifact-scan/aggregate.js +0 -4
- package/modules/security/artifact-scan/detectors/capabilities.js +1 -1
- package/modules/security/artifact-scan/detectors/config-obfuscation.js +1 -1
- package/modules/security/artifact-scan/detectors/credentials.js +1 -1
- package/modules/security/artifact-scan/detectors/dependencies.js +1 -1
- package/modules/security/artifact-scan/detectors/injection.js +1 -1
- package/modules/security/artifact-scan/detectors/network.js +1 -1
- package/modules/security/artifact-scan/detectors/npm-lifecycle.js +1 -1
- package/modules/security/artifact-scan/detectors/permissions.js +1 -1
- package/modules/security/artifact-scan/detectors/shared.js +0 -3
- package/modules/security/artifact-scan/floor.js +0 -3
- package/modules/security/artifact-scan/mirror.js +1 -1
- package/modules/security/artifact-scan/quarantine.js +0 -1
- package/modules/security/artifact-scan/rebuild-spec.js +0 -2
- package/modules/security/artifact-scan/report.js +1 -1
- package/modules/security/artifact-scan/sanitize.js +0 -2
- package/modules/security/secret-scrub.js +0 -4
- package/modules/security/security.js +0 -2
- package/modules/sessions/accessibility.js +0 -2
- package/modules/sessions/bfg.js +0 -4
- package/modules/sessions/upload-health.js +0 -3
- package/modules/specialities/db.js +1 -1
- package/modules/specialities/specialities.js +1 -1
- package/modules/ui-design/kit/lib.js +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +26 -12
- package/scripts/bongos/escape-helper-guard.js +147 -0
- package/scripts/bongos/fitness.js +3 -2
- package/scripts/bongos/key-card.js +1 -4
- package/scripts/bongos/review-sheet.js +1 -3
- package/scripts/bongos/start.js +1 -1
- package/scripts/bongos/status.js +1 -1
- package/scripts/gds/escape-helper-guard.js +6 -0
- package/scripts/status-mirror-sync.js +2 -3
- package/src/bongos/module-scope-map.js +1 -0
- package/src/bongos/platform-visibility-gate.js +2 -3
- package/src/bongos/prelaunch-gate.js +2 -3
- package/src/bongos/routes/auth.js +1 -5
- package/src/bongos/serve-internal.js +2 -6
- package/src/html-escape.js +26 -0
- package/src/module-api.js +9 -1
- package/tests/blockers_kit_pager.mjs +2 -2
- package/tests/blockers_page_ui.mjs +2 -2
- package/tests/core_move_notice.mjs +3 -1
- package/tests/core_update_banner.mjs +2 -0
- package/tests/core_upgrade_owner_door.mjs +2 -0
- package/tests/craft_mode.mjs +2 -0
- package/tests/craft_mode_nav.mjs +3 -0
- package/tests/craft_review_mode.mjs +3 -2
- package/tests/criterion_ref_status.mjs +7 -2
- package/tests/curve_kind_overlay.mjs +2 -1
- package/tests/founding_mode.mjs +10 -2
- package/tests/github_install_nudge.mjs +4 -1
- package/tests/goal_map_page.mjs +2 -2
- package/tests/hall_approval_queue.mjs +2 -2
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +2 -0
- package/tests/hall_gov_real_site.mjs +2 -0
- package/tests/hall_kit_charts.mjs +2 -1
- package/tests/hall_my_agents.mjs +3 -2
- package/tests/hall_nav.mjs +2 -0
- package/tests/hall_palette.mjs +2 -2
- package/tests/hall_reading_world.mjs +3 -1
- package/tests/hall_reference_world.mjs +2 -1
- package/tests/hall_signout.mjs +2 -0
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_studio_world.mjs +3 -1
- package/tests/hall_tweak_editor.mjs +2 -1
- package/tests/helpers.mjs +17 -2
- package/tests/html_escape.mjs +149 -0
- package/tests/lightbulb.mjs +2 -0
- package/tests/module_api.mjs +1 -0
- package/tests/module_store_howto.mjs +2 -1
- package/tests/modules_external_export_contract.mjs +48 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/npm_release_where.mjs +2 -1
- package/tests/page_notes_panel.mjs +4 -3
- package/tests/paintbrush.mjs +2 -1
- package/tests/profile_connections_ui.mjs +2 -1
- package/tests/profile_role_headline.mjs +2 -2
- package/tests/profile_role_tabs.mjs +2 -2
- package/tests/projects_hub_repo_private.mjs +4 -0
- package/tests/terms_acceptance.mjs +1 -0
- package/tests/welcome_boot_browser.mjs +1 -1
|
@@ -59,6 +59,7 @@ import fs from 'node:fs';
|
|
|
59
59
|
import path from 'node:path';
|
|
60
60
|
import vm from 'node:vm';
|
|
61
61
|
import { fileURLToPath } from 'node:url';
|
|
62
|
+
import { realOTB } from './helpers.mjs';
|
|
62
63
|
|
|
63
64
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
64
65
|
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
@@ -429,7 +430,7 @@ test('roadmap\'s two sub-24px controls clear the floor, on both axes', () => {
|
|
|
429
430
|
// md-reader.js is a browser IIFE that only touches `window` and `location`, so
|
|
430
431
|
// a two-property sandbox is the whole DOM it needs.
|
|
431
432
|
function loadReader() {
|
|
432
|
-
const sandbox = { window: {}, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/primer' } };
|
|
433
|
+
const sandbox = { window: { OTB: realOTB() }, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/primer' } };
|
|
433
434
|
vm.createContext(sandbox);
|
|
434
435
|
vm.runInContext(mdReaderJs, sandbox, { filename: 'md-reader.js' });
|
|
435
436
|
return sandbox.window.OTBMdReader;
|
|
@@ -644,6 +645,7 @@ async function bootRoadmap({ progress, versions } = {}) {
|
|
|
644
645
|
},
|
|
645
646
|
};
|
|
646
647
|
const windowObj = {
|
|
648
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
647
649
|
BongosClient: { createClient: () => client },
|
|
648
650
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
649
651
|
localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
|
|
@@ -41,6 +41,7 @@ import fs from 'node:fs';
|
|
|
41
41
|
import path from 'node:path';
|
|
42
42
|
import vm from 'node:vm';
|
|
43
43
|
import { fileURLToPath } from 'node:url';
|
|
44
|
+
import { realOTB } from './helpers.mjs';
|
|
44
45
|
|
|
45
46
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
46
47
|
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
@@ -67,7 +68,7 @@ const rule = (css, selector) => {
|
|
|
67
68
|
|
|
68
69
|
// md-reader.js is a browser IIFE that only touches `window` and `location`.
|
|
69
70
|
function loadReader() {
|
|
70
|
-
const sandbox = { window: {}, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/drachmae' } };
|
|
71
|
+
const sandbox = { window: { OTB: realOTB() }, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/drachmae' } };
|
|
71
72
|
vm.createContext(sandbox);
|
|
72
73
|
vm.runInContext(mdReaderJs, sandbox, { filename: 'md-reader.js' });
|
|
73
74
|
return sandbox.window.OTBMdReader;
|
package/tests/hall_signout.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import fs from 'node:fs';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import vm from 'node:vm';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { realOTB } from './helpers.mjs';
|
|
23
24
|
|
|
24
25
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
26
|
const SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/shell.js'), 'utf8');
|
|
@@ -66,6 +67,7 @@ function driveClick({ page = 'settings', href } = {}) {
|
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
const windowObj = {
|
|
70
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
69
71
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
70
72
|
BongosClient: { createClient: () => api },
|
|
71
73
|
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
package/tests/hall_street.mjs
CHANGED
|
@@ -487,7 +487,7 @@ test('the page holds the prototype\'s header, the street, the readings and the c
|
|
|
487
487
|
assert.match(html, /<a class="street-mark" id="streetHome" href="\/builders\/city"[^>]*>\{\{productName\}\}</);
|
|
488
488
|
for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
|
|
489
489
|
const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
490
|
-
assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
|
|
490
|
+
assert.deepEqual(scripts.filter((s) => !/^street-(deck|office)$/.test(s)), ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-depth', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the living model and its one reader (task 1004685: city-draw.js counts with it, so it loads first), the shared kit, then the card (task 1004528), the interiors (task 1004526), the living street\'s exterior (street-depth.js), the floor desk (task 1004558) and the drafted amendment (task 1004530); the deck rides beside them');
|
|
491
491
|
assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -17,6 +17,7 @@ import fs from 'node:fs';
|
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import vm from 'node:vm';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { realOTB } from './helpers.mjs';
|
|
20
21
|
|
|
21
22
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
23
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -271,7 +272,7 @@ async function bootStudio({ reviews = [], shipped = {}, rank = 'metic', flags =
|
|
|
271
272
|
const handlers = [];
|
|
272
273
|
const window = {
|
|
273
274
|
BongosClient: { createClient: () => ({ request }) },
|
|
274
|
-
OTB: {
|
|
275
|
+
OTB: { ...realOTB(),
|
|
275
276
|
// task 1003936 — the role axis vocabulary (dom-utils OTB.role*). PRESENT for
|
|
276
277
|
// the same reason categoryChip is: an undefined helper throws mid-render.
|
|
277
278
|
roleAxisLabel: () => 'Role',
|
|
@@ -574,6 +575,7 @@ function bootShell() {
|
|
|
574
575
|
};
|
|
575
576
|
const location = { hostname: 'builders.cloudbongos.com', origin: 'https://builders.cloudbongos.com', pathname: '/studio', hash: '', assign() {}, replace() {} };
|
|
576
577
|
const window = {
|
|
578
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
577
579
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
578
580
|
BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
|
|
579
581
|
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
@@ -19,7 +19,7 @@ import fs from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import vm from 'node:vm';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
-
import { makeRunner, makeEl, parseInto, walk, settle } from './helpers.mjs';
|
|
22
|
+
import { makeRunner, makeEl, parseInto, walk, settle, realOTB } from './helpers.mjs';
|
|
23
23
|
|
|
24
24
|
const require = createRequire(import.meta.url);
|
|
25
25
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -133,6 +133,7 @@ function bootEditor({ search = '?page=landing:index', answers = {}, stored = {}
|
|
|
133
133
|
},
|
|
134
134
|
};
|
|
135
135
|
const windowObj = {
|
|
136
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
136
137
|
__BRANDING__: { identity: { productName: 'Cloud Bongos' } }, __MODULES__: { enabled: {} },
|
|
137
138
|
BongosClient: { createClient: () => client },
|
|
138
139
|
location: { host: 'builders.cloudbongos.com', hostname: 'builders.cloudbongos.com', protocol: 'https:', origin: 'https://builders.cloudbongos.com', href: 'https://builders.cloudbongos.com/builders/tweak-editor', search, pathname: '/builders/tweak-editor', assign: (u) => { out.navigated = u; } },
|
package/tests/helpers.mjs
CHANGED
|
@@ -79,6 +79,20 @@ import { Readable } from 'node:stream';
|
|
|
79
79
|
|
|
80
80
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
81
81
|
|
|
82
|
+
// realOTB — the REAL window.OTB from modules/hall-ui/public/dom-utils.js, built in
|
|
83
|
+
// its own sandbox (task 1004876). Hall scripts escape user text through
|
|
84
|
+
// window.OTB.escapeHtml at call time, so a test that loads one of them without the
|
|
85
|
+
// page's dom-utils.js hands it this: `window.OTB = realOTB()` (or spread
|
|
86
|
+
// `escapeHtml: realOTB().escapeHtml` into a partial stub). The real one, never a
|
|
87
|
+
// re-typed escaper, so a test cannot pass against a weaker copy.
|
|
88
|
+
export const DOM_UTILS_JS = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'dom-utils.js'), 'utf8');
|
|
89
|
+
export function realOTB() {
|
|
90
|
+
const ctx = { window: {}, document: { querySelector: () => null, createElement: () => ({}) } };
|
|
91
|
+
vm.createContext(ctx);
|
|
92
|
+
vm.runInContext(DOM_UTILS_JS, ctx, { filename: 'dom-utils.js' });
|
|
93
|
+
return ctx.window.OTB;
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
export const SKIP_MARKER = 'HOST-TOPOLOGY-SKIP';
|
|
83
97
|
|
|
84
98
|
function emitSkip(missing, note) {
|
|
@@ -617,7 +631,7 @@ export function bootProfilePage({ signedIn = true, trail = [], roles = ['ideator
|
|
|
617
631
|
roleOptions: () => [{ value: '', label: '— any —' }, { value: 'engineer', label: 'Engineer' }, { value: 'artist', label: 'Artist' }],
|
|
618
632
|
roleIsUnset: (d) => !d || d === 'unclassified',
|
|
619
633
|
ROLE_UNSET: 'unclassified',
|
|
620
|
-
escapeHtml: (
|
|
634
|
+
escapeHtml: realOTB().escapeHtml, // the real one (task 1004876)
|
|
621
635
|
$: (sel) => documentObj.querySelector(sel),
|
|
622
636
|
fmtNum: (n) => String(n ?? 0),
|
|
623
637
|
fmtDate: (iso) => String(iso ?? '').slice(0, 10),
|
|
@@ -729,7 +743,7 @@ export function bootIdeaPage({ idea = null, status = 200, template = null, viewe
|
|
|
729
743
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
730
744
|
BongosClient: { createClient: () => client },
|
|
731
745
|
OTB: {
|
|
732
|
-
escapeHtml: (
|
|
746
|
+
escapeHtml: realOTB().escapeHtml, // the real one (task 1004876)
|
|
733
747
|
$: (sel) => documentObj.querySelector(sel),
|
|
734
748
|
fmtDate: (iso) => String(iso ?? '').slice(0, 10),
|
|
735
749
|
toast() {},
|
|
@@ -931,6 +945,7 @@ export function bootSkyPage({
|
|
|
931
945
|
const windowObj = {
|
|
932
946
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
933
947
|
BongosClient: { createClient: () => client },
|
|
948
|
+
OTB: realOTB(), // the page loads dom-utils.js first; sky-data.js escapes through it (task 1004876)
|
|
934
949
|
innerWidth, innerHeight, devicePixelRatio: 1,
|
|
935
950
|
matchMedia: () => ({ matches: reduce }),
|
|
936
951
|
localStorage: { getItem: (k) => (k in store ? store[k] : null), setItem: (k, v) => { store[k] = String(v); } },
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// tests/html_escape.mjs — the ONE HTML escaper per side (task 1004876, goal 1000135).
|
|
2
|
+
//
|
|
3
|
+
// The escaper is what keeps a builder's own words (a task title, an idea) from
|
|
4
|
+
// becoming live markup on another builder's screen. ~45 private copies were
|
|
5
|
+
// collapsed onto two: window.OTB.escapeHtml (modules/hall-ui/public/dom-utils.js)
|
|
6
|
+
// in the browser and escapeHtml (src/html-escape.js, re-exported by the module
|
|
7
|
+
// doorway) on the server. These tests pin:
|
|
8
|
+
// 1. both shared escapers escape all five characters, map null/undefined to '',
|
|
9
|
+
// and agree with each other on every input;
|
|
10
|
+
// 2. the module doorway hands modules the very same function;
|
|
11
|
+
// 3. pages that used to carry a private copy now render user text through the
|
|
12
|
+
// shared one (a title with <, & and a quote stays text);
|
|
13
|
+
// 4. the fitness check goes red on a third definition, and on a hall page that
|
|
14
|
+
// loads dom-utils.js after (or deferred behind) the scripts that need it.
|
|
15
|
+
|
|
16
|
+
import { test } from 'node:test';
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import vm from 'node:vm';
|
|
22
|
+
import { createRequire } from 'node:module';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
27
|
+
const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
28
|
+
|
|
29
|
+
const { escapeHtml: serverEscape } = require('../src/html-escape.js');
|
|
30
|
+
const guard = require('../scripts/bongos/escape-helper-guard.js');
|
|
31
|
+
|
|
32
|
+
// Load the real dom-utils.js into a sandbox and hand back its window.OTB.
|
|
33
|
+
function browserOTB() {
|
|
34
|
+
const sandbox = { window: {}, document: { querySelector: () => null } };
|
|
35
|
+
vm.createContext(sandbox);
|
|
36
|
+
vm.runInContext(fs.readFileSync(path.join(PUB, 'dom-utils.js'), 'utf8'), sandbox, { filename: 'dom-utils.js' });
|
|
37
|
+
return sandbox.window.OTB;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const HOSTILE = `<img src=x onerror="alert('x')"> & "quoted" 'single' a&b`;
|
|
41
|
+
const CASES = [HOSTILE, '', 'plain', 0, 42, -1.5, true, false, null, undefined, '<', '<<>>', "it's", '"'];
|
|
42
|
+
|
|
43
|
+
test('server escaper escapes all five HTML characters', () => {
|
|
44
|
+
assert.equal(serverEscape(`<a href="x" title='y'>&</a>`),
|
|
45
|
+
'<a href="x" title='y'>&</a>');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('server escaper: null/undefined render as empty text, numbers and booleans as their string', () => {
|
|
49
|
+
assert.equal(serverEscape(null), '');
|
|
50
|
+
assert.equal(serverEscape(undefined), '');
|
|
51
|
+
assert.equal(serverEscape(0), '0');
|
|
52
|
+
assert.equal(serverEscape(42), '42');
|
|
53
|
+
assert.equal(serverEscape(false), 'false');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('server escaper escapes an ampersand first, so an existing entity is shown literally', () => {
|
|
57
|
+
assert.equal(serverEscape('<'), '&lt;');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('browser and server escapers agree on every input', () => {
|
|
61
|
+
const OTB = browserOTB();
|
|
62
|
+
for (const c of CASES) assert.equal(OTB.escapeHtml(c), serverEscape(c), `input ${JSON.stringify(c)}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('the module doorway exports the shared server escaper', () => {
|
|
66
|
+
const api = require('../src/module-api.js');
|
|
67
|
+
assert.equal(api.escapeHtml, serverEscape);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a hall helper that dropped its private copy renders user text through window.OTB', () => {
|
|
71
|
+
// hall-kit.js used to carry its own esc() (exported as OTBKit.esc); it must now delegate, at call time.
|
|
72
|
+
const sandbox = { window: {}, document: { querySelector: () => null } };
|
|
73
|
+
vm.createContext(sandbox);
|
|
74
|
+
vm.runInContext(fs.readFileSync(path.join(PUB, 'dom-utils.js'), 'utf8'), sandbox);
|
|
75
|
+
vm.runInContext(fs.readFileSync(path.join(PUB, 'hall-kit.js'), 'utf8'), sandbox);
|
|
76
|
+
const lib = sandbox.window.OTBKit;
|
|
77
|
+
assert.ok(lib && typeof lib.esc === 'function', 'hall-kit exposes esc');
|
|
78
|
+
assert.equal(lib.esc('Fix <b> & "q" \'s\''), 'Fix <b> & "q" 's'');
|
|
79
|
+
assert.equal(lib.esc(null), '');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('fitness: a third escape table anywhere in the code is a hard fail', () => {
|
|
83
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'esc-guard-'));
|
|
84
|
+
try {
|
|
85
|
+
fs.mkdirSync(path.join(tmp, 'src'), { recursive: true });
|
|
86
|
+
fs.mkdirSync(path.join(tmp, 'modules', 'x', 'public'), { recursive: true });
|
|
87
|
+
fs.mkdirSync(path.join(tmp, 'modules', 'x', 'tests'), { recursive: true });
|
|
88
|
+
fs.writeFileSync(path.join(tmp, 'src', 'html-escape.js'), "const T = { '<': '<' };\n");
|
|
89
|
+
fs.writeFileSync(path.join(tmp, 'modules', 'x', 'tests', 't.mjs'), "const T = { '<': '<' };\n");
|
|
90
|
+
let r = guard.checkOneHtmlEscaper({ root: tmp });
|
|
91
|
+
assert.equal(r.ok, true, r.violations.join('\n'));
|
|
92
|
+
// A renamed copy is still caught: the detector reads the table, not the name.
|
|
93
|
+
fs.writeFileSync(path.join(tmp, 'modules', 'x', 'public', 'page.js'),
|
|
94
|
+
"const hesc = (s) => String(s).replace(/</g, '<');\n");
|
|
95
|
+
r = guard.checkOneHtmlEscaper({ root: tmp });
|
|
96
|
+
assert.equal(r.ok, false);
|
|
97
|
+
assert.equal(r.hardFail, true);
|
|
98
|
+
assert.match(r.violations[0], /modules\/x\/public\/page\.js:1/);
|
|
99
|
+
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('fitness: a delegate to the shared escaper is not a copy', () => {
|
|
103
|
+
assert.deepEqual(guard.findEscapeTables('function esc(s) { return window.OTB.escapeHtml(s); }'), []);
|
|
104
|
+
assert.deepEqual(guard.findEscapeTables("s.replace(/</g, '<')"), [], 'a DECODER is not an escaper');
|
|
105
|
+
assert.deepEqual(guard.findEscapeTables("x\n{ '<': \"<\" }"), [2]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('fitness: a local escaper name that does not delegate is a hard fail (the old primer fallback)', () => {
|
|
109
|
+
const browser = { browser: true };
|
|
110
|
+
assert.deepEqual(guard.findFakeDelegates(" const escapeHtml = (MD && MD.escapeHtml) || ((s) => String(s));", browser), [],
|
|
111
|
+
'names the shared one — but the line-level rule cannot see the fallback, so primer.js no longer has one');
|
|
112
|
+
assert.deepEqual(guard.findFakeDelegates(' const escapeHtml = (s) => String(s);', browser), [1]);
|
|
113
|
+
assert.deepEqual(guard.findFakeDelegates(' function esc(s) { return s; }', browser), [1]);
|
|
114
|
+
assert.deepEqual(guard.findFakeDelegates(' function esc(s) { return window.OTB.escapeHtml(s); }', browser), []);
|
|
115
|
+
assert.deepEqual(guard.findFakeDelegates(' var esc = kit.esc;', browser), []);
|
|
116
|
+
// server: `esc` is also a regex/markdown escaper name there, so only the HTML names are held
|
|
117
|
+
assert.deepEqual(guard.findFakeDelegates("const esc = (k) => k.replace(/[.*]/g, '\\\\$&');"), []);
|
|
118
|
+
assert.deepEqual(guard.findFakeDelegates('const hesc = (s) => String(s);'), [1]);
|
|
119
|
+
assert.deepEqual(guard.findFakeDelegates("const { escapeHtml: hesc } = require('../../src/html-escape');"), []);
|
|
120
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'esc-guard-'));
|
|
121
|
+
try {
|
|
122
|
+
fs.mkdirSync(path.join(tmp, 'modules', 'x', 'public'), { recursive: true });
|
|
123
|
+
fs.writeFileSync(path.join(tmp, 'modules', 'x', 'public', 'p.js'), 'const esc = (s) => String(s);\n');
|
|
124
|
+
const r = guard.checkOneHtmlEscaper({ root: tmp });
|
|
125
|
+
assert.equal(r.ok, false);
|
|
126
|
+
assert.match(r.violations[0], /p\.js:1 — defines a local HTML escaper name/);
|
|
127
|
+
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('fitness: the guard is registered in fitness.js CHECKS, so CI runs it', () => {
|
|
131
|
+
const fitness = require('../scripts/bongos/fitness.js');
|
|
132
|
+
const checks = fitness.CHECKS || fitness.checks;
|
|
133
|
+
assert.ok(Array.isArray(checks), 'fitness.js exports its CHECKS list');
|
|
134
|
+
assert.ok(checks.includes(guard.checkOneHtmlEscaper), 'checkOneHtmlEscaper is one of the fitness checks');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('fitness: a hall page must load dom-utils.js first and synchronously', () => {
|
|
138
|
+
const ok = '<script src="/builders/dom-utils.js?v=1"></script><script src="/builders/shell.js"></script>';
|
|
139
|
+
assert.equal(guard.hallPageOrderProblem(ok), null);
|
|
140
|
+
assert.equal(guard.hallPageOrderProblem('<p>no scripts</p>'), null);
|
|
141
|
+
assert.match(guard.hallPageOrderProblem('<script src="/builders/shell.js"></script><script src="/builders/dom-utils.js"></script>'), /before/);
|
|
142
|
+
assert.match(guard.hallPageOrderProblem('<script src="/builders/dom-utils.js" defer></script><script src="/builders/shell.js"></script>'), /defer/);
|
|
143
|
+
assert.match(guard.hallPageOrderProblem('<script src="/builders/shell.js"></script>'), /never/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('fitness: the real tree holds exactly the two shared escapers', () => {
|
|
147
|
+
const r = guard.checkOneHtmlEscaper();
|
|
148
|
+
assert.equal(r.ok, true, r.violations.join('\n'));
|
|
149
|
+
});
|
package/tests/lightbulb.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import path from 'node:path';
|
|
|
17
17
|
import vm from 'node:vm';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { realOTB } from './helpers.mjs';
|
|
20
21
|
|
|
21
22
|
const require = createRequire(import.meta.url);
|
|
22
23
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -265,6 +266,7 @@ function boot({ storage = {}, throwing = false, respond } = {}) {
|
|
|
265
266
|
? { ok: true, status: 200, json: { ideas: [{ id: '5', title: '<b>Bold</b> idea', author_name: 'Bo', captured_at: '2026-10-01T00:00:00Z' }] } }
|
|
266
267
|
: { ok: true, status: 201, json: { ideas: [{ id: '801', title: 'Sort drafts' }, { id: '802', title: 'Name writers' }], split_by: 'ai' } }));
|
|
267
268
|
const win = {
|
|
269
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
268
270
|
location: { pathname: '/studio' },
|
|
269
271
|
localStorage: store,
|
|
270
272
|
fetch: (url, opts) => {
|
package/tests/module_api.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const seams = require(path.join(ROOT, 'src', 'module-seams.js'));
|
|
|
20
20
|
// `withTx` was added; no enabled module at its pinned coreVersion lost a capability.
|
|
21
21
|
const PUBLISHED_SURFACE = [
|
|
22
22
|
'CORE_VERSION',
|
|
23
|
+
'escapeHtml', // task 1004876: THE server-side HTML escaper (1.22.0) — modules render user text through it, never a private copy
|
|
23
24
|
'requireBuilder', 'requireRank', 'rankMeetsThreshold', 'requireBfgPrincipal', 'allowBoxScope', 'resolveSession',
|
|
24
25
|
'requirePermission', // BV1.R105: the authority-atom gate module routes migrate onto (1.19.0; ADR 0151)
|
|
25
26
|
'requireActAlone', // added by task 1004657 (ADR 0363 D5): a building's own page asks its decision-rules row before one builder acts alone. On the doorway because eight buildings' pages live in six modules and core, and each must refuse the same way People does
|
|
@@ -16,6 +16,7 @@ import os from 'node:os';
|
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import vm from 'node:vm';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { realOTB } from './helpers.mjs';
|
|
19
20
|
|
|
20
21
|
const require = createRequire(import.meta.url);
|
|
21
22
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -173,7 +174,7 @@ test('a delisted module\'s how-to still reads for people who hold it, and only f
|
|
|
173
174
|
// ---- the page: the author's markdown is untrusted input ----
|
|
174
175
|
|
|
175
176
|
function loadReader() {
|
|
176
|
-
const ctx = { window: {}, location: { href: 'https://builders.example.com/builders/modules', hostname: 'builders.example.com' }, URL };
|
|
177
|
+
const ctx = { window: { OTB: realOTB() }, location: { href: 'https://builders.example.com/builders/modules', hostname: 'builders.example.com' }, URL };
|
|
177
178
|
vm.createContext(ctx);
|
|
178
179
|
vm.runInContext(fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/md-reader.js'), 'utf8'), ctx);
|
|
179
180
|
return ctx.window.OTBMdReader;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// tests/modules_external_export_contract.mjs — modules/ exports whose callers no
|
|
2
|
+
// code-file scan can see (task 1004891).
|
|
3
|
+
//
|
|
4
|
+
// Task 1004891 un-exported every modules/ export that no other code file names
|
|
5
|
+
// (knip's finding, filtered by countKnipFindings in scripts/bongos/fitness-ratchets.js).
|
|
6
|
+
// Six were deliberately KEPT, because their consumer is a document rather than code:
|
|
7
|
+
// - gatherInteractionSignals: the bfg scheduled task calls it by path
|
|
8
|
+
// (`node -e "require('./modules/sessions/bfg').gatherInteractionSignals(...)"`),
|
|
9
|
+
// so dropping it breaks that routine at run time with no test or scan noticing;
|
|
10
|
+
// - fieldCatalog, recordPulse, OWNERLESS_CATEGORIES, SOFT_CAP_FRACTION,
|
|
11
|
+
// CONTROL_PLANE_SHAPES: each module's CLAUDE.md names it as that module's API.
|
|
12
|
+
// This file pins them, so the next dead-export sweep cannot quietly take them.
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { test } from 'node:test';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
+
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
23
|
+
|
|
24
|
+
test('bfg.gatherInteractionSignals stays exported: the bfg scheduled task calls it by path', () => {
|
|
25
|
+
const skill = read('.claude/scheduled-tasks/bfg/SKILL.md');
|
|
26
|
+
assert.match(skill, /require\('\.\/modules\/sessions\/bfg'\)/, 'the skill still requires bfg by path');
|
|
27
|
+
assert.match(skill, /\.gatherInteractionSignals\(/, 'the skill still calls gatherInteractionSignals');
|
|
28
|
+
const bfg = require(path.join(ROOT, 'modules/sessions/bfg.js'));
|
|
29
|
+
assert.equal(typeof bfg.gatherInteractionSignals, 'function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const DOCUMENTED = [
|
|
33
|
+
['modules/builder-settings/interaction-prefs.js', 'fieldCatalog', 'function'],
|
|
34
|
+
['modules/sessions/pulse.js', 'recordPulse', 'function'],
|
|
35
|
+
['modules/economy/cost-classify.js', 'OWNERLESS_CATEGORIES', 'object'],
|
|
36
|
+
['modules/economy/cost-classify.js', 'SOFT_CAP_FRACTION', 'number'],
|
|
37
|
+
['modules/provisioning/onboard-plan.js', 'CONTROL_PLANE_SHAPES', 'object'],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const [file, name, type] of DOCUMENTED) {
|
|
41
|
+
test(`${file} keeps exporting ${name}: its module CLAUDE.md names it as the module's API`, () => {
|
|
42
|
+
const doc = read(path.join(path.dirname(file), 'CLAUDE.md'));
|
|
43
|
+
assert.ok(doc.includes(name), `${name} is still documented in ${path.dirname(file)}/CLAUDE.md`);
|
|
44
|
+
const mod = require(path.join(ROOT, file));
|
|
45
|
+
assert.ok(name in mod, `${name} is exported from ${file}`);
|
|
46
|
+
assert.equal(typeof mod[name], type);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -93,7 +93,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
93
93
|
// says a held core version was tried and rolled back; task 1004731: the one-time
|
|
94
94
|
// "Upgraded to core X" notice after a move; task 1004826: Standing is Accomplishments;
|
|
95
95
|
// task 1004846: the new logo (its bump landed late, in task 1004852).
|
|
96
|
-
const SHELL_SHA = '
|
|
96
|
+
const SHELL_SHA = 'cea38eeab6b234cc';
|
|
97
97
|
|
|
98
98
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
99
99
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|
|
@@ -18,7 +18,7 @@ import path from 'node:path';
|
|
|
18
18
|
import vm from 'node:vm';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import { createRequire } from 'node:module';
|
|
21
|
-
import { npmTarball, npmPackument, fakeHttpsGet } from './helpers.mjs';
|
|
21
|
+
import { npmTarball, npmPackument, fakeHttpsGet, realOTB } from './helpers.mjs';
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -193,6 +193,7 @@ function bootWidget(answer, { shown = null } = {}) {
|
|
|
193
193
|
const listeners = {};
|
|
194
194
|
const asked = [];
|
|
195
195
|
const windowObj = {
|
|
196
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
196
197
|
OTBTaskShown: shown,
|
|
197
198
|
BongosClient: { createClient: () => ({ request: async (m, url) => { asked.push(url); return answer(url); } }) },
|
|
198
199
|
};
|
|
@@ -13,6 +13,7 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import vm from 'node:vm';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { realOTB } from './helpers.mjs';
|
|
16
17
|
|
|
17
18
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
19
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -59,7 +60,7 @@ function boot({ session = {}, search = '' } = {}) {
|
|
|
59
60
|
};
|
|
60
61
|
const mk = (obj) => ({ getItem: (k) => (k in obj ? obj[k] : null), setItem: (k, v) => { obj[k] = String(v); }, removeItem: (k) => { delete obj[k]; } });
|
|
61
62
|
const opened = [];
|
|
62
|
-
const window = { localStorage: mk({}), sessionStorage: mk(session), matchMedia: () => ({ matches: false }), location: { search } };
|
|
63
|
+
const window = { OTB: realOTB(), localStorage: mk({}), sessionStorage: mk(session), matchMedia: () => ({ matches: false }), location: { search } };
|
|
63
64
|
// "Loading" page-notes.js: record the tag, then define the API it would define.
|
|
64
65
|
head.onAppend = (tag) => {
|
|
65
66
|
loaded.push(tag.src);
|
|
@@ -115,7 +116,7 @@ test('a ?note= link opens that doc on arrival, in any mode', async () => {
|
|
|
115
116
|
|
|
116
117
|
// ---------- the reader ----------
|
|
117
118
|
function reader() {
|
|
118
|
-
const window = { location: { pathname: '/studio' } };
|
|
119
|
+
const window = { OTB: realOTB(), location: { pathname: '/studio' } };
|
|
119
120
|
const document = { body: { dataset: { page: 'studio', pageTitle: 'Studio' } }, title: 'Studio — hall' };
|
|
120
121
|
const sandbox = { window, document };
|
|
121
122
|
vm.createContext(sandbox);
|
|
@@ -206,7 +207,7 @@ function dockedPanel({ reviewing = null, opened = false } = {}) {
|
|
|
206
207
|
experiences: () => [{ id: 'ideating', label: 'Ideator experience', short: 'Ideating' }, { id: 'building', label: 'Building experience', short: 'Building' }],
|
|
207
208
|
iconHtml: () => '<svg></svg>',
|
|
208
209
|
};
|
|
209
|
-
const window = { location: { pathname: '/studio' }, OTBCraftMode: craft };
|
|
210
|
+
const window = { OTB: realOTB(), location: { pathname: '/studio' }, OTBCraftMode: craft };
|
|
210
211
|
const sandbox = { window, document, fetch, setTimeout, clearTimeout, Promise };
|
|
211
212
|
vm.createContext(sandbox);
|
|
212
213
|
vm.runInContext(NOTES, sandbox, { filename: 'page-notes.js' });
|
package/tests/paintbrush.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import fs from 'node:fs';
|
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import vm from 'node:vm';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { realOTB } from './helpers.mjs';
|
|
20
21
|
|
|
21
22
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
23
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -157,7 +158,7 @@ function boot({ round = null, draft = null, claim = { status: 200, body: { ok: t
|
|
|
157
158
|
return respond(404, {});
|
|
158
159
|
};
|
|
159
160
|
const winListeners = {};
|
|
160
|
-
const window = { location: { pathname: '/roadmap' }, addEventListener: (t, fn) => { (winListeners[t] ||= []).push(fn); }, removeEventListener: (t, fn) => { winListeners[t] = (winListeners[t] || []).filter((f) => f !== fn); } };
|
|
161
|
+
const window = { OTB: realOTB(), location: { pathname: '/roadmap' }, addEventListener: (t, fn) => { (winListeners[t] ||= []).push(fn); }, removeEventListener: (t, fn) => { winListeners[t] = (winListeners[t] || []).filter((f) => f !== fn); } };
|
|
161
162
|
const sandbox = { window, document, fetch, Promise, setTimeout, clearTimeout, JSON, Math, String, Array, Object };
|
|
162
163
|
window.window = window;
|
|
163
164
|
vm.createContext(sandbox);
|
|
@@ -58,6 +58,7 @@ import vm from 'node:vm';
|
|
|
58
58
|
import { readFileSync } from 'node:fs';
|
|
59
59
|
import { fileURLToPath } from 'node:url';
|
|
60
60
|
import { dirname, join } from 'node:path';
|
|
61
|
+
import { realOTB } from './helpers.mjs';
|
|
61
62
|
// The REAL predicates, not a copy. accountFloor() below used to spell these
|
|
62
63
|
// itself, which made it a hand-maintained mirror of account-visibility.js —
|
|
63
64
|
// and it drifted the moment ADR 0213 made consent two columns. Imported, the
|
|
@@ -498,7 +499,7 @@ function boot({ transport, hub = HUB } = {}) {
|
|
|
498
499
|
window: {
|
|
499
500
|
OTBAsk: require('../modules/hall-ui/public/hall-ask.js'),
|
|
500
501
|
BongosClient: { createClient: () => api },
|
|
501
|
-
OTB: { $, escapeHtml, fmtDate: (d) => String(d).slice(0, 10), toast: (m) => toasts.push(m), hubOrigin: () => hub },
|
|
502
|
+
OTB: { ...realOTB(), $, escapeHtml, fmtDate: (d) => String(d).slice(0, 10), toast: (m) => toasts.push(m), hubOrigin: () => hub },
|
|
502
503
|
OTBKit: {
|
|
503
504
|
makePager: (prefix, key) => ({ prefix, key, page: 1, perPage: 25 }),
|
|
504
505
|
makeLedger: ({ mount, rowHtml, emptyMsg }) => ({
|
|
@@ -20,7 +20,7 @@ import fs from 'node:fs';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import vm from 'node:vm';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { makeRunner } from './helpers.mjs';
|
|
23
|
+
import { makeRunner, realOTB } from './helpers.mjs';
|
|
24
24
|
|
|
25
25
|
process.env.NODE_ENV = 'test';
|
|
26
26
|
const require = createRequire(import.meta.url);
|
|
@@ -129,7 +129,7 @@ async function boot(profileData) {
|
|
|
129
129
|
// The real kit draws the surface bars; the section rail needs a real DOM and
|
|
130
130
|
// is not what this suite is about, so it is left out (profile.js skips it).
|
|
131
131
|
OTBKit: { ...OTBKit, makeSectionRail: undefined },
|
|
132
|
-
OTB: {
|
|
132
|
+
OTB: { ...realOTB(),
|
|
133
133
|
$, escapeHtml,
|
|
134
134
|
fmtNum: (n) => Number(n || 0).toLocaleString('en-US'),
|
|
135
135
|
fmtDate: (d) => String(d).slice(0, 10),
|
|
@@ -22,7 +22,7 @@ import os from 'node:os';
|
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import vm from 'node:vm';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
|
-
import { makeRunner, bootProfilePage, settle } from './helpers.mjs';
|
|
25
|
+
import { makeRunner, bootProfilePage, settle, realOTB } from './helpers.mjs';
|
|
26
26
|
|
|
27
27
|
process.env.NODE_ENV = 'test';
|
|
28
28
|
const require = createRequire(import.meta.url);
|
|
@@ -95,7 +95,7 @@ await test('the route: signed in only, a bad id refused, paged, and it asks for
|
|
|
95
95
|
// ---- 2. the ideator's trail -------------------------------------------------
|
|
96
96
|
|
|
97
97
|
function loadCrafts() {
|
|
98
|
-
const sandbox = { window: {}, console };
|
|
98
|
+
const sandbox = { window: { OTB: realOTB() }, console };
|
|
99
99
|
sandbox.globalThis = sandbox;
|
|
100
100
|
vm.createContext(sandbox);
|
|
101
101
|
for (const f of ['idea-objects.js', 'profile-crafts.js']) {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { strict as assert } from 'node:assert';
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
19
|
import vm from 'node:vm';
|
|
20
|
+
import { realOTB } from './helpers.mjs';
|
|
20
21
|
|
|
21
22
|
const HUB = readFileSync(new URL('../modules/public-landing/public/projects.html', import.meta.url), 'utf8').replace(/\r\n/g, '\n');
|
|
22
23
|
|
|
@@ -30,6 +31,8 @@ async function ta(name, fn) {
|
|
|
30
31
|
function grab(head) {
|
|
31
32
|
const at = HUB.indexOf(`\n ${head}`);
|
|
32
33
|
assert.ok(at >= 0, `${head} must exist`);
|
|
34
|
+
const line = HUB.slice(at, HUB.indexOf('\n', at + 1));
|
|
35
|
+
if (/\}\s*$/.test(line)) return line; // a one-line declaration (esc, task 1004876)
|
|
33
36
|
const end = HUB.indexOf('\n }', at + 1);
|
|
34
37
|
const close = HUB.slice(end, end + 5) === '\n };' ? end + 5 : end + 4;
|
|
35
38
|
return HUB.slice(at, close);
|
|
@@ -50,6 +53,7 @@ function world({ reply = RECEIPT } = {}) {
|
|
|
50
53
|
API: '/api/bongos', manageId: '18', manageInst: null, manageRepo: null,
|
|
51
54
|
mRepoPrivSeen: false, mRepoPrivState: '',
|
|
52
55
|
$: (id) => els[id],
|
|
56
|
+
window: { OTB: realOTB() }, // the page loads dom-utils.js first (task 1004876)
|
|
53
57
|
document: { querySelector: () => null },
|
|
54
58
|
jsonOrEmpty: (res) => res.json(),
|
|
55
59
|
apiError: (data) => (data && data.error) || {},
|
|
@@ -400,6 +400,7 @@ require.cache[moduleApiPath] = {
|
|
|
400
400
|
resolveEnv: () => null,
|
|
401
401
|
userAgent: () => 'cloud-bongos-tests',
|
|
402
402
|
LIVE_RANK_LADDER: ['xenos', 'thetes', 'metic', 'archon'],
|
|
403
|
+
escapeHtml: require('../src/html-escape.js').escapeHtml, // the doorway's one escaper (task 1004876)
|
|
403
404
|
},
|
|
404
405
|
};
|
|
405
406
|
|
|
@@ -33,7 +33,7 @@ const INDEX = fs.readFileSync(path.join(HALL, 'index.html'), 'utf8')
|
|
|
33
33
|
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, '')
|
|
34
34
|
.replace(/\{\{[^}]*\}\}/g, '');
|
|
35
35
|
const STUB = '<script>window.__BRANDING__ = { identity: { worldName: "Harbor" } }; window.BongosClient = { createClient: () => ({ request: async () => ({ ok: false, status: 404, data: null }) }) };</script>';
|
|
36
|
-
const SCRIPTS = ['shell-trail.js', 'craft-mode.js', 'welcome-boot.js', 'shell.js']
|
|
36
|
+
const SCRIPTS = ['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'welcome-boot.js', 'shell.js'] // dom-utils first, as on the real page (task 1004876)
|
|
37
37
|
.map((f) => `<script src="/builders/${f}"></script>`).join('');
|
|
38
38
|
const PAGE = INDEX.replace('</body>', STUB + SCRIPTS + '</body>');
|
|
39
39
|
|