@bongos/core 1.21.33 → 1.21.34

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.
@@ -41,6 +41,16 @@
41
41
  // request, says the answer in a toast, re-reads the feeds so the pins, the
42
42
  // readings and the evening follow, and marks the building that answered (a CSS
43
43
  // class for 1.6 s: it rises or lights, and the matter's pin settles).
44
+ //
45
+ // THE FLOOR DESK (task 1004558). The chosen floor's block is the desk from the
46
+ // round 2 board (Desks.dc.html): GovDesk.deskModel() (street-desk.js) gives the
47
+ // header, tiles, rows, quiet state and record line under the line rule, and
48
+ // this file draws it. A row's buttons send the matter card's own request
49
+ // (GovMatterCard.request, the same routes), so acting happens in place. The
50
+ // game layer: the today meter in the street header (one pip per decision, kept
51
+ // for the day in this browser), a row that clears away (a CSS class, then the
52
+ // redraw), a cleared floor that lights its windows and shows a tick in the
53
+ // drawing, and the toast after every act.
44
54
 
45
55
  (function () {
46
56
  'use strict';
@@ -70,6 +80,11 @@
70
80
  var data = { docket: null, goals: [], rollups: [], earnings: null, loaded: false, tasks: {}, room: null, constitution: null };
71
81
  var card = { cv: null, form: null, error: '', busy: false, fresh: null, answer: null };
72
82
  var view = { open: null, opener: null, model: null, scale: 1, mode: null, inside: null, insideModel: null };
83
+ // the desk's own state: rows you acted on this visit, what you did per floor,
84
+ // the floors you cleared, the row whose facts or chips are open
85
+ var desk = { done: {}, mine: {}, cleared: {}, open: null, ask: null, busy: false, roomAsked: false, model: null };
86
+ var TODAY_KEY = 'gov-desk-today';
87
+ var today = { day: '', n: 0 };
73
88
 
74
89
  // ---------- light / dark ----------
75
90
  var chosen = null;
@@ -343,6 +358,7 @@
343
358
  var eveLine = $('streetEveningLine');
344
359
  if (eveLine) eveLine.textContent = m.eveningLine;
345
360
 
361
+ drawToday();
346
362
  $('hudNeeds').textContent = m.hud.needs;
347
363
  $('hudMoving').textContent = m.hud.moving;
348
364
  $('hudShare').textContent = m.hud.share;
@@ -392,7 +408,18 @@
392
408
 
393
409
  function drawBuilding(mode) {
394
410
  var st = view.inside;
395
- var bv = I.buildingModel({ key: st.key, docket: data.docket, goals: data.goals, mode: mode, now: Date.now(), floor: st.floor, drawer: st.drawer, picks: st.picks });
411
+ var D = window.GovDesk;
412
+ // a building opens on the floor that needs you first (the desk's work)
413
+ var startFloor = st.floor == null && D && data.docket ? D.firstNeeds(st.key, data.docket) : null;
414
+ var cleared = [];
415
+ Object.keys(desk.cleared).forEach(function (k) { var p = k.split(':'); if (p[0] === st.key) cleared.push(Number(p[1])); });
416
+ var bv = I.buildingModel({ key: st.key, docket: data.docket, goals: data.goals, mode: mode, now: Date.now(), floor: st.floor == null ? startFloor : st.floor, drawer: st.drawer, picks: st.picks, cleared: cleared });
417
+ // a sitting's ballot is read once, so its row can vote in place
418
+ var C = window.GovMatterCard;
419
+ if (!desk.roomAsked && !data.room && C && data.docket && data.docket.matters.some(function (x) { return x && x.building === st.key && C.needsRoom(x); })) {
420
+ desk.roomAsked = true;
421
+ loadRoom().then(function () { if (view.inside) draw(); });
422
+ }
396
423
  view.insideModel = bv;
397
424
  setText('insideKicker', bv.kicker);
398
425
  show('insidePanel', true); show('insideDHead', false); show('insideLegend', false);
@@ -481,7 +508,7 @@
481
508
  // the chosen floor: two blocks that take turns, so the block re-enters on every pick
482
509
  var on = $(bv.flipA ? 'insideOpenA' : 'insideOpenB'), off = $(bv.flipA ? 'insideOpenB' : 'insideOpenA');
483
510
  if (off) { off.hidden = true; clear(off); }
484
- if (on) { on.hidden = false; fillOpen(on, bv); }
511
+ if (on) { on.hidden = false; fillDesk(on, bv); }
485
512
  setText('insideOnFile', bv.onFile);
486
513
  var drawers = $('insideDrawers'); clear(drawers);
487
514
  bv.drawers.forEach(function (dw) {
@@ -505,28 +532,201 @@
505
532
  });
506
533
  }
507
534
 
508
- function fillOpen(el, bv) {
535
+ // ---------- the floor desk (task 1004558) ----------
536
+ function icon(d, f, size) {
537
+ var svg = svgEl('svg');
538
+ svg.setAttribute('width', String(size)); svg.setAttribute('height', String(size)); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true');
539
+ var a = svgEl('path'); a.setAttribute('d', d || '');
540
+ svg.appendChild(a);
541
+ if (f != null) { var b = svgEl('path'); b.setAttribute('d', f || ''); svg.appendChild(b); }
542
+ return svg;
543
+ }
544
+ function chipBtn(cls, text, onClick) {
545
+ var b = node('button', 'desk-chip ' + cls, text);
546
+ b.setAttribute('type', 'button');
547
+ if (desk.busy) b.setAttribute('disabled', '');
548
+ b.addEventListener('click', onClick);
549
+ return b;
550
+ }
551
+ function fillDesk(el, bv) {
509
552
  clear(el);
553
+ var D = window.GovDesk, st = view.inside;
554
+ if (!D || !st) return;
555
+ var k = st.key + ':' + bv.selFloor;
556
+ var dm = D.deskModel({ key: st.key, floor: bv.selFloor, docket: data.docket, goals: (data.goals || []).filter(function (g) { return g && g.status === 'open'; }), now: Date.now(), room: data.room, constitution: data.constitution, done: desk.done, mine: desk.mine[k] || [], cleared: !!desk.cleared[k], today: today.n, sym: bv.sym });
557
+ desk.model = dm;
558
+ el.className = 'inside-open desk' + (dm.quiet ? ' desk--quiet' : '');
559
+ el.setAttribute('data-desk', dm.key);
560
+ // the header: the floor's sign, its name, its status
510
561
  var head = node('div', 'inside-open__head');
511
562
  var plate = node('span', 'inside-plate');
512
- var svg = svgEl('svg');
513
- svg.setAttribute('width', '40'); svg.setAttribute('height', '40'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true');
514
- var a = svgEl('path'); a.setAttribute('d', bv.sym.d || '');
515
- var b = svgEl('path'); b.setAttribute('d', bv.sym.f || '');
516
- svg.appendChild(a); svg.appendChild(b);
517
- plate.appendChild(svg);
563
+ plate.appendChild(icon(bv.sym.d, bv.sym.f, 34));
518
564
  head.appendChild(plate);
519
565
  var words = node('div', 'inside-open__words');
520
- words.appendChild(node('span', 'inside-open__kicker', bv.openKicker));
521
- words.appendChild(node('h3', 'inside-open__title', bv.openTitle));
566
+ words.appendChild(node('span', 'inside-open__kicker', 'floor ' + (bv.selFloor + 1)));
567
+ words.appendChild(node('h3', 'inside-open__title', dm.name));
568
+ words.appendChild(node('span', 'desk-status' + (dm.hot ? ' desk-status--hot' : ''), dm.status));
522
569
  head.appendChild(words);
523
570
  el.appendChild(head);
524
- bv.openLines.forEach(function (l) {
525
- var row = node('div', 'inside-line');
526
- row.appendChild(node('span', '', l.text));
527
- row.appendChild(node('span', 'inside-line__meta', l.meta));
528
- el.appendChild(row);
571
+ // up to three tiles, none of them a zero
572
+ if (dm.tiles.length) {
573
+ var tiles = node('div', 'desk-tiles');
574
+ dm.tiles.forEach(function (t) {
575
+ var tl = node('div', 'desk-tile');
576
+ tl.appendChild(node('span', 'desk-tile__v', t.v));
577
+ tl.appendChild(node('span', 'desk-tile__l', t.l));
578
+ tiles.appendChild(tl);
579
+ });
580
+ el.appendChild(tiles);
581
+ }
582
+ if (dm.quiet) {
583
+ // one line, lit: never an empty list
584
+ var q = node('div', 'desk-quiet' + (dm.cleared ? ' desk-quiet--cleared' : ''));
585
+ var tick = icon('M7.5 12.2l3 3 6-6.5', null, 44);
586
+ tick.setAttribute('class', 'desk-quiet__tick');
587
+ var ring = svgEl('circle'); ring.setAttribute('cx', '12'); ring.setAttribute('cy', '12'); ring.setAttribute('r', '9.5');
588
+ tick.appendChild(ring);
589
+ q.appendChild(tick);
590
+ q.appendChild(node('span', 'desk-quiet__line', dm.clearLine));
591
+ q.appendChild(node('span', 'desk-quiet__sub', dm.clearSub));
592
+ el.appendChild(q);
593
+ } else {
594
+ var list = node('div', 'desk-rows');
595
+ dm.rows.forEach(function (r) { list.appendChild(deskRow(r)); });
596
+ el.appendChild(list);
597
+ }
598
+ // the record line: what you just did here, then what this building closed
599
+ var rec = node('div', 'desk-record');
600
+ rec.appendChild(node('span', 'desk-record__label', 'record'));
601
+ if (!dm.record.length) rec.appendChild(node('span', 'desk-record__item', 'nothing yet'));
602
+ dm.record.forEach(function (x, i) { rec.appendChild(node('span', 'desk-record__item' + (x.mine ? ' desk-record__item--mine' : '') + (x.mine && i === 0 ? ' desk-pop' : ''), x.text)); });
603
+ el.appendChild(rec);
604
+ }
605
+ function deskRow(r) {
606
+ var row = node('div', 'desk-row' + (r.needs ? ' desk-row--needs' : ''));
607
+ row.setAttribute('data-row', r.key);
608
+ var main = node('div', 'desk-row__main');
609
+ main.appendChild(node('span', 'desk-ini', r.ini));
610
+ var name = node('button', 'desk-name');
611
+ name.setAttribute('type', 'button');
612
+ name.setAttribute('aria-expanded', desk.open === r.key ? 'true' : 'false');
613
+ name.setAttribute('data-matter', r.matter);
614
+ name.appendChild(node('span', 'desk-name__t', r.name));
615
+ name.appendChild(node('span', 'desk-name__f', r.fact));
616
+ name.addEventListener('click', function () { desk.open = desk.open === r.key ? null : r.key; desk.ask = null; draw(); focusRow(r.key, 'name'); });
617
+ main.appendChild(name);
618
+ var acts = node('span', 'desk-acts');
619
+ if (r.wait) acts.appendChild(node('span', 'desk-wait', r.wait));
620
+ // the primary button sits rightmost, as on the board
621
+ r.actions.slice().reverse().forEach(function (a) {
622
+ acts.appendChild(chipBtn('desk-chip--' + a.tone, a.label, function () {
623
+ if (a.go) { window.location.href = a.go; return; }
624
+ if (a.ask) { desk.ask = desk.ask === r.key ? null : r.key; desk.open = null; draw(); focusRow(r.key, 'chip'); return; }
625
+ deskAct(r, a.key, {});
626
+ }));
529
627
  });
628
+ main.appendChild(acts);
629
+ row.appendChild(main);
630
+ if (desk.open === r.key) {
631
+ var facts = node('div', 'desk-facts');
632
+ r.facts.forEach(function (f) { facts.appendChild(node('span', 'desk-fact', f)); });
633
+ row.appendChild(facts);
634
+ }
635
+ if (desk.ask === r.key && r.chips) {
636
+ var chips = node('div', 'desk-choices');
637
+ chips.appendChild(node('span', 'desk-choices__ask', r.chips.ask));
638
+ r.chips.options.forEach(function (c) { chips.appendChild(chipBtn('desk-chip--choice', c.label, function () { deskAct(r, r.chips.for, c.input); })); });
639
+ row.appendChild(chips);
640
+ }
641
+ return row;
642
+ }
643
+ function deskRowsEl() {
644
+ var st = view.insideModel;
645
+ var on = $(st && st.flipA ? 'insideOpenA' : 'insideOpenB');
646
+ var kids = on ? on.children : [];
647
+ for (var i = 0; i < kids.length; i++) if (/\bdesk-rows\b/.test(kids[i].className)) return kids[i];
648
+ return null;
649
+ }
650
+ function focusRow(key, what) {
651
+ var list = deskRowsEl();
652
+ var rows = list ? list.children : [];
653
+ for (var i = 0; i < rows.length; i++) {
654
+ if (rows[i].getAttribute('data-row') !== key) continue;
655
+ var main = rows[i].children[0];
656
+ var target = what === 'chip' && rows[i].children[1] ? rows[i].children[1].children[1] : main && main.children[1];
657
+ if (target && target.focus) target.focus();
658
+ return;
659
+ }
660
+ }
661
+ // The row clears away (a class the stylesheet animates), then the desk redraws without it.
662
+ function clearRow(key, then) {
663
+ var list = deskRowsEl();
664
+ var rows = list ? list.children : [];
665
+ for (var i = 0; i < rows.length; i++) if (rows[i].getAttribute('data-row') === key) rows[i].className += ' desk-row--gone';
666
+ if (typeof setTimeout !== 'function') return then();
667
+ setTimeout(then, 320);
668
+ }
669
+ var DONE_WORDS = { for: 'voted yes', object: 'voted no', fine: 'let in', refuse: 'turned away', release: 'released', hold: 'held' };
670
+ // One act from a row: checked, sent through the card's own request (the
671
+ // server's permission checks decide), then the row clears, the floor counts
672
+ // it, the meter takes a pip and the toast says what happened. An act that
673
+ // records nothing (hold) clears the row for this visit and says it still waits.
674
+ function deskAct(r, key, input) {
675
+ var C = window.GovMatterCard, st = view.inside;
676
+ if (!C || !st || !r.card || desk.busy) return Promise.resolve(false);
677
+ var refused = C.check(r.card, key, input);
678
+ if (refused) { say(refused); return Promise.resolve(false); }
679
+ var k = st.key + ':' + (view.insideModel ? view.insideModel.selFloor : 0);
680
+ var req = C.request(r.card, key, input);
681
+ var settle = function (body) {
682
+ var a = C.answer(r.card, key, body);
683
+ desk.mine[k] = [DONE_WORDS[key] + ' ' + r.name].concat(desk.mine[k] || []);
684
+ desk.done[r.matter] = true;
685
+ desk.open = null; desk.ask = null;
686
+ if (desk.model && !desk.model.rows.some(function (x) { return x.matter !== r.matter; })) desk.cleared[k] = true;
687
+ if (st.floor == null && view.insideModel) st.floor = view.insideModel.selFloor;
688
+ bumpToday();
689
+ if (a.building && a.kind) card.answer = { building: a.building, matter: r.matter, kind: a.kind, at: Date.now() };
690
+ say(a.toast || 'Done.');
691
+ };
692
+ if (!req) return new Promise(function (res) { clearRow(r.key, function () { settle({}); draw(); res(true); }); });
693
+ desk.busy = true;
694
+ draw();
695
+ return send(req).then(function (out) {
696
+ desk.busy = false;
697
+ if (!out.ok) { say(C.refusal(out.status, out.body)); draw(); return false; }
698
+ return new Promise(function (res) {
699
+ clearRow(r.key, function () {
700
+ settle(out.body);
701
+ draw();
702
+ data.room = null; desk.roomAsked = false;
703
+ load().then(function () { res(true); });
704
+ });
705
+ });
706
+ });
707
+ }
708
+ // the today meter: decisions made today, kept for the day in this browser
709
+ function dayOf() { return new Date().toISOString().slice(0, 10); }
710
+ function readToday() {
711
+ var t = { day: dayOf(), n: 0 };
712
+ try {
713
+ var raw = window.localStorage ? JSON.parse(window.localStorage.getItem(TODAY_KEY) || 'null') : null;
714
+ if (raw && raw.day === t.day && isFinite(raw.n)) t.n = Math.max(0, Math.floor(raw.n));
715
+ } catch (e) { /* storage blocked: the meter starts at zero */ }
716
+ return t;
717
+ }
718
+ function bumpToday() {
719
+ if (today.day !== dayOf()) today = { day: dayOf(), n: 0 };
720
+ today.n += 1;
721
+ try { if (window.localStorage) window.localStorage.setItem(TODAY_KEY, JSON.stringify(today)); } catch (e) { /* the count still shows for this visit */ }
722
+ }
723
+ function drawToday() {
724
+ var D = window.GovDesk, pipsEl = $('streetTodayPips');
725
+ if (!D || !pipsEl) return;
726
+ var t = D.todayPips(today.n);
727
+ clear(pipsEl);
728
+ t.pips.forEach(function (p) { pipsEl.appendChild(node('span', 'street-today__pip' + (p.on ? ' street-today__pip--on' : '') + (p.fresh ? ' desk-pop' : ''))); });
729
+ setText('streetTodayN', t.text);
530
730
  }
531
731
 
532
732
  function pickFloor(i) {
@@ -534,6 +734,7 @@
534
734
  if (!st) return;
535
735
  st.floor = i;
536
736
  st.picks = (st.picks || 0) + 1;
737
+ desk.open = null; desk.ask = null;
537
738
  draw();
538
739
  var floors = $('insideFloors');
539
740
  var kids = (floors && floors.children) || [];
@@ -929,11 +1130,12 @@
929
1130
  if (win) win.addEventListener('scroll', placeCard);
930
1131
  if (media && media.addEventListener) media.addEventListener('change', function () { if (!storedTheme()) { applyTheme(); draw(); } });
931
1132
  painted = applyTheme();
1133
+ today = readToday();
932
1134
  draw();
933
1135
  watchShellTheme();
934
1136
  return load();
935
1137
  }
936
1138
 
937
- window.GovStreetPage = { boot: boot, draw: draw, load: load, fit: fit, openCard: openCard, closeCard: closeCard, actOn: actOn, pickFloor: pickFloor, goStreet: goStreet, amend: amend, fileDraft: fileDraft, FEEDS: FEEDS, THEME_KEY: THEME_KEY };
1139
+ window.GovStreetPage = { boot: boot, draw: draw, load: load, fit: fit, openCard: openCard, closeCard: closeCard, actOn: actOn, pickFloor: pickFloor, deskAct: deskAct, goStreet: goStreet, amend: amend, fileDraft: fileDraft, FEEDS: FEEDS, THEME_KEY: THEME_KEY };
938
1140
  window.GovStreetPage.ready = boot();
939
1141
  }());
@@ -373,6 +373,89 @@
373
373
  "#insideLegend"
374
374
  ]
375
375
  }
376
+ },
377
+ "desk": {
378
+ "auth": true,
379
+ "_": "The floor desk (task 1004558): inside People the building opens on the floor that needs you, and the desk shows its sign, name and orange status, the tiles, the to-do rows with their two buttons, and the record line; the street header carries the today meter.",
380
+ "url": "/builders/street#building=people",
381
+ "actions": [
382
+ [
383
+ "wait",
384
+ 900
385
+ ],
386
+ [
387
+ "clickIfVisible",
388
+ "[data-craft-close]"
389
+ ],
390
+ [
391
+ "wait",
392
+ 300
393
+ ]
394
+ ],
395
+ "expect": {
396
+ "visible": [
397
+ ".desk-rows",
398
+ ".desk-status--hot",
399
+ ".desk-record",
400
+ "#streetTodayPips"
401
+ ]
402
+ }
403
+ },
404
+ "desk-facts": {
405
+ "auth": true,
406
+ "_": "A row name tapped: at most three facts open in place under it.",
407
+ "url": "/builders/street#building=people",
408
+ "actions": [
409
+ [
410
+ "wait",
411
+ 900
412
+ ],
413
+ [
414
+ "clickIfVisible",
415
+ "[data-craft-close]"
416
+ ],
417
+ [
418
+ "wait",
419
+ 300
420
+ ],
421
+ [
422
+ "click",
423
+ ".desk-name"
424
+ ],
425
+ [
426
+ "wait",
427
+ 500
428
+ ]
429
+ ],
430
+ "expect": {
431
+ "visible": [
432
+ ".desk-facts"
433
+ ]
434
+ }
435
+ },
436
+ "desk-quiet": {
437
+ "auth": true,
438
+ "_": "A quiet floor: one line with the lights on, never an empty list.",
439
+ "url": "/builders/street#building=review",
440
+ "actions": [
441
+ [
442
+ "wait",
443
+ 900
444
+ ],
445
+ [
446
+ "clickIfVisible",
447
+ "[data-craft-close]"
448
+ ],
449
+ [
450
+ "wait",
451
+ 300
452
+ ]
453
+ ],
454
+ "expect": {
455
+ "visible": [
456
+ ".desk-quiet"
457
+ ]
458
+ }
376
459
  }
377
460
  },
