@bongos/core 1.21.163 → 1.21.165
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 +60 -45
- package/.gitattributes +6 -0
- package/docs/copy-inventory.md +127 -127
- package/docs/copy-registry.json +205 -185
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +297 -462
- package/modules/copy-desk/page-status.js +3 -0
- package/modules/copy-desk/pages.js +5 -0
- package/modules/hall-ui/public/approval-queue-lib.js +4 -0
- package/modules/hall-ui/public/approval-queue.css +2 -0
- package/modules/hall-ui/public/approval-queue.js +1 -0
- package/modules/hall-ui/public/builders.js +6 -4
- package/modules/hall-ui/public/craft-mode.js +32 -158
- package/modules/hall-ui/public/craft-review.css +7 -120
- package/modules/hall-ui/public/goal-inbox.js +100 -7
- package/modules/hall-ui/public/goals.js +2 -4
- package/modules/hall-ui/public/hall-render.js +39 -9
- package/modules/hall-ui/public/hall-widgets.js +20 -10
- package/modules/hall-ui/public/index.html +7 -0
- package/modules/hall-ui/public/page-notes.css +64 -1
- package/modules/hall-ui/public/page-notes.js +96 -11
- package/modules/hall-ui/public/style.css +95 -0
- package/modules/hall-ui/public/task-detail.js +8 -0
- package/modules/hall-ui/records/home-reshuffle.md +31 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +20 -0
- package/scripts/gds/copy-apply.js +65 -2
- package/scripts/gds/git-merge-regen.js +14 -0
- package/scripts/gds/page-reader.js +46 -0
- package/src/module-api.js +1 -1
- package/tests/conflict_resolve.mjs +6 -0
- package/tests/craft_review_mode.mjs +21 -104
- package/tests/hall_approval_queue.mjs +16 -0
- package/tests/hall_board_world.mjs +7 -6
- package/tests/home_attention_cards.mjs +119 -0
- package/tests/home_bounded_reads.mjs +1 -1
- package/tests/page_notes_panel.mjs +161 -27
- package/tests/promote_goal_id.mjs +8 -0
- package/tests/tweak_batch_apply.mjs +40 -0
- package/tests/tweak_land_heal.mjs +139 -0
|
@@ -691,6 +691,8 @@ function approvalFor({ page, pageId, rounds, round, builderId, isArtist }) {
|
|
|
691
691
|
const applied = pages.parseAppliedBlock(round.description);
|
|
692
692
|
const refusedBy = new Map();
|
|
693
693
|
if (applied.ok) for (const r of applied.applied.refused || []) if (r && r.key) refusedBy.set(String(r.key), String(r.code || 'refused'));
|
|
694
|
+
const pinnedBy = new Map();
|
|
695
|
+
if (applied.ok) for (const p of applied.applied.pinned_tests || []) if (p && p.key && Array.isArray(p.tests)) pinnedBy.set(String(p.key), p.tests.map(String));
|
|
694
696
|
const lines = batch.ok ? batch.batch.lines.map((l) => ({
|
|
695
697
|
key: l.key,
|
|
696
698
|
section: l.section == null ? null : l.section,
|
|
@@ -698,6 +700,7 @@ function approvalFor({ page, pageId, rounds, round, builderId, isArtist }) {
|
|
|
698
700
|
after: l.after,
|
|
699
701
|
placement: l.placement || null,
|
|
700
702
|
refused: refusedBy.get(String(l.key)) || null,
|
|
703
|
+
...(pinnedBy.has(String(l.key)) ? { pinned_by: pinnedBy.get(String(l.key)) } : {}),
|
|
701
704
|
})) : [];
|
|
702
705
|
const shipped = (rounds || []).filter((r) => r.status === 'shipped');
|
|
703
706
|
const now = countsFor(rounds);
|
|
@@ -164,6 +164,11 @@ function composeAppliedBlock(a) {
|
|
|
164
164
|
reading_hash_after: a.reading_hash_after,
|
|
165
165
|
lines_after: (a.lines_after || []).map(String),
|
|
166
166
|
refused: (a.refused || []).map((x) => ({ key: x.key, code: x.code })),
|
|
167
|
+
// task 1004775: tests that still assert a rewritten line's old words, named
|
|
168
|
+
// at apply time so the artist sees them before approving, not CI after.
|
|
169
|
+
...(Array.isArray(a.pinned_tests) && a.pinned_tests.length
|
|
170
|
+
? { pinned_tests: a.pinned_tests.map((p) => ({ key: p.key, tests: (p.tests || []).map(String) })) }
|
|
171
|
+
: {}),
|
|
167
172
|
});
|
|
168
173
|
}
|
|
169
174
|
|
|
@@ -119,6 +119,10 @@
|
|
|
119
119
|
marked: after && !line.refused,
|
|
120
120
|
aside: line.refused ? refusedWords(line.refused) : (after ? `was: ${line.before}` : `becomes: ${line.after}`),
|
|
121
121
|
refused: !!line.refused,
|
|
122
|
+
// task 1004775: a test still asserts the old words, so it fails once this lands.
|
|
123
|
+
...(!line.refused && Array.isArray(line.pinned_by) && line.pinned_by.length
|
|
124
|
+
? { pinned: `A test still checks the old words: ${line.pinned_by.join(', ')}. It needs updating when this ships.` }
|
|
125
|
+
: {}),
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
|
|
@@ -226,6 +226,8 @@ html.aq-open { overflow: hidden; }
|
|
|
226
226
|
}
|
|
227
227
|
.aq-line__aside { font-size: 13px; line-height: 1.45; color: var(--aq-ink-faint); overflow-wrap: anywhere; }
|
|
228
228
|
.aq-line--refused .aq-line__aside { color: var(--aq-link); }
|
|
229
|
+
/* task 1004775: a test that still checks the old words. */
|
|
230
|
+
.aq-line__pinned { font-size: 13px; line-height: 1.45; color: var(--aq-link); overflow-wrap: anywhere; }
|
|
229
231
|
.aq-lines__none { margin: 0; font-size: 14px; color: var(--aq-ink-faint); }
|
|
230
232
|
|
|
231
233
|
/* The decision row. */
|
|
@@ -152,6 +152,7 @@
|
|
|
152
152
|
${r.section ? `<span class="aq-line__section">${esc(r.section)}</span>` : ''}
|
|
153
153
|
<span class="aq-line__text">${r.marked ? `<mark class="aq-mark">${words(r.text)}</mark>` : words(r.text)}</span>
|
|
154
154
|
<span class="aq-line__aside">${words(r.aside)}</span>
|
|
155
|
+
${r.pinned ? `<span class="aq-line__pinned">${esc(r.pinned)}</span>` : ''}
|
|
155
156
|
</li>`).join('')}</ol>`;
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -929,7 +929,7 @@
|
|
|
929
929
|
|
|
930
930
|
// ---- Home "Next up" slice (task 2184) -------------------------------------
|
|
931
931
|
|
|
932
|
-
// Fill the top of the "Your work" card with the first
|
|
932
|
+
// Fill the top of the "Your work" card with the first five rows of the
|
|
933
933
|
// claimable queue (server order: priority, then calibrated estimate) so Home
|
|
934
934
|
// answers "what's my next action". Best-effort: a primer-gated 403, an auth
|
|
935
935
|
// hiccup, or an empty queue hides the slice — Home never shows an error for
|
|
@@ -947,7 +947,8 @@
|
|
|
947
947
|
// already orders by priority then calibrated estimate, so the first two rows ARE
|
|
948
948
|
// the top two. `claimable_total` comes back so the foot can say how many are
|
|
949
949
|
// waiting instead of leaving two rows to imply there are only two.
|
|
950
|
-
|
|
950
|
+
// task 1004774: five rows, level with the five Recently shipped rows beside it.
|
|
951
|
+
const resp = await getJSON(`${API}/tasks/claimable?limit=5`, { softAuth: true });
|
|
951
952
|
// task 1003464: the greeting's "N tasks are claimable right now" — rank-scoped,
|
|
952
953
|
// so it is THIS builder's number. It reads claimable_total rather than the length
|
|
953
954
|
// of a fully-downloaded feed: the same number, without the wire cost that this
|
|
@@ -959,7 +960,8 @@
|
|
|
959
960
|
? claimableTotal
|
|
960
961
|
: (resp && Array.isArray(resp.claimable) ? resp.claimable.length : null);
|
|
961
962
|
renderGreeting();
|
|
962
|
-
|
|
963
|
+
// capped as well as asked for: a server that ignores ?limit must not grow the card.
|
|
964
|
+
const rows = resp && Array.isArray(resp.claimable) ? resp.claimable.slice(0, 5) : [];
|
|
963
965
|
if (!rows.length) { wrap.hidden = true; if (subhead) subhead.hidden = true; return; }
|
|
964
966
|
const total = Number(resp.claimable_total);
|
|
965
967
|
const cur = (window.__BRANDING__ && window.__BRANDING__.currency && window.__BRANDING__.currency.label) || 'credits';
|
|
@@ -1175,7 +1177,7 @@
|
|
|
1175
1177
|
getJSON(`${API}/me/recent-ships?limit=all`),
|
|
1176
1178
|
getJSON(`${API}/versions`),
|
|
1177
1179
|
getJSON(`${API}/public/leaderboard`), // task 1004437: the list with each builder's roles
|
|
1178
|
-
getJSON(`${API}/public/progress`), // task 1003464: "<version> is N percent
|
|
1180
|
+
getJSON(`${API}/public/progress`), // task 1003464: "<version> is N percent complete"
|
|
1179
1181
|
]);
|
|
1180
1182
|
|
|
1181
1183
|
if (progress.status === 'fulfilled') {
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
governing: '<path d="M2 6 8 2.5 14 6M3.5 6.5v6M6.5 6.5v6M9.5 6.5v6M12.5 6.5v6M2 13.5h12"/>',
|
|
35
35
|
brush: '<path d="M13.6 2.4 7.4 8.6"/><path d="M7.4 8.6c-1.3-.7-3-.1-3.4 1.5-.3 1.3-.8 2.2-1.9 2.9 2.4.7 4.6.1 5.6-1.4.6-.9.5-2.1-.3-3Z"/>',
|
|
36
36
|
note: '<path d="M9.5 2.5h-6v11h9v-6"/><path d="M5.5 8h5M5.5 10.5h3.5"/><path d="M12.5 1.5v4M10.5 3.5h4"/>',
|
|
37
|
-
write: '<path d="M2.5 13.5h11"/><path d="M10.4 2.6l2.9 2.9-6.4 6.4H4V9z"/>',
|
|
38
37
|
modes: '<rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1"/><rect x="9" y="9" width="4.5" height="4.5" rx="1"/>',
|
|
39
38
|
};
|
|
40
39
|
// The four crafts. `short` is the switch's label; `line` is the
|
|
@@ -342,8 +341,9 @@
|
|
|
342
341
|
artistry: [
|
|
343
342
|
// Named "Artist review" since task 1004499: the public site's button
|
|
344
343
|
// (public-landing assets/artist-review.js) opens this same panel.
|
|
345
|
-
// `
|
|
346
|
-
|
|
344
|
+
// `docked` (task 1004771): no top-bar button. The panel itself sits on
|
|
345
|
+
// the right edge behind a small tab, and carries the review-mode choice.
|
|
346
|
+
{ id: 'palette', icon: 'artistry', short: 'Review', label: 'Artist review: write feedback on this page', script: 'page-notes.js', api: 'OTBPageNotes', docked: true },
|
|
347
347
|
// Task 1004497: this page's words, edited where they stand as a proposed wording change.
|
|
348
348
|
{ id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' },
|
|
349
349
|
],
|
|
@@ -393,21 +393,24 @@
|
|
|
393
393
|
b.setAttribute('aria-label', t.label);
|
|
394
394
|
b.title = t.label;
|
|
395
395
|
b.innerHTML = icon(t.icon, 'craft-tool__icon') + '<span class="craft-tool__label">' + esc(t.short) + '</span>';
|
|
396
|
-
b.addEventListener('click', function (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
396
|
+
b.addEventListener('click', function () { openTool(t, b); });
|
|
397
|
+
return b;
|
|
398
|
+
}
|
|
399
|
+
function toolNode(t) { return document.getElementById('craft-tool-' + t.id); }
|
|
400
|
+
// A docked tool has no button: in its mode the panel is docked at the edge,
|
|
401
|
+
// out of it the panel goes. Only a tool already loaded needs closing.
|
|
402
|
+
function applyDocked(mode) {
|
|
403
|
+
var want = toolsFor(mode);
|
|
404
|
+
for (var m in TOOLS) TOOLS[m].forEach(function (t) {
|
|
405
|
+
if (!t.docked) return;
|
|
406
|
+
if (want.indexOf(t) !== -1) loadTool(t).then(function (api) { if (api.dock) api.dock(); }, function () { /* the page still works */ });
|
|
407
|
+
else if (window[t.api] && window[t.api].close) window[t.api].close();
|
|
400
408
|
});
|
|
401
|
-
return t.menu ? reviewGroup(t, b) : b;
|
|
402
|
-
}
|
|
403
|
-
// The node a tool occupies in the bar: its button, or Review's whole group.
|
|
404
|
-
function toolNode(t) {
|
|
405
|
-
var b = document.getElementById('craft-tool-' + t.id);
|
|
406
|
-
return b && t.menu && b.parentNode && b.parentNode.id === REVIEW_GROUP ? b.parentNode : b;
|
|
407
409
|
}
|
|
408
410
|
function applyTools(mode) {
|
|
409
411
|
var sw = document.getElementById('craft-switch');
|
|
410
|
-
|
|
412
|
+
applyDocked(mode);
|
|
413
|
+
var own = toolsFor(mode).filter(function (t) { return !t.docked; });
|
|
411
414
|
var old = document.querySelectorAll ? document.querySelectorAll('[data-craft-tool]') : [];
|
|
412
415
|
for (var i = old.length - 1; i >= 0; i--) {
|
|
413
416
|
if (own.some(function (t) { return t.id === old[i].getAttribute('data-craft-tool'); })) continue;
|
|
@@ -429,26 +432,19 @@
|
|
|
429
432
|
var el = document.getElementById('craft-tool-' + t.id);
|
|
430
433
|
if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.artistry.indexOf(t) === -1);
|
|
431
434
|
});
|
|
432
|
-
var on = document.getElementById('craft-tool-palette');
|
|
433
|
-
if (on) {
|
|
434
|
-
on.classList.toggle('craft-tool--reviewing', !!review);
|
|
435
|
-
var word = on.querySelector && on.querySelector('.craft-tool__label');
|
|
436
|
-
if (word) word.textContent = review ? 'Reviewing' : 'Review';
|
|
437
|
-
}
|
|
438
435
|
}
|
|
439
436
|
|
|
440
437
|
// ---------- artist review mode (task 1004686) ----------
|
|
441
|
-
//
|
|
442
|
-
// (owner, 2026-10-09, in the owner's order and words). Picking one makes the hall
|
|
438
|
+
// The review panel's Review mode row (page-notes.js, task 1004771) offers the four
|
|
439
|
+
// experiences (owner, 2026-10-09, in the owner's order and words). Picking one makes the hall
|
|
443
440
|
// wear that mode on every page — its tabs, its look, its tools — the way a
|
|
444
441
|
// builder in it sees it, while the artist keeps Artistry's tools and small
|
|
445
442
|
// sky-style doodles are scribbled into the top bar's empty space (craft-review.js draws them,
|
|
446
|
-
// loaded only in review mode; craft-review.css styles it
|
|
443
|
+
// loaded only in review mode; craft-review.css styles it).
|
|
447
444
|
// The artist's own mode stays Artistry: the experience is this session's
|
|
448
445
|
// alone (sessionStorage), and choosing any mode from the switch or the
|
|
449
446
|
// picker ends it. Cosmetic like the mode: a tab the gate hides stays hidden.
|
|
450
447
|
var REVIEW_KEY = 'hall-craft-review';
|
|
451
|
-
var REVIEW_GROUP = 'craft-review';
|
|
452
448
|
var EXPERIENCES = [
|
|
453
449
|
{ id: 'ideating', label: 'Ideator experience' },
|
|
454
450
|
{ id: 'governing', label: 'Governing experience' },
|
|
@@ -478,138 +474,6 @@
|
|
|
478
474
|
apply('artistry');
|
|
479
475
|
}
|
|
480
476
|
|
|
481
|
-
// The menu. Hover opens it where a pointer can hover; the chevron (or, on a
|
|
482
|
-
// phone, Review itself) opens it by click, tap or keyboard. Built on every
|
|
483
|
-
// open, so the checked experience is always the live one.
|
|
484
|
-
var closeTimer = null;
|
|
485
|
-
// Hover is a mouse's alone: a touch also fires pointer events, and a laptop
|
|
486
|
-
// with a touch screen can report no hover at all, so ask the pointer itself.
|
|
487
|
-
function byMouse(e) { return !!e && e.pointerType === 'mouse'; }
|
|
488
|
-
function reviewMenuHtml() {
|
|
489
|
-
var items = '<button class="craft-review__item" type="button" role="menuitem" data-review-act="write">' +
|
|
490
|
-
icon('write', 'craft-review__icon') + '<span>Write feedback on this page</span></button>' +
|
|
491
|
-
'<p class="craft-review__head" role="presentation">Review mode</p>';
|
|
492
|
-
items += EXPERIENCES.map(function (x) {
|
|
493
|
-
var on = review === x.id;
|
|
494
|
-
return '<button class="craft-review__item" type="button" role="menuitemradio" aria-checked="' + (on ? 'true' : 'false') + '" data-review-pick="' + x.id + '">' +
|
|
495
|
-
icon(x.id, 'craft-review__icon') + '<span>' + esc(x.label) + '</span>' +
|
|
496
|
-
(on ? '<svg class="craft-review__check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>' : '') +
|
|
497
|
-
'</button>';
|
|
498
|
-
}).join('');
|
|
499
|
-
if (review) {
|
|
500
|
-
items += '<button class="craft-review__item craft-review__item--stop" type="button" role="menuitem" data-review-act="stop">' +
|
|
501
|
-
'<svg class="craft-review__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg><span>Stop reviewing</span></button>';
|
|
502
|
-
}
|
|
503
|
-
return items;
|
|
504
|
-
}
|
|
505
|
-
function menuEl() { return document.getElementById(REVIEW_GROUP + '-menu'); }
|
|
506
|
-
function menuItems() {
|
|
507
|
-
var m = menuEl();
|
|
508
|
-
return m && m.querySelectorAll ? Array.prototype.slice.call(m.querySelectorAll('[role^="menuitem"]')) : [];
|
|
509
|
-
}
|
|
510
|
-
function openReviewMenu(focusFirst) {
|
|
511
|
-
var m = menuEl(), more = document.getElementById(REVIEW_GROUP + '-more');
|
|
512
|
-
if (!m) return;
|
|
513
|
-
clearTimeout(closeTimer);
|
|
514
|
-
if (m.hasAttribute('hidden')) {
|
|
515
|
-
m.innerHTML = reviewMenuHtml();
|
|
516
|
-
m.removeAttribute('hidden');
|
|
517
|
-
if (more) more.setAttribute('aria-expanded', 'true');
|
|
518
|
-
document.addEventListener('click', onOutside, true);
|
|
519
|
-
}
|
|
520
|
-
if (focusFirst) {
|
|
521
|
-
var first = menuItems()[0];
|
|
522
|
-
if (first && first.focus) first.focus();
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function closeReviewMenu(refocus) {
|
|
526
|
-
var m = menuEl(), more = document.getElementById(REVIEW_GROUP + '-more');
|
|
527
|
-
clearTimeout(closeTimer);
|
|
528
|
-
document.removeEventListener('click', onOutside, true);
|
|
529
|
-
if (!m || m.hasAttribute('hidden')) return;
|
|
530
|
-
m.setAttribute('hidden', '');
|
|
531
|
-
if (more) more.setAttribute('aria-expanded', 'false');
|
|
532
|
-
if (refocus) {
|
|
533
|
-
var back = narrow() ? document.getElementById('craft-tool-palette') : more;
|
|
534
|
-
if (back && back.focus) back.focus();
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
function toggleReviewMenu(focusFirst) {
|
|
538
|
-
var m = menuEl();
|
|
539
|
-
if (m && !m.hasAttribute('hidden')) closeReviewMenu(false); else openReviewMenu(focusFirst);
|
|
540
|
-
}
|
|
541
|
-
function onOutside(e) {
|
|
542
|
-
var g = document.getElementById(REVIEW_GROUP);
|
|
543
|
-
if (g && g.contains && !g.contains(e.target)) closeReviewMenu(false);
|
|
544
|
-
}
|
|
545
|
-
function onMenuClick(e) {
|
|
546
|
-
var btn = e.target.closest && e.target.closest('button');
|
|
547
|
-
if (!btn) return;
|
|
548
|
-
var pick = btn.getAttribute('data-review-pick');
|
|
549
|
-
var act = btn.getAttribute('data-review-act');
|
|
550
|
-
if (!pick && !act) return;
|
|
551
|
-
closeReviewMenu(false);
|
|
552
|
-
if (pick) startReview(pick);
|
|
553
|
-
else if (act === 'stop') stopReview();
|
|
554
|
-
else if (act === 'write') openTool(toolFor('palette'), document.getElementById('craft-tool-palette'));
|
|
555
|
-
}
|
|
556
|
-
// The menu keys: arrows move, Home and End jump, Escape closes back to the
|
|
557
|
-
// button that opened it, Tab closes and lets focus move on.
|
|
558
|
-
function onMenuKey(e) {
|
|
559
|
-
var items = menuItems();
|
|
560
|
-
var at = items.indexOf(document.activeElement);
|
|
561
|
-
var to = null;
|
|
562
|
-
if (e.key === 'ArrowDown') to = (at + 1) % items.length;
|
|
563
|
-
else if (e.key === 'ArrowUp') to = (at - 1 + items.length) % items.length;
|
|
564
|
-
else if (e.key === 'Home') to = 0;
|
|
565
|
-
else if (e.key === 'End') to = items.length - 1;
|
|
566
|
-
else if (e.key === 'Escape') { e.preventDefault(); closeReviewMenu(true); return; }
|
|
567
|
-
else if (e.key === 'Tab') { closeReviewMenu(false); return; }
|
|
568
|
-
if (to === null || !items[to]) return;
|
|
569
|
-
e.preventDefault();
|
|
570
|
-
items[to].focus();
|
|
571
|
-
}
|
|
572
|
-
function reviewGroup(t, b) {
|
|
573
|
-
var g = document.createElement('span');
|
|
574
|
-
g.id = REVIEW_GROUP;
|
|
575
|
-
g.className = 'craft-review';
|
|
576
|
-
var more = document.createElement('button');
|
|
577
|
-
more.type = 'button';
|
|
578
|
-
more.id = REVIEW_GROUP + '-more';
|
|
579
|
-
more.className = 'craft-review__more';
|
|
580
|
-
more.setAttribute('aria-haspopup', 'menu');
|
|
581
|
-
more.setAttribute('aria-expanded', 'false');
|
|
582
|
-
more.setAttribute('aria-controls', REVIEW_GROUP + '-menu');
|
|
583
|
-
more.setAttribute('aria-label', 'Review mode: see the hall as another mode');
|
|
584
|
-
more.title = 'Review mode: see the hall as another mode';
|
|
585
|
-
more.innerHTML = '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4.5 6 7.5 9 4.5"/></svg>';
|
|
586
|
-
more.addEventListener('click', function (e) { toggleReviewMenu(e && e.detail === 0); });
|
|
587
|
-
more.addEventListener('keydown', function (e) {
|
|
588
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); openReviewMenu(true); }
|
|
589
|
-
});
|
|
590
|
-
var menu = document.createElement('div');
|
|
591
|
-
menu.id = REVIEW_GROUP + '-menu';
|
|
592
|
-
menu.className = 'craft-review__menu';
|
|
593
|
-
menu.setAttribute('role', 'menu');
|
|
594
|
-
menu.setAttribute('aria-label', 'Review mode');
|
|
595
|
-
menu.setAttribute('hidden', '');
|
|
596
|
-
menu.addEventListener('click', onMenuClick);
|
|
597
|
-
menu.addEventListener('keydown', onMenuKey);
|
|
598
|
-
b.addEventListener('keydown', function (e) {
|
|
599
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); openReviewMenu(true); }
|
|
600
|
-
});
|
|
601
|
-
g.addEventListener('pointerenter', function (e) { if (byMouse(e)) openReviewMenu(false); });
|
|
602
|
-
g.addEventListener('pointerleave', function (e) {
|
|
603
|
-
if (!byMouse(e)) return;
|
|
604
|
-
clearTimeout(closeTimer);
|
|
605
|
-
closeTimer = setTimeout(function () { closeReviewMenu(false); }, 220);
|
|
606
|
-
});
|
|
607
|
-
g.appendChild(b);
|
|
608
|
-
g.appendChild(more);
|
|
609
|
-
g.appendChild(menu);
|
|
610
|
-
return g;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
477
|
// The scribble wall: the top bar's empty space, doodled on while reviewing.
|
|
614
478
|
// craft-review.js draws it (decorative, aria-hidden); every motion is CSS,
|
|
615
479
|
// so the reduced-motion kill switch leaves three finished doodles standing.
|
|
@@ -637,7 +501,7 @@
|
|
|
637
501
|
loadTool(wallTool).then(function (api) {
|
|
638
502
|
var live = document.getElementById(WALL);
|
|
639
503
|
if (live === wall && review === mode) wall.innerHTML = api.wallHtml(mode);
|
|
640
|
-
}, function () { /* a bare wall; the
|
|
504
|
+
}, function () { /* a bare wall; the review panel's mode row still says which */ });
|
|
641
505
|
}
|
|
642
506
|
// sessionStorage: this session's mode, or that the picker was already shown.
|
|
643
507
|
var MODE_KEY = 'hall-craft';
|
|
@@ -703,7 +567,7 @@
|
|
|
703
567
|
function choose(mode, keep) {
|
|
704
568
|
mode = valid(mode);
|
|
705
569
|
if (!mode || !offered(mode)) return;
|
|
706
|
-
if (review) { drop('sessionStorage', REVIEW_KEY); review = null;
|
|
570
|
+
if (review) { drop('sessionStorage', REVIEW_KEY); review = null; }
|
|
707
571
|
set('sessionStorage', MODE_KEY, mode);
|
|
708
572
|
set('sessionStorage', ASKED_KEY, '1');
|
|
709
573
|
if (keep === true) set('localStorage', KEEP_KEY, mode);
|
|
@@ -909,6 +773,16 @@
|
|
|
909
773
|
review: startReview,
|
|
910
774
|
stopReview: stopReview,
|
|
911
775
|
EXPERIENCES: EXPERIENCES.map(function (x) { return x.id; }),
|
|
776
|
+
// The review panel's Review mode row (page-notes.js) draws the four
|
|
777
|
+
// experiences; `short` is the mode switch's own word for each, so the
|
|
778
|
+
// row and the switch never name one mode two ways.
|
|
779
|
+
experiences: function () {
|
|
780
|
+
return EXPERIENCES.map(function (x) {
|
|
781
|
+
var m = MODES.filter(function (y) { return y.id === x.id; })[0];
|
|
782
|
+
return { id: x.id, label: x.label, short: m ? m.short : x.label };
|
|
783
|
+
});
|
|
784
|
+
},
|
|
785
|
+
iconHtml: icon,
|
|
912
786
|
open: openPicker,
|
|
913
787
|
ALWAYS_TABS: ALWAYS_TABS.slice(),
|
|
914
788
|
tabsFor: tabsFor,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/* craft-review.css — artist review mode (task 1004686; craft-
|
|
2
|
-
|
|
1
|
+
/* craft-review.css — artist review mode (task 1004686; craft-review.js draws
|
|
2
|
+
the wall). Imported by style.css, beside page-notes.css.
|
|
3
3
|
|
|
4
|
-
The
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
The experience under review is picked in the review panel's own Review mode
|
|
5
|
+
row (page-notes.js, task 1004771); the top bar has no Review button any more.
|
|
6
|
+
While an artist reviews one, small black-and-white doodles in the sky's hand
|
|
7
|
+
are scribbled into the top bar's empty space.
|
|
8
8
|
|
|
9
9
|
Every colour is a token (tests/hall_audit.mjs holds that). Every motion is
|
|
10
10
|
CSS on transform and opacity, and the hall's reduced-motion kill switch
|
|
@@ -13,126 +13,13 @@
|
|
|
13
13
|
An @import lands BEFORE style.css's own rules, so a rule here that restyles
|
|
14
14
|
.craft-tool must out-rank it (two classes), or style.css silently wins. */
|
|
15
15
|
|
|
16
|
-
/* ---------- the split pill ---------- */
|
|
17
|
-
.craft-review {
|
|
18
|
-
position: relative;
|
|
19
|
-
display: inline-flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
flex-shrink: 0;
|
|
22
|
-
border-radius: var(--radius-pill);
|
|
23
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--craft, var(--ink)) 40%, transparent);
|
|
24
|
-
}
|
|
25
|
-
.craft-review > .craft-tool {
|
|
26
|
-
box-shadow: none;
|
|
27
|
-
border-radius: var(--radius-pill) 0 0 var(--radius-pill);
|
|
28
|
-
}
|
|
29
|
-
.craft-review__more {
|
|
30
|
-
display: inline-flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
width: 24px;
|
|
34
|
-
height: 32px;
|
|
35
|
-
padding: 0 4px 0 0;
|
|
36
|
-
border: 0;
|
|
37
|
-
border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
|
|
38
|
-
background: none;
|
|
39
|
-
color: var(--craft, var(--ink-soft));
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
}
|
|
42
|
-
.craft-review__more svg { width: 12px; height: 12px; transition: transform 200ms cubic-bezier(.2, .7, .3, 1); }
|
|
43
|
-
.craft-review__more[aria-expanded="true"] svg { transform: rotate(180deg); }
|
|
44
|
-
.craft-review__more:hover { background: color-mix(in srgb, var(--craft, var(--ink)) 12%, transparent); }
|
|
45
|
-
.craft-review__more:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
46
|
-
@media (min-width: 1181px) {
|
|
47
|
-
.craft-review > .craft-tool { padding-right: 6px; }
|
|
48
|
-
}
|
|
49
|
-
@media (max-width: 1180px) {
|
|
50
|
-
.craft-review > .craft-tool { width: 30px; }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* Reviewing: the pill takes a wash of the experience's colour, says
|
|
54
|
-
"Reviewing", and a drop of wet paint hangs from it. */
|
|
55
|
-
.craft-tool.craft-tool--reviewing { position: relative; background: color-mix(in srgb, var(--craft, var(--sea)) 14%, transparent); }
|
|
56
|
-
.craft-tool.craft-tool--reviewing::after {
|
|
57
|
-
content: '';
|
|
58
|
-
position: absolute;
|
|
59
|
-
left: 13px;
|
|
60
|
-
bottom: -4px;
|
|
61
|
-
width: 4px;
|
|
62
|
-
height: 7px;
|
|
63
|
-
border-radius: 0 0 3px 3px;
|
|
64
|
-
background: var(--craft, var(--sea));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/* ---------- the menu ---------- */
|
|
68
|
-
.craft-review__menu {
|
|
69
|
-
position: absolute;
|
|
70
|
-
top: calc(100% + 8px);
|
|
71
|
-
right: 0;
|
|
72
|
-
z-index: var(--z-drawer);
|
|
73
|
-
display: flex;
|
|
74
|
-
flex-direction: column;
|
|
75
|
-
gap: 2px;
|
|
76
|
-
min-width: 248px;
|
|
77
|
-
padding: 6px;
|
|
78
|
-
box-sizing: border-box;
|
|
79
|
-
background: var(--bg-card);
|
|
80
|
-
border-radius: var(--radius);
|
|
81
|
-
box-shadow: 0 0 0 1px var(--glass-edge), var(--shadow-pop);
|
|
82
|
-
}
|
|
83
|
-
.craft-review__menu[hidden] { display: none; }
|
|
84
|
-
/* A bridge over the gap, so a pointer travelling down to the menu never
|
|
85
|
-
leaves the pill and the hover menu does not close under it. */
|
|
86
|
-
.craft-review__menu::before { content: ''; position: absolute; left: 0; right: 0; top: -8px; height: 8px; }
|
|
87
|
-
.craft-review__head {
|
|
88
|
-
margin: 8px 12px 2px;
|
|
89
|
-
font-family: var(--label-face);
|
|
90
|
-
font-size: 11px;
|
|
91
|
-
letter-spacing: .12em;
|
|
92
|
-
text-transform: uppercase;
|
|
93
|
-
color: var(--ink-faint);
|
|
94
|
-
}
|
|
95
|
-
.craft-review__item {
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
gap: 10px;
|
|
99
|
-
min-height: 36px;
|
|
100
|
-
padding: 0 12px;
|
|
101
|
-
border: 0;
|
|
102
|
-
border-radius: var(--radius-sm);
|
|
103
|
-
background: none;
|
|
104
|
-
color: var(--ink);
|
|
105
|
-
font: inherit;
|
|
106
|
-
font-size: 14px;
|
|
107
|
-
text-align: left;
|
|
108
|
-
white-space: nowrap;
|
|
109
|
-
cursor: pointer;
|
|
110
|
-
}
|
|
111
|
-
.craft-review__item:hover { background: color-mix(in srgb, var(--craft, var(--ink)) 10%, transparent); }
|
|
112
|
-
.craft-review__item:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
|
|
113
|
-
.craft-review__icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--ink-soft); }
|
|
114
|
-
.craft-review__item[aria-checked="true"] { color: var(--craft, var(--ink)); font-weight: 500; }
|
|
115
|
-
.craft-review__item[aria-checked="true"] .craft-review__icon { color: var(--craft, var(--ink)); }
|
|
116
|
-
.craft-review__check { width: 14px; height: 14px; margin-left: auto; flex-shrink: 0; }
|
|
117
|
-
.craft-review__item--stop { margin-top: 4px; border-top: 1px solid var(--rule-soft); border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
|
|
118
|
-
|
|
119
|
-
/* A phone: no hover and no room for the chevron, so Review itself opens the
|
|
120
|
-
menu (craft-mode.js), and the menu spans the bar instead of hanging off a
|
|
121
|
-
pill that may sit near the left edge. */
|
|
122
|
-
@media (max-width: 640px) {
|
|
123
|
-
.craft-review { position: static; }
|
|
124
|
-
.craft-review__more { display: none; }
|
|
125
|
-
.craft-review > .craft-tool { width: 28px; border-radius: var(--radius-pill); }
|
|
126
|
-
.craft-review__menu { top: calc(100% + 6px); left: 12px; right: 12px; min-width: 0; }
|
|
127
|
-
.craft-review__item { min-height: 44px; }
|
|
128
|
-
}
|
|
129
16
|
/* Under 360px the bar holds two tools, not three: reviewing Ideating, its
|
|
130
17
|
own Ideas button folds away (craft-mode.js marks it a guest). */
|
|
131
18
|
@media (max-width: 359px) { .craft-tool.craft-tool--guest { display: none; } }
|
|
132
19
|
|
|
133
20
|
/* ---------- the scribble wall ---------- */
|
|
134
21
|
/* The top bar's empty space, between the title and the controls. Where none
|
|
135
|
-
is left (a phone), the
|
|
22
|
+
is left (a phone), the review panel's mode row still says which. */
|
|
136
23
|
.craft-graffiti {
|
|
137
24
|
position: relative;
|
|
138
25
|
flex: 1 1 0;
|