@bongos/core 1.23.13 → 1.23.15
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 +90 -55
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +22 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +1000 -946
- package/docs/copy-registry.json +2009 -1465
- package/docs/module-api-changelog.md +4 -2
- package/docs/page-inventory.json +10 -4
- package/docs/page-readings.json +593 -573
- package/modules/copy-desk/page-status.js +2 -2
- package/modules/copy-desk/tests/copy_page_status.mjs +1 -1
- package/modules/hall-ui/public/approval-queue-lib.js +1 -1
- package/modules/hall-ui/public/approval-queue.css +26 -12
- package/modules/hall-ui/public/approval-queue.js +40 -16
- package/modules/hall-ui/public/studio-lib.js +5 -3
- package/modules/hall-ui/public/studio.html +8 -8
- package/modules/hall-ui/public/studio.js +25 -5
- package/modules/hall-ui/public/studio.states.json +54 -4
- package/modules/hall-ui/public/welcome-boot.js +62 -12
- package/modules/platform-identity/builder-of-the-week.js +180 -0
- package/modules/platform-identity/migrations/platform_identity_032_weekly_features.sql +53 -0
- package/modules/platform-identity/routes/community-leaderboard.js +25 -1
- package/modules/public-landing/public/account.html +2 -1
- package/modules/public-landing/public/community-week-none.states.json +16 -0
- package/modules/public-landing/public/community.html +142 -13
- package/modules/public-landing/public/community.states.json +6 -4
- package/modules/public-landing/public/connections.html +2 -1
- package/modules/public-landing/public/contact.html +2 -1
- package/modules/public-landing/public/guild.html +2 -1
- package/modules/public-landing/public/index.html +2 -1
- package/modules/public-landing/public/invitations.html +2 -1
- package/modules/public-landing/public/privacy.html +2 -1
- package/modules/public-landing/public/projects.html +2 -1
- package/modules/public-landing/public/terms.html +2 -1
- package/modules/ui-design/kit/fixtures/community-week-none.json +6 -0
- package/modules/ui-design/kit/fixtures/community-week.json +11 -0
- package/modules/ui-design/kit/serve.js +8 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +15 -1
- package/scripts/bongos/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +5 -5
- package/tests/builder_of_the_week.mjs +266 -0
- package/tests/builder_of_the_week_db.mjs +166 -0
- package/tests/hall_approval_queue.mjs +33 -9
- package/tests/hall_studio_home.mjs +74 -7
- package/tests/hub_account_page.mjs +2 -2
- package/tests/hub_community_page.mjs +136 -25
- package/tests/landing_legal_pages.mjs +2 -2
- package/tests/privacy_matrix.mjs +53 -8
- package/tests/welcome_boot_browser.mjs +45 -3
|
@@ -306,7 +306,7 @@ function orderedSurfaces(inventory) {
|
|
|
306
306
|
// last update), and whether it has sat there too long. Waiting for review is
|
|
307
307
|
// the artist's own move and a draft is theirs to finish; a draft is never stuck,
|
|
308
308
|
// and a review is stuck only after 3 days (task 1004777, so its artist is told);
|
|
309
|
-
// a state the reads cannot name always needs
|
|
309
|
+
// a state the reads cannot name always needs to be reviewed.
|
|
310
310
|
// ---------------------------------------------------------------------------
|
|
311
311
|
|
|
312
312
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -318,7 +318,7 @@ const IN_FLIGHT = Object.freeze({
|
|
|
318
318
|
// task 1004777: a review left 3 days is stuck too, so its artist is told on Home.
|
|
319
319
|
waiting_for_artist: { word: 'Waiting for your review', stuck_after: 3 * DAY_MS },
|
|
320
320
|
landing: { word: 'Approved, going live', stuck_after: DAY_MS },
|
|
321
|
-
unknown: { word: 'Needs
|
|
321
|
+
unknown: { word: 'Needs to be reviewed', stuck_after: 0 },
|
|
322
322
|
});
|
|
323
323
|
const IN_FLIGHT_ORDER = ['unknown', 'landing', 'applying', 'submitted', 'waiting_for_artist', 'writing', 'draft'];
|
|
324
324
|
|
|
@@ -259,7 +259,7 @@ await test('in_flight: every open round, its plain state, how long, and stuck wh
|
|
|
259
259
|
];
|
|
260
260
|
const out = status.composePages({ inventory, readings: null, tasks, now: NOW });
|
|
261
261
|
assert.deepEqual(out.in_flight.map((f) => [f.page_id, f.state, f.state_word, f.stuck]), [
|
|
262
|
-
['builders:f', 'unknown', 'Needs
|
|
262
|
+
['builders:f', 'unknown', 'Needs to be reviewed', true],
|
|
263
263
|
['landing:c', 'landing', 'Approved, going live', true],
|
|
264
264
|
['status:h', 'applying', 'Being applied', true],
|
|
265
265
|
['landing:a', 'submitted', 'Submitted, waiting to be applied', true],
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
|
|
91
91
|
// What the studio's quick action says under "Approval queue".
|
|
92
92
|
function doorDetail(count) {
|
|
93
|
-
if (count == null) return '
|
|
93
|
+
if (count == null) return 'tweaks waiting for your sign-off';
|
|
94
94
|
if (count === 0) return 'nothing waiting for you';
|
|
95
95
|
return `${plural(count, 'page', 'pages')} applied, waiting for you`;
|
|
96
96
|
}
|
|
@@ -150,12 +150,16 @@ html.aq-open { overflow: hidden; }
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/* The page, and the words beside it. */
|
|
153
|
+
/* The words changed lead and the page is a look-if-you-want beneath them, so the
|
|
154
|
+
body is one column that scrolls as a whole. */
|
|
153
155
|
.aq-body {
|
|
154
156
|
flex-grow: 1;
|
|
155
157
|
min-height: 0;
|
|
156
158
|
min-width: 0;
|
|
157
159
|
display: flex;
|
|
158
|
-
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 18px;
|
|
162
|
+
overflow-y: auto;
|
|
159
163
|
}
|
|
160
164
|
.aq-frame {
|
|
161
165
|
flex-grow: 1;
|
|
@@ -183,16 +187,15 @@ html.aq-open { overflow: hidden; }
|
|
|
183
187
|
color: var(--aq-paper-ink);
|
|
184
188
|
}
|
|
185
189
|
.aq-lines {
|
|
186
|
-
/* 300px, the artboard's, until the page beside it has shrunk; then down to 240. */
|
|
187
|
-
width: clamp(240px, 32%, 300px);
|
|
188
190
|
flex: none;
|
|
189
191
|
min-width: 0;
|
|
190
|
-
min-height: 0;
|
|
191
|
-
overflow-y: auto;
|
|
192
192
|
display: flex;
|
|
193
193
|
flex-direction: column;
|
|
194
194
|
gap: 16px;
|
|
195
195
|
}
|
|
196
|
+
.aq-peek { display: flex; flex-direction: column; gap: 12px; min-width: 0; flex: none; }
|
|
197
|
+
.aq-peek__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
|
198
|
+
.aq-peek .aq-frame { flex-grow: 0; max-height: min(520px, 60vh); }
|
|
196
199
|
.aq-views { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
197
200
|
.aq-lines__list {
|
|
198
201
|
list-style: none;
|
|
@@ -202,7 +205,16 @@ html.aq-open { overflow: hidden; }
|
|
|
202
205
|
flex-direction: column;
|
|
203
206
|
gap: 14px;
|
|
204
207
|
}
|
|
205
|
-
|
|
208
|
+
/* One changed line: what it was on the left, what it is now on the right. */
|
|
209
|
+
.aq-line {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
212
|
+
gap: 6px 24px;
|
|
213
|
+
padding-top: 14px;
|
|
214
|
+
border-top: 1px solid var(--aq-rule);
|
|
215
|
+
}
|
|
216
|
+
.aq-line__section, .aq-line__pinned { grid-column: 1 / -1; }
|
|
217
|
+
.aq-line__aside { grid-column: 1; grid-row: 2; }
|
|
206
218
|
.aq-line__section {
|
|
207
219
|
font-family: var(--aq-mono);
|
|
208
220
|
font-size: 11px;
|
|
@@ -210,7 +222,7 @@ html.aq-open { overflow: hidden; }
|
|
|
210
222
|
text-transform: uppercase;
|
|
211
223
|
color: var(--aq-ink-faint);
|
|
212
224
|
}
|
|
213
|
-
.aq-line__text { font-size:
|
|
225
|
+
.aq-line__text { grid-column: 2; grid-row: 2; font-size: 17px; line-height: 1.5; color: var(--aq-ink); overflow-wrap: anywhere; }
|
|
214
226
|
.aq-mark { background: var(--aq-mark); color: var(--aq-ink); border-radius: 4px; padding: 0 3px; }
|
|
215
227
|
/* A brand hole ({{productName}}) shown as the name it stands for: a fixed name
|
|
216
228
|
the artist did not write and cannot rewrite, so a quiet chip (task 1004381). */
|
|
@@ -224,7 +236,7 @@ html.aq-open { overflow: hidden; }
|
|
|
224
236
|
line-height: 1.35;
|
|
225
237
|
white-space: nowrap;
|
|
226
238
|
}
|
|
227
|
-
.aq-line__aside { font-size:
|
|
239
|
+
.aq-line__aside { font-size: 15px; line-height: 1.5; color: var(--aq-ink-faint); overflow-wrap: anywhere; }
|
|
228
240
|
.aq-line--refused .aq-line__aside { color: var(--aq-link); }
|
|
229
241
|
/* task 1004775: a test that still checks the old words. */
|
|
230
242
|
.aq-line__pinned { font-size: 13px; line-height: 1.45; color: var(--aq-link); overflow-wrap: anywhere; }
|
|
@@ -248,8 +260,9 @@ html.aq-open { overflow: hidden; }
|
|
|
248
260
|
min-height: 48px;
|
|
249
261
|
}
|
|
250
262
|
.aq-accent {
|
|
251
|
-
height:
|
|
252
|
-
padding: 0
|
|
263
|
+
height: 52px;
|
|
264
|
+
padding: 0 34px;
|
|
265
|
+
font-weight: 600;
|
|
253
266
|
border-radius: 999px;
|
|
254
267
|
cursor: pointer;
|
|
255
268
|
border: 0;
|
|
@@ -312,10 +325,11 @@ html.aq-open { overflow: hidden; }
|
|
|
312
325
|
.aq-title { order: 3; flex: 1 1 100%; }
|
|
313
326
|
.aq-head__gap { display: none; }
|
|
314
327
|
.aq-head .aq-flips { order: 4; }
|
|
315
|
-
.aq-body { flex-
|
|
328
|
+
.aq-body { flex-grow: 0; overflow: visible; }
|
|
329
|
+
.aq-line { grid-template-columns: minmax(0, 1fr); }
|
|
330
|
+
.aq-line__aside, .aq-line__text { grid-column: 1; grid-row: auto; }
|
|
316
331
|
.aq-frame { flex: none; max-height: 70vh; }
|
|
317
332
|
.aq-frame__note { padding: 40px 20px; }
|
|
318
|
-
.aq-lines { width: auto; overflow: visible; }
|
|
319
333
|
.aq-foot { flex-wrap: wrap; }
|
|
320
334
|
.aq-note { flex: 1 1 100%; }
|
|
321
335
|
.aq-hint { margin-left: 0; flex: 1 1 100%; white-space: normal; }
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
renders: new Map(), // task id -> render index, or 'loading' / 'failed'
|
|
73
73
|
outcome: new Map(), // task id -> { kind: 'approved'|'sent', line }
|
|
74
74
|
noteOpen: false,
|
|
75
|
+
peek: false, // the rendered page is a look-if-you-want, never the card
|
|
75
76
|
busy: false,
|
|
76
77
|
error: null,
|
|
77
78
|
opener: null,
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
if (!pick) {
|
|
134
135
|
return L.hasAnyRender(r)
|
|
135
136
|
? `<p class="aq-frame__note">No ${esc(state.view.phase)} render is attached to this page.</p>`
|
|
136
|
-
: '<p class="aq-frame__note">No render is attached to this page yet.
|
|
137
|
+
: '<p class="aq-frame__note">No render is attached to this page yet. The words above are what changed.</p>';
|
|
137
138
|
}
|
|
138
139
|
const name = L.displayTitle(item);
|
|
139
140
|
const alt = `${name}, ${state.view.phase}, ${pick.device}, ${pick.mode}`;
|
|
@@ -145,8 +146,9 @@
|
|
|
145
146
|
return H ? H.holesHtml(text, { cls: 'aq-hole' }) : esc(text);
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
// The card IS the words: each changed line, what it was beside what it is now.
|
|
148
150
|
function linesHtml(item) {
|
|
149
|
-
const rows = (item.lines || []).map((l) => L.lineRow(l,
|
|
151
|
+
const rows = (item.lines || []).map((l) => L.lineRow(l, 'after'));
|
|
150
152
|
if (!rows.length) return '<p class="aq-lines__none">This round changed no lines.</p>';
|
|
151
153
|
return `<ol class="aq-lines__list">${rows.map((r) => `
|
|
152
154
|
<li class="aq-line${r.refused ? ' aq-line--refused' : ''}">
|
|
@@ -170,20 +172,35 @@
|
|
|
170
172
|
if (!item.can_decide) {
|
|
171
173
|
return '<div class="aq-foot"><p class="aq-sealed">Only the artist who wrote this page, or another artist, can approve it or send it back.</p><span class="aq-hint">highlighted: the words you changed</span></div>';
|
|
172
174
|
}
|
|
173
|
-
const
|
|
174
|
-
const noPicture = r && r !== 'loading' && r !== 'failed' && !L.hasAnyRender(r);
|
|
175
|
-
const approve = `<button type="button" class="aq-accent" data-approve${state.busy || noPicture ? ' disabled' : ''}>approve and ship</button>`;
|
|
175
|
+
const approve = `<button type="button" class="aq-accent" data-approve${state.busy ? ' disabled' : ''}>Approve and ship</button>`;
|
|
176
176
|
const note = state.noteOpen
|
|
177
177
|
? `<div class="aq-note">
|
|
178
178
|
<label for="aq-note" class="aq-sr">What needs changing</label>
|
|
179
179
|
<input id="aq-note" class="aq-note__input" type="text" maxlength="${L.MAX_NOTE}" placeholder="The hero line is too long on a phone." autocomplete="off">
|
|
180
180
|
<button type="button" class="aq-ghost aq-ghost--send" data-send${state.busy ? ' disabled' : ''}>send</button>
|
|
181
181
|
</div>`
|
|
182
|
-
: `<button type="button" class="aq-ghost" data-open-note${state.busy ? ' disabled' : ''}>
|
|
182
|
+
: `<button type="button" class="aq-ghost" data-open-note${state.busy ? ' disabled' : ''}>Send it back</button>`;
|
|
183
183
|
const err = state.error ? `<p class="aq-error" role="alert">${esc(state.error)}</p>` : '';
|
|
184
184
|
return `<div class="aq-foot">${approve}${note}<span class="aq-hint">highlighted: the words you changed</span></div>${err}`;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// The page as the artist would see it: closed until asked for, because a
|
|
188
|
+
// picture that may not exist is no way to open a decision.
|
|
189
|
+
function peekHtml(item) {
|
|
190
|
+
if (!state.peek) {
|
|
191
|
+
return '<div class="aq-peek"><button type="button" class="aq-ghost aq-ghost--small" data-peek aria-expanded="false">See it on the page</button></div>';
|
|
192
|
+
}
|
|
193
|
+
return `<div class="aq-peek aq-peek--open">
|
|
194
|
+
<div class="aq-peek__bar">
|
|
195
|
+
<button type="button" class="aq-ghost aq-ghost--small" data-peek aria-expanded="true">Hide the page</button>
|
|
196
|
+
${pills('phase', L.PHASES, state.view.phase, 'Before or after')}
|
|
197
|
+
${pills('device', L.DEVICES, state.view.device, 'Desktop or phone')}
|
|
198
|
+
${pills('mode', L.MODES, state.view.mode, 'Light or dark')}
|
|
199
|
+
</div>
|
|
200
|
+
<div class="aq-frame" tabindex="0" aria-label="The page, rendered">${frameHtml(item)}</div>
|
|
201
|
+
</div>`;
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
// task 1004765: submitted rounds nobody has applied, below the reviewable
|
|
188
205
|
// pages. The server lists them apart; the panel only words them, and gives
|
|
189
206
|
// them no approve or send-back verb.
|
|
@@ -204,28 +221,26 @@
|
|
|
204
221
|
const item = current();
|
|
205
222
|
const close = '<button type="button" class="aq-close" data-close>close</button>';
|
|
206
223
|
if (!item) {
|
|
207
|
-
const words = state.loadError || (state.loaded ? 'Nothing is waiting for you. A page
|
|
224
|
+
const words = state.loadError || (state.loaded ? 'Nothing is waiting for you. A page shows up here once its tweaks have been applied and checked.' : 'Opening the Approval queue…');
|
|
208
225
|
dialog.innerHTML = `
|
|
209
226
|
<div class="aq-head"><span class="aq-eyebrow">approval queue</span><h2 class="aq-title" id="aq-title">${state.loaded ? 'Nothing waiting' : 'Approval queue'}</h2><span class="aq-head__gap"></span>${close}</div>
|
|
210
227
|
<div class="aq-body aq-body--empty"><div class="aq-frame"><p class="aq-frame__note">${esc(words)}</p></div></div>
|
|
211
228
|
${notAppliedHtml()}`;
|
|
212
229
|
return;
|
|
213
230
|
}
|
|
214
|
-
loadRenders(item);
|
|
231
|
+
if (state.peek) loadRenders(item);
|
|
215
232
|
dialog.innerHTML = `
|
|
216
233
|
<div class="aq-head">
|
|
217
234
|
<span class="aq-eyebrow">${esc(L.eyebrow(item))}</span>
|
|
218
235
|
<h2 class="aq-title" id="aq-title">${esc(L.displayTitle(item))} <span class="aq-title__sub">${esc(L.subLine(item))}</span></h2>
|
|
219
236
|
<span class="aq-head__gap"></span>
|
|
220
|
-
${pills('phase', L.PHASES, state.view.phase, 'Before or after')}
|
|
221
237
|
${close}
|
|
222
238
|
</div>
|
|
223
239
|
<div class="aq-body">
|
|
224
|
-
<
|
|
225
|
-
<aside class="aq-lines" aria-label="The words you changed">
|
|
226
|
-
<div class="aq-views">${pills('device', L.DEVICES, state.view.device, 'Desktop or phone')}${pills('mode', L.MODES, state.view.mode, 'Light or dark')}</div>
|
|
240
|
+
<section class="aq-lines" aria-label="The words you changed">
|
|
227
241
|
${linesHtml(item)}
|
|
228
|
-
</
|
|
242
|
+
</section>
|
|
243
|
+
${peekHtml(item)}
|
|
229
244
|
</div>
|
|
230
245
|
${footHtml(item)}
|
|
231
246
|
${notAppliedHtml()}`;
|
|
@@ -276,7 +291,9 @@
|
|
|
276
291
|
|
|
277
292
|
// ---- open / close ----------------------------------------------------------
|
|
278
293
|
|
|
279
|
-
|
|
294
|
+
// `pageId` (optional) lands on that page's card when it is in the queue, so a
|
|
295
|
+
// row elsewhere that says "needs to be reviewed" opens its own card.
|
|
296
|
+
async function open(pageId, { reload = true } = {}) {
|
|
280
297
|
state.opener = document.activeElement;
|
|
281
298
|
state.open = true;
|
|
282
299
|
state.error = null;
|
|
@@ -285,9 +302,14 @@
|
|
|
285
302
|
document.documentElement.classList.add('aq-open');
|
|
286
303
|
paint();
|
|
287
304
|
dialog.focus();
|
|
288
|
-
await loadQueue();
|
|
305
|
+
if (reload || !state.loaded) await loadQueue();
|
|
306
|
+
if (typeof pageId === 'string') {
|
|
307
|
+
const at = state.queue.findIndex((q) => q.page_id === pageId);
|
|
308
|
+
if (at !== -1) state.idx = at;
|
|
309
|
+
else if (state.queue.length) state.error = 'That page is not waiting for your sign-off right now.';
|
|
310
|
+
}
|
|
289
311
|
paint();
|
|
290
|
-
const first = dialog.querySelector('[data-
|
|
312
|
+
const first = dialog.querySelector('[data-approve]:not([disabled])') || dialog.querySelector('[data-close]');
|
|
291
313
|
if (first) first.focus();
|
|
292
314
|
}
|
|
293
315
|
|
|
@@ -322,6 +344,7 @@
|
|
|
322
344
|
if (t.dataset.device) return flip('device', t.dataset.device);
|
|
323
345
|
if (t.dataset.mode) return flip('mode', t.dataset.mode);
|
|
324
346
|
if (('approve' in t.dataset)) return decide('approved');
|
|
347
|
+
if (('peek' in t.dataset)) { state.peek = !state.peek; paint(); return undefined; }
|
|
325
348
|
if (('openNote' in t.dataset)) { state.noteOpen = true; paint(); return focusNote(); }
|
|
326
349
|
if (('send' in t.dataset)) return decide('sent');
|
|
327
350
|
if (t.dataset.next != null) { state.idx = Number(t.dataset.next); state.noteOpen = false; state.error = null; paint(); return undefined; }
|
|
@@ -354,6 +377,7 @@
|
|
|
354
377
|
close,
|
|
355
378
|
async refresh() { await loadQueue(); paint(); return countNow(); },
|
|
356
379
|
count: countNow,
|
|
380
|
+
notAppliedCount: () => (state.notApplied || []).length,
|
|
357
381
|
};
|
|
358
382
|
}
|
|
359
383
|
|
|
@@ -164,9 +164,11 @@
|
|
|
164
164
|
page_id: p.page_id,
|
|
165
165
|
title: titleOf(p),
|
|
166
166
|
surface: surfaceWord(p.surface),
|
|
167
|
-
state: p.state_word || 'Needs
|
|
167
|
+
state: p.state_word || 'Needs to be reviewed',
|
|
168
168
|
age,
|
|
169
169
|
stuck: !!p.stuck,
|
|
170
|
+
// A page waiting on a sign-off opens its approval card.
|
|
171
|
+
review: p.state === 'waiting_for_artist',
|
|
170
172
|
// A round the reads cannot name has no action of its own: the row offers
|
|
171
173
|
// a release (task 1004837). task_id pins the round the viewer looked at.
|
|
172
174
|
release: p.state === 'unknown',
|
|
@@ -191,8 +193,8 @@
|
|
|
191
193
|
const v = num(n);
|
|
192
194
|
if (v <= 0) return '';
|
|
193
195
|
return v === 1
|
|
194
|
-
? '1 page has been asked for. It comes up first in
|
|
195
|
-
: `${v} pages have been asked for. They come up first in
|
|
196
|
+
? '1 page has been asked for. It comes up first in Work through tweaks.'
|
|
197
|
+
: `${v} pages have been asked for. They come up first in Work through tweaks.`;
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
// A "pick another" chip names the page and its surface (the artboard's label).
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
shell's top bar is the page's one <header>. -->
|
|
100
100
|
<div class="st-words" id="st-words">
|
|
101
101
|
<h1 class="st-greet" id="studio-greet" data-words>Good evening.</h1>
|
|
102
|
-
<p class="st-sub" id="studio-sub" data-words>Nothing is on your desk yet.
|
|
102
|
+
<p class="st-sub" id="studio-sub" data-words>Nothing is on your desk yet. Work through tweaks has a page ready for you.</p>
|
|
103
103
|
<button type="button" class="st-hint" id="st-hint" aria-controls="st-offer">click any words here to rewrite them</button>
|
|
104
104
|
<p class="st-editing" id="st-editing" aria-live="polite" hidden></p>
|
|
105
105
|
<div class="st-offer" id="st-offer" hidden>
|
|
@@ -120,16 +120,16 @@
|
|
|
120
120
|
<span class="st-resume__detail" id="st-resume-detail"></span>
|
|
121
121
|
</a>
|
|
122
122
|
<button type="button" class="st-action" id="st-flow" aria-haspopup="dialog">
|
|
123
|
-
<span class="st-action__label">
|
|
123
|
+
<span class="st-action__label">Work through tweaks</span>
|
|
124
124
|
<span class="st-action__detail" id="st-flow-detail">the next page to tweak</span>
|
|
125
125
|
</button>
|
|
126
|
-
<button type="button" class="st-action" id="studio-approvals" data-approvals aria-haspopup="dialog">
|
|
127
|
-
<span class="st-action__label">
|
|
128
|
-
<span class="st-action__detail" id="studio-approvals-detail">
|
|
126
|
+
<button type="button" class="st-action" id="studio-approvals" data-approvals aria-haspopup="dialog" hidden>
|
|
127
|
+
<span class="st-action__label">Approve and confirm tweaks</span>
|
|
128
|
+
<span class="st-action__detail" id="studio-approvals-detail">tweaks waiting for your sign-off</span>
|
|
129
129
|
</button>
|
|
130
130
|
<button type="button" class="st-action" id="st-status" aria-haspopup="dialog">
|
|
131
|
-
<span class="st-action__label">
|
|
132
|
-
<span class="st-action__detail" id="st-status-detail">how far each surface has come</span>
|
|
131
|
+
<span class="st-action__label">Needs to be reviewed</span>
|
|
132
|
+
<span class="st-action__detail" id="st-status-detail">pages waiting on you, and how far each surface has come</span>
|
|
133
133
|
</button>
|
|
134
134
|
<!-- Task 1004499: the reviews builders wrote on the public site with
|
|
135
135
|
its Artist review button. Each opens here, in the palette's panel. -->
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
<div class="st-overlay" id="st-panel" hidden>
|
|
151
151
|
<div class="st-dialog" role="dialog" aria-modal="true" aria-labelledby="st-panel-title">
|
|
152
152
|
<div class="st-dialog__head">
|
|
153
|
-
<h2 class="st-dialog__title" id="st-panel-title" tabindex="-1">
|
|
153
|
+
<h2 class="st-dialog__title" id="st-panel-title" tabindex="-1">Work through tweaks</h2>
|
|
154
154
|
<button type="button" class="st-close" data-close-panel>close</button>
|
|
155
155
|
</div>
|
|
156
156
|
<div class="st-dialog__body" id="st-panel-body"></div>
|
|
@@ -128,6 +128,8 @@
|
|
|
128
128
|
const RANK_ORDER = { archon: 4, metic: 3, thetes: 2, xenos: 1 };
|
|
129
129
|
const MAX_WORDS = 2000; // the abandon/merge `reason` cap the routes apply anyway
|
|
130
130
|
|
|
131
|
+
// Also the floor for the approve door: a studio viewer who cannot pass a tweak
|
|
132
|
+
// is not shown the door (cosmetic; the approve and send-back routes decide).
|
|
131
133
|
function canVerdict() {
|
|
132
134
|
const rank = String((viewer && viewer.rank) || '').toLowerCase();
|
|
133
135
|
return (RANK_ORDER[rank] ?? 0) >= RANK_ORDER.metic;
|
|
@@ -570,15 +572,31 @@
|
|
|
570
572
|
const door = $('#studio-approvals');
|
|
571
573
|
const Q = window.OTBApprovalQueue;
|
|
572
574
|
const lib = window.OTBApprovalQueueLib;
|
|
573
|
-
if (!door || !Q || !lib) return;
|
|
575
|
+
if (!door || !Q || !lib) return false;
|
|
574
576
|
approvals = Q.mount({
|
|
575
577
|
api,
|
|
576
578
|
apiBase: API,
|
|
577
579
|
writes: { approve: WRITE_ROUTES.approve, sendBack: WRITE_ROUTES.sendBack },
|
|
578
580
|
onCount: (n) => { const d = $('#studio-approvals-detail'); if (d) d.textContent = lib.doorDetail(n); },
|
|
579
581
|
});
|
|
580
|
-
door
|
|
582
|
+
// Nothing to approve means no dialog: the door's own detail line already
|
|
583
|
+
// says so, and a click on it just confirms it.
|
|
584
|
+
door.addEventListener('click', async () => {
|
|
585
|
+
const n = await approvals.refresh();
|
|
586
|
+
// Submitted-but-not-applied rounds still show in the dialog, so they count as something to see.
|
|
587
|
+
if (n === 0 && !approvals.notAppliedCount()) { toast('Nothing is waiting for your sign-off right now.'); return; }
|
|
588
|
+
approvals.open(undefined, { reload: false });
|
|
589
|
+
});
|
|
581
590
|
approvals.refresh();
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// The approve door is for artists who can pass a tweak (metic and up). Below
|
|
595
|
+
// that rank it never shows; the routes still decide who may (ADR 0016).
|
|
596
|
+
function showApprovals() {
|
|
597
|
+
const door = $('#studio-approvals');
|
|
598
|
+
if (!door || !canVerdict()) return;
|
|
599
|
+
if (mountApprovals()) door.hidden = false;
|
|
582
600
|
}
|
|
583
601
|
|
|
584
602
|
// ===========================================================================
|
|
@@ -682,7 +700,7 @@
|
|
|
682
700
|
|
|
683
701
|
// ---- the small glass panels -----------------------------------------------
|
|
684
702
|
|
|
685
|
-
const TITLES = Object.freeze({ flow: '
|
|
703
|
+
const TITLES = Object.freeze({ flow: 'Work through tweaks', status: 'Needs to be reviewed', site: 'From the live site' });
|
|
686
704
|
|
|
687
705
|
function flowHtml() {
|
|
688
706
|
if (!home.next) {
|
|
@@ -725,7 +743,7 @@
|
|
|
725
743
|
const asked = lib.askedLine(home.next && home.next.asked_pages);
|
|
726
744
|
const flight = lib.inFlightRows(home.pages, new Date()).map((f) => `
|
|
727
745
|
<li class="st-flight${f.stuck ? ' st-flight--stuck' : ''}">
|
|
728
|
-
|
|
746
|
+
${f.review && approvals ? `<a class="st-flight__page" href="#review" data-review-page="${escapeHtml(f.page_id)}">${escapeHtml(f.title)}</a>` : `<a class="st-flight__page" href="${escapeHtml(editorHref(f.page_id))}">${escapeHtml(f.title)}</a>`}
|
|
729
747
|
<span class="st-flight__where">${escapeHtml(f.surface)}</span>
|
|
730
748
|
<span class="st-flight__state">${escapeHtml(f.state)}${f.age ? ` · ${escapeHtml(f.age)}` : ''}${f.stuck ? ' · stuck' : ''}</span>
|
|
731
749
|
${f.release && canVerdict() ? `<button type="button" class="st-release" data-release-page="${escapeHtml(f.page_id)}" data-release-task="${escapeHtml(f.task_id || '')}">release</button>` : ''}
|
|
@@ -1021,6 +1039,8 @@
|
|
|
1021
1039
|
if (overlay) {
|
|
1022
1040
|
overlay.addEventListener('click', (ev) => {
|
|
1023
1041
|
if (ev.target === overlay || ev.target.closest('[data-close-panel]')) { closePanel(); return; }
|
|
1042
|
+
const review = ev.target.closest('[data-review-page]');
|
|
1043
|
+
if (review && approvals) { ev.preventDefault(); const id = review.getAttribute('data-review-page'); closePanel(); approvals.open(id); return; }
|
|
1024
1044
|
const rel = ev.target.closest('[data-release-page]');
|
|
1025
1045
|
if (rel) { releasePage(rel.getAttribute('data-release-page'), rel.getAttribute('data-release-task'), rel); return; }
|
|
1026
1046
|
const take = ev.target.closest('[data-take]');
|
|
@@ -1082,9 +1102,9 @@
|
|
|
1082
1102
|
|
|
1083
1103
|
(async () => {
|
|
1084
1104
|
startSky();
|
|
1085
|
-
mountApprovals();
|
|
1086
1105
|
const me = await api.request('GET', `${API}/me`);
|
|
1087
1106
|
if (me.ok) viewer = me.data.builder || me.data;
|
|
1107
|
+
showApprovals();
|
|
1088
1108
|
bootHome(me.ok);
|
|
1089
1109
|
// Reviews first (the richer craft), then flags — each newest-first within.
|
|
1090
1110
|
const [reviews, flags] = await Promise.all([loadReviews(), loadFlags()]);
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
},
|
|
279
279
|
"approvals": {
|
|
280
280
|
"auth": true,
|
|
281
|
-
"_": "The Approval queue (task 1004323): the studio's quick action opens the glass panel, 1 of 2
|
|
281
|
+
"_": "The Approval queue (task 1004323): the studio's quick action opens the glass panel, 1 of 2. The card is the changed lines, what each was beside what it is now, with Approve and ship and Send it back under them. Against the kit's own stub it opens on its could-not-read line; populated, run it against the hall-preview harness (fixtures copy-desk__approvals.json, tasks__visuals.json and the two CANNED_WRITES).",
|
|
282
282
|
"actions": [
|
|
283
283
|
[
|
|
284
284
|
"wait",
|
|
@@ -297,13 +297,14 @@
|
|
|
297
297
|
"visible": [
|
|
298
298
|
".aq-dialog",
|
|
299
299
|
".aq-eyebrow",
|
|
300
|
-
".aq-
|
|
300
|
+
".aq-line",
|
|
301
|
+
"[data-approve]"
|
|
301
302
|
]
|
|
302
303
|
}
|
|
303
304
|
},
|
|
304
305
|
"approvals-before": {
|
|
305
306
|
"auth": true,
|
|
306
|
-
"_": "
|
|
307
|
+
"_": "See it on the page, switched to before: the page as it was.",
|
|
307
308
|
"actions": [
|
|
308
309
|
[
|
|
309
310
|
"wait",
|
|
@@ -317,6 +318,14 @@
|
|
|
317
318
|
"wait",
|
|
318
319
|
1200
|
|
319
320
|
],
|
|
321
|
+
[
|
|
322
|
+
"click",
|
|
323
|
+
"[data-peek]"
|
|
324
|
+
],
|
|
325
|
+
[
|
|
326
|
+
"wait",
|
|
327
|
+
500
|
|
328
|
+
],
|
|
320
329
|
[
|
|
321
330
|
"click",
|
|
322
331
|
"[data-phase=\"before\"]"
|
|
@@ -336,7 +345,7 @@
|
|
|
336
345
|
},
|
|
337
346
|
"approvals-phone": {
|
|
338
347
|
"auth": true,
|
|
339
|
-
"_": "
|
|
348
|
+
"_": "See it on the page, on the phone render.",
|
|
340
349
|
"actions": [
|
|
341
350
|
[
|
|
342
351
|
"wait",
|
|
@@ -350,6 +359,14 @@
|
|
|
350
359
|
"wait",
|
|
351
360
|
1200
|
|
352
361
|
],
|
|
362
|
+
[
|
|
363
|
+
"click",
|
|
364
|
+
"[data-peek]"
|
|
365
|
+
],
|
|
366
|
+
[
|
|
367
|
+
"wait",
|
|
368
|
+
500
|
|
369
|
+
],
|
|
353
370
|
[
|
|
354
371
|
"click",
|
|
355
372
|
"[data-device=\"phone\"]"
|
|
@@ -473,6 +490,39 @@
|
|
|
473
490
|
".aq-outcome--sent"
|
|
474
491
|
]
|
|
475
492
|
}
|
|
493
|
+
},
|
|
494
|
+
"approvals-page": {
|
|
495
|
+
"auth": true,
|
|
496
|
+
"_": "See it on the page: the optional rendered page opens under the words, on the AFTER render.",
|
|
497
|
+
"actions": [
|
|
498
|
+
[
|
|
499
|
+
"wait",
|
|
500
|
+
700
|
|
501
|
+
],
|
|
502
|
+
[
|
|
503
|
+
"click",
|
|
504
|
+
"#studio-approvals"
|
|
505
|
+
],
|
|
506
|
+
[
|
|
507
|
+
"wait",
|
|
508
|
+
1200
|
|
509
|
+
],
|
|
510
|
+
[
|
|
511
|
+
"click",
|
|
512
|
+
"[data-peek]"
|
|
513
|
+
],
|
|
514
|
+
[
|
|
515
|
+
"wait",
|
|
516
|
+
700
|
|
517
|
+
]
|
|
518
|
+
],
|
|
519
|
+
"expect": {
|
|
520
|
+
"visible": [
|
|
521
|
+
".aq-dialog",
|
|
522
|
+
".aq-line",
|
|
523
|
+
".aq-render"
|
|
524
|
+
]
|
|
525
|
+
}
|
|
476
526
|
}
|
|
477
527
|
},
|
|
478
528
|
"reducedMotion": {
|