@bongos/core 1.21.97 → 1.21.98
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 +39 -29
- package/docs/adr/0152-metic-task-abandonment.md +2 -0
- package/docs/api/openapi.json +2 -2
- package/docs/architecture.md +3 -0
- package/docs/copy-inventory.md +32 -30
- package/docs/copy-registry.json +51 -33
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +10 -10
- package/migrations/core_274_parked_backlog_stays_parked.sql +140 -0
- package/modules/hall-ui/public/studio-lib.js +22 -0
- package/modules/hall-ui/public/studio.css +138 -7
- package/modules/hall-ui/public/studio.html +10 -4
- package/modules/hall-ui/public/studio.js +39 -1
- package/modules/hall-ui/records/studio.md +16 -0
- package/modules/lifecycle/db-ship.js +7 -2
- package/modules/lifecycle/db-tasks.js +25 -0
- package/modules/lifecycle/routes/tasks.js +5 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +10 -0
- package/src/module-api.js +1 -1
- package/tests/abandon_task.mjs +1 -1
- package/tests/hall_studio_home.mjs +140 -5
- package/tests/parked_backlog_stays_parked.mjs +182 -0
- package/tests/promotion_triggers_skip_cascade.mjs +5 -3
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
shell's full-screen button while it floats over the room. The hall chrome
|
|
18
18
|
around the stage keeps its own light and dark.
|
|
19
19
|
|
|
20
|
+
ONE ROOM, TWO SKIES (task 1004654). By the viewer's own clock the room is
|
|
21
|
+
the night room or the day room of docs/design/mocks/day-night-rooms (day
|
|
22
|
+
from 07:00 to 18:59, re-checked each minute; studio.js sets data-sky on the
|
|
23
|
+
body). The day palette is the mockup's [data-sky="day"] block, declared just
|
|
24
|
+
below the night tokens it replaces; the same drawing, with clouds and a sun
|
|
25
|
+
in the window, lighter towers with unlit panes, a warm wall and floor, the
|
|
26
|
+
lamp off and a patch of sun on the floor. The glass panels over the room
|
|
27
|
+
follow its sky; the hall chrome still keeps its own light and dark.
|
|
28
|
+
|
|
20
29
|
The second half is the older reviews' garden (R06 of goal 1000074, task
|
|
21
30
|
1003117), which still answers the reviews filed before Tweak Mode (TW14) in
|
|
22
31
|
its own quiet dialog, in the hall's own tokens as it always was. */
|
|
@@ -52,6 +61,9 @@ body[data-page="studio"] {
|
|
|
52
61
|
--st-accent: #fb9569;
|
|
53
62
|
--st-on-accent: #000000;
|
|
54
63
|
--st-link: #fcbfa4;
|
|
64
|
+
--st-pane: #fb9569;
|
|
65
|
+
--st-credits: #fb9569;
|
|
66
|
+
--st-focus: #fb9569;
|
|
55
67
|
--st-resume: rgba(251, 149, 105, .16);
|
|
56
68
|
--st-resume-edge: rgba(251, 149, 105, .55);
|
|
57
69
|
--st-word-hover: rgba(255, 255, 255, .28);
|
|
@@ -69,6 +81,86 @@ body[data-page="studio"] {
|
|
|
69
81
|
--st-mono: var(--font-mono, "JetBrains Mono", ui-monospace, Consolas, monospace);
|
|
70
82
|
}
|
|
71
83
|
|
|
84
|
+
/* The day room: the mockup's [data-sky="day"] tokens, on the same names. Three
|
|
85
|
+
inks are set darker than the mockup's so they hold AA on the glass over a
|
|
86
|
+
light wall (records/studio.md): the link, and the credits figure and focus
|
|
87
|
+
ring, which by night are the accent. */
|
|
88
|
+
body[data-page="studio"][data-sky="day"] {
|
|
89
|
+
--st-room: #efe7dc;
|
|
90
|
+
--st-frame: #d6c9b7;
|
|
91
|
+
--st-night: #bfdaf0;
|
|
92
|
+
--st-moon: #ffd46e;
|
|
93
|
+
--st-moon-halo: rgba(255, 196, 90, .5);
|
|
94
|
+
--st-star: #ffffff;
|
|
95
|
+
--st-tower-1: #9db2c7;
|
|
96
|
+
--st-tower-2: #8ea4bb;
|
|
97
|
+
--st-tower-3: #a8bccf;
|
|
98
|
+
--st-pane: #e9f1f8;
|
|
99
|
+
--st-floor: #e2d5c3;
|
|
100
|
+
--st-floor-edge: rgba(60, 40, 20, .08);
|
|
101
|
+
--st-glow: rgba(255, 222, 160, .55);
|
|
102
|
+
--st-glow-out: rgba(255, 222, 160, 0);
|
|
103
|
+
--st-stem: #c4b4a0;
|
|
104
|
+
--st-shade: #d5c5af;
|
|
105
|
+
--st-mug: #cbbba8;
|
|
106
|
+
--st-leaf-1: #6f9a6a;
|
|
107
|
+
--st-leaf-2: #5d8a59;
|
|
108
|
+
--st-ink: #1b1a1d;
|
|
109
|
+
--st-ink-sub: #3f3e44;
|
|
110
|
+
--st-ink-soft: #4d4c52;
|
|
111
|
+
--st-ink-faint: #5a595f;
|
|
112
|
+
--st-ink-dim: #66656b;
|
|
113
|
+
--st-ink-hint: #66656b;
|
|
114
|
+
--st-glass: rgba(255, 255, 255, .52);
|
|
115
|
+
--st-glass-edge: rgba(60, 40, 20, .16);
|
|
116
|
+
--st-rule: rgba(60, 40, 20, .14);
|
|
117
|
+
--st-link: #96410f;
|
|
118
|
+
--st-credits: #96410f;
|
|
119
|
+
--st-focus: #96410f;
|
|
120
|
+
--st-resume: rgba(251, 149, 105, .22);
|
|
121
|
+
--st-resume-edge: rgba(190, 85, 35, .6);
|
|
122
|
+
--st-word-hover: rgba(60, 40, 20, .28);
|
|
123
|
+
--st-word-ring: rgba(190, 85, 35, .6);
|
|
124
|
+
--st-panel: rgba(255, 255, 255, .75);
|
|
125
|
+
--st-panel-edge: rgba(60, 40, 20, .16);
|
|
126
|
+
--st-panel-rim: rgba(255, 255, 255, .9);
|
|
127
|
+
--st-panel-drop: rgba(60, 40, 20, .18);
|
|
128
|
+
--st-close: rgba(60, 40, 20, .08);
|
|
129
|
+
--st-chip-edge: rgba(60, 40, 20, .2);
|
|
130
|
+
--st-track: rgba(60, 40, 20, .12);
|
|
131
|
+
--st-danger: #bb1f1f;
|
|
132
|
+
}
|
|
133
|
+
/* The panels' scrim is the day wall's own tint, whatever the hall's theme, set
|
|
134
|
+
on the overlay so it outranks the theme's .42 and the phone's .88. */
|
|
135
|
+
body[data-page="studio"][data-sky="day"] .st-overlay {
|
|
136
|
+
--st-scrim: rgba(239, 231, 220, .9);
|
|
137
|
+
color-scheme: light;
|
|
138
|
+
}
|
|
139
|
+
body[data-page="studio"][data-sky="day"] .studio { color-scheme: light; }
|
|
140
|
+
/* The Approval queue (approval-queue.css) is night glass on its own root; over
|
|
141
|
+
the day room it wears the same day glass as the studio's own panels. Its
|
|
142
|
+
rendered page stays on its paper frame, and its accent button stays the
|
|
143
|
+
accent, which carries black words at AA in either sky. */
|
|
144
|
+
body[data-page="studio"][data-sky="day"] .aq-overlay {
|
|
145
|
+
color-scheme: light;
|
|
146
|
+
--aq-scrim: rgba(239, 231, 220, .9);
|
|
147
|
+
--aq-glass: rgba(255, 255, 255, .75);
|
|
148
|
+
--aq-glass-edge: rgba(60, 40, 20, .16);
|
|
149
|
+
--aq-glass-rim: rgba(255, 255, 255, .9);
|
|
150
|
+
--aq-drop: rgba(60, 40, 20, .18);
|
|
151
|
+
--aq-ink: #1b1a1d;
|
|
152
|
+
--aq-ink-soft: #4d4c52;
|
|
153
|
+
--aq-ink-faint: #5a595f;
|
|
154
|
+
--aq-ink-dim: #66656b;
|
|
155
|
+
--aq-well: rgba(60, 40, 20, .07);
|
|
156
|
+
--aq-pill-on: rgba(255, 255, 255, .9);
|
|
157
|
+
--aq-close: rgba(60, 40, 20, .08);
|
|
158
|
+
--aq-ghost-edge: rgba(60, 40, 20, .3);
|
|
159
|
+
--aq-link: #96410f;
|
|
160
|
+
--aq-rule: rgba(60, 40, 20, .12);
|
|
161
|
+
}
|
|
162
|
+
body[data-page="studio"][data-sky="day"] .aq-overlay :focus-visible { outline-color: var(--st-focus); }
|
|
163
|
+
|
|
72
164
|
/* The stage fills the content box: the hall's content column is a reading
|
|
73
165
|
width, and the artboard is the whole window. */
|
|
74
166
|
body[data-page="studio"] .app-content { max-width: none; padding: 0; }
|
|
@@ -87,7 +179,7 @@ body[data-page="studio"] .app-foot { display: none; }
|
|
|
87
179
|
}
|
|
88
180
|
html[data-fullscreen="on"] .studio { min-height: 100vh; }
|
|
89
181
|
.studio a { color: var(--st-link); }
|
|
90
|
-
.studio a:hover { color: var(--st-
|
|
182
|
+
.studio a:hover { color: var(--st-focus); }
|
|
91
183
|
.studio button { font-family: inherit; }
|
|
92
184
|
|
|
93
185
|
/* In full screen the shell's one button floats over the night room in either
|
|
@@ -140,13 +232,23 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
140
232
|
.st-star--3 { left: 260px; top: 120px; width: 3px; height: 3px; opacity: .7; }
|
|
141
233
|
.st-star--4 { left: 470px; top: 170px; width: 2px; height: 2px; }
|
|
142
234
|
.st-star--5 { left: 130px; top: 190px; width: 2px; height: 2px; opacity: .6; }
|
|
235
|
+
/* The clouds belong to the day room; at night they are not there. */
|
|
236
|
+
.st-cloud { border-radius: 40px; background: var(--st-star); opacity: 0; }
|
|
237
|
+
.st-cloud::before,
|
|
238
|
+
.st-cloud::after { content: ""; position: absolute; background: inherit; border-radius: 50%; }
|
|
239
|
+
.st-cloud--1 { left: 44px; top: 76px; width: 118px; height: 28px; }
|
|
240
|
+
.st-cloud--1::before { left: 24px; top: -17px; width: 47px; height: 41px; }
|
|
241
|
+
.st-cloud--1::after { left: 58px; top: -11px; width: 37px; height: 32px; }
|
|
242
|
+
.st-cloud--2 { left: 248px; top: 150px; width: 86px; height: 22px; }
|
|
243
|
+
.st-cloud--2::before { left: 17px; top: -13px; width: 34px; height: 30px; }
|
|
244
|
+
.st-cloud--2::after { left: 43px; top: -9px; width: 28px; height: 24px; }
|
|
143
245
|
.st-tower { bottom: 0; }
|
|
144
246
|
.st-tower--1 { left: 0; width: 120px; height: 230px; background: var(--st-tower-1); }
|
|
145
247
|
.st-tower--2 { left: 110px; width: 90px; height: 310px; background: var(--st-tower-2); }
|
|
146
248
|
.st-tower--3 { left: 200px; width: 140px; height: 200px; background: var(--st-tower-3); }
|
|
147
249
|
.st-tower--4 { left: 330px; width: 80px; height: 270px; background: var(--st-tower-2); }
|
|
148
250
|
.st-tower--5 { left: 400px; width: 140px; height: 180px; background: var(--st-tower-1); }
|
|
149
|
-
.st-light { width: 6px; height: 8px; background: var(--st-
|
|
251
|
+
.st-light { width: 6px; height: 8px; background: var(--st-pane); }
|
|
150
252
|
.st-light--1 { left: 136px; bottom: 250px; opacity: .75; }
|
|
151
253
|
.st-light--2 { left: 160px; bottom: 210px; opacity: .55; }
|
|
152
254
|
.st-light--3 { left: 30px; bottom: 150px; opacity: .6; }
|
|
@@ -161,6 +263,14 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
161
263
|
background: var(--st-floor);
|
|
162
264
|
border-top: 1px solid var(--st-floor-edge);
|
|
163
265
|
}
|
|
266
|
+
/* By day the lamp is off and a patch of sun lies on the floor instead. */
|
|
267
|
+
.st-sunpatch {
|
|
268
|
+
left: calc(50% + 140px); top: calc(712px + var(--room-drop)); width: 520px; height: 120px;
|
|
269
|
+
opacity: 0;
|
|
270
|
+
background: var(--st-glow);
|
|
271
|
+
transform: skewX(-28deg);
|
|
272
|
+
filter: blur(10px);
|
|
273
|
+
}
|
|
164
274
|
.st-glow {
|
|
165
275
|
left: calc(50% + 440px); top: calc(470px + var(--room-drop)); width: 300px; height: 300px;
|
|
166
276
|
border-radius: 50%;
|
|
@@ -173,6 +283,26 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
173
283
|
.st-leaf--1 { left: calc(50% + 180px); top: calc(610px + var(--room-drop)); width: 64px; height: 90px; background: var(--st-leaf-1); }
|
|
174
284
|
.st-leaf--2 { left: calc(50% + 220px); top: calc(590px + var(--room-drop)); width: 50px; height: 110px; background: var(--st-leaf-2); }
|
|
175
285
|
|
|
286
|
+
/* The day room: the sun rides higher and larger than the moon, the stars and
|
|
287
|
+
the lamp's glow are gone, the clouds and the patch of sun are there. */
|
|
288
|
+
body[data-sky="day"] .st-moon { left: 394px; top: 42px; width: 76px; height: 76px; box-shadow: 0 0 80px 26px var(--st-moon-halo); }
|
|
289
|
+
body[data-sky="day"] .st-star,
|
|
290
|
+
body[data-sky="day"] .st-glow { opacity: 0; }
|
|
291
|
+
body[data-sky="day"] .st-cloud { opacity: .9; }
|
|
292
|
+
body[data-sky="day"] .st-sunpatch { opacity: .7; }
|
|
293
|
+
|
|
294
|
+
/* The room's only motion: the clouds drift, and a change of sky fades over
|
|
295
|
+
.8s. Both live only without a reduced-motion preference, and style.css's
|
|
296
|
+
kill switch stops them besides. */
|
|
297
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
298
|
+
.st-cloud { animation: st-drift 60s linear infinite; }
|
|
299
|
+
.st-cloud--2 { animation-delay: -25s; }
|
|
300
|
+
.studio,
|
|
301
|
+
.st-room > *,
|
|
302
|
+
.st-window > * { transition: background-color .8s ease, opacity .8s ease, box-shadow .8s ease; }
|
|
303
|
+
@keyframes st-drift { from { transform: translateX(-30px); } to { transform: translateX(60px); } }
|
|
304
|
+
}
|
|
305
|
+
|
|
176
306
|
/* Everything the artist reads or presses sits above the room. */
|
|
177
307
|
.st-top,
|
|
178
308
|
.st-words,
|
|
@@ -206,7 +336,7 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
206
336
|
.st-tally[hidden] { display: none; }
|
|
207
337
|
.st-tally__item { display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
|
|
208
338
|
.st-tally__n { font-size: 20px; font-weight: 300; color: var(--st-ink); font-variant-numeric: tabular-nums; }
|
|
209
|
-
.st-tally__n--credits { color: var(--st-
|
|
339
|
+
.st-tally__n--credits { color: var(--st-credits); }
|
|
210
340
|
.st-tally__label { font-size: 13px; color: var(--st-ink-soft); }
|
|
211
341
|
.st-tally__rule { width: 1px; height: 20px; background: var(--st-rule); }
|
|
212
342
|
|
|
@@ -239,7 +369,7 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
239
369
|
}
|
|
240
370
|
.st-words [data-words] { cursor: text; border-radius: 2px; }
|
|
241
371
|
.st-words [data-words]:hover { box-shadow: 0 1px 0 var(--st-word-hover); }
|
|
242
|
-
.st-words [data-words][contenteditable="true"]:focus { box-shadow: 0 1px 0 var(--st-
|
|
372
|
+
.st-words [data-words][contenteditable="true"]:focus { box-shadow: 0 1px 0 var(--st-focus); }
|
|
243
373
|
.st-words [data-words][contenteditable="true"]:focus-visible {
|
|
244
374
|
outline: 1px solid var(--st-word-ring);
|
|
245
375
|
outline-offset: 6px;
|
|
@@ -356,7 +486,7 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
356
486
|
.st-resume:focus-visible,
|
|
357
487
|
.st-action:focus-visible,
|
|
358
488
|
.st-hint:focus-visible,
|
|
359
|
-
.st-older:focus-visible { outline: 2px solid var(--st-
|
|
489
|
+
.st-older:focus-visible { outline: 2px solid var(--st-focus); outline-offset: 3px; }
|
|
360
490
|
|
|
361
491
|
/* The older reviews' door: a quiet line under the actions, not a fourth row. */
|
|
362
492
|
.st-older {
|
|
@@ -421,7 +551,7 @@ html.st-open { overflow: hidden; }
|
|
|
421
551
|
gap: 22px;
|
|
422
552
|
}
|
|
423
553
|
.st-overlay button { font-family: inherit; }
|
|
424
|
-
.st-overlay :focus-visible { outline: 2px solid var(--st-
|
|
554
|
+
.st-overlay :focus-visible { outline: 2px solid var(--st-focus); outline-offset: 2px; }
|
|
425
555
|
.st-dialog__head { display: flex; align-items: center; gap: 12px; }
|
|
426
556
|
.st-dialog__title {
|
|
427
557
|
margin: 0;
|
|
@@ -510,7 +640,7 @@ html.st-open { overflow: hidden; }
|
|
|
510
640
|
color: var(--st-ink); text-decoration: none;
|
|
511
641
|
}
|
|
512
642
|
.st-site:hover { background: var(--st-glass); }
|
|
513
|
-
.st-site:focus-visible { outline: 2px solid var(--st-
|
|
643
|
+
.st-site:focus-visible { outline: 2px solid var(--st-focus); outline-offset: 1px; }
|
|
514
644
|
.st-site__head { display: flex; align-items: baseline; gap: 4px 12px; flex-wrap: wrap; }
|
|
515
645
|
.st-site__page { font-size: 17px; font-weight: 300; }
|
|
516
646
|
.st-site__meta { font-size: 13px; color: var(--st-ink-dim); }
|
|
@@ -551,6 +681,7 @@ html.st-open { overflow: hidden; }
|
|
|
551
681
|
.st-actions { margin-top: 36px; width: 100%; }
|
|
552
682
|
.st-floor { top: auto; bottom: 0; min-height: 0; height: 96px; }
|
|
553
683
|
.st-glow { left: auto; right: -110px; top: auto; bottom: -20px; }
|
|
684
|
+
.st-sunpatch { left: auto; right: 40px; top: auto; bottom: 10px; width: 300px; height: 70px; }
|
|
554
685
|
.st-lamp-stem { left: auto; right: 44px; top: auto; bottom: 96px; height: 100px; }
|
|
555
686
|
.st-lamp-shade { left: auto; right: 0; top: auto; bottom: 170px; }
|
|
556
687
|
.st-mug { left: auto; right: 116px; top: auto; bottom: 96px; }
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
15
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
16
|
-
<link rel="stylesheet" href="/builders/studio.css?v=2026-09-
|
|
16
|
+
<link rel="stylesheet" href="/builders/studio.css?v=2026-10-09-day">
|
|
17
17
|
<link rel="stylesheet" href="/builders/approval-queue.css?v=2026-09-29-aqfix">
|
|
18
18
|
</head>
|
|
19
19
|
<!-- THE STUDIO HOME (task 1003971 / BV2.TW16, goal 1000095; ADR 0341 D2, D5,
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
<!-- data-apex: the instance's own address, stamped by the server from the
|
|
41
41
|
branding pack (the landing surface's line in Artist Review Status). -->
|
|
42
42
|
<main id="hall-main" class="studio" data-apex="{{publicOrigin}}">
|
|
43
|
-
<!-- The
|
|
43
|
+
<!-- The room. Drawn, not an image: it is the artboard's own shapes. By the
|
|
44
|
+
viewer's clock it is the night room or the day room (task 1004654,
|
|
45
|
+
docs/design/mocks/day-night-rooms): studio.js sets data-sky on the
|
|
46
|
+
body, and the clouds and the patch of sun are only seen by day. -->
|
|
44
47
|
<div class="st-room" aria-hidden="true">
|
|
45
48
|
<div class="st-window">
|
|
46
49
|
<span class="st-moon"></span>
|
|
@@ -49,6 +52,8 @@
|
|
|
49
52
|
<span class="st-star st-star--3"></span>
|
|
50
53
|
<span class="st-star st-star--4"></span>
|
|
51
54
|
<span class="st-star st-star--5"></span>
|
|
55
|
+
<span class="st-cloud st-cloud--1"></span>
|
|
56
|
+
<span class="st-cloud st-cloud--2"></span>
|
|
52
57
|
<span class="st-tower st-tower--1"></span>
|
|
53
58
|
<span class="st-tower st-tower--2"></span>
|
|
54
59
|
<span class="st-tower st-tower--3"></span>
|
|
@@ -64,6 +69,7 @@
|
|
|
64
69
|
<span class="st-mullion"></span>
|
|
65
70
|
</div>
|
|
66
71
|
<div class="st-floor"></div>
|
|
72
|
+
<div class="st-sunpatch"></div>
|
|
67
73
|
<div class="st-glow"></div>
|
|
68
74
|
<div class="st-lamp-stem"></div>
|
|
69
75
|
<div class="st-lamp-shade"></div>
|
|
@@ -199,7 +205,7 @@
|
|
|
199
205
|
<!-- The page names and the draft body (the tweak editor's pure rules), then
|
|
200
206
|
the studio's own. -->
|
|
201
207
|
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
|
|
202
|
-
<script src="/builders/studio-lib.js?v=2026-09-
|
|
203
|
-
<script src="/builders/studio.js?v=2026-09-
|
|
208
|
+
<script src="/builders/studio-lib.js?v=2026-10-09-day"></script>
|
|
209
|
+
<script src="/builders/studio.js?v=2026-10-09-day"></script>
|
|
204
210
|
</body>
|
|
205
211
|
</html>
|
|
@@ -838,7 +838,10 @@
|
|
|
838
838
|
wedit.lastSent = JSON.stringify(editorLib.draftBody(wedit.readingHash, wedit.lines, wedit.values));
|
|
839
839
|
wedit.keys = new Map();
|
|
840
840
|
for (const el of wordEls()) {
|
|
841
|
-
|
|
841
|
+
// The greeting is matched by the words the page was served with first:
|
|
842
|
+
// the clock may be showing "Good morning." over the page's one greeting
|
|
843
|
+
// line. A reading taken in daylight may list only the shown words.
|
|
844
|
+
const line = (el === greetEl && lib.matchLine(servedGreeting, wedit.lines)) || lib.matchLine(el.textContent, wedit.lines);
|
|
842
845
|
if (!line) { el.setAttribute('data-unread', ''); continue; }
|
|
843
846
|
wedit.keys.set(el, line.key);
|
|
844
847
|
el.textContent = wedit.values[line.key];
|
|
@@ -929,6 +932,40 @@
|
|
|
929
932
|
if (wedit.again) { wedit.again = false; saveWords(); }
|
|
930
933
|
}
|
|
931
934
|
|
|
935
|
+
// ---- the room's sky and the greeting (task 1004654) ------------------------
|
|
936
|
+
// The day room from 07:00 to 18:59 on the viewer's own clock and the night
|
|
937
|
+
// room otherwise (docs/design/mocks/day-night-rooms), re-checked each minute.
|
|
938
|
+
// Only an attribute changes here: studio.css draws both rooms, and the kill
|
|
939
|
+
// switch stops their motion.
|
|
940
|
+
const SKY_RECHECK_MS = 60000;
|
|
941
|
+
const greetEl = byId('studio-greet');
|
|
942
|
+
const servedGreeting = greetEl ? greetEl.textContent : '';
|
|
943
|
+
|
|
944
|
+
// The greeting follows the time of day while the page still carries the
|
|
945
|
+
// stock one, and never while the artist is typing over it: an artist's tweak
|
|
946
|
+
// of the studio page always wins. Written here, not in studio-lib.js, so the
|
|
947
|
+
// copy registry places each greeting at its own assignment.
|
|
948
|
+
function paintGreeting(part) {
|
|
949
|
+
if (!greetEl || wedit.editing || !lib.clockGreets(servedGreeting)) return;
|
|
950
|
+
if (part === 'morning') greetEl.textContent = 'Good morning.';
|
|
951
|
+
else if (part === 'afternoon') greetEl.textContent = 'Good afternoon.';
|
|
952
|
+
else greetEl.textContent = servedGreeting;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function paintSky() {
|
|
956
|
+
const now = new Date();
|
|
957
|
+
if (document.body) document.body.setAttribute('data-sky', lib.skyAt(now));
|
|
958
|
+
paintGreeting(lib.dayPart(now));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Synchronous, before the first read, so the room is drawn in the right sky
|
|
962
|
+
// from the start rather than turning to day after the network answers.
|
|
963
|
+
function startSky() {
|
|
964
|
+
if (!hasDom || !lib) return;
|
|
965
|
+
paintSky();
|
|
966
|
+
if (typeof setInterval === 'function') setInterval(paintSky, SKY_RECHECK_MS);
|
|
967
|
+
}
|
|
968
|
+
|
|
932
969
|
function wireHome() {
|
|
933
970
|
const flow = byId('st-flow');
|
|
934
971
|
if (flow) flow.addEventListener('click', () => openPanel('flow', flow));
|
|
@@ -1001,6 +1038,7 @@
|
|
|
1001
1038
|
}
|
|
1002
1039
|
|
|
1003
1040
|
(async () => {
|
|
1041
|
+
startSky();
|
|
1004
1042
|
mountApprovals();
|
|
1005
1043
|
const me = await api.request('GET', `${API}/me`);
|
|
1006
1044
|
if (me.ok) viewer = me.data.builder || me.data;
|
|
@@ -23,6 +23,22 @@ The page keeps its words until the round is submitted, applied, approved and shi
|
|
|
23
23
|
|
|
24
24
|
**Why the sub-line's sentence is in studio.js, not studio-lib.js.** The copy registry places a line at its `textContent` assignment. Built inside the lib it read as unplaced, and the reader's pattern tier then matched it to the generic `{…} of {…}` row (sky-panel.js). Written where studio.js assigns it, it is placed on studio.js, which is the page's own file and wins.
|
|
25
25
|
|
|
26
|
+
## The day room (task 1004654)
|
|
27
|
+
|
|
28
|
+
From 07:00 to 18:59 on the viewer's own clock the room is the day room of the owner-approved mockup [`docs/design/mocks/day-night-rooms`](../../../docs/design/mocks/day-night-rooms/README.md) ("Studio", sky "Day"), and the night room otherwise. studio.js sets `data-sky` on the body when the page loads, before the first read, and again every minute. The rule is `skyAt` in studio-lib.js. It is the creation page's rule (projects.html), written again here because hall-ui does not import the landing's code. The day palette is the mockup's `[data-sky="day"]` block, declared on the same `--st-*` names just below the night tokens. The drawing is the same, with clouds and a sun in the window, lighter towers with unlit panes, a warm wall and floor, the lamp off and a patch of sun on the floor. The clouds drift and a change of sky fades, both only without a reduced-motion preference (and style.css's kill switch stops them besides). The hall chrome keeps its own light and dark.
|
|
29
|
+
|
|
30
|
+
The greeting follows the time of day: "Good morning." before noon by day, "Good afternoon." after it, and the page's own "Good evening." at night. It does so only while the served greeting is still the stock "Good evening." (`clockGreets`). Once an artist's tweak of the studio page has rewritten it, their words show at every hour. The clock never types over the words while the artist is editing them.
|
|
31
|
+
|
|
32
|
+
Where it differs from the mockup, and why:
|
|
33
|
+
|
|
34
|
+
- **The link, the credits figure and the focus ring are #96410f by day**, not the mockup's #a3481a link and #fb9569 accent: #a3481a on Resume's tint over the wall is 4.3:1, under AA, and the accent is 1.8:1 on the wall.
|
|
35
|
+
- **The quiet inks are #5a595f (faint) and #66656b (dim, hint)** where the mockup has the one #66656b: the studio has three quiet tiers, and each holds AA on the glass and the bare wall (the hint is the lowest, 4.7:1).
|
|
36
|
+
- **The sun, clouds and sun patch are placed for the studio's 560px window** (the mockup's is 520px): the sun at 394/42 and 76px, the clouds about 8% larger, the patch 20px right to sit under the studio's lamp.
|
|
37
|
+
- **The panels follow the sky**: by day the two glass panels and the Approval queue wear white glass (.75) over a scrim of the wall's own tint (.9) in either hall theme and on a phone. The queue's rendered page stays on its paper frame. The mockup draws no panel.
|
|
38
|
+
- **"Good morning." while being typed over shows the page's greeting line**, "Good evening.", the one line a tweak can rewrite. Rewritten, it is the greeting at every hour.
|
|
39
|
+
- **On a phone the sun patch lies on the phone's floor** (bottom right, 300 by 70); the mockup hides it under 900px, but the studio's phone keeps its desk.
|
|
40
|
+
- **The stars do not twinkle and the lamp does not breathe at night**: the night room stays as it shipped. This task adds only the day room's drift.
|
|
41
|
+
|
|
26
42
|
## The older reviews stay one quiet door away
|
|
27
43
|
|
|
28
44
|
The garden this page used to be (R06 of goal 1000074; retired in TW14) still answers the reviews filed before Tweak Mode, because they may hold a deploy under the strict artist gate. It sits behind a quiet line under the actions ("1 review from before Tweak Mode still waits"), shown only while a review waits, and opens in its own dialog in the hall's tokens with the focus card, the stones and the two links, unchanged. Its verdict writes are unchanged.
|
|
@@ -1253,6 +1253,9 @@ async function abandonTask({ taskId, reason, authorize = null }, deps = {}) {
|
|
|
1253
1253
|
//
|
|
1254
1254
|
// Restores to **backlog**, not ready: the task was retired deliberately, so it
|
|
1255
1255
|
// comes back un-workable and needs a second, explicit promote to be claimable.
|
|
1256
|
+
// It comes back PARKED (parked_at/parked_by, core_274, task 1004592): without
|
|
1257
|
+
// the stamp the promotion sweep promoted it on the next tick, so the "second,
|
|
1258
|
+
// explicit promote" never had to happen. `parkedBy` is the restoring builder.
|
|
1256
1259
|
// abandonTask stamps `value_summary = 'Abandoned: <reason>'`; a restored task
|
|
1257
1260
|
// clears exactly that stamp (and nothing else) so a live task never carries a
|
|
1258
1261
|
// retire note into its ship card / broadcast.
|
|
@@ -1264,7 +1267,7 @@ async function abandonTask({ taskId, reason, authorize = null }, deps = {}) {
|
|
|
1264
1267
|
//
|
|
1265
1268
|
// #538 test seam: a caller-supplied pool is honored ONLY under NODE_ENV==='test'
|
|
1266
1269
|
// (matches abandonTask/confirmTask). Production callers omit deps → module pool.
|
|
1267
|
-
async function restoreTask({ taskId }, deps = {}) {
|
|
1270
|
+
async function restoreTask({ taskId, parkedBy = null }, deps = {}) {
|
|
1268
1271
|
const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
1269
1272
|
const client = await activePool.connect();
|
|
1270
1273
|
try {
|
|
@@ -1296,13 +1299,15 @@ async function restoreTask({ taskId }, deps = {}) {
|
|
|
1296
1299
|
const { rows: updated } = await client.query(
|
|
1297
1300
|
`UPDATE tasks
|
|
1298
1301
|
SET status = 'backlog',
|
|
1302
|
+
parked_at = now(),
|
|
1303
|
+
parked_by = $2,
|
|
1299
1304
|
merged_into_task_id = NULL,
|
|
1300
1305
|
triage_note = NULL,
|
|
1301
1306
|
value_summary = CASE WHEN value_summary LIKE 'Abandoned: %' THEN NULL ELSE value_summary END,
|
|
1302
1307
|
updated_at = now()
|
|
1303
1308
|
WHERE id = $1
|
|
1304
1309
|
RETURNING id, title, status`,
|
|
1305
|
-
[taskId]
|
|
1310
|
+
[taskId, parkedBy]
|
|
1306
1311
|
);
|
|
1307
1312
|
|
|
1308
1313
|
await client.query('COMMIT');
|
|
@@ -1257,6 +1257,21 @@ async function updateTaskStatus(id, status) {
|
|
|
1257
1257
|
await pool.query(`UPDATE tasks SET status = $2 WHERE id = $1`, [id, status]);
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
|
+
// POST /tasks/:id/demote (task 1004592): ready -> backlog, PARKED. The stamp is
|
|
1261
|
+
// what tells the promotion sweep and the core_274 triggers that a person put
|
|
1262
|
+
// this task here, so neither undoes the demote on the next tick. The status
|
|
1263
|
+
// guard sits in the WHERE so a task that moved since the route read it is left
|
|
1264
|
+
// alone; returns whether the row was demoted.
|
|
1265
|
+
async function parkTask(id, builderId, exec) {
|
|
1266
|
+
const { rowCount } = await (exec || pool).query(
|
|
1267
|
+
`UPDATE tasks
|
|
1268
|
+
SET status = 'backlog', parked_at = now(), parked_by = $2, updated_at = now()
|
|
1269
|
+
WHERE id = $1 AND status = 'ready'`,
|
|
1270
|
+
[id, builderId == null ? null : builderId]
|
|
1271
|
+
);
|
|
1272
|
+
return rowCount > 0;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1260
1275
|
// The UNSCOPED half of auto-promotion (task 1003932) — the backlog drain that
|
|
1261
1276
|
// `runCriterionSweep` already is for criteria.
|
|
1262
1277
|
//
|
|
@@ -1305,9 +1320,18 @@ async function updateTaskStatus(id, status) {
|
|
|
1305
1320
|
//
|
|
1306
1321
|
// The exclusion is by SOURCE, not by discipline: engineer-scoping-on-full-idea
|
|
1307
1322
|
// is equally a request, and the preamble above is written on both.
|
|
1323
|
+
//
|
|
1324
|
+
// The second kind has a second source: a PERSON parking a task (task 1004592).
|
|
1325
|
+
// restoreTask and POST /tasks/:id/demote both stamp tasks.parked_at, and
|
|
1326
|
+
// `parked_at IS NULL` keeps the sweep off those rows. core_274 puts the same
|
|
1327
|
+
// clause in the three promotion triggers and clears the stamp whenever a task
|
|
1328
|
+
// leaves backlog, so a person's promote is the only way out. Hand-filed backlog
|
|
1329
|
+
// with no parked_at is still drained: create-time intent is not recorded, and
|
|
1330
|
+
// dependencies are often added after create.
|
|
1308
1331
|
const PROMOTABLE_BACKLOG_WHERE = `
|
|
1309
1332
|
WHERE t.status = 'backlog'
|
|
1310
1333
|
AND t.source IS DISTINCT FROM 'cascade'
|
|
1334
|
+
AND t.parked_at IS NULL
|
|
1311
1335
|
AND task_is_fully_unblocked(t.id)
|
|
1312
1336
|
AND NOT EXISTS (
|
|
1313
1337
|
SELECT 1
|
|
@@ -1444,6 +1468,7 @@ module.exports = {
|
|
|
1444
1468
|
updateTaskRequiresRank,
|
|
1445
1469
|
updateTaskSecuritySensitive,
|
|
1446
1470
|
updateTaskStatus,
|
|
1471
|
+
parkTask,
|
|
1447
1472
|
updateTaskValueSummary,
|
|
1448
1473
|
versionAcceptsNewWork,
|
|
1449
1474
|
};
|
|
@@ -523,8 +523,8 @@ module.exports = function buildTasksRouter() {
|
|
|
523
523
|
// down to Metic+ it needed an undo at the same rank, or a Metic's mis-retire would
|
|
524
524
|
// be unrecoverable without Archon SQL (no other route restores an abandoned task —
|
|
525
525
|
// PATCH refuses `status` outright). A restore lands in **backlog**, never straight
|
|
526
|
-
// to ready: the task was retired for a reason, so it re-enters
|
|
527
|
-
//
|
|
526
|
+
// to ready: the task was retired for a reason, so it re-enters PARKED (core_274,
|
|
527
|
+
// task 1004592 — the promotion sweep skips it) and only a second promote frees it.
|
|
528
528
|
router.post('/tasks/:id/promote', auth.requireBuilder, auth.requirePermission('task.promote'), async (req, res) => {
|
|
529
529
|
const id = Number(req.params.id);
|
|
530
530
|
const task = await db.getTask(id);
|
|
@@ -535,7 +535,7 @@ module.exports = function buildTasksRouter() {
|
|
|
535
535
|
// The restore branch returns early: no reward gate (that is a promote-to-WORKABLE
|
|
536
536
|
// concern and backlog is the pre-workable state — same reasoning as /demote).
|
|
537
537
|
if (task.status === 'abandoned') {
|
|
538
|
-
const restored = await db.restoreTask({ taskId: id });
|
|
538
|
+
const restored = await db.restoreTask({ taskId: id, parkedBy: req.builder.id });
|
|
539
539
|
return res.json({ ok: true, restored: true, status: restored.taskStatus });
|
|
540
540
|
}
|
|
541
541
|
// task 1670 / ADR 0096 + task 1673 amendment (2026-06-28): a task must never
|
|
@@ -585,7 +585,7 @@ module.exports = function buildTasksRouter() {
|
|
|
585
585
|
// any later lifecycle state). A claimed task is `active`, so this gate also
|
|
586
586
|
// refuses demoting work that's already in flight — demote is not a way to yank a
|
|
587
587
|
// task out from under its claim holder. No reward side-effects (the reward gate
|
|
588
|
-
// is a promote-only concern
|
|
588
|
+
// is a promote-only concern). The task lands PARKED so the sweep leaves it (1004592).
|
|
589
589
|
// rank: metic+archon — ready→backlog demotion; scope mutation; mirrors /promote (ADR 0090).
|
|
590
590
|
router.post('/tasks/:id/demote', auth.requireBuilder, auth.requirePermission('task.demote'), async (req, res) => {
|
|
591
591
|
const id = Number(req.params.id);
|
|
@@ -594,7 +594,7 @@ module.exports = function buildTasksRouter() {
|
|
|
594
594
|
if (task.status !== 'ready') {
|
|
595
595
|
return res.fail('cannot_demote', 409, { current_status: task.status });
|
|
596
596
|
}
|
|
597
|
-
await db.
|
|
597
|
+
if (!(await db.parkTask(id, req.builder.id))) return res.fail('cannot_demote', 409);
|
|
598
598
|
res.json({ ok: true });
|
|
599
599
|
});
|
|
600
600
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.98",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.98",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.98",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -9412,5 +9412,15 @@
|
|
|
9412
9412
|
"id": "1004652",
|
|
9413
9413
|
"text": "Connecting GitHub no longer makes a second app when something goes wrong. If a step stops, the checklist shows which one, says in one plain sentence that nothing is lost, and gives a \"Pick up where it stopped\" button that carr"
|
|
9414
9414
|
}
|
|
9415
|
+
],
|
|
9416
|
+
"1.21.98": [
|
|
9417
|
+
{
|
|
9418
|
+
"id": "1004654",
|
|
9419
|
+
"text": "The artist studio now has a day room. From 7 in the morning until 7 in the evening, by the clock on the artist's own computer, the studio shows a sunny sky with drifting clouds, a sun instead of the moon, lighter buildings wit"
|
|
9420
|
+
},
|
|
9421
|
+
{
|
|
9422
|
+
"id": "1004592",
|
|
9423
|
+
"text": "When someone restores a cut task or takes a task back off the board, it now stays put until a person promotes it. Before, the automatic promoter put it straight back on the claimable list within about five minutes."
|
|
9424
|
+
}
|
|
9415
9425
|
]
|
|
9416
9426
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.98'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/abandon_task.mjs
CHANGED
|
@@ -168,7 +168,7 @@ await test("restore: clears only an 'Abandoned: …' value_summary, leaving any
|
|
|
168
168
|
assert.match(sql, /status = 'backlog'/);
|
|
169
169
|
assert.match(sql, /value_summary LIKE 'Abandoned: %' THEN NULL ELSE value_summary END/,
|
|
170
170
|
'the retire stamp is cleared conditionally — an unrelated summary survives');
|
|
171
|
-
assert.
|
|
171
|
+
assert.deepEqual(upd.params, [TASK, null], 'the task id, then the parking builder (none passed here)');
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
await test('restore: clears merged_into_task_id + triage_note (task 1001478 grade finding)', async () => {
|