@bongos/core 1.19.1062 → 1.19.1064

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.
Files changed (56) hide show
  1. package/.bongos-core.json +123 -48
  2. package/.claude/skills/grade-recover/SKILL.md +1 -0
  3. package/.claude/skills/tweak/SKILL.md +70 -0
  4. package/clients/bongos-client/README.md +1 -1
  5. package/clients/bongos-client/bongos-client.global.js +8 -0
  6. package/clients/bongos-client/index.cjs +8 -0
  7. package/clients/bongos-client/index.d.ts +12 -0
  8. package/clients/bongos-client/index.mjs +8 -0
  9. package/docs/api/openapi.json +301 -3
  10. package/docs/api-reference.md +7 -3
  11. package/docs/copy-inventory.md +44 -7
  12. package/docs/copy-registry.json +393 -26
  13. package/docs/file-map.md +1 -0
  14. package/docs/module-api-changelog.md +4 -0
  15. package/docs/page-inventory.json +36 -4
  16. package/docs/page-readings.json +34 -1
  17. package/modules/copy-desk/page-status.js +63 -0
  18. package/modules/copy-desk/pages.js +1 -0
  19. package/modules/copy-desk/routes/copy-desk.js +30 -0
  20. package/modules/hall-ui/public/tweak-editor-lib.js +137 -0
  21. package/modules/hall-ui/public/tweak-editor.css +437 -0
  22. package/modules/hall-ui/public/tweak-editor.html +111 -0
  23. package/modules/hall-ui/public/tweak-editor.js +447 -0
  24. package/modules/hall-ui/public/tweak-editor.states.json +99 -0
  25. package/modules/hall-ui/records/tweak-editor.md +32 -0
  26. package/modules/lifecycle/db-grade.js +16 -0
  27. package/modules/lifecycle/migrations/lifecycle_013_task_visual_slots.sql +52 -0
  28. package/modules/lifecycle/page-tweak-hold.js +74 -0
  29. package/modules/lifecycle/page-tweak-reads.js +33 -1
  30. package/modules/lifecycle/routes/tasks.js +4 -2
  31. package/modules/lifecycle/routes/visuals.js +82 -0
  32. package/modules/lifecycle/task-visual-db.js +40 -1
  33. package/modules/lifecycle/task-visuals.js +24 -0
  34. package/package-lock.json +2 -2
  35. package/package.json +1 -1
  36. package/release-notes.json +12 -0
  37. package/scripts/gds/copy-apply.js +277 -1
  38. package/scripts/gds/page-reader.js +2 -0
  39. package/scripts/gds/ship-finish.js +27 -2
  40. package/scripts/gds/ship-flow.js +17 -1
  41. package/scripts/gds/ship-land.js +30 -7
  42. package/scripts/gds/ship-merge.js +43 -15
  43. package/scripts/gds/strand-watch.js +41 -1
  44. package/scripts/gds/task.js +23 -5
  45. package/scripts/gds/tweak-renders.js +200 -0
  46. package/scripts/hall-preview/server.js +21 -0
  47. package/src/bongos/serve-internal.js +5 -1
  48. package/src/module-api.js +1 -1
  49. package/tests/copy_desk_page_editor.mjs +181 -0
  50. package/tests/hall_audit.mjs +5 -0
  51. package/tests/hall_page_gate_map.mjs +3 -0
  52. package/tests/hall_tweak_editor.mjs +432 -0
  53. package/tests/page_tweak_hold.mjs +265 -0
  54. package/tests/publish_status_branch.mjs +23 -0
  55. package/tests/task_visual_slots.mjs +161 -0
  56. package/tests/tweak_batch_apply.mjs +265 -0
