@bongos/core 1.19.458 → 1.19.460

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.
Files changed (50) hide show
  1. package/.bongos-core.json +117 -42
  2. package/.claude/skills/design/SKILL.md +10 -0
  3. package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +30 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/branding-contract.md +1 -1
  6. package/docs/copy-inventory.md +52 -61
  7. package/docs/copy-registry.json +92 -203
  8. package/docs/file-map.md +1 -1
  9. package/docs/module-api-changelog.md +4 -0
  10. package/docs/recipes/ui-look-before-you-ship.md +2 -0
  11. package/modules/hall-ui/public/collab-lib.js +40 -4
  12. package/modules/hall-ui/public/collab.css +18 -34
  13. package/modules/hall-ui/public/collab.html +17 -17
  14. package/modules/hall-ui/public/collab.js +75 -39
  15. package/modules/hall-ui/public/collab.states.json +2 -2
  16. package/modules/hall-ui/public/copy-desk.css +64 -109
  17. package/modules/hall-ui/public/copy-desk.html +76 -69
  18. package/modules/hall-ui/public/copy-desk.js +175 -80
  19. package/modules/hall-ui/public/copy-desk.states.json +59 -2
  20. package/modules/hall-ui/public/oversight.css +11 -2
  21. package/modules/lifecycle/help-requests.js +18 -8
  22. package/modules/ui-design/kit/README.md +7 -5
  23. package/modules/ui-design/kit/check-mock.js +12 -5
  24. package/modules/ui-design/kit/contrast.js +13 -6
  25. package/modules/ui-design/kit/lib.js +39 -2
  26. package/modules/ui-design/kit/probe.js +4 -1
  27. package/modules/ui-design/kit/render.js +7 -4
  28. package/modules/ui-design/kit/serve.js +9 -2
  29. package/modules/ui-design/styles/README.md +54 -2
  30. package/modules/ui-design/styles/chrome-world/DESIGN.md +56 -0
  31. package/modules/ui-design/styles/chrome-world/mock.html +275 -0
  32. package/modules/ui-design/styles/chrome-world/mock.states.json +11 -0
  33. package/modules/ui-design/styles/chrome-world/pack.json +33 -0
  34. package/modules/ui-design/styles/expedition/DESIGN.md +141 -0
  35. package/modules/ui-design/styles/expedition/mock.html +285 -0
  36. package/modules/ui-design/styles/expedition/mock.states.json +11 -0
  37. package/modules/ui-design/styles/expedition/pack.json +33 -0
  38. package/modules/ui-design/styles/grove/DESIGN.md +148 -0
  39. package/modules/ui-design/styles/grove/mock.html +294 -0
  40. package/modules/ui-design/styles/grove/mock.states.json +11 -0
  41. package/modules/ui-design/styles/grove/pack.json +33 -0
  42. package/package-lock.json +2 -2
  43. package/package.json +1 -1
  44. package/src/module-api.js +1 -1
  45. package/tests/collab_page.mjs +13 -7
  46. package/tests/cosmos_token_contrast.mjs +9 -2
  47. package/tests/hall_collab_world.mjs +214 -0
  48. package/tests/hall_tokens.mjs +15 -6
  49. package/tests/ui_design_styles.mjs +218 -0
  50. package/tests/world_tokens.mjs +9 -2
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: The Grove
3
+ description: A sculpted paper garden. Pale sage paper, soft corners on everything that holds content, shadows tinted from the ink and never grey, the sea green as a second voice, one lit tan on everything pressable.
4
+ colors:
5
+ # ---- contract tier, LIGHT: the thirteen this look's pack.json carries, value for value
6
+ # (tests/ui_design_styles.mjs holds the two in lockstep). Spliced into :root at the end of
7
+ # <head> at (0,1,0) on any instance that adopts the look.
8
+ bg: "#e1eadb"
9
+ bg-card: "#f6f9f3"
10
+ bg-deep: "#cad8c2"
11
+ bg-frame: "#eaf0e5"
12
+ ink: "#0f3226"
13
+ ink-soft: "#2f5240"
14
+ ink-faint: "#3f5a48"
15
+ rule: "#9db69a"
16
+ rule-soft: "#d0ddcb"
17
+ accent: "#e3c39c"
18
+ accent-deep: "#6d4b34"
19
+ sea: "#3d8562"
20
+ terracotta: "#b07a4e"
21
+ # ---- contract tier, DARK: NOT this look's to set. A pack carries light values only; dark is
22
+ # each sheet's own black tier, which the look reaches through the tan, the sea and the faces.
23
+ # The candidate's own green dark ground (#2f6049) cannot travel in a pack.
24
+ bg-dark: "#000000"
25
+ ink-dark: "#ffffff"
26
+ accent-deep-dark: "color-mix(in srgb, var(--accent) 70%, #ffffff)"
27
+ # ---- derived: the four fields. Sea is the SLAB (a whole band sits on it), bark (accent-deep)
28
+ # is every quiet mark and every accent-as-text job, tan (accent) is the lit thing, wood
29
+ # (terracotta) is held for a small drum's shell and for warn / err. The slab's field is the
30
+ # sea deepened 38% into black in BOTH modes: the contract's sea sits in the lightness band
31
+ # the hall's info state needs, and a slab that cream can be read on is darker than that.
32
+ accent-ink: "var(--accent-deep)"
33
+ accent-ink-dark: "var(--accent)"
34
+ accent-on: "color-mix(in srgb, var(--accent) 12%, #000000)"
35
+ accent-lift: "color-mix(in srgb, var(--accent) 72%, #ffffff)"
36
+ accent-tint: "color-mix(in srgb, var(--accent) 34%, var(--bg-card))"
37
+ accent-tint-dark: "color-mix(in srgb, var(--accent) 18%, #000000)"
38
+ field: "color-mix(in srgb, var(--sea) 62%, #000000)"
39
+ on-field: "color-mix(in srgb, var(--accent) 55%, #ffffff)"
40
+ on-field-soft: "color-mix(in srgb, var(--accent) 40%, #ffffff)"
41
+ field-rule: "color-mix(in srgb, #ffffff 20%, transparent)"
42
+ focus: "var(--accent-deep)"
43
+ focus-dark: "var(--accent)"
44
+ # ---- derived: the sculpted paper. Shadows are TINTED from the ink, never grey.
45
+ shade: "color-mix(in srgb, var(--ink) 16%, transparent)"
46
+ shade-soft: "color-mix(in srgb, var(--ink) 7%, transparent)"
47
+ shade-dark: "rgba(0, 0, 0, .6)"
48
+ shade-soft-dark: "rgba(0, 0, 0, .35)"
49
+ typography:
50
+ display: { fontFamily: "Nunito, Trebuchet MS, system-ui, sans-serif", fontSize: "clamp(34px, 6.4vw, 84px)", fontWeight: 800, lineHeight: 0.96, letterSpacing: "-0.03em" }
51
+ headline: { fontFamily: "{typography.display.fontFamily}", fontSize: "clamp(24px, 3vw, 38px)", fontWeight: 800, lineHeight: 1.08, letterSpacing: "-0.02em" }
52
+ body: { fontFamily: "Nunito, Trebuchet MS, system-ui, sans-serif", fontSize: "17px", fontWeight: 400, lineHeight: 1.55 }
53
+ label: { fontFamily: "JetBrains Mono, ui-monospace, Cascadia Mono, Consolas, monospace", fontSize: "11px", fontWeight: 500, letterSpacing: "0.08em", textTransform: "uppercase" }
54
+ mono: { fontFamily: "{typography.label.fontFamily}", fontSize: "13px", fontWeight: 400, fontFeature: "tabular-nums" }
55
+ rounded:
56
+ pill: "999px"
57
+ panel: "22px"
58
+ tile: "12px"
59
+ spacing:
60
+ band: "clamp(40px, 7vh, 72px)"
61
+ gutter: "clamp(16px, 4vw, 26px)"
62
+ container: "1180px"
63
+ components:
64
+ bar-card: { backgroundColor: "{colors.bg-card}", rounded: "{rounded.pill}", shadow: "0 10px 26px -14px {colors.shade}, 0 2px 6px {colors.shade-soft}" }
65
+ door-primary: { backgroundColor: "{colors.accent}", textColor: "{colors.accent-on}", rounded: "{rounded.pill}", height: "48px", padding: "0 28px" }
66
+ door-secondary: { backgroundColor: "transparent", textColor: "{colors.ink}", rounded: "{rounded.pill}", height: "48px" }
67
+ sign-in: { backgroundColor: "{colors.field}", textColor: "{colors.on-field}", rounded: "{rounded.pill}", height: "42px" }
68
+ slab: { backgroundColor: "{colors.field}", textColor: "{colors.on-field}", rounded: "{rounded.panel}" }
69
+ tile: { backgroundColor: "{colors.bg-card}", rounded: "{rounded.tile}", padding: "16px" }
70
+ badge: { backgroundColor: "{colors.accent-tint}", textColor: "{colors.accent-ink}", rounded: "{rounded.tile}", height: "26px" }
71
+ ---
72
+
73
+ # Style: The Grove
74
+
75
+ **Origin.** The `organic-cards` candidate of the world round ([task 1003296](https://cloudbongos.com/builders#/task/1003296), `docs/design/mocks/world/organic-cards/`), kept by the owner in round 1 and not picked in round 2. Ported into the style library ([task 1003323](https://cloudbongos.com/builders#/task/1003323)) as a **branding pack**, never a sheet: the thirteen light colours and the two faces in `pack.json`, retuned against the contract. The retune is a measurement, not a taste: the candidate's sage ground (`#c8d7bf`) put the hub's fixed status vocabulary and the hall's chips under 4.5:1, so the ground lifted to `#e1eadb` and the inks deepened until every pair the three sheets paint cleared. The candidate's green **dark** ground (`#2f6049`) cannot travel in a pack (a pack carries light values only); in dark this look is the sheets' black under the tan and the sea.
76
+
77
+ ## Overview
78
+
79
+ **Creative North Star: "A sculpted paper garden"**
80
+
81
+ Everything in this look is cut from soft paper and set down in a garden. Containers have real corners (22px on a panel, 12px on a tile, a pill on anything pressable), they lift off the ground on a shadow that is tinted from the ink and never grey, and the ground itself is a pale sage rather than white. Three named fields carry the system: sea green as a slab a whole band can sit on, bark brown for every quiet mark, and a lit tan on everything that can be pressed. Type is a rounded, wide-aperture face set heavy for headlines and plain for body.
82
+
83
+ **Key Characteristics:**
84
+
85
+ - Pale sage paper (`#e1eadb`); pure black in dark, where the tan and the slab do the work.
86
+ - Soft corners on everything that holds content; pills on everything pressable.
87
+ - Tinted shadows mixed from the ink; a hairline only where a shadow would be too much.
88
+ - Nunito 800 for display, 400 for body, JetBrains Mono for labels on paper.
89
+ - The sea green as a field, not an accent; the tan as the one lit thing.
90
+
91
+ ## Colors
92
+
93
+ - **Tan** (`accent`, `#e3c39c`): the lit thing. It fills the primary door, the identity disc and the lit rail tile. It is a fill colour: at 1.6:1 against white it never paints words, so every accent-as-text job is `accent-deep`.
94
+ - **Bark** (`accent-deep`, `#6d4b34`): every quiet mark, every link, the badge's ink, 6.01:1 on the sage and 7.17:1 on the card. In dark it derives from the tan, never restated.
95
+ - **Ink on tan** (`accent-on`): `color-mix(in srgb, var(--accent) 12%, #000000)`, a near black warmed by the tan.
96
+ - **Sea** (`#3d8562`): the second voice. The contract's sea sits in the narrow lightness band the hall's info state needs (sea at 80% into white in dark, 86% into black in light), so the candidate's deep `#2a5a46` could not travel; the **slab** derives its field from it, `color-mix(in srgb, var(--sea) 62%, #000000)`, a deep green a whole band sits on with cream type (`on-field`: the tan mixed 55% into white, above 6:1). The sign-in door takes the field too, so the bar has one green thing.
97
+ - **Wood** (`terracotta`, `#b07a4e`): held for a small drum's shell and as the warn / error source.
98
+ - **Sage, card, deep, frame** (`bg`, `bg-card`, `bg-deep`, `bg-frame`): the ground and its layers. Card is a near-white with a green cast, so a tile reads as paper on paper, not white on green.
99
+ - **Ink, ink-soft, ink-faint**: deep forest inks. Ink-faint on the sage is 5.88:1; it was deepened from the candidate's `#476350` (4.29:1), the pair that failed first.
100
+ - **Rule, rule-soft**: the sage's own hairlines, for the rare divider a shadow cannot do.
101
+
102
+ ## Typography
103
+
104
+ **Display Font:** Nunito 800 (with Trebuchet MS, system-ui, sans-serif)
105
+ **Body Font:** Nunito 400/600 (same stack)
106
+ **Label / Mono Font:** JetBrains Mono 400/500 (with ui-monospace, Cascadia Mono, Consolas)
107
+
108
+ **Character:** A rounded, wide-aperture humanist sans that is friendly at every size. Display is set at 800 with tracking pulled to `-0.03em`, sentence case: the grove never shouts. Labels take the mono on paper, at a looser `0.08em` than the chrome world, and the body face in dark.
109
+
110
+ ## Shapes
111
+
112
+ **All soft.** 22px on panels and slabs, 12px on tiles and badges, a pill on every door, chip and input. The bar itself is a pill-shaped card floating on the sage. There is no square corner anywhere in this look.
113
+
114
+ ## Materials
115
+
116
+ - **Tinted shadows.** `0 10px 26px -14px shade, 0 2px 6px shade-soft`, where `shade` is the ink at 16% and `shade-soft` the ink at 7%; in dark, black at 60% and 35%. Grey shadows are banned: a shadow is the ground's own colour, deepened.
117
+ - **The slab.** A deep-green panel (the sea mixed 62% into black, the same in both modes) with cream type and a 20%-white hairline inside it.
118
+ - **Plates** (when a surface adds them): photographic leaf plates over the paper, a real-alpha hero object. A surface's plates are its own; this look brings none.
119
+
120
+ ## Motion
121
+
122
+ Gentle. Entry motion at 200ms on `cubic-bezier(.2,.7,.3,1)`; a tile lifts 2px on hover; no ambient loops in this look. The kill switch stands as the platform's.
123
+
124
+ ## Do's and Don'ts
125
+
126
+ ### Do
127
+
128
+ - **Do** fill with `accent` and write with `accent-deep`; put `accent-on` on any tan.
129
+ - **Do** tint every drop from the ink; keep the two-part lift recipe as the one elevation.
130
+ - **Do** spend the sea as a field, one slab per view at most.
131
+ - **Do** round everything: 22 / 12 / pill.
132
+ - **Do** keep the label face mono on paper and the body face in dark.
133
+
134
+ ### Don't
135
+
136
+ - **Don't** write words in the tan, or put white on it.
137
+ - **Don't** use an em dash or en dash in visible copy, an emoji, gradient text, or a coloured text glow.
138
+ - **Don't** put a square corner on a container, or a hairline where a lift would do.
139
+ - **Don't** pin the accent, sea or terracotta in a dark block; dark derives.
140
+ - **Don't** declare a `:root` in a page; page tokens live on `body`.
141
+
142
+ ## Accessibility Floors
143
+
144
+ The platform's, unchanged: 4.5:1 body text and 3:1 large text and non-text in both modes, measured from the real stylesheet; every control 24px on both sides (the doors are 48, the sign-in pill 42); the kill switch at the foot of the token layer. This pack measures clean against every pair the landing's `world.css`, the hub's `cosmos.css` and the hall's `style.css` paint (`tests/ui_design_styles.mjs` runs their suites under it) and on its own grounds (`node modules/ui-design/kit/contrast.js --pack grove`).
145
+
146
+ ## Adopting this look
147
+
148
+ Copy `pack.json`'s `theme` into the instance's `config/branding.json` (a fresh instance's `cloudbongos init` writes that file; an existing one merges the block over its own). Nothing else changes: every sheet reads the contract and only the contract, so the landing, the hub, the hall and the status pages re-theme at once. What stays the platform's: the dark ground (black), the hall's fixed green / amber / red verification vocabulary, the status ledger's pillars, and every derived recipe a sheet already carries; the surfaces keep their hairlines, since a corner radius and a shadow are a sheet's derived tier, not a pack's. Look before you ship: `node modules/ui-design/kit/render.js --page / --style grove`.
@@ -0,0 +1,294 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{productName}} · the grove, on a sheet</title>
7
+ <script>
8
+ (function(){try{var q=new URLSearchParams(location.search);var m=q.get('mode');if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
9
+ </script>
10
+ <style>
11
+ html { color-scheme: light; }
12
+ body {
13
+ /* the grove's derived tier, by the recipes in this style's DESIGN.md. The fifteen arrive
14
+ from the pack the stub splices after this sheet; nothing below is a colour of its own: a
15
+ var(), a color-mix(), or black or white with alpha. Sculpted paper: soft corners on every
16
+ container, tinted (never grey) shadows mixed from the ink, the sea green as a field a
17
+ whole band can sit on, and the tan spent only on what is pressable or live. */
18
+ --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
19
+ --glass: var(--bg-card);
20
+ --glass-strong: var(--bg-deep);
21
+ --glass-edge: var(--rule);
22
+ --accent-ink: var(--accent-deep);
23
+ --accent-lift: color-mix(in srgb, var(--accent) 72%, #ffffff);
24
+ --accent-tint: color-mix(in srgb, var(--accent) 34%, var(--bg-card));
25
+ --accent-on: color-mix(in srgb, var(--accent) 12%, #000000);
26
+ --accent-glow: 0 0 0 0 transparent;
27
+ --focus: var(--accent-deep);
28
+ --type-halo: none;
29
+ --field: color-mix(in srgb, var(--sea) 62%, #000000);
30
+ --on-field: color-mix(in srgb, var(--accent) 55%, #ffffff);
31
+ --on-field-soft: color-mix(in srgb, var(--accent) 40%, #ffffff);
32
+ --field-rule: color-mix(in srgb, #ffffff 20%, transparent);
33
+ --shade: color-mix(in srgb, var(--ink) 16%, transparent);
34
+ --shade-soft: color-mix(in srgb, var(--ink) 7%, transparent);
35
+ --lift: 0 10px 26px -14px var(--shade), 0 2px 6px var(--shade-soft);
36
+ --label-face: var(--font-mono);
37
+ --label-size: 11px;
38
+ --label-space: .08em;
39
+ --radius: 22px;
40
+ --radius-sm: 12px;
41
+ --radius-pill: 999px;
42
+ --top-h: 64px;
43
+ --gutter: clamp(16px, 4vw, 26px);
44
+ --band: clamp(40px, 7vh, 72px);
45
+ margin: 0;
46
+ background: var(--bg);
47
+ color: var(--ink);
48
+ font-family: var(--font-body);
49
+ font-size: 17px;
50
+ line-height: 1.55;
51
+ -webkit-font-smoothing: antialiased;
52
+ }
53
+ :root[data-mode="dark"] body {
54
+ color-scheme: dark;
55
+ --bg: #000000;
56
+ --bg-card: color-mix(in srgb, #ffffff 7%, #000000);
57
+ --bg-deep: #000000;
58
+ --bg-frame: color-mix(in srgb, #ffffff 4%, #000000);
59
+ --ink: #ffffff;
60
+ --ink-soft: rgba(255, 255, 255, .74);
61
+ --ink-faint: rgba(255, 255, 255, .64);
62
+ --rule: rgba(255, 255, 255, .16);
63
+ --rule-soft: rgba(255, 255, 255, .08);
64
+ --accent-deep: color-mix(in srgb, var(--accent) 70%, #ffffff);
65
+ --glass: rgba(255, 255, 255, .08);
66
+ --glass-strong: rgba(255, 255, 255, .14);
67
+ --glass-edge: rgba(255, 255, 255, .16);
68
+ --accent-ink: var(--accent);
69
+ --accent-tint: color-mix(in srgb, var(--accent) 18%, #000000);
70
+ --focus: var(--accent);
71
+ --shade: rgba(0, 0, 0, .6);
72
+ --shade-soft: rgba(0, 0, 0, .35);
73
+ --label-face: var(--font-body);
74
+ --label-size: 11.5px;
75
+ --label-space: .06em;
76
+ }
77
+ @media (prefers-color-scheme: dark) {
78
+ :root:not([data-mode="light"]) body {
79
+ color-scheme: dark;
80
+ --bg: #000000;
81
+ --bg-card: color-mix(in srgb, #ffffff 7%, #000000);
82
+ --bg-deep: #000000;
83
+ --bg-frame: color-mix(in srgb, #ffffff 4%, #000000);
84
+ --ink: #ffffff;
85
+ --ink-soft: rgba(255, 255, 255, .74);
86
+ --ink-faint: rgba(255, 255, 255, .64);
87
+ --rule: rgba(255, 255, 255, .16);
88
+ --rule-soft: rgba(255, 255, 255, .08);
89
+ --accent-deep: color-mix(in srgb, var(--accent) 70%, #ffffff);
90
+ --glass: rgba(255, 255, 255, .08);
91
+ --glass-strong: rgba(255, 255, 255, .14);
92
+ --glass-edge: rgba(255, 255, 255, .16);
93
+ --accent-ink: var(--accent);
94
+ --accent-tint: color-mix(in srgb, var(--accent) 18%, #000000);
95
+ --focus: var(--accent);
96
+ --shade: rgba(0, 0, 0, .6);
97
+ --shade-soft: rgba(0, 0, 0, .35);
98
+ --label-face: var(--font-body);
99
+ --label-size: 11.5px;
100
+ --label-space: .06em;
101
+ }
102
+ }
103
+
104
+ *, *::before, *::after { box-sizing: border-box; }
105
+ a { color: inherit; }
106
+ :focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
107
+ .skip { position: absolute; left: 12px; top: -60px; padding: 11px 18px; border-radius: var(--radius-pill); background: var(--bg-card); color: var(--ink); text-decoration: none; }
108
+ .skip:focus { top: 12px; }
109
+ .wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--gutter); }
110
+
111
+ /* the bar: a soft card floating on the paper */
112
+ .bar { height: var(--top-h); padding-top: 8px; }
113
+ .bar .wrap { height: 100%; }
114
+ .bar .card { height: 100%; display: flex; align-items: center; gap: 18px; padding: 0 8px 0 18px; border-radius: var(--radius-pill); background: var(--bg-card); box-shadow: var(--lift); }
115
+ .mark { font-weight: 800; letter-spacing: -.01em; text-decoration: none; padding: 10px 0; }
116
+ .nav { margin-left: auto; display: flex; align-items: center; gap: 4px; }
117
+ .nav a { text-decoration: none; color: var(--ink-soft); font-weight: 600; padding: 10px 10px; display: inline-block; line-height: 20px; border-radius: var(--radius-pill); }
118
+ .nav a:hover { color: var(--ink); background: var(--bg-frame); }
119
+ .pill { display: inline-flex; align-items: center; height: 42px; padding: 0 20px; border-radius: var(--radius-pill); text-decoration: none; font-weight: 700; transition: background-color 200ms cubic-bezier(.2, .7, .3, 1); }
120
+ .pill.in { background: var(--field); color: var(--on-field); }
121
+ .who { display: none; align-items: center; gap: 8px; height: 42px; padding: 0 6px; border-radius: var(--radius-pill); background: var(--bg-frame); }
122
+ .who .dot { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); }
123
+ body[data-state="signed-in"] .pill.in { display: none; }
124
+ body[data-state="signed-in"] .who { display: inline-flex; }
125
+
126
+ /* the bands */
127
+ .band { padding: var(--band) 0; }
128
+ h1 { font-family: var(--font-display); font-weight: 800; font-size: clamp(34px, 6.4vw, 84px); line-height: .96; letter-spacing: -.03em; margin: 0 0 18px; max-width: 12ch; }
129
+ h2 { font-family: var(--font-display); font-weight: 800; font-size: clamp(24px, 3vw, 38px); line-height: 1.08; letter-spacing: -.02em; margin: 0 0 14px; }
130
+ .lede { color: var(--ink-soft); max-width: 56ch; margin: 0; }
131
+ .label { font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: uppercase; color: var(--ink-faint); font-weight: 500; }
132
+ .badge { display: inline-flex; align-items: center; height: 26px; padding: 0 12px; border-radius: var(--radius-sm); background: var(--accent-tint); color: var(--accent-ink); margin-bottom: 14px; }
133
+
134
+ /* the thirteen, as sculpted tiles */
135
+ .sw { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 12px; margin: 0; padding: 0; list-style: none; }
136
+ .sw li { margin: 0; }
137
+ .sw i { display: block; height: 56px; border-radius: var(--radius-sm); background: var(--c); box-shadow: var(--lift); }
138
+ .sw b { display: block; margin-top: 8px; font-family: var(--font-mono); font-weight: 400; font-size: 11px; color: var(--ink-faint); overflow-wrap: anywhere; }
139
+
140
+ /* the slab: the second voice as a field a whole band can sit on */
141
+ .slab { border-radius: var(--radius); background: var(--field); color: var(--on-field); padding: clamp(22px, 4vw, 40px); box-shadow: var(--lift); }
142
+ .slab h2 { color: var(--on-field); }
143
+ .slab .lede { color: var(--on-field-soft); }
144
+ .slab hr { border: 0; border-top: 1px solid var(--field-rule); margin: 18px 0; }
145
+
146
+ /* the door pair: rounded, lifted, the primary in tan */
147
+ .doors { display: inline-flex; flex-wrap: wrap; gap: 8px; padding: 8px; border-radius: var(--radius-pill); background: var(--bg-card); box-shadow: var(--lift); }
148
+ .door { display: inline-flex; align-items: center; height: 48px; padding: 0 28px; border-radius: var(--radius-pill); text-decoration: none; font-weight: 700; transition: background-color 200ms cubic-bezier(.2, .7, .3, 1); }
149
+ .door.go { background: var(--accent); color: var(--accent-on); }
150
+ .door.go:hover { background: var(--accent-lift); }
151
+ .door.more { color: var(--ink); }
152
+ .door.more:hover { background: var(--bg-frame); }
153
+
154
+ /* the lifecycle rail: soft tiles, one lit */
155
+ .rail { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin: 0; padding: 0; list-style: none; }
156
+ .rail li { padding: 16px; border-radius: var(--radius-sm); background: var(--bg-card); box-shadow: var(--lift); }
157
+ .rail .dot { display: block; width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--ink-faint); margin-bottom: 10px; }
158
+ .rail .lit { background: var(--accent-tint); }
159
+ .rail .lit .dot { background: var(--accent); border-color: var(--accent-deep); }
160
+ .rail .lit .label { color: var(--ink); }
161
+ .rail p { margin: 6px 0 0; color: var(--ink-soft); font-size: 15px; }
162
+
163
+ /* ledger rows: a sculpted panel of hairline rows */
164
+ .ledger { margin: 0; padding: 6px 18px; list-style: none; border-radius: var(--radius); background: var(--bg-card); box-shadow: var(--lift); }
165
+ .ledger li { display: grid; grid-template-columns: 88px 1fr 152px 96px; gap: 20px; align-items: baseline; padding: 14px 0; border-top: 1px solid var(--rule-soft); }
166
+ .ledger li:first-child { border-top: 0; }
167
+ .ledger .id { font-family: var(--font-mono); font-size: 13px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
168
+ .ledger .by, .ledger .when { color: var(--ink-soft); }
169
+ .ledger .when { text-align: right; font-variant-numeric: tabular-nums; }
170
+ .ledger a { color: var(--accent-ink); text-underline-offset: 3px; display: inline-block; padding: 2px 0; }
171
+
172
+ footer { padding: 26px 0 40px; }
173
+ footer .wrap { display: flex; flex-wrap: wrap; gap: 8px 22px; align-items: baseline; color: var(--ink-soft); font-size: 15px; }
174
+ footer p { margin: 0; flex: 1 1 320px; }
175
+ footer a { color: var(--accent-ink); display: inline-block; padding: 3px 0; font-weight: 600; }
176
+
177
+ @media (max-width: 900px) {
178
+ body { --top-h: 60px; }
179
+ .rail { grid-template-columns: 1fr; }
180
+ }
181
+ @media (max-width: 600px) {
182
+ .ledger li { grid-template-columns: 1fr; gap: 4px; }
183
+ .ledger .when { text-align: left; }
184
+ .nav a { display: none; }
185
+ }
186
+ @media (prefers-reduced-motion: reduce) {
187
+ *, *::before, *::after { animation: none !important; transition: none !important; }
188
+ }
189
+ </style>
190
+ </head>
191
+ <body>
192
+ <!-- THESIS: the grove as a specimen sheet. The thirteen colours and the two faces the pack carries, laid through this look's own derived recipes (its DESIGN.md frontmatter): a bar that floats as a soft card, a rounded heavy display voice, tiles under tinted shadows mixed from the ink, the sea green as a slab a whole band sits on, door pills with the primary in tan, a rail of soft tiles with one lit, ledger rows inside a sculpted panel. This page declares no :root and no colour of its own: the pack is spliced by the stub (STYLE=grove) and every value is a var() or a color-mix() of it. Dark restates the platform's black tier on body, since a mock links no sheet; the candidate's green dark ground cannot travel in a pack, so dark is the black under the tan and the sea. STATES: rest (signed out), signed-in. MODES: ?mode=dark|light. -->
193
+ <a class="skip" href="#main">Skip to content</a>
194
+ <header class="bar">
195
+ <div class="wrap">
196
+ <div class="card">
197
+ <a class="mark" href="/">{{productName}}</a>
198
+ <nav class="nav" aria-label="Primary">
199
+ <a href="#swatches">Projects</a>
200
+ <a href="#type">Docs</a>
201
+ <a class="pill in" href="#main">Sign in</a>
202
+ <span class="who"><span class="dot" aria-hidden="true"></span><span class="label">builder</span></span>
203
+ </nav>
204
+ </div>
205
+ </div>
206
+ </header>
207
+ <main id="main" tabindex="-1">
208
+ <section class="band">
209
+ <div class="wrap">
210
+ <p class="label badge">A look from the style library</p>
211
+ <h1>The grove</h1>
212
+ <p class="lede">A sculpted paper garden. Pale sage paper, soft corners on everything that holds content, shadows tinted from the ink and never grey, the sea green as a second voice, and one lit tan on everything you can press.</p>
213
+ </div>
214
+ </section>
215
+ <section class="band" id="swatches">
216
+ <div class="wrap">
217
+ <h2>The thirteen</h2>
218
+ <ul class="sw">
219
+ <li><i style="--c: var(--bg)"></i><b>bg</b></li>
220
+ <li><i style="--c: var(--bg-card)"></i><b>bg-card</b></li>
221
+ <li><i style="--c: var(--bg-deep)"></i><b>bg-deep</b></li>
222
+ <li><i style="--c: var(--bg-frame)"></i><b>bg-frame</b></li>
223
+ <li><i style="--c: var(--ink)"></i><b>ink</b></li>
224
+ <li><i style="--c: var(--ink-soft)"></i><b>ink-soft</b></li>
225
+ <li><i style="--c: var(--ink-faint)"></i><b>ink-faint</b></li>
226
+ <li><i style="--c: var(--rule)"></i><b>rule</b></li>
227
+ <li><i style="--c: var(--rule-soft)"></i><b>rule-soft</b></li>
228
+ <li><i style="--c: var(--accent)"></i><b>accent</b></li>
229
+ <li><i style="--c: var(--accent-deep)"></i><b>accent-deep</b></li>
230
+ <li><i style="--c: var(--sea)"></i><b>sea</b></li>
231
+ <li><i style="--c: var(--terracotta)"></i><b>terracotta</b></li>
232
+ </ul>
233
+ </div>
234
+ </section>
235
+ <section class="band" id="type">
236
+ <div class="wrap">
237
+ <div class="slab">
238
+ <h2>The slab</h2>
239
+ <p class="lede">The sea green is a field, not an accent: a whole band sits on it, with cream type mixed from the tan and white. This is the second voice the contract holds for every pack; the grove is the look that spends it.</p>
240
+ <hr>
241
+ <p class="label" style="color: var(--on-field-soft)">Label face on the field</p>
242
+ <p style="margin: 6px 0 0; font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums">1003323 · 14,987 credits · 7 ships in 24h</p>
243
+ </div>
244
+ </div>
245
+ </section>
246
+ <section class="band">
247
+ <div class="wrap">
248
+ <h2>The doors</h2>
249
+ <p class="lede" style="margin-bottom: 22px">Two doors in one lifted pill. The primary is the tan with near-black ink on it; the secondary fills softly on hover.</p>
250
+ <div class="doors">
251
+ <a class="door more" href="#swatches">Open the map</a>
252
+ <a class="door go" href="#main">Start a project</a>
253
+ </div>
254
+ </div>
255
+ </section>
256
+ <section class="band">
257
+ <div class="wrap">
258
+ <h2>The lifecycle rail</h2>
259
+ <ol class="rail">
260
+ <li><span class="dot"></span><span class="label">Claim</span><p>A builder locks a task.</p></li>
261
+ <li><span class="dot"></span><span class="label">Build</span><p>The code is written in a worktree.</p></li>
262
+ <li class="lit" aria-current="step"><span class="dot"></span><span class="label">Grade</span><p>An adversarial panel reads the diff.</p></li>
263
+ <li><span class="dot"></span><span class="label">Ship</span><p>Green checks land it on main.</p></li>
264
+ <li><span class="dot"></span><span class="label">Credit</span><p>The ledger pays on landing.</p></li>
265
+ </ol>
266
+ </div>
267
+ </section>
268
+ <section class="band">
269
+ <div class="wrap">
270
+ <h2>Ledger rows</h2>
271
+ <ul class="ledger">
272
+ <li><span class="id">1003320</span><span>The look-before-you-ship kit: a stub, a renderer, the probes</span><span class="by">jaxri</span><span class="when">3d ago</span></li>
273
+ <li><span class="id">1003321</span><span>The vetted UI skills: the vendoring policy and the platform preamble</span><span class="by">jaxri</span><span class="when">3d ago</span></li>
274
+ <li><span class="id">1003323</span><span><a href="#main">The style library: looks as branding packs</a></span><span class="by">jaxri</span><span class="when">today</span></li>
275
+ </ul>
276
+ </div>
277
+ </section>
278
+ </main>
279
+ <footer>
280
+ <div class="wrap">
281
+ <p>{{productName}} is built by AI agents claiming tasks from a ledger. This sheet is served through the kit with the look's pack spliced in.</p>
282
+ <a href="#main">Privacy</a>
283
+ <a href="#main">Terms</a>
284
+ <a href="#main">Contact</a>
285
+ </div>
286
+ </footer>
287
+ <script>
288
+ (function () {
289
+ var state = new URLSearchParams(location.search).get('state');
290
+ if (state === 'signed-in') document.body.setAttribute('data-state', 'signed-in');
291
+ })();
292
+ </script>
293
+ </body>
294
+ </html>
@@ -0,0 +1,11 @@
1
+ {
2
+ "_doc": "The specimen's states for the kit (task 1003323): node modules/ui-design/kit/render.js --states modules/ui-design/styles/grove/mock.states.json --style grove. Both modes; the mock reads ?mode= itself.",
3
+ "page": "/mock",
4
+ "surface": "modules/ui-design/styles/grove",
5
+ "modes": ["dark", "light"],
6
+ "states": {
7
+ "rest": { "auth": false },
8
+ "signed-in": { "auth": true, "url": "/mock?state=signed-in", "expect": { "visible": [".who"], "hidden": [".pill.in"] } }
9
+ },
10
+ "reducedMotion": { "auth": false }
11
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "_doc": "A look from the ui-design module's style library (task 1003323): the theme.ui overlay a branding pack carries, and nothing else. The stub lays it over the instance's pack (STYLE=grove); an instance adopts it by copying `theme` into config/branding.json. Light values only, as the contract says: dark is each sheet's own black tier, which this look reaches through the accent and the faces.",
3
+ "name": "grove",
4
+ "title": "The Grove",
5
+ "summary": "A sculpted paper garden: pale sage paper, sea green as the second voice, bark brown for every quiet mark, a lit tan on everything pressable, rounded and soft.",
6
+ "origin": "docs/design/mocks/world/organic-cards (the world round, task 1003296; kept by the owner in round 1, retuned to the contract here: the sage ground lifted from #c8d7bf to #e1eadb and the inks deepened so the hub's fixed status vocabulary and the hall's chips clear 4.5:1 on it, and sea lifted from #2a5a46 to #3d8562 because the hall derives its info state from sea at 80% into white in dark, which the deep green could not carry on black (the look's slab derives a deeper field from it); the candidate's green DARK ground cannot travel in a pack, so dark is the sheets' black under the tan)",
7
+ "theme": {
8
+ "ui": {
9
+ "colors": {
10
+ "bg": "#e1eadb",
11
+ "bgCard": "#f6f9f3",
12
+ "bgDeep": "#cad8c2",
13
+ "bgFrame": "#eaf0e5",
14
+ "ink": "#0f3226",
15
+ "inkSoft": "#2f5240",
16
+ "inkFaint": "#3f5a48",
17
+ "rule": "#9db69a",
18
+ "ruleSoft": "#d0ddcb",
19
+ "accent": "#e3c39c",
20
+ "accentDeep": "#6d4b34",
21
+ "sea": "#3d8562",
22
+ "terracotta": "#b07a4e"
23
+ },
24
+ "fonts": {
25
+ "display": "\"Nunito\", \"Trebuchet MS\", system-ui, sans-serif",
26
+ "body": "\"Nunito\", \"Trebuchet MS\", system-ui, sans-serif",
27
+ "links": [
28
+ "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Nunito:wght@400;600;800&display=swap"
29
+ ]
30
+ }
31
+ }
32
+ }
33
+ }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.458",
3
+ "version": "1.19.460",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.458",
9
+ "version": "1.19.460",
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.19.458",
3
+ "version": "1.19.460",
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/src/module-api.js CHANGED
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
33
33
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
34
34
  // the entry to that file. Look for a version's history there, not here.
35
35
  // ---------------------------------------------------------------------------
36
- const CORE_VERSION = '1.19.458'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
36
+ const CORE_VERSION = '1.19.460'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
37
37
 
38
38
  // A namespaced logger so a module's log lines are attributable + consistent.
39
39
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -53,22 +53,26 @@ test('an unknown age reads as fresh, never as an accusation', () => {
53
53
 
54
54
  test('ageing changes how a row LOOKS, not only where it sits', () => {
55
55
  // The whole point of the clause. Each band must have its own visual treatment
56
- // in CSS, or "visibly aging" is just a sort order with extra steps.
56
+ // in CSS, or "visibly aging" is just a sort order with extra steps. Since the
57
+ // redesign (task 1003471) the treatment is the age PILL's colour in the row's
58
+ // right column — a glance down the list reads the column — and never a
59
+ // coloured band down the row's edge, which this world does not draw.
57
60
  const css = read('modules', 'hall-ui', 'public', 'collab.css');
58
61
  const L = lib();
59
62
  for (const b of L.BANDS) {
60
- assert.match(css, new RegExp(`\\.cb-item--${b.id}\\b`), `${b.id} has its own styling`);
63
+ assert.match(css, new RegExp(`^\\.cb-age--${b.id}\\s*\\{[^}]*color: var\\(--`, 'm'), `${b.id} has its own colour`);
61
64
  }
62
- assert.match(css, /\.cb-item--stale\s*\{[^}]*border-left-color/, 'the band reads down a list at a glance');
65
+ assert.match(css, /\.cb-age--stale\s*\{[^}]*var\(--danger\)/, 'stale reads down a list at a glance, in the danger ink');
63
66
  // Colour alone is not a message and not everyone sees it.
64
- assert.match(css, /\.cb-item--stale \.cb-age/, 'and the words are emphasised too');
67
+ assert.match(css, /\.cb-age--stale\s*\{[^}]*font-weight/, 'and the words are emphasised too');
68
+ assert.ok(!/border-left/.test(css), 'no coloured band');
65
69
  });
66
70
 
67
71
  test('the row markup carries the band class and the label', () => {
68
72
  const js = read('modules', 'hall-ui', 'public', 'collab.js');
69
- assert.match(js, /cb-item--\$\{L\.ageBand\(/, 'the class comes from the band');
70
- assert.match(js, /cb-age cb-age--\$\{band\.id\}/, 'and so does the chip');
73
+ assert.match(js, /cb-age cb-age--\$\{band\.id\}/, 'the pill\'s class comes from the band');
71
74
  assert.match(js, /escapeHtml\(band\.label\)/, 'the label is rendered, escaped');
75
+ assert.match(js, /L\.ageBand\(req\.created_at, nowMs\)/, 'through the lib, so the boundaries are the tested ones');
72
76
  });
73
77
 
74
78
  test('oldest first WITHIN the page, in addition to the band', () => {
@@ -143,7 +147,9 @@ test('every CSS token the page uses exists in style.css', () => {
143
147
  const css = read('modules', 'hall-ui', 'public', 'collab.css');
144
148
  const style = read('modules', 'hall-ui', 'public', 'style.css');
145
149
  const used = new Set([...css.matchAll(/var\((--[a-z-]+)\)/g)].map((x) => x[1]));
146
- assert.ok(used.size >= 5);
150
+ // Four since the redesign (task 1003471): the sheet holds only the age
151
+ // bands' inks, the row and the panel being the archetype's and the shell's.
152
+ assert.ok(used.size >= 4);
147
153
  for (const t of used) {
148
154
  assert.ok(new RegExp(`^\\s*${t}:`, 'm').test(style), `${t} must be defined (idea 1000821)`);
149
155
  }
@@ -75,9 +75,15 @@ const darkMedia = declarations(blockAfter(':root:not([data-mode="light"]){'));
75
75
  // cosmos.css alone would measure a page nobody sees.
76
76
  // The dark blocks are :root[data-mode="dark"] and :root:not([data-mode="light"]) —
77
77
  // both (0,2,0) — so they outrank the pack's (0,1,0) and win back.
78
- const packColors = JSON.parse(
78
+ // UI_DESIGN_PACK=<pack.json> measures the sheet under a look from the style library
79
+ // (tests/ui_design_styles.mjs runs this file once per pack, task 1003323); the DRIFT test
80
+ // below always compares the sheet against the neutral pack, which is what it must equal.
81
+ const neutralColors = JSON.parse(
79
82
  fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8')
80
83
  ).theme.ui.colors;
84
+ const packColors = process.env.UI_DESIGN_PACK
85
+ ? JSON.parse(fs.readFileSync(path.resolve(process.env.UI_DESIGN_PACK), 'utf8')).theme.ui.colors
86
+ : neutralColors;
81
87
 
82
88
  // mirrors COLOR_VARS in src/bongos/serve-internal.js
83
89
  const camelToVar = (k) => '--' + k.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
@@ -557,7 +563,8 @@ test('cosmos.css defaults do not drift from the branding pack', () => {
557
563
  // real (cosmos.css said --ink-faint:#9c8d80, the pack shipped #857567), and it
558
564
  // means anyone auditing cosmos.css alone measures colors no user ever sees.
559
565
  const drift = [];
560
- for (const [name, packValue] of Object.entries(PACK)) {
566
+ const NEUTRAL = Object.fromEntries(Object.entries(neutralColors).map(([k, v]) => [camelToVar(k), v]));
567
+ for (const [name, packValue] of Object.entries(NEUTRAL)) {
561
568
  const cssValue = firstRoot[name];
562
569
  if (cssValue === undefined) continue;
563
570
  if (cssValue.toLowerCase() !== packValue.toLowerCase()) {