@bongos/core 1.21.9 → 1.21.11

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.
@@ -1,43 +1,364 @@
1
1
  {
2
- "_": "THE STREET (task 1004525, goal 1000125) through modules/ui-design/kit/render.js. The page draws GET /government/docket, GET /goals, GET /goals/rollups and the viewer's GET /builders/:id/earnings; against the kit's own stub none exists, so every state renders the bare street (the nine civic buildings, no districts, no pins) with the readings saying the docket could not be read: a real state the floors must hold in. To look at the populated street, run it against the hall-preview harness, whose government__docket.json fixture carries the reference design's seven sample matters (3 need you, 2 moving, 2 settled) and whose goals fixtures carry the open goals:\n\n node scripts/hall-preview/server.js --port 4625 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/street.states.json --base-in http://localhost:4625 --base-out http://localhost:4625\n\nThe page follows the OS mode until the switch is pressed (then the hall's own 'hall-theme' preference), so the kit's dark/light emulation renders both modes.",
2
+ "_": "THE STREET (task 1004525, goal 1000125) and inside every building and district (task 1004526) through modules/ui-design/kit/render.js. The page draws GET /government/docket, GET /goals, GET /goals/rollups and the viewer's GET /builders/:id/earnings; against the kit's own stub none exists, so every state renders the bare street (the nine civic buildings, no districts, no pins) with the readings saying the docket could not be read: a real state the floors must hold in. To look at the populated street, run it against the hall-preview harness, whose government__docket.json fixture carries the reference design's seven sample matters (3 need you, 2 moving, 2 settled) and whose goals fixtures carry the open goals:\n\n node scripts/hall-preview/server.js --port 4625 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/street.states.json --base-in http://localhost:4625 --base-out http://localhost:4625\n\nThe page follows the OS mode until the switch is pressed (then the hall's own 'hall-theme' preference), so the kit's dark/light emulation renders both modes.",
3
3
  "page": "/builders/street",
4
4
  "surface": "hall-ui",
5
- "stub": { "prefix": "/builders/" },
5
+ "stub": {
6
+ "prefix": "/builders/"
7
+ },
6
8
  "modeQuery": false,
7
- "ignoreRequests": ["/api/(gds|bongos)/", "fonts\\.g(oogleapis|static)\\.com"],
8
- "ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b403\\b", "\\b404\\b", "Unauthorized", "Forbidden", "Not Found", "load failed", "fonts\\.g(oogleapis|static)\\.com"],
9
+ "ignoreRequests": [
10
+ "/api/(gds|bongos)/",
11
+ "fonts\\.g(oogleapis|static)\\.com"
12
+ ],
13
+ "ignoreConsole": [
14
+ "/api/(gds|bongos)/",
15
+ "\\b401\\b",
16
+ "\\b403\\b",
17
+ "\\b404\\b",
18
+ "Unauthorized",
19
+ "Forbidden",
20
+ "Not Found",
21
+ "load failed",
22
+ "fonts\\.g(oogleapis|static)\\.com"
23
+ ],
9
24
  "states": {
10
25
  "street": {
11
26
  "auth": true,
12
27
  "_": "The street: the header, the buildings in the order work moves with their names, state lines and group brackets, the pins, and the six readings.",
13
- "actions": [["wait", 900]],
14
- "expect": { "visible": ["#streetBoard", "#streetSigns", "#streetMode", "#streetHome"], "href": [["#streetHome", "/builders/city"]] }
28
+ "actions": [
29
+ [
30
+ "wait",
31
+ 900
32
+ ]
33
+ ],
34
+ "expect": {
35
+ "visible": [
36
+ "#streetBoard",
37
+ "#streetSigns",
38
+ "#streetMode",
39
+ "#streetHome"
40
+ ],
41
+ "href": [
42
+ [
43
+ "#streetHome",
44
+ "/builders/city"
45
+ ]
46
+ ]
47
+ }
15
48
  },
16
49
  "card": {
17
50
  "auth": true,
18
51
  "_": "A pin pressed: the matter's short card over the street.",
19
- "actions": [["wait", 900], ["click", ".street-pin"], ["wait", 400]],
20
- "expect": { "visible": ["#streetCard", "#streetCardClose"] }
52
+ "actions": [
53
+ [
54
+ "wait",
55
+ 900
56
+ ],
57
+ [
58
+ "click",
59
+ ".street-pin"
60
+ ],
61
+ [
62
+ "wait",
63
+ 400
64
+ ]
65
+ ],
66
+ "expect": {
67
+ "visible": [
68
+ "#streetCard",
69
+ "#streetCardClose"
70
+ ]
71
+ }
21
72
  },
22
73
  "switched": {
23
74
  "auth": true,
24
75
  "_": "The light/dark switch pressed once: the other mode, the street redrawn in it.",
25
- "actions": [["wait", 600], ["click", "#streetMode"], ["wait", 400]],
26
- "expect": { "visible": ["#streetBoard", "#streetSigns"] }
76
+ "actions": [
77
+ [
78
+ "wait",
79
+ 600
80
+ ],
81
+ [
82
+ "click",
83
+ "#streetMode"
84
+ ],
85
+ [
86
+ "wait",
87
+ 400
88
+ ]
89
+ ],
90
+ "expect": {
91
+ "visible": [
92
+ "#streetBoard",
93
+ "#streetSigns"
94
+ ]
95
+ }
96
+ },
97
+ "focus": {
98
+ "auth": true,
99
+ "_": "Keyboard: the wordmark back to the city focused, its ring drawn.",
100
+ "actions": [
101
+ [
102
+ "wait",
103
+ 600
104
+ ],
105
+ [
106
+ "focus",
107
+ "#streetHome"
108
+ ],
109
+ [
110
+ "wait",
111
+ 100
112
+ ]
113
+ ],
114
+ "expect": {
115
+ "activeId": "streetHome"
116
+ }
27
117
  },
28
118
  "here": {
29
119
  "auth": true,
30
- "_": "Arrived through a door: the address names the Charter, so its door and the readings that open it are marked.",
120
+ "_": "Arrived through a door: the address names the Charter, so the street gives way to the Charter in section (every floor, its sign, the lift at the chosen floor) and its panel.",
31
121
  "url": "/builders/street#building=charter",
32
- "actions": [["wait", 900]],
33
- "expect": { "visible": ["#streetBoard"] }
122
+ "actions": [
123
+ [
124
+ "wait",
125
+ 900
126
+ ]
127
+ ],
128
+ "expect": {
129
+ "visible": [
130
+ "#streetInside",
131
+ "#insideName",
132
+ "#insideLift",
133
+ "#insideBack",
134
+ "#streetSigns"
135
+ ],
136
+ "href": [
137
+ [
138
+ "#insideBack",
139
+ "/builders/street"
140
+ ]
141
+ ]
142
+ }
34
143
  },
35
- "focus": {
144
+ "inside-rules": {
36
145
  "auth": true,
37
- "_": "Keyboard: the wordmark back to the city focused, its ring drawn.",
38
- "actions": [["wait", 600], ["focus", "#streetHome"], ["wait", 100]],
39
- "expect": { "activeId": "streetHome" }
146
+ "_": "Inside rules: its floors in section, its panel, its record.",
147
+ "url": "/builders/street#building=rules",
148
+ "actions": [
149
+ [
150
+ "wait",
151
+ 900
152
+ ]
153
+ ],
154
+ "expect": {
155
+ "visible": [
156
+ "#streetInside",
157
+ "#insideName",
158
+ "#insidePanel"
159
+ ]
160
+ }
161
+ },
162
+ "inside-disputes": {
163
+ "auth": true,
164
+ "_": "Inside disputes: its floors in section, its panel, its record.",
165
+ "url": "/builders/street#building=disputes",
166
+ "actions": [
167
+ [
168
+ "wait",
169
+ 900
170
+ ]
171
+ ],
172
+ "expect": {
173
+ "visible": [
174
+ "#streetInside",
175
+ "#insideName",
176
+ "#insidePanel"
177
+ ]
178
+ }
179
+ },
180
+ "inside-people": {
181
+ "auth": true,
182
+ "_": "Inside people: its floors in section, its panel, its record.",
183
+ "url": "/builders/street#building=people",
184
+ "actions": [
185
+ [
186
+ "wait",
187
+ 900
188
+ ]
189
+ ],
190
+ "expect": {
191
+ "visible": [
192
+ "#streetInside",
193
+ "#insideName",
194
+ "#insidePanel"
195
+ ]
196
+ }
197
+ },
198
+ "inside-planning": {
199
+ "auth": true,
200
+ "_": "Inside planning: its floors in section, its panel, its record.",
201
+ "url": "/builders/street#building=planning",
202
+ "actions": [
203
+ [
204
+ "wait",
205
+ 900
206
+ ]
207
+ ],
208
+ "expect": {
209
+ "visible": [
210
+ "#streetInside",
211
+ "#insideName",
212
+ "#insidePanel"
213
+ ]
214
+ }
215
+ },
216
+ "inside-automation": {
217
+ "auth": true,
218
+ "_": "Inside automation: its floors in section, its panel, its record.",
219
+ "url": "/builders/street#building=automation",
220
+ "actions": [
221
+ [
222
+ "wait",
223
+ 900
224
+ ]
225
+ ],
226
+ "expect": {
227
+ "visible": [
228
+ "#streetInside",
229
+ "#insideName",
230
+ "#insidePanel"
231
+ ]
232
+ }
233
+ },
234
+ "inside-review": {
235
+ "auth": true,
236
+ "_": "Inside review: its floors in section, its panel, its record.",
237
+ "url": "/builders/street#building=review",
238
+ "actions": [
239
+ [
240
+ "wait",
241
+ 900
242
+ ]
243
+ ],
244
+ "expect": {
245
+ "visible": [
246
+ "#streetInside",
247
+ "#insideName",
248
+ "#insidePanel"
249
+ ]
250
+ }
251
+ },
252
+ "inside-release": {
253
+ "auth": true,
254
+ "_": "Inside release: its floors in section, its panel, its record.",
255
+ "url": "/builders/street#building=release",
256
+ "actions": [
257
+ [
258
+ "wait",
259
+ 900
260
+ ]
261
+ ],
262
+ "expect": {
263
+ "visible": [
264
+ "#streetInside",
265
+ "#insideName",
266
+ "#insidePanel"
267
+ ]
268
+ }
269
+ },
270
+ "inside-treasury": {
271
+ "auth": true,
272
+ "_": "Inside treasury: its floors in section, its panel, its record.",
273
+ "url": "/builders/street#building=treasury",
274
+ "actions": [
275
+ [
276
+ "wait",
277
+ 900
278
+ ]
279
+ ],
280
+ "expect": {
281
+ "visible": [
282
+ "#streetInside",
283
+ "#insideName",
284
+ "#insidePanel"
285
+ ]
286
+ }
287
+ },
288
+ "floor": {
289
+ "auth": true,
290
+ "_": "A floor picked: the lift slides to it, its frame and sign turn orange, and its block re-enters with the sign large.",
291
+ "url": "/builders/street#building=people",
292
+ "actions": [
293
+ [
294
+ "wait",
295
+ 900
296
+ ],
297
+ [
298
+ "click",
299
+ ".inside-floor[data-floor=\"0\"]"
300
+ ],
301
+ [
302
+ "wait",
303
+ 900
304
+ ]
305
+ ],
306
+ "expect": {
307
+ "visible": [
308
+ "#insideOpenB"
309
+ ]
310
+ }
311
+ },
312
+ "amend": {
313
+ "auth": true,
314
+ "_": "Change how this works pressed: it says, in words, that drafting an amendment from here is not built yet (task 1004530).",
315
+ "url": "/builders/street#building=charter",
316
+ "actions": [
317
+ [
318
+ "wait",
319
+ 900
320
+ ],
321
+ [
322
+ "click",
323
+ "#insideAmend"
324
+ ],
325
+ [
326
+ "wait",
327
+ 200
328
+ ]
329
+ ],
330
+ "expect": {
331
+ "visible": [
332
+ "#insideAmendNote"
333
+ ]
334
+ }
335
+ },
336
+ "district": {
337
+ "auth": true,
338
+ "_": "Inside a district: the goal's tasks as buildings on a road, shape the craft, stage the status, label the kind.",
339
+ "url": "/builders/street#district=25",
340
+ "actions": [
341
+ [
342
+ "wait",
343
+ 1200
344
+ ]
345
+ ],
346
+ "expect": {
347
+ "visible": [
348
+ "#streetInside",
349
+ "#insideDName",
350
+ "#insideLegend"
351
+ ]
352
+ }
40
353
  }
41
354
  },
42
- "reducedMotion": { "auth": true, "actions": [["wait", 600]] }
355
+ "reducedMotion": {
356
+ "auth": true,
357
+ "actions": [
358
+ [
359
+ "wait",
360
+ 600
361
+ ]
362
+ ]
363
+ }
43
364
  }