378
461
  "reducedMotion": {
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.33",
3
+ "version": "1.21.34",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.33",
9
+ "version": "1.21.34",
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.21.33",
3
+ "version": "1.21.34",
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",
@@ -8958,5 +8958,11 @@
8958
8958
  "id": "1004494",
8959
8959
  "text": "The look for each craft mode is decided. Artistry, Ideating and Governing each get their own colour (moonlight, star gold, city blue) on the mode switch, the tab you are on and the top bar, the mode's name beside the page titl"
8960
8960
  }
8961
+ ],
8962
+ "1.21.34": [
8963
+ {
8964
+ "id": "1004558",
8965
+ "text": "Inside every building in the governor city, the floor you pick is now a desk you can act from, built from the new floor desks design. It shows the floor's name and how many things wait on you, up to three short numbers, and on"
8966
+ }
8961
8967
  ]
8962
8968
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.33'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.34'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -258,7 +258,7 @@ async function bootPage({ feeds, posts = {} }) {
258
258
  };
259
259
  const ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, Date, console, encodeURIComponent, decodeURIComponent, isNaN, isFinite });
260
260
  ctx.globalThis = ctx;
261
- for (const f of ['city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street.js']) vm.runInContext(read(f), ctx);
261
+ for (const f of ['city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street-desk.js', 'street.js']) vm.runInContext(read(f), ctx);
262
262
  await window.GovStreetPage.ready;
