@bongos/core 1.23.20 → 1.23.21
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 +32 -22
- package/docs/module-api-changelog.md +2 -0
- package/modules/copy-desk/chrome-pages.js +178 -0
- package/modules/copy-desk/page-data.js +34 -2
- package/modules/copy-desk/tests/copy_chrome_pages.mjs +127 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/bongos/copy-apply.js +3 -1
- package/scripts/bongos/tweak-renders.js +9 -2
- package/src/module-api.js +1 -1
- package/tests/copy_desk_page_docx.mjs +3 -3
- package/tests/copy_desk_page_editor.mjs +6 -6
- package/tests/copy_desk_page_next.mjs +3 -2
- package/tests/copy_desk_page_reads.mjs +2 -1
- package/tests/copy_desk_page_submit.mjs +30 -2
- package/tests/profile_role_stats.mjs +2 -1
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.23.
|
|
6
|
-
"core_contract": "1.23.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.23.21",
|
|
6
|
+
"core_contract": "1.23.21",
|
|
7
|
+
"source_commit": "5624212239928e3e082efd5b058186e35a6cd51d",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-10-
|
|
9
|
+
"built_at": "2026-10-29T02:04:20.723Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 616,
|
|
13
13
|
"agent_docs_stubbed": 29,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 3606,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 4252,
|
|
20
|
+
"tree_sha256": "74b0778836bfca86383004de0425bcf0d9cf92a310070c7d2d704786a2110c70",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2937,7 +2937,7 @@
|
|
|
2937
2937
|
{
|
|
2938
2938
|
"path": "docs/module-api-changelog.md",
|
|
2939
2939
|
"mode": "0000644",
|
|
2940
|
-
"sha256": "
|
|
2940
|
+
"sha256": "7cca771f6d0fc816d449fa7478f5b01c9d5f25ec21bef1db28338c7f2f4e38e0"
|
|
2941
2941
|
},
|
|
2942
2942
|
{
|
|
2943
2943
|
"path": "docs/modules-contract.md",
|
|
@@ -4489,6 +4489,11 @@
|
|
|
4489
4489
|
"mode": "0000644",
|
|
4490
4490
|
"sha256": "f9f4e8e009fab44c47fd3651c5d44da94cec1fd4cb7ad0931515fadbd6526280"
|
|
4491
4491
|
},
|
|
4492
|
+
{
|
|
4493
|
+
"path": "modules/copy-desk/chrome-pages.js",
|
|
4494
|
+
"mode": "0000644",
|
|
4495
|
+
"sha256": "970750a57f2bd8324a908dbf9845407713d0cd99a94686dce9f75a02cc8e1538"
|
|
4496
|
+
},
|
|
4492
4497
|
{
|
|
4493
4498
|
"path": "modules/copy-desk/docx.js",
|
|
4494
4499
|
"mode": "0000644",
|
|
@@ -4527,7 +4532,7 @@
|
|
|
4527
4532
|
{
|
|
4528
4533
|
"path": "modules/copy-desk/page-data.js",
|
|
4529
4534
|
"mode": "0000644",
|
|
4530
|
-
"sha256": "
|
|
4535
|
+
"sha256": "dcce3339f174c63ba5ae84f48dd0c167c1308aeb17eb31280f459aa1c549e8e1"
|
|
4531
4536
|
},
|
|
4532
4537
|
{
|
|
4533
4538
|
"path": "modules/copy-desk/page-decisions.js",
|
|
@@ -4589,6 +4594,11 @@
|
|
|
4589
4594
|
"mode": "0000644",
|
|
4590
4595
|
"sha256": "662ef9fbb323904f50fd60f3636517e474e6c74a0679dd05b7a6b9135d671c3d"
|
|
4591
4596
|
},
|
|
4597
|
+
{
|
|
4598
|
+
"path": "modules/copy-desk/tests/copy_chrome_pages.mjs",
|
|
4599
|
+
"mode": "0000644",
|
|
4600
|
+
"sha256": "8103d82f34ab9506b9e9dca5e9f50892eb75d86f5175a2aec193b09fa4a26b3c"
|
|
4601
|
+
},
|
|
4592
4602
|
{
|
|
4593
4603
|
"path": "modules/copy-desk/tests/copy_docx.mjs",
|
|
4594
4604
|
"mode": "0000644",
|
|
@@ -4602,7 +4612,7 @@
|
|
|
4602
4612
|
{
|
|
4603
4613
|
"path": "modules/copy-desk/tests/copy_no_cms.mjs",
|
|
4604
4614
|
"mode": "0000644",
|
|
4605
|
-
"sha256": "
|
|
4615
|
+
"sha256": "74a8241778ef5d0055b555fad41530a92cc88fe5760c25cda033d2898d206d89"
|
|
4606
4616
|
},
|
|
4607
4617
|
{
|
|
4608
4618
|
"path": "modules/copy-desk/tests/copy_page_auto_close.mjs",
|
|
@@ -11022,12 +11032,12 @@
|
|
|
11022
11032
|
{
|
|
11023
11033
|
"path": "package-lock.json",
|
|
11024
11034
|
"mode": "0000644",
|
|
11025
|
-
"sha256": "
|
|
11035
|
+
"sha256": "3e38ce5d5f5522e4d6479a3430df28858f36be6e79d2cd11808ed7e128fc4f3b"
|
|
11026
11036
|
},
|
|
11027
11037
|
{
|
|
11028
11038
|
"path": "package.json",
|
|
11029
11039
|
"mode": "0000644",
|
|
11030
|
-
"sha256": "
|
|
11040
|
+
"sha256": "2b11d8a46c71afe00d28af274de396b51a4d9b02c69bdd8b94cd2a1a42245495"
|
|
11031
11041
|
},
|
|
11032
11042
|
{
|
|
11033
11043
|
"path": "public-docs/index.html",
|
|
@@ -11047,7 +11057,7 @@
|
|
|
11047
11057
|
{
|
|
11048
11058
|
"path": "release-notes.json",
|
|
11049
11059
|
"mode": "0000644",
|
|
11050
|
-
"sha256": "
|
|
11060
|
+
"sha256": "bac27f8f10894962e543895c14c9288b5bf5b950189c0fd57b97fec57c776cac"
|
|
11051
11061
|
},
|
|
11052
11062
|
{
|
|
11053
11063
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -11377,7 +11387,7 @@
|
|
|
11377
11387
|
{
|
|
11378
11388
|
"path": "scripts/bongos/copy-apply.js",
|
|
11379
11389
|
"mode": "0000644",
|
|
11380
|
-
"sha256": "
|
|
11390
|
+
"sha256": "9bbad1fba796c563cb103951e7f583effd9c2f9bccf3eff26ebe3e5fe53b6116"
|
|
11381
11391
|
},
|
|
11382
11392
|
{
|
|
11383
11393
|
"path": "scripts/bongos/copy-inventory.js",
|
|
@@ -12652,7 +12662,7 @@
|
|
|
12652
12662
|
{
|
|
12653
12663
|
"path": "scripts/bongos/tweak-renders.js",
|
|
12654
12664
|
"mode": "0000644",
|
|
12655
|
-
"sha256": "
|
|
12665
|
+
"sha256": "4cd4613fac825b740b3e1dd33b4919b51149afaf4c87eff1608e884198d3b83d"
|
|
12656
12666
|
},
|
|
12657
12667
|
{
|
|
12658
12668
|
"path": "scripts/bongos/uat.js",
|
|
@@ -14922,7 +14932,7 @@
|
|
|
14922
14932
|
{
|
|
14923
14933
|
"path": "src/module-api.js",
|
|
14924
14934
|
"mode": "0000644",
|
|
14925
|
-
"sha256": "
|
|
14935
|
+
"sha256": "292f7c0252f9ce177228d2e07178fb0d0aadb70a21bb228c33209fb0501d491e"
|
|
14926
14936
|
},
|
|
14927
14937
|
{
|
|
14928
14938
|
"path": "src/module-loader/catalog.js",
|
|
@@ -15952,22 +15962,22 @@
|
|
|
15952
15962
|
{
|
|
15953
15963
|
"path": "tests/copy_desk_page_docx.mjs",
|
|
15954
15964
|
"mode": "0000644",
|
|
15955
|
-
"sha256": "
|
|
15965
|
+
"sha256": "af625c8c4819d7f0da0ebc676d9c41f579b9e8fe6fae8490c3d35f4a0dc48972"
|
|
15956
15966
|
},
|
|
15957
15967
|
{
|
|
15958
15968
|
"path": "tests/copy_desk_page_editor.mjs",
|
|
15959
15969
|
"mode": "0000644",
|
|
15960
|
-
"sha256": "
|
|
15970
|
+
"sha256": "80fa1c728842097c1db2644f6d06e4e8f9c69a2f637a5ee8efe0b2b76c0508ed"
|
|
15961
15971
|
},
|
|
15962
15972
|
{
|
|
15963
15973
|
"path": "tests/copy_desk_page_next.mjs",
|
|
15964
15974
|
"mode": "0000644",
|
|
15965
|
-
"sha256": "
|
|
15975
|
+
"sha256": "ac23b3e050c66a2f5a4ac796c73a22830d641706fc1fa98e65f0a545ee99d4cc"
|
|
15966
15976
|
},
|
|
15967
15977
|
{
|
|
15968
15978
|
"path": "tests/copy_desk_page_reads.mjs",
|
|
15969
15979
|
"mode": "0000644",
|
|
15970
|
-
"sha256": "
|
|
15980
|
+
"sha256": "54e0c9aaa64709c3804aeaf74107a46ca8aeae4a4054377a290c97ea58ce4163"
|
|
15971
15981
|
},
|
|
15972
15982
|
{
|
|
15973
15983
|
"path": "tests/copy_desk_page_release.mjs",
|
|
@@ -15977,7 +15987,7 @@
|
|
|
15977
15987
|
{
|
|
15978
15988
|
"path": "tests/copy_desk_page_submit.mjs",
|
|
15979
15989
|
"mode": "0000644",
|
|
15980
|
-
"sha256": "
|
|
15990
|
+
"sha256": "ee649108f713c33ffcca804b81efb77e5ccb33ea01a6053d18d2aade3f29a755"
|
|
15981
15991
|
},
|
|
15982
15992
|
{
|
|
15983
15993
|
"path": "tests/copy_guarded_lines.mjs",
|
|
@@ -19272,7 +19282,7 @@
|
|
|
19272
19282
|
{
|
|
19273
19283
|
"path": "tests/profile_role_stats.mjs",
|
|
19274
19284
|
"mode": "0000644",
|
|
19275
|
-
"sha256": "
|
|
19285
|
+
"sha256": "f800cbd9b93e57b4a7e45c79c1879263d55ad0617730f6471ff57103a1262793"
|
|
19276
19286
|
},
|
|
19277
19287
|
{
|
|
19278
19288
|
"path": "tests/profile_role_tabs.mjs",
|
|
@@ -3255,5 +3255,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3255
3255
|
landed since 1.23.18 with no explicit bump. run 40613099968. (task 1002620)
|
|
3256
3256
|
1.23.20 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3257
3257
|
landed since 1.23.19 with no explicit bump. run 40709473534. (task 1002620)
|
|
3258
|
+
1.23.21 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3259
|
+
landed since 1.23.20 with no explicit bump. run 40804017640. (task 1002620)
|
|
3258
3260
|
---------------------------------------------------------------------------
|
|
3259
3261
|
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// modules/copy-desk/chrome-pages.js — the shared page chrome as its own pages
|
|
2
|
+
// (task 1004904, ADR 0341 D3's "shared" placement).
|
|
3
|
+
//
|
|
4
|
+
// The reader (scripts/bongos/page-reader.js) records every visible line of a page,
|
|
5
|
+
// and the skip link, top bar, navigation and footer are on every page of a
|
|
6
|
+
// surface, so each page's reading carried the same few dozen lines. An artist
|
|
7
|
+
// tweaking one page was shown, and could rewrite, words that change on all of
|
|
8
|
+
// them. This is the read-side split:
|
|
9
|
+
//
|
|
10
|
+
// * A real page's reading loses its chrome lines. They are not shown, not
|
|
11
|
+
// editable, not counted, not in its drift.
|
|
12
|
+
// * Each surface gains one page per chrome section: "<surface>:top-bar",
|
|
13
|
+
// "<surface>:navigation", "<surface>:footer", "<surface>:skip-link". Each
|
|
14
|
+
// lists every DISTINCT line of that section once, in the order it first
|
|
15
|
+
// appears, and goes through the same claim, draft, submit and approve flow
|
|
16
|
+
// as any page.
|
|
17
|
+
//
|
|
18
|
+
// PURE, like pages.js: it derives from the two committed artifacts and writes
|
|
19
|
+
// nothing. The generators and the committed files are untouched, so the
|
|
20
|
+
// reader's --stale check and the registry are exactly as they were, and a
|
|
21
|
+
// surface with no chrome gains no page.
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const crypto = require('node:crypto');
|
|
26
|
+
|
|
27
|
+
// section name the reader writes -> the page it becomes
|
|
28
|
+
const CHROME = Object.freeze([
|
|
29
|
+
Object.freeze({ section: 'top bar', slug: 'top-bar', title: 'Top bar' }),
|
|
30
|
+
Object.freeze({ section: 'navigation', slug: 'navigation', title: 'Navigation' }),
|
|
31
|
+
Object.freeze({ section: 'footer', slug: 'footer', title: 'Footer' }),
|
|
32
|
+
Object.freeze({ section: 'skip link', slug: 'skip-link', title: 'Skip link' }),
|
|
33
|
+
Object.freeze({ section: 'sidebar', slug: 'sidebar', title: 'Sidebar' }),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const CHROME_SECTIONS = new Set(CHROME.map((c) => c.section));
|
|
37
|
+
|
|
38
|
+
function isChromeLine(line) {
|
|
39
|
+
return !!line && line.placement === 'shared' && CHROME_SECTIONS.has(line.section);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hash(parts) {
|
|
43
|
+
return crypto.createHash('sha256').update(parts.join('\n')).digest('hex').slice(0, 16);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function key(n) { return `L${String(n).padStart(4, '0')}`; }
|
|
47
|
+
|
|
48
|
+
function countsOf(lines) {
|
|
49
|
+
return {
|
|
50
|
+
lines: lines.length,
|
|
51
|
+
placed: lines.filter((l) => l.placement === 'placed').length,
|
|
52
|
+
shared: lines.filter((l) => l.placement === 'shared').length,
|
|
53
|
+
unplaced: lines.filter((l) => l.placement === 'unplaced').length,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The chrome page ids a surface can have, so a caller can tell one from a page.
|
|
58
|
+
// A reading names its surface; one that does not is read from its id.
|
|
59
|
+
function surfaceOf(page) { return page.surface || String(page.id || '').split(':')[0]; }
|
|
60
|
+
|
|
61
|
+
function chromePageId(surface, slug) { return `${surface}:${slug}`; }
|
|
62
|
+
function isChromePageId(id) {
|
|
63
|
+
const m = /^[a-z0-9-]+:([a-z0-9-]+)$/.exec(String(id || ''));
|
|
64
|
+
return !!m && CHROME.some((c) => c.slug === m[1]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// readings -> readings, every real page without its chrome and the chrome pages
|
|
68
|
+
// after them. Returns { readings, chrome } where chrome maps a surface to the
|
|
69
|
+
// pages it gained (the inventory is built from it).
|
|
70
|
+
function splitReadings(readings) {
|
|
71
|
+
const pages = Array.isArray(readings && readings.pages) ? readings.pages : [];
|
|
72
|
+
const real = [];
|
|
73
|
+
// surface -> slug -> { lines: Map text -> line, from: page, states: Set, sources: Set of pages }
|
|
74
|
+
const gathered = new Map();
|
|
75
|
+
const order = [];
|
|
76
|
+
for (const p of pages) {
|
|
77
|
+
const lines = Array.isArray(p && p.lines) ? p.lines : [];
|
|
78
|
+
const kept = [];
|
|
79
|
+
for (const l of lines) {
|
|
80
|
+
if (!isChromeLine(l)) { kept.push(l); continue; }
|
|
81
|
+
const spec = CHROME.find((c) => c.section === l.section);
|
|
82
|
+
const surface = surfaceOf(p);
|
|
83
|
+
if (!gathered.has(surface)) { gathered.set(surface, new Map()); order.push(surface); }
|
|
84
|
+
const bySlug = gathered.get(surface);
|
|
85
|
+
if (!bySlug.has(spec.slug)) bySlug.set(spec.slug, { spec, lines: new Map(), from: p, states: new Set(), sources: new Set() });
|
|
86
|
+
const g = bySlug.get(spec.slug);
|
|
87
|
+
const text = String(l.text);
|
|
88
|
+
const seen = g.lines.get(text);
|
|
89
|
+
if (!seen) g.lines.set(text, { ...l });
|
|
90
|
+
else if (!seen.file && l.file) { seen.file = l.file; seen.line = l.line; seen.string_id = l.string_id; }
|
|
91
|
+
if (!g.sources.has(p)) {
|
|
92
|
+
g.sources.add(p);
|
|
93
|
+
for (const s of p.states_read || []) g.states.add(s);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (kept.length === lines.length) { real.push(p); continue; }
|
|
97
|
+
real.push({
|
|
98
|
+
...p,
|
|
99
|
+
...(p.counts ? { counts: countsOf(kept) } : {}),
|
|
100
|
+
lines: kept,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const chrome = new Map();
|
|
104
|
+
const made = [];
|
|
105
|
+
for (const surface of order) {
|
|
106
|
+
const bySlug = gathered.get(surface);
|
|
107
|
+
const list = [];
|
|
108
|
+
for (const spec of CHROME) {
|
|
109
|
+
const g = bySlug.get(spec.slug);
|
|
110
|
+
if (!g) continue;
|
|
111
|
+
const lines = [...g.lines.values()].map((l, i) => ({ ...l, key: key(i + 1) }));
|
|
112
|
+
const id = chromePageId(surface, spec.slug);
|
|
113
|
+
made.push({
|
|
114
|
+
id,
|
|
115
|
+
surface,
|
|
116
|
+
title: spec.title,
|
|
117
|
+
states_read: [...g.states],
|
|
118
|
+
files_hash: hash([...g.sources].map((s) => String(s.files_hash || ''))),
|
|
119
|
+
reading_hash: hash(lines.map((l) => `${l.section}\t${l.text}`)),
|
|
120
|
+
counts: countsOf(lines),
|
|
121
|
+
omitted: {},
|
|
122
|
+
notes: [`The ${spec.title.toLowerCase()} shown on every ${surface} page, each line once. Read from ${g.sources.size} pages.`],
|
|
123
|
+
lines,
|
|
124
|
+
});
|
|
125
|
+
list.push({ id, spec, from: g.from, sources: g.sources, lines });
|
|
126
|
+
}
|
|
127
|
+
chrome.set(surface, list);
|
|
128
|
+
}
|
|
129
|
+
const out = { ...readings, pages: [...real, ...made] };
|
|
130
|
+
if (readings.counts) {
|
|
131
|
+
// Totals are counted from the lines, not from each page's own counts: a
|
|
132
|
+
// reading need not carry them.
|
|
133
|
+
const all = countsOf(out.pages.flatMap((p) => p.lines || []));
|
|
134
|
+
out.counts = { ...readings.counts, pages: out.pages.length, ...all };
|
|
135
|
+
}
|
|
136
|
+
return { readings: out, chrome };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// inventory -> inventory, with the chrome pages of each surface after that
|
|
140
|
+
// surface's own pages. A chrome page opens on the first page it was read from
|
|
141
|
+
// (the live page shows it), and its files are the ones its lines come from, so
|
|
142
|
+
// the claim's touches and the "changed since" read name the shell, not a page.
|
|
143
|
+
function addChromePages(inventory, chrome) {
|
|
144
|
+
const pages = Array.isArray(inventory && inventory.pages) ? inventory.pages : [];
|
|
145
|
+
const byId = new Map(pages.map((p) => [p.id, p]));
|
|
146
|
+
const added = [];
|
|
147
|
+
for (const [surface, list] of chrome) {
|
|
148
|
+
for (const c of list) {
|
|
149
|
+
const src = byId.get(c.from.id) || {};
|
|
150
|
+
const files = [...new Set(c.lines.map((l) => l.file).filter(Boolean))];
|
|
151
|
+
added.push({
|
|
152
|
+
id: c.id,
|
|
153
|
+
surface,
|
|
154
|
+
module: src.module || null,
|
|
155
|
+
page: c.spec.slug,
|
|
156
|
+
id_from: 'shared-chrome',
|
|
157
|
+
title: c.spec.title,
|
|
158
|
+
document_title: `${c.spec.title} — shown on every ${surface} page`,
|
|
159
|
+
path: src.path || null,
|
|
160
|
+
url: src.url || null,
|
|
161
|
+
files,
|
|
162
|
+
states_files: [],
|
|
163
|
+
states: [],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const all = [...pages, ...added];
|
|
168
|
+
const out = { ...inventory, pages: all };
|
|
169
|
+
if (inventory.counts) out.counts = { ...inventory.counts, pages: all.length };
|
|
170
|
+
if (Array.isArray(inventory.surfaces)) {
|
|
171
|
+
const tally = new Map();
|
|
172
|
+
for (const p of all) tally.set(p.surface, (tally.get(p.surface) || 0) + 1);
|
|
173
|
+
out.surfaces = inventory.surfaces.map((s) => ({ ...s, pages: tally.get(s.id) || 0 }));
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { isChromeLine, isChromePageId, splitReadings, addChromePages };
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
const fs = require('node:fs');
|
|
26
26
|
const path = require('node:path');
|
|
27
|
+
const chromePages = require('./chrome-pages');
|
|
27
28
|
|
|
28
29
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
29
30
|
|
|
@@ -73,7 +74,38 @@ function load(which) {
|
|
|
73
74
|
return value;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
// The shared chrome (skip link, top bar, navigation, footer) is read out of
|
|
78
|
+
// every page and handed back as pages of its own (chrome-pages.js, task
|
|
79
|
+
// 1004904). Derived per raw load, so the committed files stay what the
|
|
80
|
+
// generators wrote; a WeakMap keeps one derivation per cached read.
|
|
81
|
+
const _derived = new WeakMap();
|
|
82
|
+
function derive() {
|
|
83
|
+
const rawReadings = load('readings');
|
|
84
|
+
if (!rawReadings.ok) return null;
|
|
85
|
+
const hit = _derived.get(rawReadings);
|
|
86
|
+
if (hit) return hit;
|
|
87
|
+
const split = chromePages.splitReadings(rawReadings.data);
|
|
88
|
+
const value = { readings: { ok: true, data: split.readings, at: rawReadings.at }, chrome: split.chrome };
|
|
89
|
+
_derived.set(rawReadings, value);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const _derivedInventory = new WeakMap();
|
|
94
|
+
function loadInventory() {
|
|
95
|
+
const raw = load('inventory');
|
|
96
|
+
if (!raw.ok) return raw;
|
|
97
|
+
const d = derive();
|
|
98
|
+
if (!d) return raw;
|
|
99
|
+
const key = d.readings;
|
|
100
|
+
const hit = _derivedInventory.get(raw);
|
|
101
|
+
if (hit && hit.key === key) return hit.value;
|
|
102
|
+
const value = { ok: true, data: chromePages.addChromePages(raw.data, d.chrome), at: raw.at };
|
|
103
|
+
_derivedInventory.set(raw, { key, value });
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function loadReadings() {
|
|
107
|
+
const d = derive();
|
|
108
|
+
return d ? d.readings : load('readings');
|
|
109
|
+
}
|
|
78
110
|
|
|
79
111
|
module.exports = { ARTIFACTS, loadInventory, loadReadings, _setRoot };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// modules/copy-desk/tests/copy_chrome_pages.mjs — the shared page chrome as its
|
|
2
|
+
// own pages (task 1004904), pure.
|
|
3
|
+
//
|
|
4
|
+
// The skip link, top bar, navigation and footer are on every page of a surface.
|
|
5
|
+
// A real page's reading must not carry them, and each surface must gain one
|
|
6
|
+
// page per chrome section, each line once. Run over a small hand-made reading
|
|
7
|
+
// (the rules) and over the committed artifacts (the real data holds them).
|
|
8
|
+
|
|
9
|
+
import { strict as assert } from 'node:assert';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const chrome = require('../chrome-pages.js');
|
|
14
|
+
const pageData = require('../page-data.js');
|
|
15
|
+
|
|
16
|
+
let passed = 0;
|
|
17
|
+
let failed = 0;
|
|
18
|
+
function test(name, fn) {
|
|
19
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
20
|
+
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.stack || err.message}`); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const L = (key, section, text, placement, row) => ({ key, section, text, placement, ...(row || {}) });
|
|
24
|
+
const READINGS = {
|
|
25
|
+
counts: { pages: 3, lines: 9, placed: 3, shared: 5, unplaced: 1 },
|
|
26
|
+
pages: [
|
|
27
|
+
{ id: 'landing:index', surface: 'landing', reading_hash: 'h-index', files_hash: 'f1', states_read: ['in'], lines: [
|
|
28
|
+
L('L0001', 'skip link', 'Skip to content', 'shared', { file: 'shell.js', line: 9, string_id: 'a1' }),
|
|
29
|
+
L('L0002', 'top bar', 'Projects', 'shared', { file: 'nav.js', line: 3, string_id: 'a2' }),
|
|
30
|
+
L('L0003', 'hero', 'Build here', 'placed', { file: 'index.html', line: 4, string_id: 'a3' }),
|
|
31
|
+
] },
|
|
32
|
+
{ id: 'landing:account', surface: 'landing', reading_hash: 'h-acct', counts: { lines: 5, placed: 1, shared: 3, unplaced: 1 }, files_hash: 'f2', states_read: ['in', 'out'], lines: [
|
|
33
|
+
L('L0001', 'skip link', 'Skip to content', 'shared'),
|
|
34
|
+
L('L0002', 'top bar', 'Projects', 'shared'),
|
|
35
|
+
L('L0003', 'top bar', 'Sign in', 'shared'),
|
|
36
|
+
L('L0004', 'hero', 'Your account', 'placed'),
|
|
37
|
+
L('L0005', 'list', 'Something unplaced', 'unplaced'),
|
|
38
|
+
] },
|
|
39
|
+
// No surface on the reading: it is read from the id.
|
|
40
|
+
{ id: 'status:explore', reading_hash: 'h-exp', files_hash: 'f3', lines: [L('L0001', 'hero', 'Explore', 'placed')] },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
const INVENTORY = {
|
|
44
|
+
counts: { pages: 3, surfaces: 2 },
|
|
45
|
+
surfaces: [{ id: 'landing', pages: 2 }, { id: 'status', pages: 1 }],
|
|
46
|
+
pages: [
|
|
47
|
+
{ id: 'landing:index', surface: 'landing', module: 'public-landing', page: 'index', title: 'Home', path: '/', url: 'https://{apex}/', files: ['index.html'] },
|
|
48
|
+
{ id: 'landing:account', surface: 'landing', module: 'public-landing', page: 'account', title: 'Account', path: '/account', url: 'https://{apex}/account', files: ['account.html'] },
|
|
49
|
+
{ id: 'status:explore', surface: 'status', module: 'status-ui', page: 'explore', title: 'Explore', path: '/explore', url: 'https://status.{apex}/explore', files: ['explore.html'] },
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const split = chrome.splitReadings(READINGS);
|
|
54
|
+
const byId = (rd, id) => rd.pages.find((p) => p.id === id);
|
|
55
|
+
|
|
56
|
+
test('a real page no longer carries its chrome, and keeps its own lines and keys', () => {
|
|
57
|
+
assert.deepEqual(byId(split.readings, 'landing:index').lines.map((l) => [l.key, l.section]), [['L0003', 'hero']]);
|
|
58
|
+
assert.deepEqual(byId(split.readings, 'landing:account').lines.map((l) => l.key), ['L0004', 'L0005']);
|
|
59
|
+
assert.equal(byId(split.readings, 'landing:index').reading_hash, 'h-index', 'a draft on the reading stays on it');
|
|
60
|
+
assert.deepEqual(byId(split.readings, 'landing:account').counts, { lines: 2, placed: 1, shared: 0, unplaced: 1 });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('each chrome section becomes one page per surface, every distinct line once, in first-seen order', () => {
|
|
64
|
+
const top = byId(split.readings, 'landing:top-bar');
|
|
65
|
+
assert.deepEqual(top.lines.map((l) => [l.key, l.text]), [['L0001', 'Projects'], ['L0002', 'Sign in']]);
|
|
66
|
+
assert.deepEqual(byId(split.readings, 'landing:skip-link').lines.map((l) => l.text), ['Skip to content']);
|
|
67
|
+
assert.equal(byId(split.readings, 'landing:footer'), undefined, 'a surface with no footer gains no footer page');
|
|
68
|
+
assert.equal(byId(split.readings, 'status:top-bar'), undefined, 'a surface with no chrome gains no chrome page');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('a chrome line keeps the registry row of its first occurrence that has one', () => {
|
|
72
|
+
const top = byId(split.readings, 'landing:top-bar').lines[0];
|
|
73
|
+
assert.deepEqual([top.file, top.line, top.string_id, top.placement], ['nav.js', 3, 'a2', 'shared']);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('the reading totals follow the pages', () => {
|
|
77
|
+
assert.equal(split.readings.counts.pages, 5);
|
|
78
|
+
assert.equal(split.readings.counts.lines, 1 + 2 + 1 + 2 + 1);
|
|
79
|
+
assert.equal(split.readings.counts.shared, 3);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('the inventory gains the same pages, after their surface, opening on a page that shows them', () => {
|
|
83
|
+
const inv = chrome.addChromePages(INVENTORY, split.chrome);
|
|
84
|
+
assert.deepEqual(inv.pages.map((p) => p.id), ['landing:index', 'landing:account', 'status:explore', 'landing:top-bar', 'landing:skip-link']);
|
|
85
|
+
const top = inv.pages.find((p) => p.id === 'landing:top-bar');
|
|
86
|
+
assert.deepEqual([top.surface, top.page, top.title, top.url], ['landing', 'top-bar', 'Top bar', 'https://{apex}/']);
|
|
87
|
+
assert.deepEqual(top.files, ['nav.js']);
|
|
88
|
+
assert.equal(inv.counts.pages, 5);
|
|
89
|
+
assert.deepEqual(inv.surfaces.map((s) => [s.id, s.pages]), [['landing', 4], ['status', 1]]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('a chrome page id is told from a page id', () => {
|
|
93
|
+
assert.equal(chrome.isChromePageId('landing:top-bar'), true);
|
|
94
|
+
assert.equal(chrome.isChromePageId('landing:index'), false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('only a shared line in a chrome section is chrome', () => {
|
|
98
|
+
assert.equal(chrome.isChromeLine(L('L1', 'top bar', 'x', 'placed')), false);
|
|
99
|
+
assert.equal(chrome.isChromeLine(L('L1', 'hero', 'x', 'shared')), false);
|
|
100
|
+
assert.equal(chrome.isChromeLine(L('L1', 'top bar', 'x', 'shared')), true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('the committed readings: no real page lists a skip link, top bar, navigation or footer line', () => {
|
|
104
|
+
const rd = pageData.loadReadings();
|
|
105
|
+
assert.equal(rd.ok, true);
|
|
106
|
+
for (const p of rd.data.pages) {
|
|
107
|
+
if (chrome.isChromePageId(p.id)) continue;
|
|
108
|
+
const bad = p.lines.filter(chrome.isChromeLine);
|
|
109
|
+
assert.equal(bad.length, 0, `${p.id} still lists ${bad.length} chrome lines`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('the committed data: every surface that had a top bar has a Top bar page, in both reads', () => {
|
|
114
|
+
const rd = pageData.loadReadings().data;
|
|
115
|
+
const inv = pageData.loadInventory().data;
|
|
116
|
+
for (const surface of ['builders', 'landing', 'status']) {
|
|
117
|
+
const id = `${surface}:top-bar`;
|
|
118
|
+
const r = rd.pages.find((p) => p.id === id);
|
|
119
|
+
assert.ok(r && r.lines.length > 0, `${id} in the readings`);
|
|
120
|
+
assert.ok(inv.pages.find((p) => p.id === id), `${id} in the inventory`);
|
|
121
|
+
assert.equal(new Set(r.lines.map((l) => l.text)).size, r.lines.length, `${id} lists each line once`);
|
|
122
|
+
}
|
|
123
|
+
assert.equal(rd.pages.length, inv.pages.length, 'the two reads name the same pages');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
127
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -70,7 +70,7 @@ const stripComments = (src) => src
|
|
|
70
70
|
// Task 1004435 added routes/copy-desk-artist.js (one artist's approved pages, a
|
|
71
71
|
// read) and artist-stats.js's readArtistPages under it.
|
|
72
72
|
const ROUTE_FILES = fs.readdirSync(path.join(MODULE_DIR, 'routes')).filter((f) => f.endsWith('.js')).sort().map((f) => `routes/${f}`);
|
|
73
|
-
const MODULE_FILES = ['registry.js', 'flags.js', 'proposals.js', 'pages.js', 'page-status.js', 'page-data.js', 'docx.js', 'page-decisions.js', 'artist-stats.js', 'auto-close.js', ...ROUTE_FILES];
|
|
73
|
+
const MODULE_FILES = ['registry.js', 'flags.js', 'proposals.js', 'pages.js', 'page-status.js', 'page-data.js', 'chrome-pages.js', 'docx.js', 'page-decisions.js', 'artist-stats.js', 'auto-close.js', ...ROUTE_FILES];
|
|
74
74
|
|
|
75
75
|
console.log('copy-desk: NO LIVE CMS');
|
|
76
76
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.21",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.23.
|
|
9
|
+
"version": "1.23.21",
|
|
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.23.
|
|
3
|
+
"version": "1.23.21",
|
|
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
|
@@ -10509,5 +10509,11 @@
|
|
|
10509
10509
|
"id": "1002983",
|
|
10510
10510
|
"text": "The Community home now shows a rising builder and the projects on the move this month, under the builders of the week, month and year."
|
|
10511
10511
|
}
|
|
10512
|
+
],
|
|
10513
|
+
"1.23.21": [
|
|
10514
|
+
{
|
|
10515
|
+
"id": "1004904",
|
|
10516
|
+
"text": "In Tweak Mode, the shared top bar, navigation, footer and skip link no longer clutter every page; each is now its own page to review once."
|
|
10517
|
+
}
|
|
10512
10518
|
]
|
|
10513
10519
|
}
|
|
@@ -62,6 +62,7 @@ const ci = require('./copy-inventory.js');
|
|
|
62
62
|
const proposals = require('../../modules/copy-desk/proposals.js');
|
|
63
63
|
// The page tweak format's one reader and writer (ADR 0341 D4), for --batch.
|
|
64
64
|
const pages = require('../../modules/copy-desk/pages.js');
|
|
65
|
+
const chromePages = require('../../modules/copy-desk/chrome-pages.js');
|
|
65
66
|
const { apiCall, arg, hasFlag, cliExit } = require('./cli-lib.js');
|
|
66
67
|
|
|
67
68
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -559,7 +560,8 @@ function rereadStalePages({ spawn = spawnSync } = {}) {
|
|
|
559
560
|
function readingFor(pageId, { readFile = (p) => fs.readFileSync(p, 'utf8') } = {}) {
|
|
560
561
|
try {
|
|
561
562
|
const doc = JSON.parse(readFile(path.join(REPO_ROOT, 'docs', 'page-readings.json')));
|
|
562
|
-
|
|
563
|
+
// A shared-chrome page (task 1004904) exists only in the derived reading.
|
|
564
|
+
return (chromePages.splitReadings(doc).readings.pages || []).find((p) => p.id === pageId) || null;
|
|
563
565
|
} catch { return null; }
|
|
564
566
|
}
|
|
565
567
|
|
|
@@ -87,10 +87,17 @@ function slotFilesIn(dir, { exists = (p) => fs.existsSync(p) } = {}) {
|
|
|
87
87
|
|
|
88
88
|
// ---- the browser half --------------------------------------------------------
|
|
89
89
|
|
|
90
|
+
// The inventory as Tweak Mode sees it, shared-chrome pages included (task 1004904):
|
|
91
|
+
// the committed file does not list them, so planning from it alone is unknown_page.
|
|
92
|
+
function derivedInventory() {
|
|
93
|
+
const r = require('../../modules/copy-desk/page-data.js').loadInventory();
|
|
94
|
+
return r.ok ? r.data : { pages: [] };
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
async function shoot({ pageId, phase, out }) {
|
|
91
98
|
const reader = require('./page-reader.js');
|
|
92
99
|
const kit = require('../../modules/ui-design/kit/lib');
|
|
93
|
-
const inventory =
|
|
100
|
+
const inventory = derivedInventory();
|
|
94
101
|
const planned = planForPage(pageId, { inventory, planRenders: reader.planRenders });
|
|
95
102
|
if (!planned.ok) { console.error(`tweak-renders: ${planned.code} (${pageId})`); return 2; }
|
|
96
103
|
const { page, plan } = planned;
|
|
@@ -172,7 +179,7 @@ async function main(args) {
|
|
|
172
179
|
if (!pageId) { console.error('usage: node scripts/bongos/tweak-renders.js shoot --page <id> --phase before|after --out <dir>'); return 2; }
|
|
173
180
|
if (cmd === 'plan') {
|
|
174
181
|
const reader = require('./page-reader.js');
|
|
175
|
-
const inventory =
|
|
182
|
+
const inventory = derivedInventory();
|
|
176
183
|
const planned = planForPage(pageId, { inventory, planRenders: reader.planRenders });
|
|
177
184
|
if (!planned.ok) { console.error(`tweak-renders: ${planned.code} (${pageId})`); return 2; }
|
|
178
185
|
for (const phase of taskVisuals.TWEAK_RENDER_PHASES) for (const s of shotsFor(phase, planned.plan)) console.log(`${s.slot}\t${s.width}px\t${s.renderMode}\t${planned.plan.url}`);
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.23.
|
|
84
|
+
const CORE_VERSION = '1.23.21'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -75,12 +75,12 @@ const errDetail = (b) => (b && b.error && b.error.details) || {};
|
|
|
75
75
|
|
|
76
76
|
const L = (key, section, text, placement, row) => ({ key, section, text, placement, ...(row || {}) });
|
|
77
77
|
const STUDIO_LINES = [
|
|
78
|
-
L('L0001', '
|
|
79
|
-
L('L0002', '
|
|
78
|
+
L('L0001', 'shell bar', 'Studio', 'shared', { file: 'modules/hall-ui/public/shell.js', line: 40, string_id: 'sh0001' }),
|
|
79
|
+
L('L0002', 'shell bar', 'Ctrl K', 'shared'),
|
|
80
80
|
L('L0003', 'greeting', 'Welcome back', 'placed', { file: 'modules/hall-ui/public/studio.html', line: 12, string_id: 'st0003' }),
|
|
81
81
|
L('L0004', 'greeting', '{…} pages tweaked', 'placed', { file: 'modules/hall-ui/public/studio.js', line: 88, string_id: 'st0004' }),
|
|
82
82
|
L('L0005', 'greeting', 'Your desk is quiet today', 'unplaced'),
|
|
83
|
-
L('L0006', '
|
|
83
|
+
L('L0006', 'shell foot', 'Resume where you left off', 'placed', { file: 'modules/hall-ui/public/studio.html', line: 90, string_id: 'st0006' }),
|
|
84
84
|
];
|
|
85
85
|
const INDEX_LINES = [
|
|
86
86
|
L('L0001', 'hero', 'Build here', 'placed', { file: 'modules/public-landing/public/index.html', line: 3, string_id: 'ix0001' }),
|
|
@@ -64,11 +64,11 @@ const errCode = (b) => (b && b.error && b.error.code) || null;
|
|
|
64
64
|
|
|
65
65
|
const L = (key, section, text, placement, row) => ({ key, section, text, placement, ...(row || {}) });
|
|
66
66
|
const STUDIO_LINES = [
|
|
67
|
-
L('L0001', '
|
|
68
|
-
L('L0002', '
|
|
67
|
+
L('L0001', 'shell bar', 'Studio', 'shared', { file: 'modules/hall-ui/public/shell.js', line: 40, string_id: 'sh0001' }),
|
|
68
|
+
L('L0002', 'shell bar', 'Ctrl K', 'shared'),
|
|
69
69
|
L('L0003', 'greeting', 'Welcome back', 'placed', { file: 'modules/hall-ui/public/studio.html', line: 12, string_id: 'st0003' }),
|
|
70
70
|
L('L0004', 'greeting', 'Your desk is quiet today', 'unplaced'),
|
|
71
|
-
L('L0005', '
|
|
71
|
+
L('L0005', 'shell foot', 'Resume where you left off', 'placed', { file: 'modules/hall-ui/public/studio.html', line: 90, string_id: 'st0005' }),
|
|
72
72
|
];
|
|
73
73
|
const readingsWith = (studioHash, lines = STUDIO_LINES) => ({
|
|
74
74
|
schema: 1,
|
|
@@ -96,13 +96,13 @@ try {
|
|
|
96
96
|
assert.equal(r.body.page.surface_word, 'builders hall');
|
|
97
97
|
assert.equal(r.body.reading.reading_hash, 'studio-h1');
|
|
98
98
|
assert.deepEqual(r.body.reading.lines.map((l) => [l.key, l.section, l.text, l.note]), [
|
|
99
|
-
['L0001', '
|
|
99
|
+
['L0001', 'shell bar', 'Studio', 'shared'],
|
|
100
100
|
// Shared shell text with no registry row cannot be applied mechanically,
|
|
101
101
|
// so it goes to an engineer at submit, and the note says so.
|
|
102
|
-
['L0002', '
|
|
102
|
+
['L0002', 'shell bar', 'Ctrl K', 'engineer'],
|
|
103
103
|
['L0003', 'greeting', 'Welcome back', null],
|
|
104
104
|
['L0004', 'greeting', 'Your desk is quiet today', 'engineer'],
|
|
105
|
-
['L0005', '
|
|
105
|
+
['L0005', 'shell foot', 'Resume where you left off', null],
|
|
106
106
|
]);
|
|
107
107
|
assert.deepEqual(r.body.notes, { shared: 'changes this on every page', engineer: 'goes to an engineer', guarded: 'must still say' });
|
|
108
108
|
assert.equal(r.body.round, null);
|
|
@@ -101,7 +101,8 @@ try {
|
|
|
101
101
|
|
|
102
102
|
const { status, body } = await getNext(ALICE);
|
|
103
103
|
assert.equal(status, 200, JSON.stringify(body));
|
|
104
|
-
assert.deepEqual(order(body), ['landing:projects', 'status:explore', 'builders:studio', 'landing:index']);
|
|
104
|
+
assert.deepEqual(order(body), ['landing:projects', 'status:explore', 'builders:studio', 'landing:index', 'landing:skip-link']);
|
|
105
|
+
// landing:skip-link is the surface's shared chrome, a page of its own, untweaked
|
|
105
106
|
assert.equal(body.recommended.why, '2 people asked for it');
|
|
106
107
|
assert.equal(body.recommended.why_line, 'landing · 2 people asked for it · untweaked');
|
|
107
108
|
assert.equal(body.alternatives[1].why, '1 line changed since your last pass', 'the round artist is the web claim, alice');
|
|
@@ -145,7 +146,7 @@ try {
|
|
|
145
146
|
assert.equal(take.body.page_id, 'status:explore');
|
|
146
147
|
const after = (await getNext(CAROL)).body;
|
|
147
148
|
assert.deepEqual(after.skipped.map((s) => s.page_id).sort(), ['landing:projects', 'status:explore']);
|
|
148
|
-
assert.deepEqual(order(after), ['builders:studio', 'landing:index']);
|
|
149
|
+
assert.deepEqual(order(after), ['builders:studio', 'landing:index', 'landing:skip-link', 'builders:top-bar']);
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
await test('a submitted round is in flight: skipped, and the claim agrees', async () => {
|
|
@@ -115,7 +115,8 @@ try {
|
|
|
115
115
|
assert.equal(p('builders:studio').status_label, 'Text tweaked 1x');
|
|
116
116
|
assert.equal(p('builders:studio').drift.changed_count, 1);
|
|
117
117
|
assert.equal(p('builders:studio').open_round.state, 'writing');
|
|
118
|
-
|
|
118
|
+
// Each surface's shared chrome is a page of its own now: landing gains a skip link page, builders a top bar page.
|
|
119
|
+
assert.deepEqual(body.surfaces.map((s) => s.line), ['1 of 3 tweaked', '1 of 2 tweaked', '0 of 1 tweaked']);
|
|
119
120
|
assert.deepEqual(body.readings, { ok: true, at: 'docs/page-readings.json' });
|
|
120
121
|
assert.deepEqual(body.inventory, { at: 'docs/page-inventory.json' });
|
|
121
122
|
});
|
|
@@ -65,8 +65,8 @@ const errDetail = (b) => (b && b.error && b.error.details) || {};
|
|
|
65
65
|
// and an unplaced line.
|
|
66
66
|
const L = (key, section, text, placement, row) => ({ key, section, text, placement, ...(row || {}) });
|
|
67
67
|
const STUDIO_LINES = [
|
|
68
|
-
L('L0001', '
|
|
69
|
-
L('L0002', '
|
|
68
|
+
L('L0001', 'shell bar', 'Studio', 'shared', { file: 'modules/hall-ui/public/shell.js', line: 40, string_id: 'sh0001' }),
|
|
69
|
+
L('L0002', 'shell bar', 'Ctrl K', 'shared'),
|
|
70
70
|
L('L0003', 'greeting', 'Welcome back', 'placed', { file: 'modules/hall-ui/public/studio.html', line: 12, string_id: 'st0003' }),
|
|
71
71
|
L('L0004', 'greeting', '{…} pages tweaked', 'placed', { file: 'modules/hall-ui/public/studio.js', line: 88, string_id: 'st0004' }),
|
|
72
72
|
L('L0005', 'greeting', 'Your desk is quiet today', 'unplaced'),
|
|
@@ -360,6 +360,34 @@ try {
|
|
|
360
360
|
guarded._setList(null);
|
|
361
361
|
}
|
|
362
362
|
});
|
|
363
|
+
|
|
364
|
+
// Task 1004904: a shared-chrome page is a page like any other, whole path.
|
|
365
|
+
await test('a chrome page: claimed, drafted and submitted through the real routes, its lines placed from the first occurrence', async () => {
|
|
366
|
+
const TOP = '/copy-desk/pages/builders:top-bar';
|
|
367
|
+
const chromeLine = (key, text, row) => L(key, 'top bar', text, 'shared', row);
|
|
368
|
+
const r0 = readingsWith(
|
|
369
|
+
{ hash: 'studio-h1', lines: [chromeLine('L0001', 'Projects', { file: 'modules/hall-ui/public/shell.js', line: 41, string_id: 'sh0041' }), chromeLine('L0002', 'Sign out', { file: 'modules/hall-ui/public/shell.js', line: 42, string_id: 'sh0042' }), ...STUDIO_LINES.slice(2)] },
|
|
370
|
+
{ hash: 'index-h2', lines: INDEX_LINES },
|
|
371
|
+
);
|
|
372
|
+
writeReadings(r0);
|
|
373
|
+
const studio = await call('GET', '/copy-desk/pages/builders:studio', ALICE);
|
|
374
|
+
assert.equal(studio.status, 200);
|
|
375
|
+
const page = await call('GET', TOP, ALICE);
|
|
376
|
+
assert.equal(page.status, 200, JSON.stringify(page.body));
|
|
377
|
+
assert.equal((await call('POST', `${TOP}/claim`, ALICE)).status, 201);
|
|
378
|
+
const read = await call('GET', `${TOP}/draft`, ALICE);
|
|
379
|
+
assert.equal(read.status, 200, JSON.stringify(read.body));
|
|
380
|
+
const hash = read.body.reading.reading_hash || read.body.reading_hash;
|
|
381
|
+
assert.ok(hash, 'the chrome page has a reading hash of its own');
|
|
382
|
+
assert.deepEqual(read.body.reading.lines.map((l) => l.text), ['Projects', 'Sign out']);
|
|
383
|
+
const saved = await call('PUT', `${TOP}/draft`, ALICE, { reading_hash: hash, lines: [{ key: 'L0001', after: 'Your projects' }] });
|
|
384
|
+
assert.equal(saved.status, 200, JSON.stringify(saved.body));
|
|
385
|
+
const sub = await call('POST', `${TOP}/submit`, ALICE);
|
|
386
|
+
assert.equal(sub.status, 200, JSON.stringify(sub.body));
|
|
387
|
+
const round = db.tasks.find((t) => t.source_ref === 'page-tweak/builders:top-bar/r1');
|
|
388
|
+
const batch = pages.parseBatchBlock(round.description).batch;
|
|
389
|
+
assert.deepEqual(batch.lines.map((l) => [l.key, l.file, l.line, l.before, l.after]), [['L0001', 'modules/hall-ui/public/shell.js', 41, 'Projects', 'Your projects']]);
|
|
390
|
+
});
|
|
363
391
|
} finally {
|
|
364
392
|
server.close();
|
|
365
393
|
pageData._setRoot(null);
|
|
@@ -69,7 +69,8 @@ const LEDGER_ROWS = [
|
|
|
69
69
|
// ---- the page-tweak ledger (Ines and one other artist) ----------------------
|
|
70
70
|
|
|
71
71
|
const pages = require('../modules/copy-desk/pages.js');
|
|
72
|
-
|
|
72
|
+
// The studio's own inventory: the real pages plus each surface's chrome pages.
|
|
73
|
+
const inventory = require('../modules/copy-desk/page-data.js').loadInventory().data;
|
|
73
74
|
const onSurface = (s) => inventory.pages.filter((p) => p.surface === s).map((p) => p.id);
|
|
74
75
|
const LANDING = onSurface('landing');
|
|
75
76
|
const BUILDERS = onSurface('builders');
|