@@ -1,4 +1,4 @@
1
- # The Governor City's street — /street, the six readings, and the doors to the interiors (task 1004525)
1
+ # The Governor City's street — /street, the six readings, and the interiors behind its doors (tasks 1004525, 1004526)
2
2
 
3
3
  **R03 of goal 1000125.** Built 1:1 on the street view of the reference design [`docs/design/mocks/governor-city/Street.dc.html`](../../../docs/design/mocks/governor-city/README.md): one front-on row in the order work moves under five group brackets, a name and one state line under every building, pins with their labels always showing, work queued at Review as crates by its door, the header (wordmark back to the city, needs you, moving, your share this month, the light/dark switch) and the six readings along the bottom. The home city (`/city`, task 1004524) links here from start governing.
4
4
 
@@ -9,6 +9,25 @@ What it settled:
9
9
  - **Standalone, like the city.** No hall sidebar or top bar; it is the second entry in `tests/hall_audit.mjs`'s `STANDALONE_EXEMPT`, with the city's reason. It still loads `style.css` and reads and writes the hall's own `hall-theme`.
10
10
  - **The artboard is the street band.** The prototype's 1440 x 900 board less its 64 px header and 128 px footer, which the page lays out as real rows; `street.js` scales the 1440 x 708 band to the window, and on a phone keeps it near full size and lets the street scroll sideways inside its own window, so a pin stays a pin. The header fits the 80 px top-bar floor at 390 and 320 by going to two rows, its three figures as their own sideways strip.