@@ -0,0 +1,437 @@
1
+ /* modules/hall-ui/public/tweak-editor.css — the tweak editor, /builders/tweak-editor
2
+ (task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and D11).
3
+
4
+ THE PIXEL CONTRACT. docs/design/mocks/tweak-mode/Editor.dc.html is the
5
+ master design the owner approved, and this sheet is its inline styles, one
6
+ rule per element: the night studio behind, the glass top bar, the paper
7
+ document, the margin notes, the accent mark on a changed line, and the
8
+ "is in" panel. The values are the artboard's own, so the sheet is its own
9
+ token tier (the sky precedent, thinking.css; tests/hall_audit.mjs
10
+ LITERAL_EXEMPT names it). What a gate or a route forced to differ is in the
11
+ task's ship notes and modules/hall-ui/records/tweak-editor.md, one line each.
12
+
13
+ ONE STAGE, BOTH MODES. The artboard is the studio at night with a sheet of
14
+ paper over it. The paper is paper and the room is the room whatever the
15
+ hall's theme is, so the palette sits on the page's STAGE (main.tw-stage),
16
+ never on :root (the pack's :root is spliced after this sheet) and never on
17
+ body, so the hall chrome around the stage keeps its own light and dark.
18
+ The editor opens full screen (tweak-editor.js), which is the artboard. */
19
+
20
+ body[data-page="tweak-editor"] .tw-stage {
21
+ color-scheme: dark;
22
+ --tw-room: #07080d;
23
+ --tw-frame: #14151b;
24
+ --tw-night: #0c1122;
25
+ --tw-moon: #f1e6d2;
26
+ --tw-floor: #0d0d12;
27
+ --tw-lamp: rgba(251, 149, 105, .16);
28
+ --tw-lamp-out: rgba(251, 149, 105, 0);
29
+ --tw-ink: #ffffff;
30
+ --tw-ink-soft: #c0c0c0;
31
+ --tw-ink-faint: #a8a8a8;
32
+ --tw-ink-dim: #8a8a90;
33
+ --tw-glass: rgba(255, 255, 255, .07);
34
+ --tw-bar-back: rgba(7, 8, 13, .84);
35
+ --tw-glass-lift: rgba(255, 255, 255, .12);
36
+ --tw-glass-edge: rgba(255, 255, 255, .16);
37
+ --tw-glass-rim: rgba(255, 255, 255, .2);
38
+ --tw-panel: rgba(255, 255, 255, .08);
39
+ --tw-panel-edge: rgba(255, 255, 255, .18);
40
+ --tw-panel-rim: rgba(255, 255, 255, .22);
41
+ --tw-panel-rule: rgba(255, 255, 255, .12);
42
+ --tw-scrim: rgba(4, 5, 9, .88);
43
+ --tw-drop: rgba(0, 0, 0, .55);
44
+ --tw-accent: #fb9569;
45
+ --tw-on-accent: #000000;
46
+ --tw-link: #fcbfa4;
47
+ --tw-paper: #fbfafc;
48
+ --tw-paper-ink: #17181a;
49
+ --tw-paper-soft: #55565a;
50
+ --tw-paper-faint: #65656a;
51
+ --tw-shared: #4a5bb8;
52
+ --tw-engineer: #a3481a;
53
+ --tw-line-hover: rgba(23, 24, 26, .035);
54
+ --tw-line-focus: rgba(251, 149, 105, .08);
55
+ --tw-face: var(--font-body, Manrope, "Segoe UI", system-ui, sans-serif);
56
+ --tw-mono: var(--font-mono, "JetBrains Mono", ui-monospace, Consolas, monospace);
57
+ }
58
+
59
+ /* The stage fills the content box: the hall's content column is a reading
60
+ width, and the artboard is the whole window. */
61
+ body[data-page="tweak-editor"] .app-content { max-width: none; padding: 0; }
62
+ body[data-page="tweak-editor"] .app-foot { display: none; }
63
+ .tw-stage {
64
+ position: relative;
65
+ min-height: calc(100vh - var(--topbar-h, 56px));
66
+ padding: 28px 0 24px;
67
+ background: var(--tw-room);
68
+ color: var(--tw-ink);
69
+ font-family: var(--tw-face);
70
+ isolation: isolate;
71
+ }
72
+ html[data-fullscreen="on"] .tw-stage { min-height: 100vh; }
73
+ .tw-stage a { color: var(--tw-link); }
74
+ .tw-stage a:hover { color: var(--tw-accent); }
75
+ .tw-stage button,
76
+ .tw-stage textarea { font-family: inherit; }
77
+
78
+ /* The room behind: the window with its moon, the floor, the lamp's glow. It
79
+ stays put while the paper scrolls over it (fixed, beside the rail and under
80
+ the top bar; the whole window in full screen), and it is clipped so it can
81
+ never widen the document. Positions are the artboard's, measured from the
82
+ centre of its 1440 frame. */
83
+ .tw-room {
84
+ position: fixed;
85
+ top: var(--topbar-h, 56px);
86
+ left: var(--sidebar-w-rail, 64px);
87
+ right: 0;
88
+ bottom: 0;
89
+ overflow: hidden;
90
+ pointer-events: none;
91
+ z-index: -1;
92
+ }
93
+ html[data-fullscreen="on"] .tw-room { top: 0; left: 0; }
94
+ .tw-window {
95
+ position: absolute;
96
+ left: calc(50% + 60px);
97
+ top: 86px;
98
+ width: 560px;
99
+ height: 560px;
100
+ box-sizing: border-box;
101
+ border: 16px solid var(--tw-frame);
102
+ border-radius: 6px;
103
+ background: var(--tw-night);
104
+ opacity: .45;
105
+ }
106
+ .tw-moon {
107
+ position: absolute;
108
+ left: 372px;
109
+ top: 64px;
110
+ width: 64px;
111
+ height: 64px;
112
+ border-radius: 50%;
113
+ background: var(--tw-moon);
114
+ }
115
+ .tw-floor {
116
+ position: absolute;
117
+ left: 0;
118
+ right: 0;
119
+ top: 780px;
120
+ bottom: 0;
121
+ background: var(--tw-floor);
122
+ }
123
+ .tw-lamp {
124
+ position: absolute;
125
+ left: calc(50% + 440px);
126
+ top: 560px;
127
+ width: 300px;
128
+ height: 300px;
129
+ border-radius: 50%;
130
+ background: radial-gradient(circle, var(--tw-lamp) 0%, var(--tw-lamp-out) 65%);
131
+ }
132
+
133
+ /* In full screen the shell's one button floats over the night room in either
134
+ theme, so on this page it wears the room's glass, not the hall's light ink. */
135
+ html[data-fullscreen="on"] body[data-page="tweak-editor"] .fullscreen-toggle {
136
+ color: #ffffff;
137
+ background: rgba(255, 255, 255, .07);
138
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16);
139
+ }
140
+
141
+ /* THE GLASS TOP BAR. Sticky, so the four doors stay in reach down a long page
142
+ and the paper passes under the glass. In full screen it steps in far enough
143
+ that the shell's one button (top right) never sits on it. */
144
+ .tw-bar {
145
+ position: sticky;
146
+ top: 28px;
147
+ z-index: 2;
148
+ width: min(1200px, calc(100% - 48px));
149
+ margin: 0 auto;
150
+ box-sizing: border-box;
151
+ min-height: 64px;
152
+ padding: 0 12px 0 22px;
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 14px;
156
+ border-radius: 999px;
157
+ /* The artboard's glass (.07 white) over the room. The bar is sticky and the
158
+ white paper scrolls under it, where .07 glass alone turns white and its
159
+ white words vanish, so the glass lies on a backing of the room's own
160
+ black: over the room it reads exactly as the artboard's bar. */
161
+ background: linear-gradient(var(--tw-glass), var(--tw-glass)), var(--tw-bar-back);
162
+ border: 1px solid var(--tw-glass-edge);
163
+ backdrop-filter: blur(28px) saturate(160%);
164
+ -webkit-backdrop-filter: blur(28px) saturate(160%);
165
+ box-shadow: inset 0 1px 0 var(--tw-glass-rim);
166
+ }
167
+ html[data-fullscreen="on"] .tw-bar { width: min(1200px, calc(100% - 144px)); }
168
+ /* With the chrome showing, the shell's fixed top bar holds the window's top,
169
+ so the stuck bar stops under it instead of sliding beneath it. */
170
+ html:not([data-fullscreen="on"]) .tw-bar { top: calc(var(--topbar-h, 56px) + 12px); }
171
+ .tw-stage .tw-back {
172
+ display: inline-flex;
173
+ align-items: center;
174
+ min-height: 44px;
175
+ font-size: 13.5px;
176
+ text-decoration: none;
177
+ color: var(--tw-ink-soft);
178
+ white-space: nowrap;
179
+ }
180
+ .tw-name { font-size: 15px; color: var(--tw-ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
181
+ .tw-surface { color: var(--tw-ink-dim); }
182
+ .tw-counter {
183
+ margin-left: auto;
184
+ font-size: 13px;
185
+ color: var(--tw-ink-faint);
186
+ font-variant-numeric: tabular-nums;
187
+ white-space: nowrap;
188
+ }
189
+ .tw-actions { display: flex; align-items: center; gap: 14px; }
190
+ .tw-stage .tw-btn {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ height: 44px;
194
+ padding: 0 16px;
195
+ border-radius: 999px;
196
+ border: 0;
197
+ cursor: pointer;
198
+ font-size: 13.5px;
199
+ text-decoration: none;
200
+ white-space: nowrap;
201
+ color: var(--tw-ink);
202
+ background: var(--tw-glass);
203
+ }
204
+ .tw-stage .tw-btn:hover { color: var(--tw-ink); background: var(--tw-glass-lift); }
205
+ .tw-stage .tw-btn--submit {
206
+ padding: 0 20px;
207
+ font-size: 14px;
208
+ color: var(--tw-on-accent);
209
+ background: var(--tw-accent);
210
+ }
211
+ .tw-stage .tw-btn--submit:hover { color: var(--tw-on-accent); background: var(--tw-link); }
212
+ .tw-stage .tw-btn:disabled { cursor: default; opacity: .5; }
213
+ .tw-stage .tw-btn:focus-visible,
214
+ .tw-stage .tw-back:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 2px; }
215
+
216
+ /* THE PAPER. Every word of the page, in the order it shows them. It grows
217
+ with the page and the window scrolls (the hall's one-scroller seam), where
218
+ the artboard's fixed 860px sheet scrolled inside itself. */
219
+ .tw-paper {
220
+ position: relative;
221
+ z-index: 1;
222
+ width: min(860px, calc(100% - 48px));
223
+ margin: 24px auto 0;
224
+ box-sizing: border-box;
225
+ border-radius: 8px;
226
+ background: var(--tw-paper);
227
+ color: var(--tw-paper-ink);
228
+ box-shadow: 0 30px 80px var(--tw-drop);
229
+ padding: 64px 88px 72px 88px;
230
+ }
231
+ .tw-head { display: flex; flex-direction: column; gap: 6px; margin-bottom: 34px; }
232
+ .tw-paper h1 {
233
+ margin: 0;
234
+ font-family: var(--tw-face);
235
+ font-size: 40px;
236
+ font-weight: 300;
237
+ line-height: 1.2;
238
+ letter-spacing: -0.03em;
239
+ color: var(--tw-paper-ink);
240
+ }
241
+ .tw-intro { margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--tw-paper-soft); }
242
+ .tw-section { margin-bottom: 26px; }
243
+ .tw-paper h2 {
244
+ margin: 0 0 6px 0;
245
+ font-family: var(--tw-mono);
246
+ font-size: 11px;
247
+ font-weight: 400;
248
+ line-height: 1.4;
249
+ letter-spacing: 0.14em;
250
+ text-transform: uppercase;
251
+ color: var(--tw-paper-faint);
252
+ }
253
+ .tw-row { display: grid; grid-template-columns: minmax(0, 1fr) 150px; gap: 18px; align-items: start; }
254
+ .tw-line {
255
+ display: block;
256
+ box-sizing: border-box;
257
+ width: 100%;
258
+ resize: none;
259
+ overflow: hidden;
260
+ border: 0;
261
+ border-radius: 4px;
262
+ padding: 5px 10px;
263
+ margin: 0 0 0 -10px;
264
+ font-size: 17px;
265
+ line-height: 1.6;
266
+ color: var(--tw-paper-ink);
267
+ background: transparent;
268
+ }
269
+ .tw-line:hover { background: var(--tw-line-hover); }
270
+ .tw-line:focus { outline: none; background: var(--tw-line-focus); }
271
+ /* The artboard's focus is the warm wash alone; the hall's focus floor wants a
272
+ ring a keyboard reader can find, so a hairline of the accent joins it. */
273
+ .tw-line:focus-visible { outline: 1px solid var(--tw-accent); outline-offset: -1px; background: var(--tw-line-focus); }
274
+ /* The accent mark: a changed line carries a small bar of the accent. */
275
+ .tw-line--changed { box-shadow: inset 3px 0 0 var(--tw-accent); }
276
+ .tw-line[readonly] { cursor: default; }
277
+ .tw-line[readonly]:hover { background: transparent; }
278
+ .tw-note { padding-top: 9px; font-size: 12px; line-height: 1.4; color: var(--tw-paper-faint); overflow-wrap: anywhere; }
279
+ .tw-note--shared { color: var(--tw-shared); }
280
+ .tw-note--engineer { color: var(--tw-engineer); }
281
+ .tw-sr {
282
+ position: absolute;
283
+ width: 1px;
284
+ height: 1px;
285
+ overflow: hidden;
286
+ clip: rect(0 0 0 0);
287
+ white-space: nowrap;
288
+ }
289
+ .tw-loading { margin: 0; font-size: 15px; line-height: 1.6; color: var(--tw-paper-soft); }
290
+
291
+ /* A notice inside the paper, above the words: the page is someone else's, the
292
+ upload was refused, the submit named lines. Not on the artboard, which has
293
+ no failing state; it wears the paper's own ink and the engineer note's warm
294
+ brown, and its one action is a paper pill. */
295
+ .tw-notice {
296
+ margin: 0 0 30px 0;
297
+ padding: 16px 18px;
298
+ border-radius: 8px;
299
+ background: var(--tw-line-hover);
300
+ box-shadow: inset 3px 0 0 var(--tw-engineer);
301
+ color: var(--tw-paper-ink);
302
+ font-size: 14.5px;
303
+ line-height: 1.5;
304
+ }
305
+ .tw-notice p { margin: 0; }
306
+ .tw-notice ul { margin: 8px 0 0 0; padding-left: 18px; }
307
+ .tw-notice li { margin: 2px 0; }
308
+ .tw-notice__act {
309
+ margin-top: 12px;
310
+ height: 44px;
311
+ padding: 0 20px;
312
+ border-radius: 999px;
313
+ border: 0;
314
+ cursor: pointer;
315
+ font-size: 14px;
316
+ color: var(--tw-on-accent);
317
+ background: var(--tw-accent);
318
+ }
319
+ .tw-notice__act:focus-visible { outline: 2px solid var(--tw-paper-ink); outline-offset: 2px; }
320
+
321
+ /* THE "IS IN" PANEL. Glass over the dimmed room, the next page named, and
322
+ three ways on. Fixed, so it sits over the whole window. The artboard's
323
+ scrim is .45, but the panel sits over the white PAPER, and its white and
324
+ grey words on paper seen through .45 fall under AA (2.9:1). At .88 the paper
325
+ dims to the room's own dark and every word in the panel clears 4.5:1. */
326
+ .tw-done {
327
+ position: fixed;
328
+ inset: 0;
329
+ z-index: var(--z-modal, 160);
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ padding: 16px;
334
+ background: var(--tw-scrim);
335
+ }
336
+ .tw-done[hidden] { display: none; }
337
+ .tw-done__panel {
338
+ width: 560px;
339
+ max-width: 100%;
340
+ box-sizing: border-box;
341
+ padding: 36px;
342
+ border-radius: 30px;
343
+ background: var(--tw-panel);
344
+ border: 1px solid var(--tw-panel-edge);
345
+ backdrop-filter: blur(30px) saturate(160%);
346
+ -webkit-backdrop-filter: blur(30px) saturate(160%);
347
+ box-shadow: inset 0 1px 0 var(--tw-panel-rim), 0 40px 90px var(--tw-drop);
348
+ display: flex;
349
+ flex-direction: column;
350
+ gap: 22px;
351
+ }
352
+ .tw-done__head { display: flex; flex-direction: column; gap: 8px; }
353
+ .tw-done__title { margin: 0; font-size: 30px; font-weight: 200; line-height: 1.2; letter-spacing: -0.03em; color: var(--tw-ink); }
354
+ .tw-done__title:focus { outline: none; }
355
+ .tw-done__title:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 4px; }
356
+ .tw-done__sub { margin: 0; font-size: 15px; line-height: 1.5; color: var(--tw-ink-soft); }
357
+ .tw-next { display: flex; flex-direction: column; gap: 4px; padding-top: 18px; border-top: 1px solid var(--tw-panel-rule); }
358
+ .tw-next__label { font-family: var(--tw-mono); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tw-ink-faint); }
359
+ .tw-next__title { font-size: 24px; font-weight: 300; line-height: 1.3; color: var(--tw-ink); }
360
+ .tw-next__why { font-size: 14px; line-height: 1.5; color: var(--tw-ink-soft); }
361
+ .tw-picks { display: flex; flex-wrap: wrap; gap: 8px; }
362
+ .tw-pick {
363
+ height: 44px;
364
+ padding: 0 16px;
365
+ border-radius: 999px;
366
+ cursor: pointer;
367
+ background: var(--tw-glass);
368
+ border: 1px solid var(--tw-glass-edge);
369
+ color: var(--tw-ink);
370
+ font-size: 13.5px;
371
+ }
372
+ .tw-pick:hover { background: var(--tw-glass-lift); }
373
+ .tw-done__acts { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
374
+ .tw-take {
375
+ height: 48px;
376
+ padding: 0 26px;
377
+ border-radius: 999px;
378
+ cursor: pointer;
379
+ border: 0;
380
+ background: var(--tw-accent);
381
+ color: var(--tw-on-accent);
382
+ font-size: 15px;
383
+ }
384
+ .tw-take:hover { background: var(--tw-link); }
385
+ .tw-take:disabled { cursor: default; opacity: .5; }
386
+ /* "pick another" is the artboard's link, drawn as a button because it opens
387
+ the other pages in place (the studio artboard's own "pick another"). */
388
+ .tw-stage .tw-linkish {
389
+ display: inline-flex;
390
+ align-items: center;
391
+ min-height: 44px;
392
+ padding: 0;
393
+ border: 0;
394
+ background: none;
395
+ cursor: pointer;
396
+ font-size: 13.5px;
397
+ color: var(--tw-ink-faint);
398
+ text-decoration: underline;
399
+ }
400
+ .tw-stage .tw-linkish:hover { color: var(--tw-accent); }
401
+ .tw-stage .tw-home { margin-left: auto; }
402
+ .tw-take:focus-visible,
403
+ .tw-pick:focus-visible,
404
+ .tw-stage .tw-linkish:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 2px; }
405
+ .tw-done__error { margin: 0; font-size: 14px; line-height: 1.5; color: var(--tw-link); }
406
+
407
+ /* PHONE WIDTH (not on the artboard, which is 1440 wide). The bar stops being
408
+ one pill and becomes a glass card of two rows: where you are and how far,
409
+ then the four doors, wrapping. The paper takes the width, and each margin
410
+ note drops under its line. */
411
+ @media (max-width: 760px) {
412
+ .tw-stage { padding-top: 12px; }
413
+ .tw-bar,
414
+ html[data-fullscreen="on"] .tw-bar {
415
+ top: 8px;
416
+ width: calc(100% - 24px);
417
+ flex-wrap: wrap;
418
+ gap: 4px 12px;
419
+ padding: 6px 12px 12px 16px;
420
+ border-radius: 24px;
421
+ }
422
+ /* The shell's button floats in the top right corner in full screen. */
423
+ html[data-fullscreen="on"] .tw-bar { padding-right: 56px; }
424
+ .tw-name { flex: 1 1 0; }
425
+ .tw-counter { flex: 1 0 100%; margin-left: 0; }
426
+ .tw-actions { flex: 1 0 100%; flex-wrap: wrap; gap: 8px; padding-top: 6px; }
427
+ html[data-fullscreen="on"] .tw-actions { margin-right: -44px; }
428
+ .tw-paper { width: calc(100% - 24px); margin-top: 16px; padding: 36px 20px 44px; }
429
+ .tw-paper h1 { font-size: 32px; }
430
+ .tw-row { grid-template-columns: minmax(0, 1fr); gap: 0; }
431
+ .tw-note { padding: 0 0 8px 0; }
432
+ .tw-note:empty { display: none; }
433
+ .tw-done__panel { padding: 28px 24px; border-radius: 24px; }
434
+ .tw-done__title { font-size: 26px; }
435
+ .tw-stage .tw-home { margin-left: 0; }
436
+ }
437
+ @media (max-width: 768px) { .tw-room { left: 0; } }
@@ -0,0 +1,111 @@
1
+ <!doctype html>
2
+ <!-- data-artist="on": the studio's mode marker (studio.html). The editor is
3
+ the artist's own room too, entered from the studio. -->
4
+ <html lang="en" data-artist="on">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <title>Tweak a page — {{worldName}}</title>
9
+ <meta name="description" content="Tweak Mode's editor: every word of one page as a single document, typed over in place and saved as you go, then submitted as one batch.">
10
+ <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
14
+ <link rel="stylesheet" href="/builders/tweak-editor.css?v=2026-09-28-tw10">
15
+ </head>
16
+ <!-- THE TWEAK EDITOR (task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and
17
+ D11). docs/design/mocks/tweak-mode/Editor.dc.html is the master design:
18
+ the glass top bar, one paper document holding every word of the page in
19
+ page order, typed over in place and autosaved, the margin notes, the
20
+ accent mark and "was:" on a changed line, and the "is in" panel with take
21
+ next, pick another and back to the studio. Every difference a gate or a
22
+ route forced is listed in modules/hall-ui/records/tweak-editor.md.
23
+
24
+ Which page: ?page=<page id> (landing:index). With none, the page the
25
+ caller holds (GET /copy-desk/next's resume). The reads and writes are
26
+ copy-desk's: GET and PUT /copy-desk/pages/:pageId/draft, the .docx pair,
27
+ the submit, GET /copy-desk/next and the claim "take next" posts.
28
+
29
+ data-fullscreen-switch: the shell's full-screen switch (task 1004327). The
30
+ artboard is the editor over the whole window, so the editor opens full
31
+ screen the first time (tweak-editor.js), and the switch is remembered. -->
32
+ <body data-page="tweak-editor" data-page-title="Tweak a page" data-fullscreen-switch>
33
+
34
+ <div class="app">
35
+ <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
36
+ <div class="app-scrim" id="app-scrim" hidden></div>
37
+ <div class="app-body">
38
+ <header class="app-topbar" id="app-topbar"></header>
39
+ <div class="app-content">
40
+
41
+ <main id="hall-main" class="tw-stage">
42
+ <!-- The studio at night, dimmed behind the paper. -->
43
+ <div class="tw-room" aria-hidden="true">
44
+ <div class="tw-window"><div class="tw-moon"></div></div>
45
+ <div class="tw-floor"></div>
46
+ <div class="tw-lamp"></div>
47
+ </div>
48
+
49
+ <!-- A <div>, not a <header>: the shell's top bar is the page's one
50
+ <header> (the kit's one-header-main-h1 floor). -->
51
+ <div class="tw-bar" id="tw-bar">
52
+ <a class="tw-back" href="/builders/studio">← studio</a>
53
+ <span class="tw-name"><span id="tw-name">Tweak a page</span> <span class="tw-surface" id="tw-surface"></span></span>
54
+ <span class="tw-counter" id="tw-counter" aria-live="polite"></span>
55
+ <div class="tw-actions">
56
+ <a class="tw-btn" id="tw-open" href="/builders/studio" target="_blank" rel="noopener">open the page ↗</a>
57
+ <button class="tw-btn" id="tw-download" type="button" disabled>download .docx</button>
58
+ <button class="tw-btn" id="tw-upload" type="button" disabled>upload .docx</button>
59
+ <input id="tw-file" type="file" accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document" hidden>
60
+ <button class="tw-btn tw-btn--submit" id="tw-submit" type="button" disabled>submit page</button>
61
+ </div>
62
+ </div>
63
+
64
+ <article class="tw-paper" id="tw-paper" aria-labelledby="tw-title">
65
+ <div class="tw-head">
66
+ <h1 id="tw-title">Tweak a page</h1>
67
+ <p class="tw-intro">Every word on the page, in the order it shows them. Type over anything, or download it, work in Word, and upload it back.</p>
68
+ </div>
69
+ <div class="tw-notice" id="tw-notice" role="status" hidden></div>
70
+ <div id="tw-doc">
71
+ <p class="tw-loading">Reading the page…</p>
72
+ </div>
73
+ </article>
74
+
75
+ <!-- Submit: "<page> is in", then next up. Hidden until the submit lands. -->
76
+ <div class="tw-done" id="tw-done" hidden>
77
+ <div class="tw-done__panel" role="dialog" aria-modal="true" aria-labelledby="tw-done-title">
78
+ <div class="tw-done__head">
79
+ <h2 class="tw-done__title" id="tw-done-title" tabindex="-1">The page is in.</h2>
80
+ <p class="tw-done__sub">It comes back to your studio once it is applied, and nothing ships until you have seen it.</p>
81
+ </div>
82
+ <div class="tw-next" id="tw-next">
83
+ <span class="tw-next__label">next up</span>
84
+ <span class="tw-next__title" id="tw-next-title">Finding the next page…</span>
85
+ <span class="tw-next__why" id="tw-next-why"></span>
86
+ </div>
87
+ <p class="tw-done__error" id="tw-done-error" role="alert" hidden></p>
88
+ <div class="tw-done__acts">
89
+ <button class="tw-take" id="tw-take" type="button" disabled>take next</button>
90
+ <button class="tw-linkish" id="tw-pick" type="button" aria-expanded="false" aria-controls="tw-picks" hidden>pick another</button>
91
+ <a class="tw-linkish tw-home" href="/builders/studio">back to the studio</a>
92
+ </div>
93
+ <div class="tw-picks" id="tw-picks" hidden></div>
94
+ </div>
95
+ </div>
96
+ </main>
97
+
98
+ <footer class="app-foot" id="app-foot"></footer>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
104
+
105
+ <script src="/builders/shell.js?v=2026-09-27-fullscreen"></script>
106
+ <script src="/builders/palette.js" defer></script>
107
+ <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
108
+ <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw10"></script>
109
+ <script src="/builders/tweak-editor.js?v=2026-09-28-tw10"></script>
110
+ </body>
111
+ </html>