@bongos/core 1.24.89 → 1.24.90

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.
@@ -1063,6 +1063,8 @@
1063
1063
  spriteSrc: spriteSrc,
1064
1064
  // task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
1065
1065
  me: function () { return lastMe; },
1066
- openTool: function (id) { var t = toolFor(id); if (t) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
1066
+ // A tool opens only for the character whose tool it is (task 1005207): the
1067
+ // review sheet is the Artist's, so no other character can open it this way.
1068
+ openTool: function (id) { var t = toolFor(id); if (t && toolsFor(wearing()).indexOf(t) !== -1) loadTool(t).then(function (api) { api.open(); }, function () { /* tool did not load */ }); },
1067
1069
  };
1068
1070
  })();
@@ -58,6 +58,10 @@ html.pn-open { overflow: hidden; }
58
58
  box-shadow: 0 0 0 1px var(--craft, var(--rule)), var(--pn-shadow-pop);
59
59
  cursor: pointer;
60
60
  }
61
+ /* Game mode (task 1005207): the glass shows only the visor, so the parked tab
62
+ is not drawn. The visor's "Review this page" (R) opens the sheet, and closing
63
+ it leaves nothing at the edge. Classic mode keeps the tab. */
64
+ html[data-game="on"] .pn__tab { display: none; }
61
65
  .pn__tab:hover { background: var(--pn-glass); }
62
66
  .pn__tab:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
63
67
  .pn__tab-icon { width: 18px; height: 18px; }
@@ -361,7 +361,13 @@
361
361
  if (tab) {
362
362
  tab.setAttribute('aria-expanded', on ? 'false' : 'true');
363
363
  tab.setAttribute('aria-label', on ? 'Show the review panel' : 'Hide the review panel');
364
- if (on && !quiet) tab.focus();
364
+ // In game mode the tab is not drawn (page-notes.css, task 1005207), so focus
365
+ // leaves the tucked panel for the page instead of a tab nobody can see.
366
+ if (on && !quiet) {
367
+ if (tab.getClientRects && tab.getClientRects().length === 0) {
368
+ if (sheet.contains(document.activeElement) && document.activeElement.blur) document.activeElement.blur();
369
+ } else tab.focus();
370
+ }
365
371
  }
366
372
  if (on) save();
367
373
  else if (state.pending) { state.pending = false; openDraft(); }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.89",
3
+ "version": "1.24.90",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.89",
9
+ "version": "1.24.90",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.89",
3
+ "version": "1.24.90",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11637,5 +11637,11 @@
11637
11637
  "id": "1005203",
11638
11638
  "text": "In the game, the visor now shows four small bars for how much of the current version is built, and every main button in the game now belongs on the visor."
11639
11639
  }
11640
+ ],
11641
+ "1.24.90": [
11642
+ {
11643
+ "id": "1005207",
11644
+ "text": "In the game view the Artist's feedback tab no longer sits on the screen; the Artist opens the feedback sheet from the visor, and only the Artist can open it."
11645
+ }
11640
11646
  ]