11
11
  - **Real data where the prototype had samples.** One district per open goal (up to eight plots; past that seven stand and the eighth is a staked lot that opens the goals page), each goal's tasks drawn in proportion (built, scaffold, the prototype's `ready` slab), Review held while any source that stands in it can be read, its open matters as crates and the dashed "waiting for a tester" count pin, a reading off drawn as a dash and its reason. The full list is the header of `street-draw.js`.
12
- - **Every building and district is a door** to `/street#building=<key>` or `/street#district=<goal id>`, where task 1004526 builds the interiors; until then the street marks the building the address names. A pin opens the matter's short card (task 1004528 makes it act); its link is offered only for an on-site path.
12
+ - **Every building and district is a door** to `/street#building=<key>` or `/street#district=<goal id>`, where its interior opens in place of the street (below). A pin opens the matter's short card (task 1004528 makes it act); its link is offered only for an on-site path.
13
13
 
14
14
  Tests: `tests/hall_street.mjs` holds the kit, the row, the pins, the doors and the crates equal to the prototype by running its own `Component` class beside `street-draw.js` in both modes, then the docket rules and the page itself in a vm. The states file is `public/street.states.json`; render it against the hall-preview harness, whose `government__docket.json` fixture carries the seven sample matters.
15
+
16
+
17
+ ## Inside every building and district (task 1004526, R04)
18
+
19
+ Built 1:1 on `buildingView()` and `districtView()` of the same prototype. The interior is a view of this page, not a page of its own: when the address names a building or a district, `#streetInside` replaces the street band, and the header and the six readings stay (the reading for that building is marked).
20
+
21
+ Files: `street-inside.js` (`window.GovInside`, node-require-safe, pure): the prototype's `crown()`, `workItem()`, `floorSymbol()`, `symbolPath()` (all forty symbols) and the section drawing itself, verbatim, plus the real-data adapters. `street.js` builds the nodes; `street.css` carries the interior's styles under the `inside-` prefix.
22
+
23
+ - **The building in section.** Every storey a real floor (slab, cut walls with glazing, three lamps, a partition with a doorway, the framed sign with the floor's symbol, its name and state lettered on the wall, its count, its level number), dark empty floors up to the building's capacity, the crown per building, the lift in its shaft, the orange frame and orange sign on the chosen floor, and the annex lot once the building is full. An unheld Review is the dashed ghost (`GovStreet.reviewHeldOf()`, so the street and the interior agree).
24
+ - **The right panel.** Name and what it holds, who holds it (seat chips), how it decides (the rule glyph), **change how this works**, the fill meter with its growth line, on the table here, the chosen floor with its sign large, and the record as monthly filing drawers.
25
+ - **Floors are the prototype's structure; a number appears only when the docket holds it.** `GovInside.PLAN` keeps each building's floors, order and capacity. A floor bound to a docket source shows that source's open matters at this building (count, line, needs you, the lines); one bound to a reading shows its figure; Planning's Goals floor counts the open goals. Every other floor says "not recorded yet" on the wall, lettered faint, with no number. A source that is off, coming, not yours or unreadable says which, never a figure. Filling every floor with real data is task 1004527, through `floorFor()` and `PLAN`.
26
+ - **Who holds it.** The docket names no holders yet (task 1004529), so the seat is an empty dashed chip and says "who holds it is not set on this project yet". The rule is the one the building's open matters are decided by, else the design's default, which the words call "by default". `holderOf()` reads a docket building's `holders` / `rule` / `who` first, so 1004529 lands without touching the drawing.
27
+ - **The lift and the flip.** The lift is one node kept across draws, so picking a floor slides it by a CSS transform transition; the chosen floor's block is two blocks that take turns (`flipA` / `flipB`), so it re-enters on every pick. Both are dead under reduced motion.
28
+ - **Change how this works** is present and honest: pressed, it says drafting an amendment from here is not built yet (task 1004530 wires it).
29
+ - **On the table here** rows open the same short card a pin opens, through `openCard()`; Escape gives focus back to the row.
30
+ - **Districts.** The goal's tasks (`GET /tasks?goal_id=N`, read once per goal): shape from the discipline (tower engineer, studio artist, glass front UI, pavilion ideator; none is a tower), stage from the status (backlog and blocked a staked plot, ready a slab, active a scaffold, completed and confirmed built with a flag, shipped lit), label the kind; height from the estimate. Up to ten stand on the road, work in flight first; the rest are counted with a link to the goal. Each one opens its task.
31
+ - **Narrow.** Under 900 px the board becomes a column: the drawing in its own sideways strip (never scaled under 0.6, so a floor stays a 24 px target), then the panel.
32
+
33
+ Tests: `tests/hall_street_inside.mjs` runs the prototype's own `Component` beside `street-inside.js` on the prototype's sample floors and holds every building's section, panel bindings and every district equal to it in both modes, then the docket rules (no invented numbers) and the page in a vm (the persistent lift, the flip, the drawers, the amendment note, the card from a table row, back to the street, a district's tasks). The states file renders every building, a floor pick, the amendment note and a district.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.9",
3
+ "version": "1.21.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.9",
9
+ "version": "1.21.11",
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.9",
3
+ "version": "1.21.11",
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",
@@ -8810,5 +8810,17 @@
8810
8810
  "id": "1004529",
