@bongos/core 1.21.181 → 1.22.1
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 +159 -144
- package/docs/copy-inventory.md +165 -165
- package/docs/copy-registry.json +184 -184
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +12 -0
- package/docs/page-inventory.json +35 -26
- package/docs/page-readings.json +389 -388
- 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/lifecycle/ship-card.js +1 -4
- package/modules/npm-release/preview/proxy.js +2 -3
- package/modules/npm-release/public/task-widget.js +1 -1
- package/modules/platform-identity/routes/sso.js +1 -3
- package/modules/provisioning/routes/callback-page.js +2 -6
- package/modules/public-landing/public/projects.html +3 -5
- package/modules/raise/raise.js +1 -1
- 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/government_protected_surfaces.mjs +15 -96
- 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/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
- package/modules/government/protected-surfaces.js +0 -88
|
@@ -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');
|
|
@@ -26,9 +26,23 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
26
26
|
const GOV = path.join(ROOT, 'modules', 'government');
|
|
27
27
|
|
|
28
28
|
const ppc = require(path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
|
|
29
|
-
const registry = require(path.join(GOV, 'protected-surfaces.js'));
|
|
30
29
|
const catalog = require(path.join(GOV, 'catalog.js'));
|
|
31
30
|
|
|
31
|
+
// The registry exactly as the live kernel matcher loads it — the one loader the
|
|
32
|
+
// enforcers (pre-push hook, grader pre-pass, main audit, goal-scope check) use.
|
|
33
|
+
// Task 1004890 deleted modules/government/protected-surfaces.js, a wrapper over
|
|
34
|
+
// this same matcher that nothing but this test ever required; FLOOR and
|
|
35
|
+
// SYSTEM_PERMISSIONS are derived here the way it derived them.
|
|
36
|
+
const registry = {
|
|
37
|
+
SURFACES: ppc.PROTECTED_SURFACES,
|
|
38
|
+
PROTECTED_GLOBS: ppc.PROTECTED_GLOBS,
|
|
39
|
+
FLOOR: catalog.RANK_ORDER[Math.min(...ppc.PROTECTED_SURFACES.map((s) => catalog.RANK_ORDER.indexOf(s.floor)))],
|
|
40
|
+
SYSTEM_PERMISSIONS: [...new Set(ppc.PROTECTED_SURFACES.map((s) => s.permission))].sort(),
|
|
41
|
+
surfaceFor: (file) => ppc.surfaceFor(file),
|
|
42
|
+
floorFor: (file) => ppc.floorFor(file),
|
|
43
|
+
isProtected: (file) => ppc.surfaceFor(file) !== null,
|
|
44
|
+
};
|
|
45
|
+
|
|
32
46
|
// The verbatim PROTECTED_GLOBS array as it stood immediately before R101.
|
|
33
47
|
const EXPECTED_GLOBS_BEFORE_R101 = [
|
|
34
48
|
'src/bongos/auth.js',
|
|
@@ -979,91 +993,6 @@ test('every entry maps to a permission the catalog defines as system:true', () =
|
|
|
979
993
|
assert.deepEqual(registry.SYSTEM_PERMISSIONS, ['path.protected.modify']);
|
|
980
994
|
});
|
|
981
995
|
|
|
982
|
-
// Feed a BAD registry through the real loader in a cold subprocess and assert the
|
|
983
|
-
// module refuses to load. The pattern is the sibling probe below ("a missing
|
|
984
|
-
// registry fails CLOSED"): patch fs.readFileSync for the registry path only, then
|
|
985
|
-
// require the module under test and let its load-time throw be the result.
|
|
986
|
-
//
|
|
987
|
-
// WHY A SUBPROCESS AND NOT AN IN-PROCESS ASSERTION (task 1003196, finding 9). The
|
|
988
|
-
// version of this test before that task read the catalog and checked that a
|
|
989
|
-
// grantable key exists and an unknown key returns null — both true regardless of
|
|
990
|
-
// whether the guard exists. Demonstrated: deleting the whole validation loop from
|
|
991
|
-
// modules/government/protected-surfaces.js left the entire suite green. The guard
|
|
992
|
-
// is load-time, so only a fresh module load can exercise it, and the registry is
|
|
993
|
-
// read from disk, so only a patched read can make it bad.
|
|
994
|
-
//
|
|
995
|
-
// This matters because the sibling test above ("every entry maps to a permission
|
|
996
|
-
// the catalog defines as system:true") only checks the COMMITTED file. The
|
|
997
|
-
// load-time throw is what protects an out-of-band registry write — the Government
|
|
998
|
-
// tab editing an instance file at runtime — and nothing was watching it.
|
|
999
|
-
function loadWithRegistry(registryJson) {
|
|
1000
|
-
execFileSync(process.execPath, ['-e',
|
|
1001
|
-
"const fs=require('node:fs');const real=fs.readFileSync;" +
|
|
1002
|
-
'const fake=' + JSON.stringify(JSON.stringify(registryJson)) + ';' +
|
|
1003
|
-
"fs.readFileSync=(p,...r)=>(String(p).includes('protected-surfaces.json') ? fake : real(p,...r));" +
|
|
1004
|
-
"require('./modules/government/protected-surfaces.js');",
|
|
1005
|
-
], { cwd: ROOT, stdio: 'pipe' });
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
test('a surface mapped to a GRANTABLE permission fails loud at load', () => {
|
|
1009
|
-
// A grantable (system:false) key is the dangerous case ADR 0151 §4 names: a
|
|
1010
|
-
// custom rank could be handed the very power the substrate reserves.
|
|
1011
|
-
const grantable = catalog.grantableKeys()[0];
|
|
1012
|
-
assert.ok(grantable, 'the catalog must define at least one grantable key for this probe');
|
|
1013
|
-
assert.equal(catalog.isSystem(grantable), false, 'fixture sanity: the probe key must be grantable');
|
|
1014
|
-
assert.throws(
|
|
1015
|
-
() => loadWithRegistry({
|
|
1016
|
-
floor: 'metic',
|
|
1017
|
-
permission: grantable,
|
|
1018
|
-
surfaces: [{ glob: 'src/probe/grantable-surface.js' }],
|
|
1019
|
-
}),
|
|
1020
|
-
/which is not system:true/,
|
|
1021
|
-
'a surface mapping to a grantable permission must refuse to load',
|
|
1022
|
-
);
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
test('a surface mapped to an UNKNOWN permission fails loud at load', () => {
|
|
1026
|
-
assert.equal(catalog.byKey('not.a.real.permission'), null, 'fixture sanity: the key must be unknown');
|
|
1027
|
-
assert.throws(
|
|
1028
|
-
() => loadWithRegistry({
|
|
1029
|
-
floor: 'metic',
|
|
1030
|
-
permission: 'not.a.real.permission',
|
|
1031
|
-
surfaces: [{ glob: 'src/probe/unknown-surface.js' }],
|
|
1032
|
-
}),
|
|
1033
|
-
/which the permission catalog does not define/,
|
|
1034
|
-
'a surface mapping to a permission the catalog never defines must refuse to load',
|
|
1035
|
-
);
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
test('the probe itself is honest — a GOOD registry loads clean through the same path', () => {
|
|
1039
|
-
// Without this, both assertions above would pass if the subprocess threw for an
|
|
1040
|
-
// unrelated reason (a bad patch, a missing cwd, a syntax error in the -e script).
|
|
1041
|
-
assert.doesNotThrow(() => loadWithRegistry({
|
|
1042
|
-
floor: 'metic',
|
|
1043
|
-
permission: registry.SYSTEM_PERMISSIONS[0],
|
|
1044
|
-
surfaces: [{ glob: 'src/probe/good-surface.js' }],
|
|
1045
|
-
}), 'a well-formed registry must still load — otherwise the probes above prove nothing');
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
test('an unknown FLOOR is refused before the catalog check ever sees it', () => {
|
|
1049
|
-
// The validation loop's third branch (`!catalog.RANK_ORDER.includes(s.floor)`) is
|
|
1050
|
-
// a DRIFT guard, not dead code, and this test records why it cannot be provoked
|
|
1051
|
-
// through the registry: the kernel loader keeps its own local RANK_TIER map
|
|
1052
|
-
// (permission-path-check.js) and rejects an unknown floor first. The government
|
|
1053
|
-
// branch only becomes reachable if those two rank lists ever disagree — which is
|
|
1054
|
-
// exactly the drift it exists to catch, and is not craftable from JSON alone.
|
|
1055
|
-
assert.throws(
|
|
1056
|
-
() => loadWithRegistry({
|
|
1057
|
-
floor: 'emperor',
|
|
1058
|
-
permission: registry.SYSTEM_PERMISSIONS[0],
|
|
1059
|
-
surfaces: [{ glob: 'src/probe/bad-floor.js' }],
|
|
1060
|
-
}),
|
|
1061
|
-
/unknown floor/,
|
|
1062
|
-
);
|
|
1063
|
-
assert.deepEqual(catalog.RANK_ORDER, ['xenos', 'thetes', 'metic', 'archon'],
|
|
1064
|
-
'if this list and the kernel RANK_TIER drift, the government floor branch wakes up');
|
|
1065
|
-
});
|
|
1066
|
-
|
|
1067
996
|
// ---------- synchronous, no DB, no server ----------
|
|
1068
997
|
|
|
1069
998
|
test('the registry loads synchronously from the repo with no DB and no server', () => {
|
|
@@ -1080,12 +1009,6 @@ test('the registry loads synchronously from the repo with no DB and no server',
|
|
|
1080
1009
|
test('the registry data file carries no DB/network dependency', () => {
|
|
1081
1010
|
const json = fs.readFileSync(path.join(GOV, 'protected-surfaces.json'), 'utf8');
|
|
1082
1011
|
assert.doesNotThrow(() => JSON.parse(json), 'the canonical form must be plain parseable data');
|
|
1083
|
-
const code = fs.readFileSync(path.join(GOV, 'protected-surfaces.js'), 'utf8')
|
|
1084
|
-
.split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
|
|
1085
|
-
for (const banned of ['./db', 'pool', 'fetch(', 'await ']) {
|
|
1086
|
-
assert.ok(!code.includes(banned),
|
|
1087
|
-
`protected-surfaces.js must not introduce "${banned}" — the substrate reads this with no DB and no server (ADR 0151 §4)`);
|
|
1088
|
-
}
|
|
1089
1012
|
});
|
|
1090
1013
|
|
|
1091
1014
|
// ---------- policy owns / substrate enforces ----------
|
|
@@ -1095,10 +1018,6 @@ test('the kernel does not require the governance module (ADR 0083 one-way rule)'
|
|
|
1095
1018
|
const requires = [...kernel.matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1]);
|
|
1096
1019
|
assert.deepEqual(requires.filter((r) => r.includes('modules/')), [],
|
|
1097
1020
|
'core must never require a module — it reads the registry as data');
|
|
1098
|
-
// …and the flow runs the legal way: governance reaches the matcher through the doorway.
|
|
1099
|
-
const gov = fs.readFileSync(path.join(GOV, 'protected-surfaces.js'), 'utf8');
|
|
1100
|
-
assert.ok(/require\(\s*['"]\.\.\/\.\.\/src\/module-api['"]\s*\)/.test(gov),
|
|
1101
|
-
'governance must reach the kernel matcher through the published doorway');
|
|
1102
1021
|
});
|
|
1103
1022
|
|
|
1104
1023
|
test('the hardcoded protected list is gone from the kernel — one source, no drift', () => {
|
|
@@ -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,
|
|
@@ -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); } },
|