@bongos/core 1.21.41 → 1.21.43
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 +64 -64
- package/docs/copy-inventory.md +10 -11
- package/docs/copy-registry.json +11 -20
- package/docs/design/craft-mode-direction.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -2
- package/docs/page-readings.json +3467 -3507
- package/modules/autonomy/governor-docket.js +6 -1
- package/modules/hall-ui/public/atlas.html +3 -3
- package/modules/hall-ui/public/blockers.html +3 -3
- package/modules/hall-ui/public/board-room.html +3 -3
- package/modules/hall-ui/public/city.html +3 -3
- package/modules/hall-ui/public/collab.html +3 -3
- package/modules/hall-ui/public/commands.html +3 -3
- package/modules/hall-ui/public/copy-desk.html +3 -3
- package/modules/hall-ui/public/craft-mode.js +10 -15
- package/modules/hall-ui/public/deploy.html +3 -3
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/drachmae.html +3 -3
- package/modules/hall-ui/public/fleet.html +3 -3
- package/modules/hall-ui/public/gate.html +3 -3
- package/modules/hall-ui/public/goal-map.html +3 -3
- package/modules/hall-ui/public/goals.html +3 -3
- package/modules/hall-ui/public/government.html +3 -3
- package/modules/hall-ui/public/idea.html +3 -3
- package/modules/hall-ui/public/ideas.html +3 -3
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +3 -3
- package/modules/hall-ui/public/primer.html +3 -3
- package/modules/hall-ui/public/profile.html +3 -3
- package/modules/hall-ui/public/project-settings.html +3 -3
- package/modules/hall-ui/public/ranks.html +3 -3
- package/modules/hall-ui/public/roadmap.html +3 -3
- package/modules/hall-ui/public/roster.html +3 -3
- package/modules/hall-ui/public/sessions.html +3 -3
- package/modules/hall-ui/public/settings.html +3 -3
- package/modules/hall-ui/public/street-card.css +13 -0
- package/modules/hall-ui/public/street-card.js +61 -9
- package/modules/hall-ui/public/street-card.states.json +72 -0
- package/modules/hall-ui/public/street.html +3 -3
- package/modules/hall-ui/public/studio.html +3 -3
- package/modules/hall-ui/public/style.css +4 -22
- package/modules/hall-ui/public/task.html +3 -3
- package/modules/hall-ui/public/thinking.html +3 -3
- package/modules/hall-ui/public/tweak-editor.html +3 -3
- package/modules/hall-ui/public/watch.html +3 -3
- package/modules/hall-ui/public/work.html +3 -3
- package/modules/hall-ui/records/craft-mode.md +5 -5
- package/modules/hall-ui/records/governor-street.md +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/craft_mode.mjs +27 -10
- package/tests/craft_mode_looks.mjs +5 -5
- package/tests/craft_mode_nav.mjs +7 -7
- package/tests/governor_docket.mjs +3 -1
- package/tests/hall_matter_card.mjs +71 -0
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/page_notes_panel.mjs +1 -1
|
@@ -101,10 +101,25 @@
|
|
|
101
101
|
if (names.length <= 1) return names.join('');
|
|
102
102
|
return names.slice(0, -1).join(', ') + ' and ' + names[names.length - 1];
|
|
103
103
|
}
|
|
104
|
+
// a text as one run of words: every whitespace run is one space
|
|
105
|
+
function norm(s) { return str(s).replace(/\s+/g, ' ').trim(); }
|
|
104
106
|
function clip(s, n) {
|
|
105
|
-
var t =
|
|
107
|
+
var t = norm(s);
|
|
106
108
|
return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, '') + '…' : t;
|
|
107
109
|
}
|
|
110
|
+
// THE COMPACT CARD'S LINE RULE (task 1004568). The card is only as tall as
|
|
111
|
+
// its words need: every part of it has a cap, and anything longer is cut at
|
|
112
|
+
// a word boundary with an ellipsis (clip(): the desk's line rule, GovDesk.fit(),
|
|
113
|
+
// but never mid-word), with the whole text under "more". The caps are
|
|
114
|
+
// characters at the card's 390px width: the kicker, the fact and a listed
|
|
115
|
+
// line are one line; the title two; a voice's reason two; the proposer's
|
|
116
|
+
// words three.
|
|
117
|
+
var LINE = { where: 40, title: 64, fact: 44, line: 44, reason: 96, quote: 132 };
|
|
118
|
+
// the first line of a text, as words (a fact is one line of it)
|
|
119
|
+
function firstLine(s) {
|
|
120
|
+
var lines = str(s).split(/\r?\n/).map(function (l) { return l.replace(/^[#>*\-\s]+/, '').replace(/\s+/g, ' ').trim(); }).filter(Boolean);
|
|
121
|
+
return lines[0] || '';
|
|
122
|
+
}
|
|
108
123
|
// the first paragraph of a markdown text, as words
|
|
109
124
|
function firstPara(md) {
|
|
110
125
|
var paras = str(md).split(/\r?\n\s*\r?\n/).map(function (p) { return p.replace(/^[#>*\-\s]+/gm, '').replace(/\s+/g, ' ').trim(); }).filter(Boolean);
|
|
@@ -273,7 +288,7 @@
|
|
|
273
288
|
var votes = (item && item.votes) || [];
|
|
274
289
|
var out = [];
|
|
275
290
|
votes.filter(function (v) { return v.direction === 'no'; }).forEach(function (v) {
|
|
276
|
-
out.push({ side: 'against', by: firstName(personName(v)) + ' · objects to ' + sectionWords(v.section_key), words:
|
|
291
|
+
out.push({ side: 'against', by: firstName(personName(v)) + ' · objects to ' + sectionWords(v.section_key), words: norm(v.reason_md) });
|
|
277
292
|
});
|
|
278
293
|
var yes = votes.filter(function (v) { return v.direction === 'yes'; }).map(function (v) { return firstName(personName(v)); });
|
|
279
294
|
if (yes.length && out.length) out.unshift({ side: 'for', by: 'for', words: list(yes) });
|
|
@@ -319,7 +334,7 @@
|
|
|
319
334
|
var kind = subjectKind(item);
|
|
320
335
|
var proposer = m.proposer ? firstName(m.proposer.name || m.proposer.login) : '';
|
|
321
336
|
var words = proposerWords(item);
|
|
322
|
-
if (words) cv.quote = { by: proposer, words:
|
|
337
|
+
if (words) cv.quote = { by: proposer, words: words };
|
|
323
338
|
cv.change = changeOf(item, ctx.docket, ctx.constitution);
|
|
324
339
|
cv.revision = revisionOf(item, ctx.room);
|
|
325
340
|
var b = ballotOf(item, m, ctx.viewerId);
|
|
@@ -399,6 +414,9 @@
|
|
|
399
414
|
var who = m.proposer ? str(m.proposer.name || m.proposer.login) : '';
|
|
400
415
|
cv.subject = { kind: m.source, id: id, who: who, goalId: m.source === 'goal_join_requests' ? joinGoalId(m) : null };
|
|
401
416
|
var mine = m.state === 'needs';
|
|
417
|
+
// its one fact (task 1004568): the first line of the matter's detail (the
|
|
418
|
+
// switch's reason, a blocker's body), cut by compact(); "more" has it whole
|
|
419
|
+
if (m.detail) cv.fact = firstLine(m.detail);
|
|
402
420
|
if (m.source === 'access_requests') {
|
|
403
421
|
if (who) cv.asked = who + ' asked to join as a builder' + (/vouched/.test(str(m.where)) ? ', and someone vouched for them.' : '.');
|
|
404
422
|
if (!mine) { cv.readOnly = true; cv.result = 'Not yours to decide.'; return cv; }
|
|
@@ -470,7 +488,7 @@
|
|
|
470
488
|
if (cv.revision) history.push('an earlier sitting sent it back to be revised');
|
|
471
489
|
(item.votes || []).slice().sort(function (a, b) { return (Date.parse(a.cast_at) || 0) - (Date.parse(b.cast_at) || 0); }).forEach(function (v) {
|
|
472
490
|
var by = personName(v) || 'someone';
|
|
473
|
-
var said = v.direction === 'yes' ? by + ' voted for' : by + ' objected to ' + sectionWords(v.section_key) + (v.reason_md ? ': ' +
|
|
491
|
+
var said = v.direction === 'yes' ? by + ' voted for' : by + ' objected to ' + sectionWords(v.section_key) + (v.reason_md ? ': ' + bound(str(v.reason_md).replace(/\s+/g, ' ').trim()) : '');
|
|
474
492
|
history.push([dayOf(v.cast_at), said].filter(Boolean).join(' · '));
|
|
475
493
|
});
|
|
476
494
|
} else if (m) {
|
|
@@ -497,9 +515,9 @@
|
|
|
497
515
|
key: short.key, building: short.building, where: short.where, when: short.when, title: short.title, href: short.href,
|
|
498
516
|
source: null, quote: null, change: null, revision: null, seats: [], tally: '', voices: [], counsel: null,
|
|
499
517
|
result: short.result, actions: [], textActions: [], reviseForm: null, readOnly: false, loading: false, lines: short.lines || [], subject: null,
|
|
500
|
-
more: null, row: null,
|
|
518
|
+
more: null, row: null, fact: '',
|
|
501
519
|
};
|
|
502
|
-
if (short.key === 'queue') { cv.source = 'queue'; cv.readOnly = true; return cv; }
|
|
520
|
+
if (short.key === 'queue') { cv.source = 'queue'; cv.readOnly = true; return compact(cv); }
|
|
503
521
|
var m = findMatter(c.docket, c.key);
|
|
504
522
|
cv.source = m.source;
|
|
505
523
|
var viewerId = c.viewerId != null ? c.viewerId : (c.docket && c.docket.viewer ? c.docket.viewer.id : null);
|
|
@@ -511,14 +529,47 @@
|
|
|
511
529
|
cv.readOnly = true;
|
|
512
530
|
cv.tally = short.tally;
|
|
513
531
|
cv.result = c.room ? 'This sitting could not be read just now.' : 'Reading the sitting…';
|
|
514
|
-
return cv;
|
|
532
|
+
return compact(cv);
|
|
515
533
|
}
|
|
516
534
|
boardCard(cv, m, item, { docket: c.docket, room: c.room, constitution: c.constitution, viewerId: viewerId });
|
|
517
535
|
cv.more = moreOf(cv, m, item);
|
|
518
|
-
return cv;
|
|
536
|
+
return compact(cv);
|
|
519
537
|
}
|
|
520
538
|
sourceCard(cv, m);
|
|
521
539
|
cv.more = moreOf(cv, m, null);
|
|
540
|
+
return compact(cv);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Every part of the compact card under the line rule (task 1004568): the
|
|
544
|
+
// kicker, the title, the fact, the proposer's words, the listed lines and a
|
|
545
|
+
// voice's reason. What
|
|
546
|
+
// was cut and is not already in "more" is added there whole, so nothing a
|
|
547
|
+
// cut hides is lost.
|
|
548
|
+
function compact(cv) {
|
|
549
|
+
var whole = [];
|
|
550
|
+
function cut(s, n) {
|
|
551
|
+
var t = norm(s);
|
|
552
|
+
var c = clip(t, n);
|
|
553
|
+
if (c !== t) whole.push(t);
|
|
554
|
+
return c;
|
|
555
|
+
}
|
|
556
|
+
cv.where = cut(cv.where, LINE.where);
|
|
557
|
+
cv.title = cut(cv.title, LINE.title);
|
|
558
|
+
cv.fact = cut(cv.fact, LINE.fact);
|
|
559
|
+
if (cv.quote && cv.quote.words) cv.quote = { by: cv.quote.by, words: cut(cv.quote.words, LINE.quote) };
|
|
560
|
+
cv.lines = (cv.lines || []).map(function (l) { return cut(l, LINE.line); });
|
|
561
|
+
cv.voices = (cv.voices || []).map(function (v) {
|
|
562
|
+
var w = cut(v.words, LINE.reason);
|
|
563
|
+
return w === v.words ? v : { side: v.side, by: v.by, words: w };
|
|
564
|
+
});
|
|
565
|
+
if (whole.length) {
|
|
566
|
+
var mo = cv.more || { text: [], who: '', when: '', history: [] };
|
|
567
|
+
var have = mo.text.map(function (x) { return x.words; }).concat(mo.history).join('\n');
|
|
568
|
+
// compared as "more" holds it: bound() may have shortened a long text there
|
|
569
|
+
var add = whole.filter(function (t) { return have.indexOf(bound(t).replace(/…$/, '')) < 0; }).map(function (t) { return { label: '', words: bound(t) }; });
|
|
570
|
+
mo.text = add.concat(mo.text);
|
|
571
|
+
cv.more = mo;
|
|
572
|
+
}
|
|
522
573
|
return cv;
|
|
523
574
|
}
|
|
524
575
|
|
|
@@ -807,6 +858,7 @@
|
|
|
807
858
|
function render(doc, host, cv, on, ui) {
|
|
808
859
|
var u = ui || {};
|
|
809
860
|
while (host.firstChild) host.removeChild(host.firstChild);
|
|
861
|
+
if (cv.fact) host.appendChild(el(doc, 'p', 'mcard-fact', cv.fact));
|
|
810
862
|
if (cv.quote && cv.quote.words) host.appendChild(el(doc, 'p', 'mcard-quote', (cv.quote.by ? cv.quote.by + ': ' : '') + cv.quote.words));
|
|
811
863
|
if (cv.lines && cv.lines.length) {
|
|
812
864
|
var ul = el(doc, 'ul', 'street-card__lines');
|
|
@@ -997,7 +1049,7 @@
|
|
|
997
1049
|
var api = {
|
|
998
1050
|
API: API, SECTIONS: SECTIONS, needsRoom: needsRoom, itemIdOf: itemIdOf, subjectKind: subjectKind,
|
|
999
1051
|
model: model, actsCard: actsCard, moreOf: moreOf, request: request, orderRequest: orderRequest, check: check, answer: answer, refusal: refusal, render: render,
|
|
1000
|
-
memberWords: memberWords, windowWords: windowWords,
|
|
1052
|
+
memberWords: memberWords, windowWords: windowWords, LINE: LINE, compact: compact,
|
|
1001
1053
|
// the wording change, for a drafted amendment's struck and inserted text (task 1004530)
|
|
1002
1054
|
change: changeOf,
|
|
1003
1055
|
};
|
|
@@ -649,6 +649,78 @@
|
|
|
649
649
|
"#mcardMore"
|
|
650
650
|
]
|
|
651
651
|
}
|
|
652
|
+
},
|
|
653
|
+
"automation": {
|
|
654
|
+
"auth": true,
|
|
655
|
+
"_": "the switch is off with a long reason (task 1004568): the kicker is building · floor only, the reason is one cut line, and the card is only as tall as its words.",
|
|
656
|
+
"actions": [
|
|
657
|
+
[
|
|
658
|
+
"wait",
|
|
659
|
+
900
|
|
660
|
+
],
|
|
661
|
+
[
|
|
662
|
+
"clickIfVisible",
|
|
663
|
+
"[data-craft-close]"
|
|
664
|
+
],
|
|
665
|
+
[
|
|
666
|
+
"wait",
|
|
667
|
+
300
|
|
668
|
+
],
|
|
669
|
+
[
|
|
670
|
+
"dispatchClick",
|
|
671
|
+
".street-pin[data-matter=\"autobongos_fence:fence\"]"
|
|
672
|
+
],
|
|
673
|
+
[
|
|
674
|
+
"wait",
|
|
675
|
+
600
|
|
676
|
+
]
|
|
677
|
+
],
|
|
678
|
+
"expect": {
|
|
679
|
+
"visible": [
|
|
680
|
+
"#streetCard",
|
|
681
|
+
".mcard-fact",
|
|
682
|
+
"[data-act=\"switch_on\"]"
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
"automation-more": {
|
|
687
|
+
"auth": true,
|
|
688
|
+
"_": "the same card with more open: the switch reason in full (task 1004568).",
|
|
689
|
+
"actions": [
|
|
690
|
+
[
|
|
691
|
+
"wait",
|
|
692
|
+
900
|
|
693
|
+
],
|
|
694
|
+
[
|
|
695
|
+
"clickIfVisible",
|
|
696
|
+
"[data-craft-close]"
|
|
697
|
+
],
|
|
698
|
+
[
|
|
699
|
+
"wait",
|
|
700
|
+
300
|
|
701
|
+
],
|
|
702
|
+
[
|
|
703
|
+
"dispatchClick",
|
|
704
|
+
".street-pin[data-matter=\"autobongos_fence:fence\"]"
|
|
705
|
+
],
|
|
706
|
+
[
|
|
707
|
+
"wait",
|
|
708
|
+
600
|
|
709
|
+
],
|
|
710
|
+
[
|
|
711
|
+
"click",
|
|
712
|
+
"[data-act=\"more\"]"
|
|
713
|
+
],
|
|
714
|
+
[
|
|
715
|
+
"wait",
|
|
716
|
+
300
|
|
717
|
+
]
|
|
718
|
+
],
|
|
719
|
+
"expect": {
|
|
720
|
+
"visible": [
|
|
721
|
+
"#mcardMore"
|
|
722
|
+
]
|
|
723
|
+
}
|
|
652
724
|
}
|
|
653
725
|
},
|
|
654
726
|
"reducedMotion": {
|
|
@@ -203,9 +203,9 @@
|
|
|
203
203
|
</div>
|
|
204
204
|
</div>
|
|
205
205
|
|
|
206
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
207
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
208
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
206
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
207
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
208
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
209
209
|
<script src="/builders/palette.js" defer></script>
|
|
210
210
|
<script src="/builders/city-draw.js?v=2026-10-02-street1"></script>
|
|
211
211
|
<script src="/builders/street-draw.js?v=2026-10-02-street1"></script>
|
|
@@ -176,9 +176,9 @@
|
|
|
176
176
|
|
|
177
177
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
178
178
|
|
|
179
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
180
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
181
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
179
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
180
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
181
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
182
182
|
<script src="/builders/palette.js" defer></script>
|
|
183
183
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
184
184
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -1204,7 +1204,7 @@ html[data-fullscreen="on"][data-fullscreen-plain="on"] .app-content { padding-to
|
|
|
1204
1204
|
}
|
|
1205
1205
|
|
|
1206
1206
|
/* THE CRAFT MODE (task 1004492, craft-mode.js). The top bar's switch: one pill
|
|
1207
|
-
holding the four crafts
|
|
1207
|
+
holding the four crafts, the current one lit. Narrower windows
|
|
1208
1208
|
drop the words, and a phone shows only the current mode (or "Mode"), which
|
|
1209
1209
|
opens the picker. The picker below asks once per session. Each mode's look
|
|
1210
1210
|
(its colour on the current pill) is THE CRAFT MODE LOOKS, further down. */
|
|
@@ -1251,7 +1251,7 @@ html[data-fullscreen="on"][data-fullscreen-plain="on"] .app-content { padding-to
|
|
|
1251
1251
|
.craft-switch--unset .craft-switch__open { display: inline-flex; padding: 0 7px; }
|
|
1252
1252
|
}
|
|
1253
1253
|
|
|
1254
|
-
/* The picker: a dialog over a scrim, four doors in a row
|
|
1254
|
+
/* The picker: a dialog over a scrim, four doors in a row above the
|
|
1255
1255
|
"ask me again" box. Each door carries its scene from the approved prototype,
|
|
1256
1256
|
which is night-dark in either theme because every scene is a night one. */
|
|
1257
1257
|
html.craft-picker-open { overflow: hidden; }
|
|
@@ -1396,24 +1396,7 @@ html.craft-picker-open { overflow: hidden; }
|
|
|
1396
1396
|
cursor: pointer;
|
|
1397
1397
|
}
|
|
1398
1398
|
.craft-picker__again input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); }
|
|
1399
|
-
.craft-picker__side { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; }
|
|
1400
1399
|
.craft-picker__hint { font-size: 13px; color: var(--ink-faint); }
|
|
1401
|
-
.craft-picker__legacy {
|
|
1402
|
-
display: inline-flex;
|
|
1403
|
-
align-items: center;
|
|
1404
|
-
height: 40px;
|
|
1405
|
-
padding: 0 18px;
|
|
1406
|
-
border: 0;
|
|
1407
|
-
border-radius: var(--radius-pill);
|
|
1408
|
-
background: none;
|
|
1409
|
-
color: var(--ink);
|
|
1410
|
-
font: inherit;
|
|
1411
|
-
font-size: 14px;
|
|
1412
|
-
cursor: pointer;
|
|
1413
|
-
box-shadow: inset 0 0 0 1px var(--rule);
|
|
1414
|
-
}
|
|
1415
|
-
.craft-picker__legacy:hover { background: var(--glass); }
|
|
1416
|
-
.craft-picker__legacy:focus-visible,
|
|
1417
1400
|
.craft-picker__close:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
1418
1401
|
@media (max-width: 900px) {
|
|
1419
1402
|
.craft-picker__doors { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
@@ -1428,12 +1411,11 @@ html.craft-picker-open { overflow: hidden; }
|
|
|
1428
1411
|
.craft-door__head { padding: 10px 16px 0; }
|
|
1429
1412
|
.craft-door__name { font-size: 18px; }
|
|
1430
1413
|
.craft-door__line { flex: 0 0 100%; padding: 0 16px; box-sizing: border-box; }
|
|
1431
|
-
.craft-picker__legacy { height: auto; min-height: 40px; white-space: normal; text-align: left; }
|
|
1432
1414
|
}
|
|
1433
1415
|
|
|
1434
1416
|
/* THE CRAFT MODE LOOKS (task 1004495; direction docs/design/craft-mode-direction.md).
|
|
1435
|
-
Three modes wear a look: Artistry, Ideating, Governing. Building
|
|
1436
|
-
|
|
1417
|
+
Three modes wear a look: Artistry, Ideating, Governing. Building and no mode
|
|
1418
|
+
wear none — nothing below names them, so they render as the hall always
|
|
1437
1419
|
did (tests/craft_mode_looks.mjs pins it). --craft marks WHERE you are, never
|
|
1438
1420
|
what to press: the accent stays the action colour in every mode. Light values
|
|
1439
1421
|
clear 5.4:1 on the hall's paper; the dark values are the prototype's. Light
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
56
56
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
57
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
58
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
59
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
57
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
58
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
59
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
60
60
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
61
61
|
<script src="/builders/palette.js" defer></script>
|
|
62
62
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,9 +138,9 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
142
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
143
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
141
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
142
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
144
144
|
<script src="/builders/palette.js" defer></script>
|
|
145
145
|
|
|
146
146
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -142,9 +142,9 @@
|
|
|
142
142
|
|
|
143
143
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
144
144
|
|
|
145
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
146
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
147
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
145
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
146
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
147
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
148
148
|
<script src="/builders/palette.js" defer></script>
|
|
149
149
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
150
150
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,9 +162,9 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
166
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
167
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
165
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
166
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
167
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
168
168
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
169
169
|
<script src="/builders/palette.js" defer></script>
|
|
170
170
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,9 +171,9 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell-trail.js?v=2026-10-04-
|
|
175
|
-
<script src="/builders/craft-mode.js?v=2026-10-04-
|
|
176
|
-
<script src="/builders/shell.js?v=2026-10-04-
|
|
174
|
+
<script src="/builders/shell-trail.js?v=2026-10-04-nolegacy"></script>
|
|
175
|
+
<script src="/builders/craft-mode.js?v=2026-10-04-nolegacy"></script>
|
|
176
|
+
<script src="/builders/shell.js?v=2026-10-04-nolegacy"></script>
|
|
177
177
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
178
178
|
<script src="/builders/palette.js" defer></script>
|
|
179
179
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -4,9 +4,9 @@ Covers: every shelled hall page (`craft-mode.js`, `shell.js`, `style.css`), and
|
|
|
4
4
|
|
|
5
5
|
## What it is
|
|
6
6
|
|
|
7
|
-
- On entering the hall, once per browser session, a signed-in builder is asked **"What are you doing now?"**: Building, Artistry, Ideating
|
|
8
|
-
- The answer is ONE attribute, `html[data-craft="building|artistry|ideating|governing
|
|
9
|
-
- The top bar carries a switch with all
|
|
7
|
+
- On entering the hall, once per browser session, a signed-in builder is asked **"What are you doing now?"**: Building, Artistry, Ideating or Governing. A fifth choice, **Legacy mode** (2026-10-01: the hall exactly as it was), was removed in task 1004567 (owner, 2026-10-04: "builder has all those tabs anyways"); a browser that still has `legacy` stored reads it as no mode, because `valid()` ignores any id that is not a mode. The approved prototype is the design canvas linked on the task; the doors carry its four scenes (the work desk, the night room, the sky, the governor city).
|
|
8
|
+
- The answer is ONE attribute, `html[data-craft="building|artistry|ideating|governing"]`, readable by every hall page. Every change also fires `otb:craft` on `document` with `{ detail: { mode } }`, so a page can react without polling.
|
|
9
|
+
- The top bar carries a switch with all four; pressing one changes the mode without a reload. Under 1180px it drops the words; at phone width only the current mode shows (or a "Choose your mode" button when there is none), and tapping it opens the picker.
|
|
10
10
|
- Artistry is pre-selected for a builder whose `main_discipline` is `artist` (or, with no main role, whose first craft is).
|
|
11
11
|
|
|
12
12
|
## The rules an edit here must keep
|
|
@@ -23,14 +23,14 @@ Covers: every shelled hall page (`craft-mode.js`, `shell.js`, `style.css`), and
|
|
|
23
23
|
|
|
24
24
|
## The nav follows the mode (task 1004493)
|
|
25
25
|
|
|
26
|
-
- **Each craft has a named tab list**, `MODE_TABS` in `craft-mode.js`, by NAV id. `ALWAYS_TABS` is the owner's list of tabs no mode may hide (2026-10-01: Builders, NAV id `roster`, first), and every mode's list is `ALWAYS_TABS` plus its own.
|
|
26
|
+
- **Each craft has a named tab list**, `MODE_TABS` in `craft-mode.js`, by NAV id. `ALWAYS_TABS` is the owner's list of tabs no mode may hide (2026-10-01: Builders, NAV id `roster`, first), and every mode's list is `ALWAYS_TABS` plus its own. No mode has no entry, so it shows every tab, as before.
|
|
27
27
|
- **Government shows only in Governing**, and outside that group Governing is the shortest list (Blockers, Board Room). Building is every tab except Government and the Board Room; Artistry is Studio, Goals, Your thinking; Ideating is Sky, Your thinking, Goals. The prototype names rooms that do not exist yet (Store, Page reviews, Treasury); a list names only real NAV items, and `tests/craft_mode_nav.mjs` requires every NAV item to sit in some mode.
|
|
28
28
|
- **The mode only ever takes away, and never through `hidden`.** The access gate owns `hidden`; `applyNav()` marks what the mode leaves out with `[data-craft-off]`, which `style.css` hides. So a mode can never show a tab the gate hid, and switching mode just moves the attribute. A group head goes too once nothing under it shows.
|
|
29
29
|
- **Two callers.** `apply()` runs it on every mode change (live, no reload), and `shell.js` `applyAccess` runs it right after its gate loop, so a later `/me` answer (a promotion, a permission) brings a head back with its tabs. Hidden tabs still open by their URL; nothing is enforced (ADR 0016).
|
|
30
30
|
|
|
31
31
|
## Each mode's look (task 1004495)
|
|
32
32
|
|
|
33
|
-
- **Three modes wear a look; Building
|
|
33
|
+
- **Three modes wear a look; Building and no mode wear none.** Artistry, Ideating and Governing each set `--craft` (where you are) and `--craft-on` (text on it) on `html[data-craft="<mode>"]`, with a dark twin on `html[data-theme="dark"][data-craft="<mode>"]` that also darkens the ground and tints the glass (Artistry, Ideating). Values and contrast are the direction's, `docs/design/craft-mode-direction.md`. No rule in `style.css` names `building`, and none matches any mode at all; `tests/craft_mode_looks.mjs` pins both, and re-measures every colour pair.
|
|
34
34
|
- **The accent stays the action colour.** `--craft` paints only the switch's current pill, the current tab (tint, icon, label, marker) and the top bar's hairline.
|
|
35
35
|
- **Two pieces are drawn by `craft-mode.js` `applyLook()`, on every mode change:** the eyebrow beside `#topbar-title` (`LOOKS`: "the studio · night", "the sky", "the city"; hidden at <=640px, where it overflowed phones) and the scene plate under `.app-sidebar__brand` (40px closed rail, 64px open). The plate's markup IS `artHtml(mode)`, the picker's door art, exported rather than copied; light mode recolours it to each scene's day (the city is the dark one through `invert(1) hue-rotate(180deg)`). It does not animate.
|
|
36
36
|
- **Always-present tabs widened** (owner, 2026-10-03): `ALWAYS_TABS` is Builders, Collab and Profile, so every mode keeps the You and Community groups.
|
|
@@ -10,6 +10,7 @@ What it settled:
|
|
|
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
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
|
+
- **A card is only as tall as its words** (task 1004568). Every part of the compact card has a cap (`LINE` in `street-card.js`): the kicker, the fact and a listed line one line, the title about two, a voice's reason two, the proposer's words three. Longer text is cut at a whole word with an ellipsis, and whatever was cut is whole under "more". The kicker is building · floor only: the automation switch's off-reason is no longer joined into it, it is the card's fact. Nothing on the card has a min-height. Rendered through `street-card.states.json` (`automation`, `automation-more`, beside the blocker and Full Idea states) at 1440 and 390, light and dark, on the hall-preview harness.
|
|
13
14
|
|
|
14
15
|
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
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.43",
|
|
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.43",
|
|
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.43",
|
|
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
|
@@ -9010,5 +9010,17 @@
|
|
|
9010
9010
|
"id": "1004496",
|
|
9011
9011
|
"text": "Artists can now write feedback on any page of the hall. In Artistry mode a Feedback button sits beside the mode switch. It opens a doc about the page you are on: write as much as you need, add reference images, and press \"Turn"
|
|
9012
9012
|
}
|
|
9013
|
+
],
|
|
9014
|
+
"1.21.42": [
|
|
9015
|
+
{
|
|
9016
|
+
"id": "1004568",
|
|
9017
|
+
"text": "Street cards are now only as tall as their words need. Long titles, reasons and lines are cut short with an ellipsis at a whole word, and the full text is one tap away under \"more\". The automation card no longer stretches: its"
|
|
9018
|
+
}
|
|
9019
|
+
],
|
|
9020
|
+
"1.21.43": [
|
|
9021
|
+
{
|
|
9022
|
+
"id": "1004567",
|
|
9023
|
+
"text": "Legacy mode is gone from the builders hall. The mode picker now offers four doors (Building, Artistry, Ideating, Governing) and the top-bar switch shows those four. Anyone who still had Legacy saved simply lands with no mode c"
|
|
9024
|
+
}
|
|
9013
9025
|
]
|
|
9014
9026
|
}
|
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.43'; // 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/craft_mode.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/craft_mode.mjs — the hall's craft mode picker (task 1004492, goal 1000095 WA6).
|
|
2
2
|
//
|
|
3
3
|
// On entering the hall a picker asks once per session "What are you doing
|
|
4
|
-
// now?" (Building, Artistry, Ideating
|
|
4
|
+
// now?" (Building, Artistry, Ideating or Governing); the answer is
|
|
5
5
|
// html[data-craft], and a switch in the top bar changes it without a reload.
|
|
6
6
|
// These cases boot the REAL shell-trail.js + craft-mode.js + shell.js in a DOM
|
|
7
7
|
// stub, in the order every hall page loads them, and drive them the way a
|
|
@@ -142,7 +142,7 @@ function boot({ craft = true, session = {}, store = {}, throws = false, optOut =
|
|
|
142
142
|
picker,
|
|
143
143
|
signIn: (builder = { id: 7, rank: 'thetes' }) => shell.applyAccess({ authed: true, rank: builder.rank, builder }),
|
|
144
144
|
signOut: () => shell.applyAccess({ authed: false }),
|
|
145
|
-
// Press a picker button: a door
|
|
145
|
+
// Press a picker button: a door or the close button.
|
|
146
146
|
pick(attr, value, { askAgain = true } = {}) {
|
|
147
147
|
const p = picker();
|
|
148
148
|
const box = p.querySelector('[data-craft-again]');
|
|
@@ -161,7 +161,7 @@ function boot({ craft = true, session = {}, store = {}, throws = false, optOut =
|
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
const MODES = ['building', 'artistry', 'ideating', 'governing'
|
|
164
|
+
const MODES = ['building', 'artistry', 'ideating', 'governing'];
|
|
165
165
|
const noSwitch = (html) => html.replace(/<div class="craft-switch[\s\S]*?<\/div>/, '');
|
|
166
166
|
|
|
167
167
|
test('NO MODE IS THE HALL AS TODAY: with no mode chosen, nothing but the switch differs from a hall without craft modes', () => {
|
|
@@ -184,7 +184,8 @@ test('a signed-in builder is asked ONCE per session, and a reload does not ask a
|
|
|
184
184
|
assert.match(html, /role="dialog" aria-modal="true"/);
|
|
185
185
|
assert.match(html, /What are you doing now\?/);
|
|
186
186
|
for (const m of ['building', 'artistry', 'ideating', 'governing']) assert.match(html, new RegExp(`data-craft-pick="${m}"`), `a ${m} door`);
|
|
187
|
-
assert.
|
|
187
|
+
assert.equal([...html.matchAll(/data-craft-pick="/g)].length, 4, 'four doors and nothing else to pick');
|
|
188
|
+
assert.doesNotMatch(html, /legacy/i, 'Legacy mode is gone (owner, 2026-10-04: Building has all those tabs anyway)');
|
|
188
189
|
p.escape();
|
|
189
190
|
assert.equal(p.picker(), null, 'Escape closes it without choosing');
|
|
190
191
|
assert.equal(p.mode(), undefined, 'and leaves the hall with no mode');
|
|
@@ -229,17 +230,33 @@ test('unticking "Ask me again next session" keeps the mode for later sessions',
|
|
|
229
230
|
assert.equal(tomorrow.picker(), null, 'and nobody is asked');
|
|
230
231
|
});
|
|
231
232
|
|
|
232
|
-
test('THE HEADER SWITCH changes the mode without a reload, and offers
|
|
233
|
+
test('THE HEADER SWITCH changes the mode without a reload, and offers the four crafts', () => {
|
|
233
234
|
const p = boot();
|
|
234
235
|
const opts = [...p.topbar.innerHTML.matchAll(/data-craft-opt="([a-z]+)"/g)].map((m) => m[1]);
|
|
235
|
-
assert.deepEqual(opts, MODES, 'the four crafts,
|
|
236
|
+
assert.deepEqual(opts, MODES, 'the four crafts, and no Legacy');
|
|
236
237
|
p.press('artistry');
|
|
237
238
|
assert.equal(p.mode(), 'artistry');
|
|
238
|
-
p.press('
|
|
239
|
-
assert.equal(p.mode(), '
|
|
240
|
-
assert.deepEqual(p.pressed(), ['
|
|
239
|
+
p.press('building');
|
|
240
|
+
assert.equal(p.mode(), 'building', 'anyone can switch at any time');
|
|
241
|
+
assert.deepEqual(p.pressed(), ['building']);
|
|
241
242
|
assert.equal(p.location.reloads, 0, 'no navigation happened');
|
|
242
|
-
assert.equal(p.session['hall-craft'], '
|
|
243
|
+
assert.equal(p.session['hall-craft'], 'building', 'and the session remembers it');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('a browser that still has Legacy stored falls back to NO MODE (task 1004567)', () => {
|
|
247
|
+
const session = { 'hall-craft': 'legacy', 'hall-craft-asked': '1' };
|
|
248
|
+
const store = { 'hall-craft-keep': 'legacy' };
|
|
249
|
+
const p = boot({ session, store });
|
|
250
|
+
p.signIn();
|
|
251
|
+
assert.equal(p.mode(), undefined, 'no html[data-craft]: the hall with every tab');
|
|
252
|
+
assert.deepEqual(p.pressed(), [], 'no switch option lit');
|
|
253
|
+
assert.equal(p.picker(), null, 'this session already saw the picker, so it is not asked again');
|
|
254
|
+
const kept = boot({ store });
|
|
255
|
+
kept.signIn();
|
|
256
|
+
assert.equal(kept.mode(), undefined, 'a kept Legacy from an earlier session is no mode too');
|
|
257
|
+
assert.ok(kept.picker(), 'and a new session is asked to pick one of the four');
|
|
258
|
+
p.press('building');
|
|
259
|
+
assert.equal(p.mode(), 'building', 'and any of the four can be chosen from there');
|
|
243
260
|
});
|
|
244
261
|
|
|
245
262
|
test('on a phone the switch shows only the current mode, and tapping it opens the picker', () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Artistry, Ideating and Governing each wear a look: --craft / --craft-on tokens
|
|
5
5
|
// (light + dark twins) in style.css, the header eyebrow beside the page title and
|
|
6
|
-
// the door's scene framed under the brand. Building
|
|
6
|
+
// the door's scene framed under the brand. Building and no mode wear
|
|
7
7
|
// NOTHING — the hall as it always was. These cases run the REAL craft-mode.js over
|
|
8
8
|
// a small DOM and read the REAL style.css, so a look that leaks into Building, a
|
|
9
9
|
// plate that is a second copy of the art, or a colour that loses its contrast reds.
|
|
@@ -94,11 +94,11 @@ test('each looked mode frames its own door scene under the brand, and names itse
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
test('BUILDING
|
|
97
|
+
test('BUILDING AND NO MODE WEAR NO LOOK: no plate, no eyebrow, and switching to them takes the look away', () => {
|
|
98
98
|
const none = boot();
|
|
99
99
|
assert.equal(none.root.dataset.craft, undefined);
|
|
100
100
|
assert.equal(none.plates().length + none.marks().length, 0, 'no mode draws nothing');
|
|
101
|
-
for (const m of ['building'
|
|
101
|
+
for (const m of ['building']) {
|
|
102
102
|
const p = boot({ 'hall-craft': m });
|
|
103
103
|
assert.equal(p.plates().length + p.marks().length, 0, `${m} at boot draws nothing`);
|
|
104
104
|
p.craft.choose('governing');
|
|
@@ -118,10 +118,10 @@ test('the art is drawn once: craft-mode.js has one artHtml and exports it', () =
|
|
|
118
118
|
// Every selector in style.css that names a craft mode.
|
|
119
119
|
const CRAFT_SELECTORS = [...CSS.matchAll(/data-craft="([a-z]+)"/g)].map((m) => m[1]);
|
|
120
120
|
|
|
121
|
-
test('style.css names only the three looked modes — Building
|
|
121
|
+
test('style.css names only the three looked modes — Building has no rule at all', () => {
|
|
122
122
|
assert.ok(CRAFT_SELECTORS.length >= 12, `found ${CRAFT_SELECTORS.length} craft selectors`);
|
|
123
123
|
for (const m of CRAFT_SELECTORS) assert.ok(LOOKED.includes(m), `a rule names data-craft="${m}"`);
|
|
124
|
-
assert.doesNotMatch(CSS, /data-craft="
|
|
124
|
+
assert.doesNotMatch(CSS, /data-craft="building"/);
|
|
125
125
|
assert.doesNotMatch(CSS, /data-craft\]/, 'no rule matches ANY mode, which would dress Building too');
|
|
126
126
|
});
|
|
127
127
|
|