8811
8811
  "text": "Each project can now set who decides what in each part of its government: the charter, rules, disputes, people, planning, automation, review, release and treasury. For each part it names who decides and how: one person decides"
8812
8812
  }
8813
+ ],
8814
+ "1.21.10": [
8815
+ {
8816
+ "id": "1004545",
8817
+ "text": "The list of every piece of on-screen text now comes out the same on a Windows computer as on the build server, so updating it on Windows no longer breaks the automatic checks. Notes the authors left inside page source are no l"
8818
+ }
8819
+ ],
8820
+ "1.21.11": [
8821
+ {
8822
+ "id": "1004526",
8823
+ "text": "Every building on the governor's street now opens. Step through a door and the building appears in section, drawn exactly like the prototype: each floor a real storey with its sign, name and state on the wall, dark empty floor"
8824
+ }
8813
8825
  ]
8814
8826
  }
@@ -119,8 +119,12 @@ function lineAt(starts, index) {
119
119
 
120
120
  // Does this look like prose a person reads, rather than code that happens to be a
121
121
  // string? Conservative on purpose — a false positive costs an artist's attention.
122
+ //
123
+ // Measured with CRLF folded to LF: the length cap must give a multi-line run the
124
+ // same verdict on a Windows checkout as on CI's, or the committed registry differs
125
+ // by platform (task 1004545 — a 396-char run was 403 under CRLF and vanished).
122
126
  function looksLikeCopy(raw) {
123
- const s = String(raw).trim();
127
+ const s = String(raw).replace(/\r\n/g, '\n').trim();
124
128
  if (s.length < 4 || s.length > 400) return false;
125
129
  if (!/[A-Za-z]{2}/.test(s)) return false; // needs actual letters
126
130
  if (!/\s/.test(s) && !/[.!?]$/.test(s)) return false; // one bare word is a token
@@ -155,12 +159,25 @@ function trimmedSpan(group, groupStart) {
155
159
  return { start: groupStart + lead, end: groupStart + group.length - trail };
156
160
  }
157
161
 
158
- // Strip <script>/<style> bodies so their contents are not read as page text.
159
- // Replaced with spaces of equal length so every line number stays truthful.
162
+ // Strip HTML comments and <script>/<style> bodies so their contents are not read
163
+ // as page text. Replaced with spaces of equal length so every line number — and
164
+ // every span copy-apply.js writes through — stays truthful.
165
+ //
166
+ // Comments are author notes, never something a visitor reads (task 1004545: a
167
+ // header comment's literal `<a>` opened a fake text node). ONE alternation, not
168
+ // three passes, so whichever construct opens first wins, as in a browser: a
169
+ // `<style>` named inside a comment cannot swallow the page up to the real
170
+ // `</style>`, and a `<!--` inside a script string cannot eat past `</script>`.
171
+ //
172
+ // A comment keeps its `<` and `>` and blanks only its inside: marker comments
173
+ // (`<!--apex-only:start-->`, `<!--brand-if:…-->`) split a sentence into segments
174
+ // the server shows or drops independently, so each segment must stay its own row
175
+ // with its own stable id — blanking the whole comment would fuse them.
160
176
  function stripEmbedded(html) {
161
- return html
162
- .replace(/<script\b[\s\S]*?<\/script>/gi, (m) => m.replace(/[^\n]/g, ' '))
163
- .replace(/<style\b[\s\S]*?<\/style>/gi, (m) => m.replace(/[^\n]/g, ' '));
177
+ return html.replace(/<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script>|<style\b[\s\S]*?<\/style>/gi, (m) => {
178
+ const blank = m.replace(/[^\r\n]/g, ' ');
179
+ return m.startsWith('<!--') ? `<${blank.slice(1, -1)}>` : blank;
180
+ });
164
181
  }
165
182
 
166
183
  function fromHtml({ source, rel }) {
@@ -193,7 +210,7 @@ function fromHtml({ source, rel }) {
193
210
 
194
211
  // 3. <title> is the page's own name — always copy.
195
212
  const titleRe = /<title>([^<]{2,200})<\/title>/gi;
196
- while ((m = titleRe.exec(source)) !== null) {
213
+ while ((m = titleRe.exec(cleaned)) !== null) {
197
214
  const titleStart = m.index + '<title>'.length;
198
215
  out.push({ text: m[1].replace(/\s+/g, ' ').trim(), file: rel, line: lineAt(starts, m.index), origin: 'html-title', confidence: 'certain', ...trimmedSpan(m[1], titleStart) });
199
216
  }
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.9'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.11'; // 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');
@@ -98,6 +98,68 @@ test('stripEmbedded keeps line numbers truthful', () => {
98
98
  assert.equal(copy.line, 6, 'a stripped script must not shift the lines after it — a wrong line number sends an artist to the wrong place');
99
99
  });
100
100
 
101
+ // Task 1004545. street.html's header comment held a literal `<a>`, which opened a
102
+ // fake text node; under CRLF that run was 403 characters, under LF 396 — so a
103
+ // Windows regen dropped a row CI kept and redded "the committed artifacts match".
104
+ // The fixture rebuilds both halves: a tag inside a comment, and a run sized to
105
+ // straddle the cap.
106
+ // Eight 45-char lines under a 28-char head: 396 chars on LF, 404 on CRLF.
107
+ const NEAR_CAP_LINES = Array.from({ length: 8 }, () => ` ${'word '.repeat(8)}s.`);
108
+ const EOL_FIXTURE = [
109
+ '<!doctype html>',
110
+ '<!-- Author note: a building is a door (an <a>) to its interior.',
111
+ ' The template is street.css, the <style> on the tokens.',
112
+ '-->',
113
+ '<style>.x { color: red; }</style>',
114
+ '<p>A real sentence on the page,',
115
+ ...NEAR_CAP_LINES.slice(0, -1),
116
+ `${NEAR_CAP_LINES.at(-1)}</p>`,
117
+ '<p title="A tooltip a person reads">Second real line.</p>',
118
+ ].join('\n');
119
+
120
+ test('LF and CRLF checkouts extract identical rows (task 1004545)', () => {
121
+ const lf = inv.fromHtml({ source: EOL_FIXTURE, rel: 'p.html' });
122
+ const crlf = inv.fromHtml({ source: EOL_FIXTURE.replace(/\n/g, '\r\n'), rel: 'p.html' });
123
+ const strip = (rows) => rows.map(({ text, line, origin }) => ({ text, line, origin }));
124
+ assert.deepEqual(strip(crlf), strip(lf), 'the registry must not depend on the checkout\'s line endings');
125
+ const run = EOL_FIXTURE.slice(EOL_FIXTURE.indexOf('A real'), EOL_FIXTURE.indexOf('</p>'));
126
+ assert.ok(run.length <= 400 && run.replace(/\n/g, '\r\n').length > 400, 'the fixture must straddle the cap, or this test proves nothing');
127
+ // The multi-line sentence sits under the cap only when CRLF counts as one
128
+ // character — so this is the case that reds if the length rule regresses.
129
+ assert.ok(lf.some((r) => r.text.startsWith('A real sentence on the page')), 'a multi-line run under the cap is copy on LF');
130
+ assert.ok(crlf.some((r) => r.text.startsWith('A real sentence on the page')), 'and the same run is copy on CRLF');
131
+ });
132
+
133
+ test('HTML comments are never copy, and a tag named inside one swallows nothing', () => {
134
+ const rows = inv.fromHtml({ source: EOL_FIXTURE, rel: 'p.html' });
135
+ const texts = rows.map((r) => r.text);
136
+ assert.ok(!texts.some((t) => /Author note|its interior|the tokens/.test(t)), 'comment text is an author note, not page copy');
137
+ // The comment names `<style>`: a strip pass that ran style-first would blank from
138
+ // there to the real </style> — harmless here, but the same shape with <script>
139
+ // ahead of real copy would erase it. Both real lines must survive.
140
+ assert.ok(texts.includes('Second real line.'));
141
+ assert.ok(texts.includes('A tooltip a person reads'));
142
+ const real = rows.find((r) => r.text === 'Second real line.');
143
+ assert.equal(real.line, 15, 'a stripped comment must not shift the lines after it');
144
+ // A span still addresses the file's own characters (copy-apply writes through it).
145
+ assert.equal(EOL_FIXTURE.slice(real.start, real.end), 'Second real line.');
146
+ });
147
+
148
+ test('a marker comment still splits copy into the segments the server drops independently', () => {
149
+ // The landing footer: the first sentence is apex-only, the second is not. One
150
+ // fused row would give both a new id and hide that a child instance drops half.
151
+ const src = '<p><!--apex-only:start-->A non-profit, capture-resistant commons. <!--apex-only:end-->Built by agents, on the platform.</p>';
152
+ const texts = inv.fromHtml({ source: src, rel: 'p.html' }).map((r) => r.text);
153
+ assert.ok(texts.includes('A non-profit, capture-resistant commons.'));
154
+ assert.ok(texts.includes('Built by agents, on the platform.'));
155
+ });
156
+
157
+ test('a comment opener inside a script string does not eat the page after it', () => {
158
+ const src = '<script>const s = "<!--";</script>\n<p>Copy after the script.</p>\n<!-- x -->';
159
+ const texts = inv.fromHtml({ source: src, rel: 'p.html' }).map((r) => r.text);
160
+ assert.ok(texts.includes('Copy after the script.'));
161
+ });
162
+
101
163
  test('the registry is deterministic and internally consistent', () => {
102
164
  const a = inv.buildRegistry();
103
165
  const b = inv.buildRegistry();
@@ -377,7 +377,7 @@ test('the page holds the prototype\'s header, the street, the readings and the c
377
377
  for (const id of ['hudNeeds', 'hudMoving', 'hudShare', 'streetMode', 'streetSigns', 'streetPins', 'streetDoors', 'streetCard']) assert.match(html, new RegExp(`id="${id}"`), id);
378
378
  assert.doesNotMatch(html, /app-sidebar|shell\.js/, 'no hall chrome, as the city');
379
379
  const scripts = [...html.matchAll(/<script src="\/builders\/([a-z-]+)\.js/g)].map((m) => m[1]);
380
- assert.deepEqual(scripts, ['city-draw', 'street-draw', 'street'], 'the shared kit loads first');
380
+ assert.deepEqual(scripts, ['city-draw', 'street-draw', 'street-inside', 'street'], 'the shared kit loads first, then the interiors (task 1004526)');
381
381
  assert.equal(City.STREET_HREF, '/builders/street', 'the city\'s door is this page');
382
382
  });
383
383