11641
11647
  }
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.89'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.90'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -0,0 +1,86 @@
1
+ // tests/hall_game_review_tab.mjs — no parked review tab on the game's glass (task 1005207).
2
+ //
3
+ // Game mode (html[data-game="on"]) shows only the visor, so the Artist's docked review
4
+ // sheet (page-notes.js + page-notes.css) draws no tab at the right edge. The visor's
5
+ // "Review this page" (R) still opens the sheet, and closing it leaves nothing behind.
6
+ // Classic mode keeps the tab exactly as it was. Driven in a real browser over the
7
+ // REAL page-notes.js and the hall's real sheets.
8
+ import assert from 'node:assert/strict';
9
+ import { test, after } from 'node:test';
10
+ import { createRequire } from 'node:module';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const HALL = path.join(ROOT, 'modules/hall-ui/public');
18
+
19
+ const lib = require(path.join(ROOT, 'modules', 'ui-design', 'kit', 'lib.js'));
20
+ let browser = null;
21
+ try { browser = await lib.launchBrowser(); } catch (e) { if (!(e instanceof lib.NoBrowserError)) throw e; }
22
+ after(() => browser && browser.close());
23
+ const skip = !browser && 'no system Edge or Chrome';
24
+
25
+ const ORIGIN = 'http://hall.test';
26
+ const TYPES = { '.js': 'text/javascript', '.css': 'text/css' };
27
+ const PAGE = (game) => `<!doctype html><html data-theme="dark"${game ? ' data-game="on"' : ''}><head><meta charset="utf-8">
28
+ <link rel="stylesheet" href="/builders/style.css"></head>
29
+ <body data-page="home"><button id="before">page</button>
30
+ <script>window.OTB = { escapeHtml: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => '&#' + c.charCodeAt(0) + ';') };</script>
31
+ <script src="/builders/page-notes.js"></script></body></html>`;
32
+
33
+ async function open(game) {
34
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
35
+ await ctx.route(ORIGIN + '/**', (route) => {
36
+ const p = new URL(route.request().url()).pathname;
37
+ if (p === '/') return route.fulfill({ contentType: 'text/html', body: PAGE(game) });
38
+ // The sheet's reads: an empty draft and an empty list are enough to draw it.
39
+ if (p.startsWith('/api/bongos/page-notes')) return route.fulfill({ contentType: 'application/json', body: JSON.stringify({ note: { id: 1, body_md: '', status: 'draft' }, images: [], notes: [] }) });
40
+ const f = path.join(HALL, p.replace(/^\/builders\//, ''));
41
+ if (p.startsWith('/builders/') && TYPES[path.extname(p)] && fs.existsSync(f)) return route.fulfill({ contentType: TYPES[path.extname(p)], body: fs.readFileSync(f) });
42
+ return route.fulfill({ status: 404, body: '' });
43
+ });
44
+ const page = await ctx.newPage();
45
+ const errors = [];
46
+ page.on('pageerror', (e) => errors.push(String(e.message)));
47
+ await page.goto(ORIGIN + '/');
48
+ await page.waitForFunction(() => !!window.OTBPageNotes);
49
+ // What craft-mode.js does for the Artist: dock the sheet, tucked away behind its tab.
50
+ await page.evaluate(() => window.OTBPageNotes.dock());
51
+ return { ctx, page, errors };
52
+ }
53
+ const tabShown = (page) => page.evaluate(() => {
54
+ const t = document.querySelector('#page-notes .pn__tab');
55
+ return !!t && getComputedStyle(t).display !== 'none' && t.getClientRects().length > 0;
56
+ });
57
+ const panelShown = (page) => page.evaluate(() => getComputedStyle(document.querySelector('#page-notes .pn__panel')).visibility !== 'hidden'
58
+ && !document.getElementById('page-notes').classList.contains('pn--collapsed'));
59
+
60
+ test('classic mode keeps the parked tab at the right edge', { skip }, async () => {
61
+ const { ctx, page, errors } = await open(false);
62
+ assert.equal(await tabShown(page), true);
63
+ assert.deepEqual(errors, []);
64
+ await ctx.close();
65
+ });
66
+
67
+ test('game mode: no parked tab; R opens the sheet, and closing leaves nothing at the edge', { skip }, async () => {
68
+ const { ctx, page, errors } = await open(true);
69
+ assert.equal(await tabShown(page), false, 'parked, nothing shows on the glass');
70
+ await page.waitForTimeout(350); // the slide-out
71
+ assert.equal(await panelShown(page), false);
72
+ // The visor's "Review this page" is OTBCraftMode.openTool('palette') → OTBPageNotes.open().
73
+ await page.evaluate(() => window.OTBPageNotes.open());
74
+ assert.equal(await panelShown(page), true, 'R still opens the sheet');
75
+ assert.equal(await tabShown(page), false, 'open, still no tab beside it');
76
+ await page.click('#page-notes [data-pn-close]');
77
+ await page.waitForTimeout(350);
78
+ assert.equal(await panelShown(page), false, 'closed, the sheet is gone');
79
+ assert.equal(await tabShown(page), false, 'and no tab is left behind');
80
+ assert.equal(await page.evaluate(() => document.getElementById('page-notes').contains(document.activeElement)), false,
81
+ 'focus leaves the tucked sheet instead of landing on a tab nobody can see');
82
+ await page.evaluate(() => window.OTBPageNotes.open());
83
+ assert.equal(await panelShown(page), true, 'and R opens it again');
84
+ assert.deepEqual(errors, []);
85
+ await ctx.close();
86
+ });
@@ -95,6 +95,26 @@ test('THE PANEL: docked at the edge in Artist alone, with no top-bar button, and
95
95
  assert.deepEqual(none.loaded, [], 'and nothing loads');
96
96
  });
97
97
 
98
+ test('the review sheet is the Artist\'s alone: openTool (the visor\'s R) opens it for no other character (task 1005207)', async () => {
99
+ const a = boot({ session: { 'hall-craft': 'artist' } });
100
+ await flush();
101
+ a.craft.openTool('palette');
102
+ await flush();
103
+ assert.deepEqual(a.opened, ['dock', 'open'], 'the Artist opens it');
104
+ for (const m of ['artisan', 'patron']) {
105
+ const p = boot({ session: { 'hall-craft': m } });
106
+ await flush();
107
+ p.craft.openTool('palette');
108
+ await flush();
109
+ assert.deepEqual(p.opened, [], `${m}: nothing opens`);
110
+ assert.deepEqual(p.loaded, [], `${m}: the sheet's script never loads`);
111
+ }
112
+ const none = boot();
113
+ none.craft.openTool('palette');
114
+ await flush();
115
+ assert.deepEqual(none.opened, [], 'with no character, nothing opens');
116
+ });
117
+
98
118
  test('a screen in the options menu frame docks no second panel: the menu around it has one', async () => {
99
119
  const p = boot({ session: { 'hall-craft': 'artist' }, embedded: true });
100
120
  await flush();