@bongos/core 1.23.13 → 1.23.14

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.
@@ -3241,7 +3241,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3241
3241
  landed since 1.23.10 with no explicit bump. run 40552168441. (task 1002620)
3242
3242
  1.23.12 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3243
3243
  landed since 1.23.11 with no explicit bump. run 40583533060. (task 1002620)
3244
- 1.23.13 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3245
- landed since 1.23.12 with no explicit bump. run 40596643484. (task 1002620)
3244
+ 1.23.14 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3245
+ landed since 1.23.12 with no explicit bump. run 40600119011. (task 1002620)
3246
3246
  ---------------------------------------------------------------------------
3247
3247
  ```
@@ -13,7 +13,7 @@
13
13
  "pages": 47,
14
14
  "surfaces": 3,
15
15
  "with_states": 40,
16
- "states": 342
16
+ "states": 343
17
17
  },
18
18
  "surfaces": [
19
19
  {
@@ -1201,7 +1201,8 @@
1201
1201
  "approvals-phone",
1202
1202
  "approvals-note",
1203
1203
  "approved",
1204
- "sent-back"
1204
+ "sent-back",
1205
+ "approvals-page"
1205
1206
  ]
1206
1207
  },
1207
1208
  {
@@ -1380,8 +1380,8 @@
1380
1380
  "surface": "builders",
1381
1381
  "title": "Builders' Hall",
1382
1382
  "states_read": ["home","standing","standing-karma","out","founding","genesis","decide","demo"],
1383
- "files_hash": "6f48e2429d6454ec",
1384
- "reading_hash": "c24fc6afc7453776",
1383
+ "files_hash": "9ae2154234599eee",
1384
+ "reading_hash": "5f0fcdc8f240a233",
1385
1385
  "counts": {"lines":110,"placed":56,"shared":10,"unplaced":44},
1386
1386
  "omitted": {"data":102},
1387
1387
  "notes": ["founding: an action failed (page.focus: Timeout 6000ms exceeded.); read as it stood"],
@@ -1391,7 +1391,7 @@
1391
1391
  {"key":"L0003","section":"top bar","text":"Home","placement":"shared"},
1392
1392
  {"key":"L0004","section":"top bar","text":"Welcome back, {…}.","placement":"shared","file":"modules/hall-ui/public/hall-render.js","line":596,"string_id":"0658f0358ffd"},
1393
1393
  {"key":"L0005","section":"top bar","text":"Jump to…","placement":"shared","file":"modules/hall-ui/public/shell.js","line":540,"string_id":"eba610d25523"},
1394
- {"key":"L0006","section":"top bar","text":"⌘K","placement":"shared"},
1394
+ {"key":"L0006","section":"top bar","text":"Ctrl K","placement":"shared"},
1395
1395
  {"key":"L0007","section":"top bar","text":"Building","placement":"shared"},
1396
1396
  {"key":"L0008","section":"top bar","text":"Artistry","placement":"shared"},
1397
1397
  {"key":"L0009","section":"top bar","text":"Ideating","placement":"shared"},
@@ -1472,7 +1472,7 @@
1472
1472
  {"key":"L0084","section":"Achievements","text":"Won {…}","placement":"placed","file":"modules/hall-ui/public/builders.js","line":481,"string_id":"c5fd76a9e03d","state":"standing","occurrences":10},
1473
1473
  {"key":"L0085","section":"Achievements","text":"{…} of {…}","placement":"placed","file":"modules/hall-ui/public/sky-panel.js","line":120,"string_id":"73c1372b43d9","state":"standing","occurrences":2},
1474
1474
  {"key":"L0086","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":516,"string_id":"1883ad7f07fc","state":"standing","occurrences":2},
1475
- {"key":"L0087","section":"Achievements","text":"core 1.23.0","placement":"unplaced","state":"standing"},
1475
+ {"key":"L0087","section":"Achievements","text":"core 1.23.12","placement":"unplaced","state":"standing"},
1476
1476
  {"key":"L0088","section":"Achievements","text":"Skip","placement":"unplaced","state":"standing"},
1477
1477
  {"key":"L0089","section":"Next up — ready to claim","text":"Next up — ready to claim","placement":"placed","file":"modules/hall-ui/public/hall-widgets.js","line":110,"string_id":"659d8256139c"},
1478
1478
  {"key":"L0090","section":"Next up — ready to claim","text":"#1003412 · ~60m · 51 {…} est.","placement":"unplaced"},
@@ -1489,12 +1489,12 @@
1489
1489
  {"key":"L0101","section":"Recently shipped","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":2},
1490
1490
  {"key":"L0102","section":"Recently shipped","text":"All your work →","placement":"placed","file":"modules/hall-ui/public/hall-widgets.js","line":118,"string_id":"b38c01691857"},
1491
1491
  {"key":"L0103","section":"Recently shipped","text":"{…} of {…}","placement":"placed","file":"modules/hall-ui/public/sky-panel.js","line":120,"string_id":"73c1372b43d9"},
1492
- {"key":"L0104","section":"Recently shipped","text":"core 1.23.0","placement":"unplaced"},
1493
- {"key":"L0105","section":"Good morning, {…}","text":"Where do you want to start the day?","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":164,"string_id":"0ec8d133f878","state":"founding"},
1494
- {"key":"L0106","section":"Good morning, {…}","text":"Start with your role","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":166,"string_id":"58cef7963b6c","state":"founding"},
1495
- {"key":"L0107","section":"Good morning, {…}","text":"Pick today’s craft and go straight to its room.","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":166,"string_id":"100e4665514d","state":"founding"},
1496
- {"key":"L0108","section":"Good morning, {…}","text":"Your home page","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":167,"string_id":"2e6866532113","state":"founding"},
1497
- {"key":"L0109","section":"Good morning, {…}","text":"Your work, what needs you, and what shipped.","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":167,"string_id":"e8d92c773db0","state":"founding"},
1492
+ {"key":"L0104","section":"Recently shipped","text":"core 1.23.12","placement":"unplaced"},
1493
+ {"key":"L0105","section":"Good morning, {…}","text":"Where do you want to start the day?","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":170,"string_id":"0ec8d133f878","state":"founding"},
1494
+ {"key":"L0106","section":"Good morning, {…}","text":"Start with your role","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":172,"string_id":"58cef7963b6c","state":"founding"},
1495
+ {"key":"L0107","section":"Good morning, {…}","text":"Pick today’s craft and go straight to its room.","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":172,"string_id":"100e4665514d","state":"founding"},
1496
+ {"key":"L0108","section":"Good morning, {…}","text":"Your home page","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":173,"string_id":"2e6866532113","state":"founding"},
1497
+ {"key":"L0109","section":"Good morning, {…}","text":"Your work, what needs you, and what shipped.","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":173,"string_id":"e8d92c773db0","state":"founding"},
1498
1498
  {"key":"L0110","section":"Recently shipped","text":"Skip","placement":"unplaced"}
1499
1499
  ]
1500
1500
  },
@@ -3867,12 +3867,12 @@
3867
3867
  "id": "builders:studio",
3868
3868
  "surface": "builders",
3869
3869
  "title": "Studio",
3870
- "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back"],
3871
- "files_hash": "4538426333de6374",
3872
- "reading_hash": "55e9090fdd96a979",
3870
+ "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
3871
+ "files_hash": "e25ff80c76808d45",
3872
+ "reading_hash": "420a20ec8bb680bb",
3873
3873
  "counts": {"lines":30,"placed":16,"shared":6,"unplaced":8},
3874
3874
  "omitted": {"data":4},
3875
- "notes": ["flow: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","flow-pick: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","status: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","words-offer: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","words-edit: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","reviews: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","full: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","full-back: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-before: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-phone: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-note: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approved: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","sent-back: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood"],
3875
+ "notes": ["flow: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","flow-pick: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","status: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","words-offer: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","words-edit: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","reviews: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","full: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","full-back: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-before: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-phone: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-note: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approved: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","sent-back: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","approvals-page: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood"],
3876
3876
  "lines": [
3877
3877
  {"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/hall-ui/public/shell.js","line":868,"string_id":"87cee78ca134"},
3878
3878
  {"key":"L0002","section":"top bar","text":"Jump to…","placement":"shared","file":"modules/hall-ui/public/shell.js","line":540,"string_id":"eba610d25523"},
@@ -3884,15 +3884,15 @@
3884
3884
  {"key":"L0008","section":"page top","text":"pages tweaked","placement":"placed","file":"modules/hall-ui/public/studio.html","line":89,"string_id":"271de72308a4"},
3885
3885
  {"key":"L0009","section":"page top","text":"this week","placement":"placed","file":"modules/hall-ui/public/studio.html","line":91,"string_id":"130c3731ad83"},
3886
3886
  {"key":"L0010","section":"Good evening.","text":"Good evening.","placement":"placed","file":"modules/hall-ui/public/studio.html","line":101,"string_id":"80fd51db38d7"},
3887
- {"key":"L0011","section":"Good evening.","text":"{…}, on the {…} page, is where you left it. {…} of its {…} lines are yours so far.","placement":"placed","file":"modules/hall-ui/public/studio.js","line":659,"string_id":"7763e6f28c67"},
3887
+ {"key":"L0011","section":"Good evening.","text":"{…}, on the {…} page, is where you left it. {…} of its {…} lines are yours so far.","placement":"placed","file":"modules/hall-ui/public/studio.js","line":677,"string_id":"7763e6f28c67"},
3888
3888
  {"key":"L0012","section":"Good evening.","text":"click any words here to rewrite them","placement":"placed","file":"modules/hall-ui/public/studio.html","line":103,"string_id":"8d46c902a9d5"},
3889
3889
  {"key":"L0013","section":"Good evening.","text":"Resume","placement":"unplaced"},
3890
3890
  {"key":"L0014","section":"Good evening.","text":"{…} of {…}","placement":"placed","file":"modules/hall-ui/public/sky-panel.js","line":120,"string_id":"73c1372b43d9","occurrences":2},
3891
- {"key":"L0015","section":"Good evening.","text":"Tweak/CopyWrite Flow","placement":"placed","file":"modules/hall-ui/public/studio.html","line":123,"string_id":"18801fb6845f"},
3891
+ {"key":"L0015","section":"Good evening.","text":"Work through tweaks","placement":"placed","file":"modules/hall-ui/public/studio.html","line":123,"string_id":"51458d92e5bc"},
3892
3892
  {"key":"L0016","section":"Good evening.","text":"next up: {…} — {…}","placement":"unplaced"},
3893
- {"key":"L0017","section":"Good evening.","text":"Copy-write Approval queue","placement":"placed","file":"modules/hall-ui/public/studio.html","line":127,"string_id":"f770588750ef"},
3893
+ {"key":"L0017","section":"Good evening.","text":"Approve and confirm tweaks","placement":"placed","file":"modules/hall-ui/public/studio.html","line":127,"string_id":"2bd592da42f8"},
3894
3894
  {"key":"L0018","section":"Good evening.","text":"2 pages applied, waiting for you","placement":"unplaced"},
3895
- {"key":"L0019","section":"Good evening.","text":"Artist Review Status","placement":"placed","file":"modules/hall-ui/public/studio.html","line":131,"string_id":"4d3b1bcae3eb"},
3895
+ {"key":"L0019","section":"Good evening.","text":"Needs to be reviewed","placement":"placed","file":"modules/hall-ui/public/studio.html","line":131,"string_id":"dcff7dcecf55"},
3896
3896
  {"key":"L0020","section":"Good evening.","text":"From the live site","placement":"placed","file":"modules/hall-ui/public/studio.html","line":137,"string_id":"edced5d528c3"},
3897
3897
  {"key":"L0021","section":"Good evening.","text":"2 reviews from the live site","placement":"unplaced"},
3898
3898
  {"key":"L0022","section":"Good evening.","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e"},
@@ -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 a look.
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 a look', stuck_after: 0 },
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 a look', true],
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 'the pages /tweak has applied';
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
- gap: 16px;
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
- .aq-line { display: flex; flex-direction: column; gap: 4px; padding-top: 14px; border-top: 1px solid var(--aq-rule); }
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: 15px; line-height: 1.5; color: var(--aq-ink); overflow-wrap: anywhere; }
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: 13px; line-height: 1.45; color: var(--aq-ink-faint); overflow-wrap: anywhere; }
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: 48px;
252
- padding: 0 26px;
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-direction: column; flex-grow: 0; }
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. /tweak attaches them when it applies a page; until then there is nothing to approve from, so send it back or wait.</p>';
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, state.view.phase));
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 r = state.renders.get(item.task_id);
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' : ''}>send it back</button>`;
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 appears here once /tweak has applied it and its grade has passed.' : 'Opening the Approval queue…');
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
- <div class="aq-frame" tabindex="0" aria-label="The page, rendered">${frameHtml(item)}</div>
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
- </aside>
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
- async function open() {
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-phase][aria-pressed="true"]') || dialog.querySelector('[data-close]');
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 a look',
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 the Tweak/CopyWrite Flow.'
195
- : `${v} pages have been asked for. They come up first in the Tweak/CopyWrite Flow.`;
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. The Tweak/CopyWrite Flow has a page ready for you.</p>
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">Tweak/CopyWrite Flow</span>
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">Copy-write Approval queue</span>
128
- <span class="st-action__detail" id="studio-approvals-detail">the pages /tweak has applied</span>
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">Artist Review Status</span>
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">Tweak/CopyWrite Flow</h2>
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.addEventListener('click', () => approvals.open());
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: 'Tweak/CopyWrite Flow', status: 'Artist Review Status', site: 'From the live site' });
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
- <a class="st-flight__page" href="${escapeHtml(editorHref(f.page_id))}">${escapeHtml(f.title)}</a>
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()]);