263
263
  return { window, byId, calls, focused };
264
264
  }
@@ -376,7 +376,7 @@ test('the page holds the prototype\'s header, the street, the readings and the c
376
376
  assert.match(html, /<a class="street-mark" id="streetHome" href="\/builders\/city"[^>]*>\{\{productName\}\}</);
377
377
  for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
378
378
  const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
379
- assert.deepEqual(scripts, ['shell-trail', 'craft-mode', 'shell', 'palette', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-amend', 'street'], 'the hall shell (task 1004544), the shared kit, then the card (task 1004528), the interiors (task 1004526) and the drafted amendment (task 1004530)');
379
+ assert.deepEqual(scripts, ['shell-trail', 'craft-mode', 'shell', 'palette', 'city-draw', 'street-draw', 'street-card', 'street-inside', 'street-desk', 'street-amend', 'street'], 'the hall shell (task 1004544), the shared kit, then the card (task 1004528), the interiors (task 1004526), the floor desk (task 1004558) and the drafted amendment (task 1004530)');
380
380
  assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
381
381
  });
382
382
 
@@ -253,7 +253,7 @@ async function bootPage({ hash = '#building=review', cons = constitution(), answ
253
253
  };
254
254
  const ctx = vm.createContext({ window, document, fetch, Promise, Math, String, Number, Array, Object, JSON, Date, console, encodeURIComponent, decodeURIComponent, isNaN, isFinite, Map, setTimeout, clearTimeout });
255
255
  ctx.globalThis = ctx;
256
- for (const f of ['city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street-amend.js', 'street.js']) vm.runInContext(read(f), ctx);
256
+ for (const f of ['city-draw.js', 'street-draw.js', 'street-card.js', 'street-inside.js', 'street-desk.js', 'street-amend.js', 'street.js']) vm.runInContext(read(f), ctx);
257
257
  await window.GovStreetPage.ready;
258
258
  await tick();
259
259
  return { window, byId, focused, gets, posts, history };