@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
|
@@ -24,9 +24,7 @@ const path = require('node:path');
|
|
|
24
24
|
const { pathToFileURL } = require('node:url');
|
|
25
25
|
const { spawn } = require('node:child_process');
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
29
|
-
}
|
|
27
|
+
const { escapeHtml } = require('../../src/html-escape'); // the one server escaper (task 1004876)
|
|
30
28
|
|
|
31
29
|
// parseArgs — the flags above; `shots` keep their order. A --shot with no "="
|
|
32
30
|
// is an error, not a guess: the caption and the path cannot be told apart.
|
package/scripts/bongos/start.js
CHANGED
|
@@ -268,7 +268,7 @@ function claimableTable(rows, rewardFromServer, goals) {
|
|
|
268
268
|
// the same card every time, app-native. Falls back to the markdown card when
|
|
269
269
|
// the client can't draw widgets. Design system: flat, 0.5px borders, CSS
|
|
270
270
|
// theme vars (light/dark), Tabler icons, no emoji.
|
|
271
|
-
const hesc = (
|
|
271
|
+
const { escapeHtml: hesc } = require('../../src/html-escape'); // the one server escaper (task 1004876); it also escapes '
|
|
272
272
|
const jsq = (s) => String(s == null ? '' : s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, ' ');
|
|
273
273
|
|
|
274
274
|
function miniBar(pct) {
|
package/scripts/bongos/status.js
CHANGED
|
@@ -223,7 +223,7 @@ function clabel(c) {
|
|
|
223
223
|
// builder-start card (flat, 0.5px borders, CSS theme vars, Tabler icons, no
|
|
224
224
|
// emoji); classes are `.st-*` so they can't collide with builder-start's `.bs-*`
|
|
225
225
|
// or the ship card's `.cc-*`.
|
|
226
|
-
const sesc = (
|
|
226
|
+
const { escapeHtml: sesc } = require('../../src/html-escape'); // the one server escaper (task 1004876); it also escapes '
|
|
227
227
|
|
|
228
228
|
// A value going inside a single-quoted JS string inside an HTML attribute needs
|
|
229
229
|
// BOTH escapes, in that order. Task ids are digits so the existing handlers get
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// scripts/gds/escape-helper-guard.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
+
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
+
// The real script is scripts/bongos/escape-helper-guard.js. This forwarder keeps callers that still name
|
|
4
|
+
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
+
'use strict';
|
|
6
|
+
require('../bongos/forwarder.js').forward(module, 'escape-helper-guard.js');
|
|
@@ -49,6 +49,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
49
49
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
50
50
|
const SRC = path.join(REPO_ROOT, 'modules', 'status-ui', 'public');
|
|
51
51
|
const { branding, clientBranding, applyBrandConditionals } = require('../src/branding');
|
|
52
|
+
const { escapeHtml } = require('../src/html-escape');
|
|
52
53
|
|
|
53
54
|
// Stamp instance-identity tokens into the mirror HTML. The off-box mirror is
|
|
54
55
|
// served by a static Pages host, NOT the branding-injecting Node server — so this
|
|
@@ -64,9 +65,7 @@ function brandTokens(html, brand = {}, publicOrigin = '', buildersOrigin = '') {
|
|
|
64
65
|
const cur = (brand.currency && brand.currency.label) || 'credits';
|
|
65
66
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
66
67
|
const company = (brand.identity && brand.identity.company) || {};
|
|
67
|
-
const esc =
|
|
68
|
-
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
69
|
-
.replace(/"/g, '"').replace(/'/g, ''');
|
|
68
|
+
const esc = escapeHtml; // the one server escaper (task 1004876)
|
|
70
69
|
return applyBrandConditionals(html, { companyName: company.name, companySite: company.site, companyTagline: company.tagline })
|
|
71
70
|
.split('{{worldName}}').join(esc(world))
|
|
72
71
|
.split('{{productName}}').join(esc(product))
|
|
@@ -464,6 +464,7 @@ const MODULE_GLOBS = {
|
|
|
464
464
|
'scripts/bongos/route-shadow-guard.js', 'scripts/bongos/routine-schedule.js',
|
|
465
465
|
'scripts/bongos/seam-wiring-guard.js', 'scripts/bongos/skill-preflight.js',
|
|
466
466
|
'scripts/bongos/test-path-guard.js', 'scripts/bongos/version-literal-guard.js', 'scripts/bongos/dev-box-guard.js',
|
|
467
|
+
'scripts/bongos/escape-helper-guard.js', // task 1004876
|
|
467
468
|
|
|
468
469
|
// ADOPTED (task 1004111): the enforcement surfaces this module already IS.
|
|
469
470
|
// install-git-hooks.js + scripts/bongos/hooks/ are the pre-push hook beside the
|
|
@@ -177,9 +177,8 @@ function relativeReturn(raw) {
|
|
|
177
177
|
return raw === '/' ? null : raw;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
180
|
+
// The one server escaper (task 1004876); it also escapes ' , which this copy did not.
|
|
181
|
+
const { escapeHtml: escAttr } = require('../html-escape');
|
|
183
182
|
|
|
184
183
|
// The door. Self-contained on purpose (the pre-launch gate's shape): it is
|
|
185
184
|
// served to someone who may not see the project, so it pulls no stylesheet, no
|
|
@@ -180,9 +180,8 @@ function relativeReturn(raw) {
|
|
|
180
180
|
return raw;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
183
|
+
// The one server escaper (task 1004876); it also escapes ' , which this copy did not.
|
|
184
|
+
const { escapeHtml: escAttr } = require('../html-escape');
|
|
186
185
|
|
|
187
186
|
// The form. Self-contained and token-free on purpose: it is served BEFORE the
|
|
188
187
|
// visitor is allowed to see anything, so it must not pull the branding bundle,
|
|
@@ -44,11 +44,7 @@ const seams = require('../../module-seams');
|
|
|
44
44
|
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
45
45
|
const log = require('../logger').logger.child({ src: 'auth' });
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
49
|
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
50
|
-
}[c]));
|
|
51
|
-
}
|
|
47
|
+
const { escapeHtml } = require('../../html-escape'); // the one server escaper (task 1004876)
|
|
52
48
|
|
|
53
49
|
// Render a small standalone HTML page in the builders' hall palette. Used for
|
|
54
50
|
// OAuth error branches so the visitor sees something coherent rather than a
|
|
@@ -264,12 +264,8 @@ function dropEmptyHrefLinks(html, values) {
|
|
|
264
264
|
// No token is ever emitted into a <script> or a CSS context (checked across
|
|
265
265
|
// public-landing/, hall-ui/, status-ui/ — all under modules/ since task 1003322)
|
|
266
266
|
// — that would need different escaping.
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
return String(s == null ? '' : s)
|
|
270
|
-
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
271
|
-
.replace(/"/g, '"').replace(/'/g, ''');
|
|
272
|
-
}
|
|
267
|
+
// The one server escaper (src/html-escape.js, task 1004876).
|
|
268
|
+
const { escapeHtml: esc } = require('../html-escape');
|
|
273
269
|
// The instance apex origin from the full branding pack (domains is server-only,
|
|
274
270
|
// so it never rides clientBranding). Best-effort so a branding hiccup can't crash
|
|
275
271
|
// serving.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/html-escape.js — THE server-side HTML escaper (task 1004876, goal 1000135).
|
|
2
|
+
//
|
|
3
|
+
// Escapes the five characters that let text become markup — & < > " ' — so a
|
|
4
|
+
// builder's own words (a task title, an error detail, a project name) stay text
|
|
5
|
+
// when they are written into a page, an attribute or an email. null and
|
|
6
|
+
// undefined render as empty text; anything else is String()-ed first.
|
|
7
|
+
//
|
|
8
|
+
// ONE copy per side, on purpose. The browser twin is window.OTB.escapeHtml in
|
|
9
|
+
// modules/hall-ui/public/dom-utils.js and the two must agree on every input
|
|
10
|
+
// (tests/html_escape.mjs runs both). Modules reach this one through the doorway
|
|
11
|
+
// (`api.escapeHtml`, src/module-api.js — ADR 0083); core files and scripts
|
|
12
|
+
// require it directly. scripts/bongos/escape-helper-guard.js fails fitness on a
|
|
13
|
+
// third hand-rolled copy anywhere in the code.
|
|
14
|
+
//
|
|
15
|
+
// Zero dependencies by design: the CLI requires it, so it must cost nothing to load.
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const TABLE = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
20
|
+
|
|
21
|
+
/** HTML-escape `s` for text or a quoted attribute value. null/undefined → ''. */
|
|
22
|
+
function escapeHtml(s) {
|
|
23
|
+
return String(s ?? '').replace(/[&<>"']/g, (c) => TABLE[c]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { escapeHtml };
|
package/src/module-api.js
CHANGED
|
@@ -57,6 +57,10 @@ const { buildInfo } = require('./build-info');
|
|
|
57
57
|
// (`responsibilitiesFor` via src/modules.js, `ROLE_RESPONSIBILITIES` via
|
|
58
58
|
// scripts/bongos/gen-role-responsibilities.js) were never flagged and this one was.
|
|
59
59
|
const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibilities');
|
|
60
|
+
// Eager and destructured for the same reasons: html-escape.js requires nothing.
|
|
61
|
+
// escapeHtml is THE server-side HTML escaper (task 1004876) — a module renders
|
|
62
|
+
// user text into a page through this, never through a private copy.
|
|
63
|
+
const { escapeHtml } = require('./html-escape');
|
|
60
64
|
|
|
61
65
|
// ---------------------------------------------------------------------------
|
|
62
66
|
// CORE_VERSION — the version of THIS published surface (semver).
|
|
@@ -75,7 +79,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
79
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
80
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
81
|
|
|
78
|
-
const CORE_VERSION = '1.
|
|
82
|
+
const CORE_VERSION = '1.22.2'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
83
|
|
|
80
84
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
85
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -122,6 +126,10 @@ function logger(moduleKey) {
|
|
|
122
126
|
module.exports = {
|
|
123
127
|
CORE_VERSION,
|
|
124
128
|
|
|
129
|
+
// escapeHtml(s) — the one server-side HTML escaper (& < > " '; null → ''). The
|
|
130
|
+
// browser twin is window.OTB.escapeHtml; fitness fails on a third copy (task 1004876).
|
|
131
|
+
escapeHtml,
|
|
132
|
+
|
|
125
133
|
// --- auth & rank (ADR 0016) — the gates the loader composes around a module's
|
|
126
134
|
// routes; a module declares the rank it needs and never wires auth itself.
|
|
127
135
|
get requireBuilder() { return require('./bongos/auth').requireBuilder; },
|
|
@@ -22,7 +22,7 @@ import fs from 'node:fs';
|
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
import vm from 'node:vm';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
|
-
import { makeRunner } from './helpers.mjs';
|
|
25
|
+
import { makeRunner, realOTB } from './helpers.mjs';
|
|
26
26
|
|
|
27
27
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
28
28
|
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
@@ -97,7 +97,7 @@ function harness({ total = 57, failAfter = null, perPagePref = null } = {}) {
|
|
|
97
97
|
getItem: (k) => (store.has(k) ? store.get(k) : null),
|
|
98
98
|
setItem: (k, v) => store.set(k, String(v)),
|
|
99
99
|
},
|
|
100
|
-
OTB: { toast: (msg) => toasts.push(msg) },
|
|
100
|
+
OTB: { ...realOTB(), toast: (msg) => toasts.push(msg) },
|
|
101
101
|
addEventListener() {},
|
|
102
102
|
// loadViewer() calls this for GET /me; signed-out (401) keeps isReviewer()
|
|
103
103
|
// false, which is fine for these paging tests — they don't inspect the
|
|
@@ -26,7 +26,7 @@ import path from 'node:path';
|
|
|
26
26
|
import vm from 'node:vm';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
import { createRequire } from 'node:module';
|
|
29
|
-
import { makeRunner, makeEl, walk } from './helpers.mjs';
|
|
29
|
+
import { makeRunner, makeEl, walk, realOTB } from './helpers.mjs';
|
|
30
30
|
|
|
31
31
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
32
32
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -114,7 +114,7 @@ async function boot(opts = {}) {
|
|
|
114
114
|
getElementById(id) { return walk(root, (el) => (el.id === id ? el : null)); },
|
|
115
115
|
};
|
|
116
116
|
const windowObj = {
|
|
117
|
-
OTB: { toast: (m) => toasts.push(m) },
|
|
117
|
+
OTB: { ...realOTB(), toast: (m) => toasts.push(m) },
|
|
118
118
|
OTBBlockersLib,
|
|
119
119
|
OTBTaskDetail: { href: (id) => '/task/' + id },
|
|
120
120
|
BongosClient: { createClient: () => api },
|
|
@@ -15,6 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
import vm from 'node:vm';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
|
+
import { realOTB } from './helpers.mjs';
|
|
18
19
|
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -233,6 +234,7 @@ function boot({ data, permissions = { 'core.pin.move': true }, stored = {} } = {
|
|
|
233
234
|
};
|
|
234
235
|
const location = { hostname: 'h', origin: 'https://h', pathname: '/', hash: '', assign() {}, replace() {} };
|
|
235
236
|
const windowObj = {
|
|
237
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
236
238
|
__BRANDING__: {}, __MODULES__: { enabled: { provisioning: false } },
|
|
237
239
|
BongosClient: { createClient: () => ({ request: (method, p, opts) => {
|
|
238
240
|
requested.push({ method, p, body: opts && opts.body });
|
|
@@ -313,7 +315,7 @@ async function panel(data) {
|
|
|
313
315
|
const el = () => ({ hidden: true, textContent: '', _html: '', dataset: {}, set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; }, querySelectorAll: () => [] });
|
|
314
316
|
const els = new Map([['software-update-scroll', el()], ['su-body', el()], ['su-note', el()], ['settings-groups', el()]]);
|
|
315
317
|
els.get('settings-groups').dataset.active = 'update';
|
|
316
|
-
const windowObj = { BongosClient: { createClient: () => ({ request: async () => ({ ok: true, status: 200, data }) }) }, OTB: { escapeHtml }, location: { assign() {} } };
|
|
318
|
+
const windowObj = { BongosClient: { createClient: () => ({ request: async () => ({ ok: true, status: 200, data }) }) }, OTB: { ...realOTB(), escapeHtml }, location: { assign() {} } };
|
|
317
319
|
const sandbox = { window: windowObj, document: { getElementById: (id) => els.get(id) || null, addEventListener() {} }, console, setTimeout, Date, Math, URL, Promise, String, Number };
|
|
318
320
|
vm.createContext(sandbox);
|
|
319
321
|
vm.runInContext(PANEL_SRC, sandbox, { filename: 'settings-software-update.js' });
|
|
@@ -12,6 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import vm from 'node:vm';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { realOTB } from './helpers.mjs';
|
|
15
16
|
|
|
16
17
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
18
|
const require_ = createRequire(import.meta.url);
|
|
@@ -265,6 +266,7 @@ function boot({ permissions, update, status = 200, dismissed = null, provisionin
|
|
|
265
266
|
};
|
|
266
267
|
const location = { hostname: 'h', origin: 'https://h', pathname, hash: '', assign() {}, replace() {} };
|
|
267
268
|
const windowObj = {
|
|
269
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
268
270
|
__BRANDING__: {}, __MODULES__: { enabled: { provisioning } },
|
|
269
271
|
BongosClient: {
|
|
270
272
|
createClient: () => ({
|
|
@@ -23,6 +23,7 @@ import fs from 'node:fs';
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import vm from 'node:vm';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { realOTB } from './helpers.mjs';
|
|
26
27
|
|
|
27
28
|
const require = createRequire(import.meta.url);
|
|
28
29
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -409,6 +410,7 @@ function boot({ provisioning, deployUrl, pathname = '/settings', hash = '', held
|
|
|
409
410
|
const location = { hostname: 'h', origin: 'https://h', pathname, hash, assign() {}, replace() {} };
|
|
410
411
|
const update = { running: '1.19.871', latest: '1.19.1033', behind: true, reading: 'ok' };
|
|
411
412
|
const windowObj = {
|
|
413
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
412
414
|
__BRANDING__: {}, __MODULES__: { enabled: { provisioning } },
|
|
413
415
|
BongosClient: {
|
|
414
416
|
createClient: () => ({
|
package/tests/craft_mode.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import vm from 'node:vm';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { realOTB } from './helpers.mjs';
|
|
17
18
|
|
|
18
19
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
20
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -115,6 +116,7 @@ function boot({ craft = true, session = {}, store = {}, throws = false, optOut =
|
|
|
115
116
|
removeItem: (k) => { if (throws) throw new Error('SecurityError'); delete obj[k]; },
|
|
116
117
|
});
|
|
117
118
|
const windowObj = {
|
|
119
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
118
120
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
119
121
|
BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
|
|
120
122
|
matchMedia: (q) => ({ matches: narrow && /max-width: 640px/.test(q), addEventListener() {} }),
|
package/tests/craft_mode_nav.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import vm from 'node:vm';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { realOTB } from './helpers.mjs';
|
|
17
18
|
|
|
18
19
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
20
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -90,6 +91,7 @@ function boot({ session = {}, perms = { 'board.vote.cast': true } } = {}) {
|
|
|
90
91
|
const location = { hostname: 'h', origin: 'https://h', href: 'https://h/work', pathname: '/work', hash: '', search: '', assign() {}, replace() {}, reload() {} };
|
|
91
92
|
const mk = (obj) => ({ getItem: (k) => (k in obj ? obj[k] : null), setItem: (k, v) => { obj[k] = String(v); }, removeItem: (k) => { delete obj[k]; } });
|
|
92
93
|
const windowObj = {
|
|
94
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
93
95
|
__BRANDING__: {}, __MODULES__: { enabled: { provisioning: true } },
|
|
94
96
|
BongosClient: { createClient: () => ({ request: () => new Promise(() => {}) }) },
|
|
95
97
|
matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
@@ -274,6 +276,7 @@ function bootTree({ mode = null, activeId = null } = {}) {
|
|
|
274
276
|
addEventListener() {}, removeEventListener() {},
|
|
275
277
|
};
|
|
276
278
|
const windowObj = {
|
|
279
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
277
280
|
location: { search: '' }, matchMedia: () => ({ matches: false }),
|
|
278
281
|
localStorage: mk({}), sessionStorage: mk(mode ? { 'hall-craft': mode } : {}),
|
|
279
282
|
};
|
|
@@ -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');
|
|
@@ -77,7 +78,7 @@ function boot({ session = {}, store = {}, narrow = false } = {}) {
|
|
|
77
78
|
dispatchEvent() { return true; },
|
|
78
79
|
};
|
|
79
80
|
const mk = (obj) => ({ getItem: (k) => (k in obj ? obj[k] : null), setItem: (k, v) => { obj[k] = String(v); }, removeItem: (k) => { delete obj[k]; } });
|
|
80
|
-
const window = { localStorage: mk(store), sessionStorage: mk(session), matchMedia: (q) => ({ matches: narrow && /max-width: 640px/.test(q) }), location: { search: '' } };
|
|
81
|
+
const window = { OTB: realOTB(), localStorage: mk(store), sessionStorage: mk(session), matchMedia: (q) => ({ matches: narrow && /max-width: 640px/.test(q) }), location: { search: '' } };
|
|
81
82
|
const sandbox = { window, document, Promise, setTimeout, clearTimeout, CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } } };
|
|
82
83
|
vm.createContext(sandbox);
|
|
83
84
|
// "Loading" a script: the wall runs the REAL craft-review.js; the palette is a fake panel.
|
|
@@ -208,7 +209,7 @@ test('THE WALL is scribbled in CSS only, in ink alone, and stands still under re
|
|
|
208
209
|
});
|
|
209
210
|
|
|
210
211
|
test('craft-review.js draws six small sky doodles, each with its pen, the wide ones foldable', () => {
|
|
211
|
-
const sandbox = { window: {} };
|
|
212
|
+
const sandbox = { window: { OTB: realOTB() } };
|
|
212
213
|
vm.createContext(sandbox);
|
|
213
214
|
vm.runInContext(REVIEW, sandbox);
|
|
214
215
|
const api = sandbox.window.OTBCraftReview;
|
|
@@ -194,8 +194,13 @@ test('a slug carrying an apostrophe cannot break the click handler', () => {
|
|
|
194
194
|
// single-quoted JS string inside an HTML attribute.
|
|
195
195
|
const odd = { ...crit(2, "o'hara-rule", '1000999') };
|
|
196
196
|
const html = status.renderStatusWidget({ ...DATA, criteria: [odd] }, null);
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
// The attribute is HTML-escaped AFTER the JS escape, so the apostrophe arrives as
|
|
198
|
+
// \' (task 1004876: the shared escaper escapes ' too) — the browser decodes the
|
|
199
|
+
// attribute first, and the JS it then parses is the \' asserted below.
|
|
200
|
+
const onclick = html.match(/onclick="([^"]*o\\'hara-rule[^"]*)"/);
|
|
201
|
+
assert.ok(onclick, 'the apostrophe was not escaped for the JS string');
|
|
202
|
+
assert.ok(onclick[1].replace(/'/g, "'").includes("sendPrompt('/status o\\'hara-rule')"),
|
|
203
|
+
'the decoded handler carries the JS-escaped apostrophe');
|
|
199
204
|
});
|
|
200
205
|
|
|
201
206
|
console.log(`\ncriterion ref — read side (task 1004069): ${passed} passed, ${failed} failed`);
|
|
@@ -26,12 +26,13 @@ import fs from 'node:fs';
|
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import vm from 'node:vm';
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { realOTB } from './helpers.mjs';
|
|
29
30
|
|
|
30
31
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
31
32
|
|
|
32
33
|
function loadKit() {
|
|
33
34
|
const src = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/hall-kit.js'), 'utf8');
|
|
34
|
-
const win = {};
|
|
35
|
+
const win = { OTB: realOTB() }; // the page loads dom-utils.js first (task 1004876)
|
|
35
36
|
const ctx = vm.createContext({ window: win, document: { createElement: () => ({}) }, console });
|
|
36
37
|
vm.runInContext(src, ctx);
|
|
37
38
|
assert.ok(win.OTBKit, 'hall-kit.js did not publish window.OTBKit');
|
package/tests/founding_mode.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { createRequire } from 'node:module';
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { realOTB } from './helpers.mjs';
|
|
16
17
|
|
|
17
18
|
const require = createRequire(import.meta.url);
|
|
18
19
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -135,6 +136,13 @@ function makeHall() {
|
|
|
135
136
|
|
|
136
137
|
// The REAL escaper and the real wake map are sliced out of shell.js with the function,
|
|
137
138
|
// so the band's escaping is the shell's own, not a stand-in that agrees with the test.
|
|
139
|
+
// shell.js escapes through window.OTB (dom-utils.js, task 1004876), so the window it is
|
|
140
|
+
// handed carries the real one.
|
|
141
|
+
function sliceEsc() {
|
|
142
|
+
const line = SHELL.split("\n").find((l) => l.startsWith(" function esc("));
|
|
143
|
+
assert.ok(line, "esc was located in shell.js");
|
|
144
|
+
return line;
|
|
145
|
+
}
|
|
138
146
|
function sliceFn(name) {
|
|
139
147
|
const start = SHELL.indexOf(' function ' + name + '(');
|
|
140
148
|
const end = SHELL.indexOf('\n }\n', start);
|
|
@@ -145,8 +153,8 @@ function loadApplyFounding(document, access, brand = { identity: { productName:
|
|
|
145
153
|
const wakeStart = SHELL.indexOf(' var WAKE_TEXT = {');
|
|
146
154
|
const wakeEnd = SHELL.indexOf('};', wakeStart) + 2;
|
|
147
155
|
assert.ok(wakeStart > -1, 'WAKE_TEXT was located in shell.js');
|
|
148
|
-
const src = [SHELL.slice(wakeStart, wakeEnd),
|
|
149
|
-
return new Function('document', 'brand', 'access', `${src}\nreturn applyFounding;`)(document, brand, access);
|
|
156
|
+
const src = [SHELL.slice(wakeStart, wakeEnd), sliceEsc(), sliceFn('applyFounding')].join('\n');
|
|
157
|
+
return new Function('document', 'brand', 'access', 'window', `${src}\nreturn applyFounding;`)(document, brand, access, { OTB: realOTB() });
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
const snapshot = (h) => JSON.stringify({
|
|
@@ -22,6 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import vm from 'node:vm';
|
|
23
23
|
import { createRequire } from 'node:module';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { realOTB } from './helpers.mjs';
|
|
25
26
|
|
|
26
27
|
const require = createRequire(import.meta.url);
|
|
27
28
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -159,12 +160,14 @@ test('GET /provisioning/instances and /provisioning/instances/:id carry github_i
|
|
|
159
160
|
function grab(head) {
|
|
160
161
|
const at = HUB.indexOf(`\n ${head}`);
|
|
161
162
|
assert.ok(at >= 0, `${head} must exist`);
|
|
163
|
+
const line = HUB.slice(at, HUB.indexOf('\n', at + 1));
|
|
164
|
+
if (/\}\s*$/.test(line)) return line; // a one-line declaration (esc, task 1004876)
|
|
162
165
|
const end = HUB.indexOf('\n }', at + 1);
|
|
163
166
|
const close = HUB.slice(end, end + 5) === '\n };' ? end + 5 : end + 4;
|
|
164
167
|
return HUB.slice(at, close);
|
|
165
168
|
}
|
|
166
169
|
function page() {
|
|
167
|
-
const ctx = { STATE_GROUP: { active: 'live', error: 'attention', torn_down: 'gone' } };
|
|
170
|
+
const ctx = { window: { OTB: realOTB() }, STATE_GROUP: { active: 'live', error: 'attention', torn_down: 'gone' } };
|
|
168
171
|
vm.createContext(ctx);
|
|
169
172
|
vm.runInContext(['function esc(', 'function ghInstallRow(', 'function groupFor('].map(grab).join('\n'), ctx);
|
|
170
173
|
return ctx;
|
package/tests/goal_map_page.mjs
CHANGED
|
@@ -431,9 +431,9 @@ await test('the page is the sky\'s own surface — the same sheet and the same s
|
|
|
431
431
|
assert.match(html, /<main id="hall-main" class="sky-stage">/, 'the sky is drawn in the stage, the skip link\'s target');
|
|
432
432
|
const scripts = (h) => [...h.matchAll(/<script src="\/builders\/([^"?]+)\?v=([^"]+)"/g)].map((m) => [m[1], m[2]]);
|
|
433
433
|
// criterion-uat-ui.js (task 1004402): the rings' UAT state words, shared with the goal page.
|
|
434
|
-
assert.deepEqual(scripts(html).filter((s) => !['shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0])).map((s) => s[0]), ['idea-objects.js', 'criterion-uat-ui.js', 'sky-data.js', 'sky-layout.js', 'sky-draw.js', 'sky-interaction.js', 'sky-panel.js', 'sky-composer.js', 'sky-tour.js']);
|
|
434
|
+
assert.deepEqual(scripts(html).filter((s) => !['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0])).map((s) => s[0]), ['idea-objects.js', 'criterion-uat-ui.js', 'sky-data.js', 'sky-layout.js', 'sky-draw.js', 'sky-interaction.js', 'sky-panel.js', 'sky-composer.js', 'sky-tour.js']);
|
|
435
435
|
// Both doors load the shell (tasks 1004327, 1004430); the SKY's files are the same.
|
|
436
|
-
const skyScripts = (h) => scripts(h).filter((s) => !['shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0]));
|
|
436
|
+
const skyScripts = (h) => scripts(h).filter((s) => !['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].includes(s[0]));
|
|
437
437
|
assert.deepEqual(skyScripts(html), skyScripts(thinking), 'the two doors load the same sky files at the same stamp');
|
|
438
438
|
assert.equal(html.match(/thinking\.css\?v=([^"]+)/)[1], thinking.match(/thinking\.css\?v=([^"]+)/)[1]);
|
|
439
439
|
assert.match(read('thinking.css'), /body\[data-page\] \.sky-stage \{/, 'the sheet dresses the one stage both doors draw in inside the hall');
|
|
@@ -18,7 +18,7 @@ import fs from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import vm from 'node:vm';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import { makeRunner, makeEl, walk, settle } from './helpers.mjs';
|
|
21
|
+
import { makeRunner, makeEl, walk, settle, 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)), '..');
|
|
@@ -105,7 +105,7 @@ function bootPanel({ answers = {}, writes = null, mode = 'light' } = {}) {
|
|
|
105
105
|
return val.__status ? { ok: false, status: val.__status, data: val.data } : { ok: true, status: 200, data: val };
|
|
106
106
|
},
|
|
107
107
|
};
|
|
108
|
-
const win = { matchMedia: () => ({ matches: false }), __BRANDING__: { identity: { productName: 'Cloud Bongos', worldName: 'the studio' } } };
|
|
108
|
+
const win = { OTB: realOTB(), matchMedia: () => ({ matches: false }), __BRANDING__: { identity: { productName: 'Cloud Bongos', worldName: 'the studio' } } };
|
|
109
109
|
const ctx = vm.createContext({ window: win, document: documentObj, console, setTimeout, clearTimeout, encodeURIComponent });
|
|
110
110
|
win.window = win;
|
|
111
111
|
vm.runInContext(read('brand-holes.js'), ctx, { filename: 'brand-holes.js' });
|
package/tests/hall_city.mjs
CHANGED
|
@@ -288,7 +288,7 @@ test('the city sits in the hall chrome, behind the shell\'s full-screen switch',
|
|
|
288
288
|
// task 1004685 (the living city): gov-living.js, the model under the drawing, and
|
|
289
289
|
// gov-feeds.js, its one reader per page (shared with the shell's tally), load before it
|
|
290
290
|
// task 1004702: the card answers its first ask in place, so the deck's card and sender (and the desk rows under them) load before the page
|
|
291
|
-
assert.deepEqual(scripts, ['shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, as on every hall page');
|
|
291
|
+
assert.deepEqual(scripts, ['dom-utils', 'shell-trail', 'craft-mode', 'shell', 'palette', 'gov-living', 'gov-feeds', 'city-draw', 'hall-kit', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-deck', 'city-decide', 'city-switchboard', 'city'], 'the shell first, as on every hall page');
|
|
292
292
|
const css = read('city.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
293
293
|
assert.match(css, /\.city-main \{ position: fixed; top: var\(--topbar-h, 56px\); left: var\(--sidebar-w-rail, 64px\);[^}]*contain: layout paint;/);
|
|
294
294
|
assert.match(css, /html\[data-fullscreen="on"\] \.city-main \{ top: 0; left: 0; \}/);
|
|
@@ -18,6 +18,7 @@ import fs from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import vm from 'node:vm';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { realOTB } from './helpers.mjs';
|
|
21
22
|
|
|
22
23
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
24
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -104,6 +105,7 @@ function boot({ page = 'thinking', optIn = true, store = {}, storageThrows = fal
|
|
|
104
105
|
};
|
|
105
106
|
let resizes = 0;
|
|
106
107
|
const windowObj = {
|
|
108
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
107
109
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
108
110
|
// `meId` answers /me once with that builder; otherwise the read never settles.
|
|
109
111
|
BongosClient: { createClient: () => ({ request: () => (meId == null ? new Promise(() => {})
|
|
@@ -31,6 +31,7 @@ import path from 'node:path';
|
|
|
31
31
|
import vm from 'node:vm';
|
|
32
32
|
import { createRequire } from 'node:module';
|
|
33
33
|
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { realOTB } from './helpers.mjs';
|
|
34
35
|
|
|
35
36
|
const require = createRequire(import.meta.url);
|
|
36
37
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -300,6 +301,7 @@ function baseWindow(hash = '') {
|
|
|
300
301
|
const store = new Map();
|
|
301
302
|
const ls = { getItem: (k) => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: (k) => store.delete(k) };
|
|
302
303
|
return {
|
|
304
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
303
305
|
innerWidth: 1440, innerHeight: 900, location: { hash, href: '', search: '', pathname: '/' },
|
|
304
306
|
localStorage: ls, sessionStorage: ls,
|
|
305
307
|
matchMedia: () => ({ matches: true, addEventListener() {} }), addEventListener() {}, removeEventListener() {},
|
|
@@ -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 HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
@@ -299,7 +300,7 @@ function runPage(file, extraWindow = {}) {
|
|
|
299
300
|
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
300
301
|
const sandbox = {
|
|
301
302
|
window: {
|
|
302
|
-
OTB: { escapeHtml: esc, $: () => null, fmtNum: (n) => String(n ?? 0), fmtDate: (s) => String(s ?? '') },
|
|
303
|
+
OTB: { ...realOTB(), escapeHtml: esc, $: () => null, fmtNum: (n) => String(n ?? 0), fmtDate: (s) => String(s ?? '') },
|
|
303
304
|
__BRANDING__: { currency: { label: 'drachmae' } },
|
|
304
305
|
...extraWindow,
|
|
305
306
|
},
|
package/tests/hall_my_agents.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import path from 'node:path';
|
|
|
26
26
|
import vm from 'node:vm';
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { realOTB } from './helpers.mjs';
|
|
29
30
|
|
|
30
31
|
const require = createRequire(import.meta.url);
|
|
31
32
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -378,7 +379,7 @@ function bootCraft({ session = {} } = {}) {
|
|
|
378
379
|
getElementById: () => null, querySelector: () => null, querySelectorAll: () => [],
|
|
379
380
|
createElement: () => el(), addEventListener() {}, removeEventListener() {}, dispatchEvent() { return true; },
|
|
380
381
|
};
|
|
381
|
-
const window = { location: { search: '' }, matchMedia: () => ({ matches: false }), sessionStorage: mk(session), localStorage: mk({}) };
|
|
382
|
+
const window = { OTB: realOTB(), location: { search: '' }, matchMedia: () => ({ matches: false }), sessionStorage: mk(session), localStorage: mk({}) };
|
|
382
383
|
const sandbox = { window, document, console, CustomEvent: class { constructor(t, i) { this.type = t; Object.assign(this, i); } } };
|
|
383
384
|
sandbox.globalThis = sandbox;
|
|
384
385
|
vm.createContext(sandbox);
|
|
@@ -441,7 +442,7 @@ test('PAGE: in the hall family, with no prototype key', () => {
|
|
|
441
442
|
const sheets = [...html.matchAll(/href="\/builders\/([a-z-]+\.css)/g)].map((m) => m[1]);
|
|
442
443
|
assert.deepEqual(sheets, ['style.css', 'oversight.css', 'my-agents.css'], 'rides the oversight archetype, linked before its own sheet');
|
|
443
444
|
const scripts = [...html.matchAll(/src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
|
|
444
|
-
assert.deepEqual(scripts.slice(0,
|
|
445
|
+
assert.deepEqual(scripts.slice(0, 4), ['dom-utils', 'shell-trail', 'craft-mode', 'shell']); // dom-utils first: the shell escapes through it (task 1004876)
|
|
445
446
|
for (const s of ['gov-living', 'gov-positions', 'my-agents']) assert.ok(scripts.includes(s), `loads ${s}.js`);
|
|
446
447
|
assert.doesNotMatch(html, /ma-key|data-proto|Prototype data/, 'nothing on the page is prototype data, so there is no key');
|
|
447
448
|
assert.doesNotMatch(read('my-agents.js'), /governor-overlay/, 'and it reads no overlay');
|
package/tests/hall_nav.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 HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -136,6 +137,7 @@ function boot({ page = 'settings', hash = '', pathname = '/settings', search = '
|
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
const windowObj = {
|
|
140
|
+
OTB: realOTB(), // the page loads dom-utils.js first (task 1004876)
|
|
139
141
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
140
142
|
// Index pages never reach probeMe (builders.js owns /me there); a non-index
|
|
141
143
|
// boot would, so hand back a never-settling promise rather than fake auth —
|
package/tests/hall_palette.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import fs from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import vm from 'node:vm';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import { hallSource } from './helpers.mjs';
|
|
21
|
+
import { hallSource, realOTB } from './helpers.mjs';
|
|
22
22
|
|
|
23
23
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
24
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
@@ -79,7 +79,7 @@ function bootPalette({ navItems = [], visibleIds = ['nav-standing', 'nav-work',
|
|
|
79
79
|
addEventListener() {},
|
|
80
80
|
activeElement: null,
|
|
81
81
|
};
|
|
82
|
-
const windowObj = { location: { hash: '', assign() {} }, addEventListener() {}, open() {} };
|
|
82
|
+
const windowObj = { OTB: realOTB(), location: { hash: '', assign() {} }, addEventListener() {}, open() {} };
|
|
83
83
|
const sandbox = {
|
|
84
84
|
window: windowObj, document: documentObj, navigator: { platform: 'Win32', userAgent: 'test' },
|
|
85
85
|
console, setTimeout, clearTimeout, Promise, JSON, Number, String, Array, Object,
|