@bongos/core 1.24.46 → 1.24.48
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 +48 -28
- package/config/design-tokens.neutral.json +10 -10
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +35 -35
- package/modules/hall-ui/public/collab.css +3 -22
- package/modules/hall-ui/public/controls.css +223 -0
- package/modules/hall-ui/public/goals.css +4 -23
- package/modules/hall-ui/public/my-agents.css +3 -25
- package/modules/hall-ui/public/oversight.css +3 -17
- package/modules/hall-ui/public/panel.css +6 -48
- package/modules/hall-ui/public/speciality-tools.js +14 -14
- package/modules/hall-ui/public/style.css +24 -246
- package/modules/ui-design/scripts/gen-design-tokens.js +102 -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/bulk_toolbar.mjs +4 -1
- package/tests/design_tokens_generated.mjs +56 -0
- package/tests/hall_board_world.mjs +6 -2
- package/tests/hall_builders_page_pass.mjs +6 -4
- package/tests/hall_controls.mjs +274 -0
- package/tests/hall_oversight_world.mjs +7 -4
- package/tests/hall_record_world.mjs +21 -7
- package/tests/hall_settings_world.mjs +5 -2
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
shadows, motion, stacking — every surface loads. Imported FIRST so this
|
|
61
61
|
sheet's own rules follow it; the hall's scale names below are aliases of it. */
|
|
62
62
|
@import url("/modules/ui-design/public/tokens.css");
|
|
63
|
+
/* The hall's one button and one field (task 1005084): .btn and its modifiers,
|
|
64
|
+
the text field / select / textarea. Straight after the scales it spends. */
|
|
65
|
+
@import url("controls.css");
|
|
63
66
|
@import url("page-notes.css");
|
|
64
67
|
@import url("craft-review.css");
|
|
65
68
|
|
|
@@ -430,20 +433,7 @@ code, kbd, pre {
|
|
|
430
433
|
the composition around it. */
|
|
431
434
|
.num, .tabular { font-variant-numeric: tabular-nums; }
|
|
432
435
|
|
|
433
|
-
|
|
434
|
-
font-family: var(--font-body);
|
|
435
|
-
font-size: 14px;
|
|
436
|
-
color: var(--ink);
|
|
437
|
-
background: none;
|
|
438
|
-
border: 1px solid var(--rule);
|
|
439
|
-
border-radius: var(--radius-pill);
|
|
440
|
-
min-height: 30px;
|
|
441
|
-
padding: 5px 12px;
|
|
442
|
-
}
|
|
443
|
-
select:hover, input[type="text"]:hover, input[type="search"]:hover { border-color: var(--ink-faint); }
|
|
444
|
-
textarea { border-radius: var(--radius-sm); }
|
|
445
|
-
select:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
446
|
-
select option { background: var(--bg-card); color: var(--ink); }
|
|
436
|
+
/* The field (text input, select, textarea) is controls.css's (task 1005084). */
|
|
447
437
|
|
|
448
438
|
button { font-family: var(--font-body); }
|
|
449
439
|
|
|
@@ -1422,8 +1412,7 @@ html.setup-gate-open { overflow: hidden; }
|
|
|
1422
1412
|
}
|
|
1423
1413
|
.setup-gate code { font-family: var(--font-mono); font-size: 14px; padding: 1px 6px; border-radius: 6px; background: var(--glass); }
|
|
1424
1414
|
.setup-gate__crafts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
1425
|
-
.setup-gate__craft
|
|
1426
|
-
.setup-gate__save {
|
|
1415
|
+
.setup-gate__craft {
|
|
1427
1416
|
min-height: 36px;
|
|
1428
1417
|
padding: 0 16px;
|
|
1429
1418
|
border-radius: var(--radius-pill);
|
|
@@ -1435,8 +1424,8 @@ html.setup-gate-open { overflow: hidden; }
|
|
|
1435
1424
|
cursor: pointer;
|
|
1436
1425
|
}
|
|
1437
1426
|
.setup-gate__craft[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-tint); }
|
|
1438
|
-
|
|
1439
|
-
.setup-gate__save
|
|
1427
|
+
/* The save is the shared primary door (controls.css, task 1005084). */
|
|
1428
|
+
.setup-gate__save { margin-top: 12px; }
|
|
1440
1429
|
.setup-gate__err { margin: 8px 0 0; font-size: 14px; color: var(--danger); }
|
|
1441
1430
|
.setup-gate__foot { margin: 24px 0 0; font-size: 14px; }
|
|
1442
1431
|
.setup-gate__foot a { color: var(--ink-soft); }
|
|
@@ -2634,23 +2623,16 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
2634
2623
|
wherever there is room and drops it where there is not. */
|
|
2635
2624
|
min-width: min(150px, 100%);
|
|
2636
2625
|
max-width: 100%;
|
|
2637
|
-
min-height: 32px;
|
|
2638
|
-
font-family: var(--font-body);
|
|
2639
|
-
padding: 4px 8px;
|
|
2640
|
-
font-size: 13.5px;
|
|
2641
2626
|
text-transform: none;
|
|
2642
2627
|
letter-spacing: normal;
|
|
2643
2628
|
font-weight: 400;
|
|
2644
2629
|
}
|
|
2645
|
-
/* task 1829: the Work Board free-text search box
|
|
2646
|
-
|
|
2630
|
+
/* task 1829: the Work Board free-text search box. Both it and the selects are
|
|
2631
|
+
the shared field (controls.css, task 1005084); the filter only sets widths
|
|
2632
|
+
and undoes the label voice they would inherit. */
|
|
2647
2633
|
.filter input[type="search"] {
|
|
2648
2634
|
min-width: min(200px, 100%);
|
|
2649
2635
|
max-width: 100%;
|
|
2650
|
-
min-height: 32px;
|
|
2651
|
-
font-family: var(--font-body);
|
|
2652
|
-
padding: 4px 8px;
|
|
2653
|
-
font-size: 13.5px;
|
|
2654
2636
|
text-transform: none;
|
|
2655
2637
|
letter-spacing: normal;
|
|
2656
2638
|
font-weight: 400;
|
|
@@ -2673,94 +2655,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
2673
2655
|
}
|
|
2674
2656
|
.pager__range { font-family: var(--font-mono); font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
|
|
2675
2657
|
|
|
2676
|
-
/*
|
|
2677
|
-
|
|
2678
|
-
the live thing. 28px of box plus the row’s own air clears the 24px floor. */
|
|
2679
|
-
.btn-ghost {
|
|
2680
|
-
display: inline-flex;
|
|
2681
|
-
align-items: center;
|
|
2682
|
-
justify-content: center;
|
|
2683
|
-
gap: 6px;
|
|
2684
|
-
min-height: 30px;
|
|
2685
|
-
min-width: 56px;
|
|
2686
|
-
padding: 0 14px;
|
|
2687
|
-
font-size: 13px;
|
|
2688
|
-
text-transform: lowercase;
|
|
2689
|
-
color: var(--ink-soft);
|
|
2690
|
-
background: none;
|
|
2691
|
-
border: 1px solid var(--rule);
|
|
2692
|
-
border-radius: var(--radius-pill);
|
|
2693
|
-
cursor: pointer;
|
|
2694
|
-
}
|
|
2695
|
-
.btn-ghost:hover:not(:disabled) { border-color: var(--ink-faint); color: var(--ink); text-decoration: none; }
|
|
2696
|
-
.btn-ghost:disabled { color: var(--ink-faint); border-color: var(--rule-soft); cursor: default; }
|
|
2697
|
-
|
|
2698
|
-
/* The accent door: the page’s one primary action. --accent-on is the dark ink
|
|
2699
|
-
that goes on the salmon; white on it fails AA and is banned. */
|
|
2700
|
-
.btn-accent {
|
|
2701
|
-
display: inline-flex;
|
|
2702
|
-
align-items: center;
|
|
2703
|
-
justify-content: center;
|
|
2704
|
-
gap: 6px;
|
|
2705
|
-
min-height: 34px;
|
|
2706
|
-
min-width: 74px;
|
|
2707
|
-
padding: 0 16px;
|
|
2708
|
-
font-size: 13.5px;
|
|
2709
|
-
font-weight: 500;
|
|
2710
|
-
text-transform: lowercase;
|
|
2711
|
-
color: var(--accent-on);
|
|
2712
|
-
background: var(--accent);
|
|
2713
|
-
border: 0;
|
|
2714
|
-
border-radius: var(--radius-pill);
|
|
2715
|
-
cursor: pointer;
|
|
2716
|
-
}
|
|
2717
|
-
.btn-accent:hover:not(:disabled) { background: var(--accent-lift); color: var(--accent-on); text-decoration: none; }
|
|
2718
|
-
.btn-accent:disabled { background: var(--glass); color: var(--ink-faint); cursor: default; }
|
|
2719
|
-
|
|
2720
|
-
/* `.btn` / `.btn--ghost` — the SECOND button vocabulary in the hall, used by
|
|
2721
|
-
ideas, idea, copy-desk, my-projects and collab. Task 1003305 found it had no
|
|
2722
|
-
rule anywhere in the surface: every one of those buttons was rendering as a
|
|
2723
|
-
browser default control, on a page whose every other shape is a pill. They
|
|
2724
|
-
are not renamed to .btn-accent/.btn-ghost because the class is spelled into
|
|
2725
|
-
markup and into tests/idea_spark_hall.mjs; instead the two vocabularies are
|
|
2726
|
-
made to say the same thing. `.btn` is the accent door and `.btn--ghost` its
|
|
2727
|
-
quiet sibling, which is exactly how the calling pages already use the pair
|
|
2728
|
-
(submit vs. discard, mark vs. withdraw). --accent-on is the dark ink for the
|
|
2729
|
-
salmon; white on it fails AA and DESIGN.md bans it. */
|
|
2730
|
-
.btn {
|
|
2731
|
-
display: inline-flex;
|
|
2732
|
-
align-items: center;
|
|
2733
|
-
justify-content: center;
|
|
2734
|
-
gap: 6px;
|
|
2735
|
-
min-height: 34px;
|
|
2736
|
-
min-width: 74px;
|
|
2737
|
-
padding: 0 16px;
|
|
2738
|
-
font-family: var(--font-body);
|
|
2739
|
-
font-size: 13.5px;
|
|
2740
|
-
font-weight: 500;
|
|
2741
|
-
text-transform: lowercase;
|
|
2742
|
-
color: var(--accent-on);
|
|
2743
|
-
background: var(--accent);
|
|
2744
|
-
/* transparent rather than absent: collab.css's `.btn--quiet` sets only a
|
|
2745
|
-
border-COLOUR, and a modifier cannot paint an edge that was never reserved. */
|
|
2746
|
-
border: var(--line) solid transparent;
|
|
2747
|
-
border-radius: var(--radius-pill);
|
|
2748
|
-
cursor: pointer;
|
|
2749
|
-
}
|
|
2750
|
-
.btn:hover:not(:disabled) { background: var(--accent-lift); color: var(--accent-on); text-decoration: none; }
|
|
2751
|
-
.btn:disabled { background: var(--glass); color: var(--ink-faint); cursor: default; }
|
|
2752
|
-
.btn--ghost {
|
|
2753
|
-
min-height: 30px;
|
|
2754
|
-
min-width: 56px;
|
|
2755
|
-
padding: 0 14px;
|
|
2756
|
-
font-size: 13px;
|
|
2757
|
-
font-weight: 400;
|
|
2758
|
-
color: var(--ink-soft);
|
|
2759
|
-
background: none;
|
|
2760
|
-
border-color: var(--rule);
|
|
2761
|
-
}
|
|
2762
|
-
.btn--ghost:hover:not(:disabled) { background: none; border-color: var(--ink-faint); color: var(--ink); }
|
|
2763
|
-
.btn--ghost:disabled { background: none; color: var(--ink-faint); border-color: var(--rule-soft); }
|
|
2658
|
+
/* The buttons — .btn and its modifiers, and the older names pages still write
|
|
2659
|
+
(.btn-accent, .btn-ghost, .btn--ghost …) — are controls.css's (task 1005084). */
|
|
2764
2660
|
|
|
2765
2661
|
/* ---------- profile / standing ---------- */
|
|
2766
2662
|
|
|
@@ -3040,11 +2936,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
3040
2936
|
gap keeps the label and the select together when the lede wraps. */
|
|
3041
2937
|
.curve__filter { display: inline-flex; align-items: center; gap: 6px; margin-left: 10px; }
|
|
3042
2938
|
.curve__filter-label { color: var(--ink-soft); }
|
|
3043
|
-
.
|
|
3044
|
-
|
|
3045
|
-
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
|
3046
|
-
padding: 2px 6px; max-width: 16ch;
|
|
3047
|
-
}
|
|
2939
|
+
/* the shared field (controls.css); only its width is the filter's */
|
|
2940
|
+
.curve__filter-select { max-width: 16ch; }
|
|
3048
2941
|
|
|
3049
2942
|
.curve__headline {
|
|
3050
2943
|
display: grid;
|
|
@@ -3297,22 +3190,6 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
3297
3190
|
}
|
|
3298
3191
|
.setup__divider::before, .setup__divider::after { content: ""; flex: 1; border-top: 1px solid var(--rule-soft); }
|
|
3299
3192
|
|
|
3300
|
-
.setup__btn {
|
|
3301
|
-
display: inline-flex;
|
|
3302
|
-
align-items: center;
|
|
3303
|
-
gap: 8px;
|
|
3304
|
-
height: 34px;
|
|
3305
|
-
padding: 0 16px;
|
|
3306
|
-
border-radius: var(--radius-sm);
|
|
3307
|
-
border: 1px solid var(--accent);
|
|
3308
|
-
background: var(--accent);
|
|
3309
|
-
color: #fff;
|
|
3310
|
-
font-size: 13.5px;
|
|
3311
|
-
font-weight: 600;
|
|
3312
|
-
cursor: pointer;
|
|
3313
|
-
}
|
|
3314
|
-
.setup__btn:hover:not(:disabled) { background: var(--accent-deep); border-color: var(--accent-deep); color: #fff; text-decoration: none; }
|
|
3315
|
-
.setup__btn:disabled, .setup__btn--open[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
|
|
3316
3193
|
.setup__getcode { margin: 10px 0; }
|
|
3317
3194
|
/* A <summary> is a control, so it owes the 24px floor like any other; it
|
|
3318
3195
|
measured 20px tall (task 1003304). The height is bought with padding rather
|
|
@@ -3387,31 +3264,9 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
3387
3264
|
line-height: 18px;
|
|
3388
3265
|
}
|
|
3389
3266
|
.landing__cta { margin: 14px 0; }
|
|
3390
|
-
.landing__btn
|
|
3391
|
-
display: inline-flex;
|
|
3392
|
-
align-items: center;
|
|
3393
|
-
justify-content: center;
|
|
3394
|
-
gap: 10px;
|
|
3395
|
-
min-height: 46px;
|
|
3396
|
-
padding: 0 22px;
|
|
3397
|
-
border-radius: var(--radius-pill);
|
|
3398
|
-
border: 0;
|
|
3399
|
-
background: var(--accent);
|
|
3400
|
-
color: var(--accent-on);
|
|
3401
|
-
font-size: 14px;
|
|
3402
|
-
font-weight: 500;
|
|
3403
|
-
text-transform: lowercase;
|
|
3404
|
-
cursor: pointer;
|
|
3405
|
-
}
|
|
3406
|
-
.landing__btn:hover { background: var(--accent-lift); text-decoration: none; color: var(--accent-on); }
|
|
3267
|
+
/* .landing__btn is the shared primary door, --quiet its ghost (controls.css). */
|
|
3407
3268
|
.landing__btn--block { display: flex; width: 100%; }
|
|
3408
3269
|
.landing__gh { flex-shrink: 0; }
|
|
3409
|
-
.landing__btn--quiet {
|
|
3410
|
-
background: transparent;
|
|
3411
|
-
color: var(--ink);
|
|
3412
|
-
box-shadow: inset 0 0 0 1px var(--border-strong);
|
|
3413
|
-
}
|
|
3414
|
-
.landing__btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
|
|
3415
3270
|
.landing__note { font-size: 12.5px; color: var(--ink-faint); }
|
|
3416
3271
|
.landing__error { color: var(--danger); }
|
|
3417
3272
|
.landing__error-h { color: var(--danger); margin: 0 0 4px; font-size: 14px; }
|
|
@@ -3642,29 +3497,8 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
3642
3497
|
the touches chip and the whole-card selection target (promoted from work.css,
|
|
3643
3498
|
task 1003659, so their cascade position is the same on every page). */
|
|
3644
3499
|
|
|
3645
|
-
/* The page's one primary action, and the only accent FILL on the board
|
|
3646
|
-
|
|
3647
|
-
DESIGN.md bans it outright, which is what the old `color: #fff` was. */
|
|
3648
|
-
.task__claim {
|
|
3649
|
-
display: inline-flex;
|
|
3650
|
-
align-items: center;
|
|
3651
|
-
justify-content: center;
|
|
3652
|
-
min-height: 34px;
|
|
3653
|
-
min-width: 74px;
|
|
3654
|
-
padding: 0 16px;
|
|
3655
|
-
background: var(--accent);
|
|
3656
|
-
border: 0;
|
|
3657
|
-
color: var(--accent-on);
|
|
3658
|
-
border-radius: var(--radius-pill);
|
|
3659
|
-
font-size: 13.5px;
|
|
3660
|
-
font-weight: 500;
|
|
3661
|
-
text-transform: lowercase;
|
|
3662
|
-
cursor: pointer;
|
|
3663
|
-
white-space: nowrap;
|
|
3664
|
-
transition: background 150ms, opacity 150ms;
|
|
3665
|
-
}
|
|
3666
|
-
.task__claim:hover:not(:disabled) { background: var(--accent-lift); color: var(--accent-on); }
|
|
3667
|
-
.task__claim:disabled { background: var(--glass); color: var(--ink-faint); cursor: default; }
|
|
3500
|
+
/* The page's one primary action, and the only accent FILL on the board: the
|
|
3501
|
+
shared primary door at the row size (controls.css, task 1005084). */
|
|
3668
3502
|
|
|
3669
3503
|
/* Files a claimable task is responsible for — a compact count chip in the meta
|
|
3670
3504
|
row; the full path list rides the tooltip (task 2170 density pass). The mono
|
|
@@ -3778,27 +3612,13 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
3778
3612
|
color: var(--ink-faint);
|
|
3779
3613
|
}
|
|
3780
3614
|
|
|
3781
|
-
.wip__release
|
|
3782
|
-
|
|
3783
|
-
align-items: center;
|
|
3784
|
-
justify-content: center;
|
|
3785
|
-
min-height: 30px;
|
|
3786
|
-
padding: 0 14px;
|
|
3787
|
-
background: none;
|
|
3788
|
-
border: var(--line) solid var(--rule);
|
|
3789
|
-
color: var(--ink-soft);
|
|
3790
|
-
border-radius: var(--radius-pill);
|
|
3791
|
-
font-size: 13px;
|
|
3792
|
-
text-transform: lowercase;
|
|
3793
|
-
cursor: pointer;
|
|
3794
|
-
transition: background 150ms, color 150ms, border-color 150ms;
|
|
3795
|
-
}
|
|
3615
|
+
/* .wip__release is the shared ghost at the row size (controls.css); a release
|
|
3616
|
+
is the undo of a claim, so its hover warns. */
|
|
3796
3617
|
.wip__release:hover:not(:disabled) {
|
|
3797
3618
|
background: var(--danger-weak);
|
|
3798
3619
|
color: var(--danger);
|
|
3799
3620
|
border-color: color-mix(in srgb, var(--danger) 40%, transparent);
|
|
3800
3621
|
}
|
|
3801
|
-
.wip__release:disabled { opacity: 0.55; cursor: default; }
|
|
3802
3622
|
|
|
3803
3623
|
.wip__select {
|
|
3804
3624
|
appearance: none;
|
|
@@ -4064,38 +3884,8 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
4064
3884
|
.td-dep:hover { text-decoration: underline; }
|
|
4065
3885
|
.td-dep-status { color: var(--ink-faint); font-size: 12px; margin-left: 6px; }
|
|
4066
3886
|
.td-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 18px 0 0; }
|
|
4067
|
-
/*
|
|
4068
|
-
|
|
4069
|
-
— the exact pairing DESIGN.md bans, because it fails AA. */
|
|
4070
|
-
.td-btn {
|
|
4071
|
-
display: inline-flex;
|
|
4072
|
-
align-items: center;
|
|
4073
|
-
justify-content: center;
|
|
4074
|
-
font: inherit;
|
|
4075
|
-
font-size: 13px;
|
|
4076
|
-
min-height: 30px;
|
|
4077
|
-
min-width: 56px;
|
|
4078
|
-
padding: 0 14px;
|
|
4079
|
-
border: var(--line) solid var(--rule);
|
|
4080
|
-
border-radius: var(--radius-pill);
|
|
4081
|
-
background: none;
|
|
4082
|
-
color: var(--ink-soft);
|
|
4083
|
-
text-transform: lowercase;
|
|
4084
|
-
cursor: pointer;
|
|
4085
|
-
}
|
|
4086
|
-
.td-btn:hover:not(:disabled) { border-color: var(--ink-faint); color: var(--ink); text-decoration: none; }
|
|
4087
|
-
.td-btn--primary {
|
|
4088
|
-
min-height: 34px;
|
|
4089
|
-
min-width: 74px;
|
|
4090
|
-
padding: 0 16px;
|
|
4091
|
-
font-weight: 500;
|
|
4092
|
-
background: var(--accent);
|
|
4093
|
-
color: var(--accent-on);
|
|
4094
|
-
border-color: var(--accent);
|
|
4095
|
-
}
|
|
4096
|
-
.td-btn--primary:hover:not(:disabled) { background: var(--accent-lift); border-color: var(--accent-lift); color: var(--accent-on); }
|
|
4097
|
-
.td-btn:disabled { color: var(--ink-faint); border-color: var(--rule-soft); cursor: default; }
|
|
4098
|
-
.td-btn--primary:disabled { background: var(--glass); }
|
|
3887
|
+
/* .td-btn is the shared ghost at the row size and .td-btn--primary the accent
|
|
3888
|
+
door (controls.css, task 1005084). */
|
|
4099
3889
|
.td-action-note { flex-basis: 100%; font-size: 12.5px; color: var(--ink-soft); margin: 4px 0 0; }
|
|
4100
3890
|
|
|
4101
3891
|
/* Idea triage inside the record overlay (task 1002447). A two-column grid so
|
|
@@ -4103,17 +3893,9 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
4103
3893
|
.td-form { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; margin: 14px 0 0; }
|
|
4104
3894
|
.td-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
4105
3895
|
.td-field--wide { grid-column: 1 / -1; }
|
|
4106
|
-
/* The
|
|
4107
|
-
|
|
4108
|
-
.td-field input, .td-field select {
|
|
4109
|
-
font-size: 13px;
|
|
4110
|
-
min-width: 0;
|
|
4111
|
-
max-width: 100%;
|
|
4112
|
-
}
|
|
4113
|
-
/* The 24px floor (task 1004301 found it failing): at 13px the base field's padding left a
|
|
4114
|
-
number input 21px tall beside a select that clears it — the task page's "Wait for task"
|
|
4115
|
-
box. Sized to sit level with its select. */
|
|
4116
|
-
.td-field input { min-height: 26px; box-sizing: border-box; }
|
|
3896
|
+
/* The field is the shared one (controls.css); the two-column grid only needs
|
|
3897
|
+
it to shrink with its column. */
|
|
3898
|
+
.td-field input, .td-field select { min-width: 0; max-width: 100%; }
|
|
4117
3899
|
.td-form .td-actions, .td-form-hint, .td-form .td-action-note { grid-column: 1 / -1; }
|
|
4118
3900
|
.td-form .td-actions { margin: 4px 0 0; }
|
|
4119
3901
|
.td-form-hint { font-size: 12.5px; color: var(--ink-soft); margin: 0; }
|
|
@@ -4316,7 +4098,6 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
4316
4098
|
.filter-views__tag { font-size: 11px; color: var(--ink-faint); margin-left: 6px; }
|
|
4317
4099
|
.filter-views__empty, .filter-views__note { font-size: 12.5px; color: var(--ink-soft); margin: 0 0 10px; }
|
|
4318
4100
|
.filter-views__note:empty { display: none; }
|
|
4319
|
-
.filter-views__row .btn-ghost { min-height: 24px; padding: 0 8px; font-size: 12px; }
|
|
4320
4101
|
.filter-views__save { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
|
|
4321
4102
|
.filter-views__save .filter { flex: 1 1 180px; }
|
|
4322
4103
|
/* On a phone the button can sit anywhere in a wrapped row, so the menu spans the
|
|
@@ -4378,9 +4159,6 @@ html[data-door="out"] #hall-main { width: 100%; }
|
|
|
4378
4159
|
than edit the markup, the name joins the lede it meant. */
|
|
4379
4160
|
.scroll__note { margin: 0 0 14px; font-size: 13.5px; color: var(--ink-soft); max-width: var(--measure); }
|
|
4380
4161
|
|
|
4381
|
-
/* collab.js:73-74 — the settle buttons asked for a small variant of .btn that
|
|
4382
|
-
does not exist, so "Someone helped" rendered full-size on every row. */
|
|
4383
|
-
.btn--sm { min-height: 26px; padding: 0 11px; font-size: 12.5px; }
|
|
4384
4162
|
|
|
4385
4163
|
/* The notice line: roster.js and blockers.js notice() set body.className='note'
|
|
4386
4164
|
for an empty / error message, and neither page's sheet defines it. */
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// modules/ui-design/scripts/gen-design-tokens.js — build config/design-tokens.neutral.json
|
|
4
|
+
// from the two files that already own its values (task 1005082, goal 1000138).
|
|
5
|
+
//
|
|
6
|
+
// WHY. The design-tool token file (read by the Claude Design and Figma adapters)
|
|
7
|
+
// was hand-written, and its spacing (4/8/16/24/40/64) and motion (200/250ms) had
|
|
8
|
+
// drifted from what every page actually uses (the 6/10/14/22/34/52 rhythm and the
|
|
9
|
+
// 150/200ms durations in the shared token sheet). A designer starting from it got
|
|
10
|
+
// a spacing scale no page has. Now nothing in that file is typed by hand:
|
|
11
|
+
//
|
|
12
|
+
// colours + font stacks + font links ← config/branding.neutral.json theme.ui
|
|
13
|
+
// spacing, corners, motion ← modules/ui-design/public/tokens.css (--ds-*)
|
|
14
|
+
//
|
|
15
|
+
// The file keeps its existing key names (config/design-tokens.schema.json closes
|
|
16
|
+
// them, and an instance's config/design-tokens.json overrides by those names); each
|
|
17
|
+
// key is mapped to the --ds-* step it means, below.
|
|
18
|
+
//
|
|
19
|
+
// node modules/ui-design/scripts/gen-design-tokens.js write the file
|
|
20
|
+
// node modules/ui-design/scripts/gen-design-tokens.js --check exit 1 if it is stale
|
|
21
|
+
//
|
|
22
|
+
// Guarded by tests/design_tokens_generated.mjs.
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
|
|
27
|
+
const ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
28
|
+
const SHEET = path.join(ROOT, 'modules', 'ui-design', 'public', 'tokens.css');
|
|
29
|
+
const PACK = path.join(ROOT, 'config', 'branding.neutral.json');
|
|
30
|
+
const OUT = path.join(ROOT, 'config', 'design-tokens.neutral.json');
|
|
31
|
+
|
|
32
|
+
// The schema's key → the --ds-* custom property it is generated from.
|
|
33
|
+
const MAP = Object.freeze({
|
|
34
|
+
scale: { xs: '--ds-space-hair', sm: '--ds-space-xs', md: '--ds-space-sm', lg: '--ds-space-md', xl: '--ds-space-lg', '2xl': '--ds-space-xl' },
|
|
35
|
+
radius: { sm: '--ds-radius-tile', md: '--ds-radius-panel', lg: '--ds-radius-panel', pill: '--ds-radius-pill' },
|
|
36
|
+
motion: { fast: '--ds-dur-fast', normal: '--ds-dur-base', slow: '--ds-dur-arrive', easing: '--ds-ease' },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// The pack carries no mono stack (it is not one of the fifteen), so the design
|
|
40
|
+
// side states the label/mono face DESIGN.md names (typography.label).
|
|
41
|
+
const MONO = '"JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace';
|
|
42
|
+
|
|
43
|
+
const NOTE = 'GENERATED by node modules/ui-design/scripts/gen-design-tokens.js; do not edit by hand. '
|
|
44
|
+
+ 'Colours, font stacks and font links are config/branding.neutral.json theme.ui; scale, radius and motion are '
|
|
45
|
+
+ 'the --ds-* steps of modules/ui-design/public/tokens.css (the mapping is in the generator). To override, copy '
|
|
46
|
+
+ 'values into config/design-tokens.json (the instance pack). See DESIGN.md "The hall" and '
|
|
47
|
+
+ 'modules/ui-design/docs/design-contract.md.';
|
|
48
|
+
|
|
49
|
+
// The first :root { … } block of the sheet: the light/base values. The dark and
|
|
50
|
+
// reduced-motion blocks restate a few of them and are deliberately not read.
|
|
51
|
+
function readSheetVars(css) {
|
|
52
|
+
const noComments = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
53
|
+
const m = noComments.match(/:root\s*\{([^}]*)\}/);
|
|
54
|
+
if (!m) throw new Error('tokens.css: no :root block');
|
|
55
|
+
const vars = {};
|
|
56
|
+
for (const decl of m[1].split(';')) {
|
|
57
|
+
const d = decl.match(/^\s*(--[a-z0-9-]+)\s*:\s*([\s\S]+?)\s*$/i);
|
|
58
|
+
if (d) vars[d[1]] = d[2].replace(/\s+/g, ' ');
|
|
59
|
+
}
|
|
60
|
+
return vars;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function build({ sheet = fs.readFileSync(SHEET, 'utf8'), pack = JSON.parse(fs.readFileSync(PACK, 'utf8')) } = {}) {
|
|
64
|
+
const vars = readSheetVars(sheet);
|
|
65
|
+
const ui = (pack.theme && pack.theme.ui) || {};
|
|
66
|
+
const pick = (group) => {
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const [key, prop] of Object.entries(MAP[group])) {
|
|
69
|
+
if (!(prop in vars)) throw new Error(`tokens.css: ${prop} (for ${group}.${key}) is not declared`);
|
|
70
|
+
out[key] = vars[prop];
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
const fonts = { display: ui.fonts.display, body: ui.fonts.body, mono: MONO };
|
|
75
|
+
if (Array.isArray(ui.fonts.links)) fonts.links = ui.fonts.links.slice();
|
|
76
|
+
const tokens = {
|
|
77
|
+
_note: NOTE,
|
|
78
|
+
colors: Object.assign({}, ui.colors),
|
|
79
|
+
fonts,
|
|
80
|
+
scale: pick('scale'),
|
|
81
|
+
radius: pick('radius'),
|
|
82
|
+
motion: pick('motion'),
|
|
83
|
+
};
|
|
84
|
+
return JSON.stringify(tokens, null, 2) + '\n';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { build, readSheetVars, MAP, OUT };
|
|
88
|
+
|
|
89
|
+
if (require.main === module) {
|
|
90
|
+
const next = build();
|
|
91
|
+
const current = fs.existsSync(OUT) ? fs.readFileSync(OUT, 'utf8') : '';
|
|
92
|
+
if (process.argv.includes('--check')) {
|
|
93
|
+
if (current === next) { process.stdout.write('design-tokens.neutral.json: current\n'); process.exitCode = 0; }
|
|
94
|
+
else {
|
|
95
|
+
process.stderr.write('design-tokens.neutral.json is STALE: run node modules/ui-design/scripts/gen-design-tokens.js\n');
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if (current !== next) fs.writeFileSync(OUT, next);
|
|
100
|
+
process.stdout.write((current === next ? 'design-tokens.neutral.json: unchanged' : 'design-tokens.neutral.json: written') + '\n');
|
|
101
|
+
}
|
|
102
|
+
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.48",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.48",
|
|
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.24.
|
|
3
|
+
"version": "1.24.48",
|
|
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
|
@@ -11247,5 +11247,17 @@
|
|
|
11247
11247
|
"id": "1005080",
|
|
11248
11248
|
"text": "The screenshot checker builders use before shipping a design change shows the real hall pages again, instead of a pop-up covering every page. Two small accessibility fixes came with it: a few tiny links are now big enough to t"
|
|
11249
11249
|
}
|
|
11250
|
+
],
|
|
11251
|
+
"1.24.47": [
|
|
11252
|
+
{
|
|
11253
|
+
"id": "1005082",
|
|
11254
|
+
"text": "The design rulebook now describes the builders' hall as it really is, and spells out the name: 'cloud bongos' in the logo, 'Cloud Bongos' in sentences. The design-tool token file is now built automatically from the real styles"
|
|
11255
|
+
}
|
|
11256
|
+
],
|
|
11257
|
+
"1.24.48": [
|
|
11258
|
+
{
|
|
11259
|
+
"id": "1005084",
|
|
11260
|
+
"text": "The builders' hall now has one button and one text box style everywhere instead of eight button sizes and five box styles; main buttons are the full 46px pill with dark text on the orange, and the setup screen's save button is"
|
|
11261
|
+
}
|
|
11250
11262
|
]
|
|
11251
11263
|
}
|
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.24.
|
|
84
|
+
const CORE_VERSION = '1.24.48'; // 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');
|
package/tests/bulk_toolbar.mjs
CHANGED
|
@@ -196,7 +196,10 @@ test('goals.html no longer borrows work.css, and work.css no longer holds what i
|
|
|
196
196
|
assert.ok(!new RegExp(`^${sel.replace('.', '\\.')}(\\s|:|,|\\{)`, 'm').test(work), `work.css must not restate ${sel}`);
|
|
197
197
|
}
|
|
198
198
|
const style = read('style.css');
|
|
199
|
-
for (const sel of ['.claim-count-line', '.
|
|
199
|
+
for (const sel of ['.claim-count-line', '.wip__select']) {
|
|
200
200
|
assert.ok(new RegExp(`^${sel.replace('.', '\\.')} \\{`, 'm').test(style), `style.css carries ${sel}`);
|
|
201
201
|
}
|
|
202
|
+
// task 1005084: .wip__release is the shared ghost (controls.css); style.css keeps its warning hover
|
|
203
|
+
assert.match(read('controls.css'), /\.wip__release/, 'the shared sheet draws .wip__release');
|
|
204
|
+
assert.match(style, /^\.wip__release:hover:not\(:disabled\) \{/m, 'style.css keeps the release\'s warning hover');
|
|
202
205
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// tests/design_tokens_generated.mjs — config/design-tokens.neutral.json is GENERATED
|
|
2
|
+
// from the branding pack and the shared token sheet, and regenerates byte-identical
|
|
3
|
+
// (task 1005082, goal 1000138).
|
|
4
|
+
//
|
|
5
|
+
// WHY. The hand-written file's spacing and motion had drifted from every page's
|
|
6
|
+
// (4/8/16px against the 6/10/14px rhythm). The generator removes the hand; this
|
|
7
|
+
// test is what notices a hand coming back, or a token-sheet edit nobody regenerated.
|
|
8
|
+
//
|
|
9
|
+
// Run: node --test tests/design_tokens_generated.mjs
|
|
10
|
+
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import { test } from 'node:test';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const gen = require(path.join(ROOT, 'modules', 'ui-design', 'scripts', 'gen-design-tokens.js'));
|
|
21
|
+
|
|
22
|
+
test('the committed token file is exactly what the generator writes', () => {
|
|
23
|
+
const committed = fs.readFileSync(path.join(ROOT, 'config', 'design-tokens.neutral.json'), 'utf8');
|
|
24
|
+
assert.equal(committed, gen.build(),
|
|
25
|
+
'config/design-tokens.neutral.json is stale or hand-edited: run node modules/ui-design/scripts/gen-design-tokens.js');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('every scale, radius and motion value is the shared sheet\'s --ds-* value', () => {
|
|
29
|
+
const vars = gen.readSheetVars(fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'public', 'tokens.css'), 'utf8'));
|
|
30
|
+
const tokens = JSON.parse(gen.build());
|
|
31
|
+
for (const group of ['scale', 'radius', 'motion']) {
|
|
32
|
+
for (const [key, prop] of Object.entries(gen.MAP[group])) {
|
|
33
|
+
assert.equal(tokens[group][key], vars[prop], `${group}.${key} must be ${prop}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
assert.equal(tokens.scale.xs, '6px', 'the rhythm starts at the 6px hair step');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('a token-sheet change reaches the generated file (the generator reads the sheet, not a copy)', () => {
|
|
40
|
+
const sheet = ':root { --ds-space-hair: 7px; --ds-space-xs: 10px; --ds-space-sm: 14px; --ds-space-md: 22px;'
|
|
41
|
+
+ ' --ds-space-lg: 34px; --ds-space-xl: 52px; --ds-radius-tile: 10px; --ds-radius-panel: 16px;'
|
|
42
|
+
+ ' --ds-radius-pill: 999px; --ds-dur-fast: 150ms; --ds-dur-base: 200ms; --ds-dur-arrive: 700ms;'
|
|
43
|
+
+ ' --ds-ease: cubic-bezier(.2, .7, .3, 1); }';
|
|
44
|
+
const pack = JSON.parse(fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8'));
|
|
45
|
+
assert.equal(JSON.parse(gen.build({ sheet, pack })).scale.xs, '7px');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a --ds-* step the mapping needs but the sheet lost is an error, not a silent gap', () => {
|
|
49
|
+
const pack = JSON.parse(fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8'));
|
|
50
|
+
assert.throws(() => gen.build({ sheet: ':root { --ds-space-hair: 6px; }', pack }), /is not declared/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('only the base :root block is read: the dark and reduced-motion restatements do not leak in', () => {
|
|
54
|
+
const vars = gen.readSheetVars(':root { --ds-dur-fast: 150ms; } @media (prefers-reduced-motion: reduce) { :root { --ds-dur-fast: 0ms; } }');
|
|
55
|
+
assert.equal(vars['--ds-dur-fast'], '150ms');
|
|
56
|
+
});
|
|
@@ -42,6 +42,7 @@ const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
|
42
42
|
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
43
43
|
|
|
44
44
|
const styleCss = read('style.css');
|
|
45
|
+
const controlsCss = read('controls.css');
|
|
45
46
|
const workCss = read('work.css');
|
|
46
47
|
const workHtml = read('work.html');
|
|
47
48
|
|
|
@@ -104,7 +105,8 @@ test('a prose link buys BOTH axes — padding for width, negative margin to give
|
|
|
104
105
|
// ---- 3. the world's own bans ------------------------------------------------
|
|
105
106
|
|
|
106
107
|
test('nothing on the board paints text white on the accent', () => {
|
|
107
|
-
|
|
108
|
+
// task 1005084: the claim is the shared primary door, drawn in controls.css
|
|
109
|
+
const claim = controlsCss.replace(/\/\*[\s\S]*?\*\//g, '').match(/([^{}]*\.task__claim\b[^{}]*)\{([^{}]*background:[^{}]*)\}/)?.[2] ?? null;
|
|
108
110
|
assert.ok(claim, '.task__claim has a rule');
|
|
109
111
|
assert.match(claim, /background:\s*var\(--accent\)/, 'the claim button is the page\'s one accent fill');
|
|
110
112
|
assert.match(claim, /color:\s*var\(--accent-on\)/,
|
|
@@ -116,7 +118,9 @@ test('nothing on the board paints text white on the accent', () => {
|
|
|
116
118
|
});
|
|
117
119
|
|
|
118
120
|
test('the shared task + bulk-toolbar rules live in style.css (the shared base), not the page-local work.css', () => {
|
|
119
|
-
|
|
121
|
+
// .task__claim left for the shared controls.css (task 1005084); work.css still must not restate it
|
|
122
|
+
assert.equal(rule(workCss, '.task__claim'), null, 'work.css must not restate .task__claim');
|
|
123
|
+
for (const sel of ['.task__age', '.task__age--stale', '.task__tag--newcomer', '.task__tag--mine', '.task__tag--rank', '.task__tag--rank-open', '.task__touches', '.task', '.bulk-toolbar', '.bulk-toolbar__count', '.bulk-toolbar__actions', '.bulk-toolbar__btn', '.bulk-toolbar__clear']) {
|
|
120
124
|
assert.ok(rule(styleCss, sel), `style.css carries ${sel}, beside .task__tag`);
|
|
121
125
|
assert.equal(rule(workCss, sel), null, `work.css must not restate ${sel}`);
|
|
122
126
|
}
|
|
@@ -147,10 +147,12 @@ test('A: an older branding pack with no joinability falls back to the apply door
|
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
test('A: the quiet fill is a defined variant, not an inline colour', () => {
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
// task 1005084: .landing__btn--quiet is the shared ghost in controls.css
|
|
151
|
+
const css = read('controls.css').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
152
|
+
const m = css.match(/([^{}]*\.landing__btn--quiet[\s,][^{}]*)\{([^{}]*)\}/);
|
|
153
|
+
assert.ok(m, 'controls.css defines .landing__btn--quiet');
|
|
154
|
+
const block = m[2];
|
|
155
|
+
assert.match(block, /background:\s*transparent/, 'the quiet variant is the ghost');
|
|
154
156
|
assert.deepEqual(block.match(/#[0-9a-f]{3,8}\b/gi) || [], [], 'the variant carries no literal colour');
|
|
155
157
|
// Same geometry as the request button means it inherits .landing__btn's
|
|
156
158
|
// min-height and padding rather than restating them.
|