@bongos/core 1.19.477 → 1.19.479
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 +73 -38
- package/.claude/skills/design/SKILL.md +6 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -1
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +36 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +84 -3
- package/docs/api-reference.md +5 -3
- package/docs/copy-inventory.md +6 -6
- package/docs/copy-registry.json +6 -6
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/economy/credits.js +78 -0
- package/modules/economy/module.json +1 -1
- package/modules/economy/reward.js +4 -0
- package/modules/economy/routes/credits.js +151 -0
- package/modules/hall-ui/public/md-reader.js +24 -1
- package/modules/lifecycle/closeness.js +87 -37
- package/modules/lifecycle/routes/closeness.js +17 -8
- package/modules/platform-identity/platform-identity.js +1 -1
- package/modules/ui-design/module.json +1 -1
- package/modules/ui-design/skills/README.md +6 -2
- package/modules/ui-design/skills/gpt-taste/SKILL.md +110 -0
- package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +110 -0
- package/modules/ui-design/skills/minimalist-ui/SKILL.md +103 -0
- package/modules/ui-design/skills/stitch-design-taste/SKILL.md +91 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/closeness.mjs +151 -31
- package/tests/credit_ledger_read.mjs +347 -0
- package/tests/hall_reading_world.mjs +80 -0
- package/tests/own_scope_guard.mjs +24 -2
- package/tests/ui_design_skills.mjs +56 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: minimalist-ui
|
|
3
|
+
description: Quiet editorial product interfaces built out of type and white space, inside the instance's world. Macro whitespace first, a capped measure, one display job and one body job, exactly one hairline weight for every border in the sheet, washed accents measured against their own ink, flat tile cards with no shadow, box-less accordions, keycap shortcuts, one depth treatment per band, entry motion inside the Kill Switch. Triggers on "make it minimal", "clean and editorial", "quiet", "document-like", "this feels busy". Declines dense surfaces like dashboards and ledgers. Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198); the palette is the pack and the nearest library look is expedition.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
|
|
7
|
+
> **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
|
|
8
|
+
>
|
|
9
|
+
> 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
|
|
10
|
+
> 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
|
|
11
|
+
> 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
|
|
12
|
+
> 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
|
|
13
|
+
> 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
|
|
14
|
+
> 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
|
|
15
|
+
> 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
|
|
16
|
+
>
|
|
17
|
+
> The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
|
|
18
|
+
<!-- END PLATFORM PREAMBLE -->
|
|
19
|
+
|
|
20
|
+
## Rebuilt from
|
|
21
|
+
|
|
22
|
+
This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `minimalist-skill` entry), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325) — the `[minimalist-ui]` requirement group — and this file is its port under [task 1003327](https://cloudbongos.com/builders#/task/1003327); it implements those requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); what a *look* skill is allowed to be is [ADR 0230](../../../../docs/adr/<redacted>.md).
|
|
23
|
+
|
|
24
|
+
What changed in translation, on purpose: the upstream shipped a **palette** (white or warm bone canvas, near-white cards, washed-out pastel accents each with its own darker text tone). Here that palette is a **look in the library** — `modules/ui-design/styles/expedition` is the warm-paper entry this skill was tuned against — and the canvas is whatever `bg` / `bg-card` the instance's pack carries. The upstream's editorial serif for hero headings becomes the pack's `display` stack; a face this skill wants and the pack does not have is a pack change, not a page's `@font-face`. Its pastels become `color-mix()` of the pack's `accent`, `sea` and `terracotta` against `bg-card`, each paired with the contract's own ink for that field, so a badge is measured rather than guessed. Its imagery clause is gone: **seeded placeholder photography does not exist here**, and the only rasters this skill may ask for are text-free hero plates under the owner's rule. Its ban on pill shapes for primary buttons is a default an instance's `DESIGN.md` overrules — Cloud Bongos's own door pill is that world's signature and wins. Its entry motion survives only inside the Kill Switch.
|
|
25
|
+
|
|
26
|
+
## Where this look lives
|
|
27
|
+
|
|
28
|
+
The **grammar** below is this skill's. The **palette** is not: it comes from the instance's pack, and a variant comes from the style library at `modules/ui-design/styles/`. The nearest library entry to this look is `expedition` (warm paper, hairline frames, one lamp yellow as a fill); `grove` reads the same grammar cooler; `chrome-world` is where the grammar has to fight the void and usually loses. Render under one with `--style <name>` before you commit to it. Adopting one is the owner's `config/branding.json`; a page never picks its own.
|
|
29
|
+
|
|
30
|
+
## Fires on
|
|
31
|
+
|
|
32
|
+
Clean, document-like product interfaces built out of type and white space: a reading surface, a settings sheet, a changelog, a pricing page, a documentation shell, an editorial marketing page whose argument is the words. It does **not** fire on a surface whose job is density (a dashboard, a data table, a ledger) — the quiet grammar makes a dense surface harder to read, not calmer. Say so and route to `impeccable layout` or `design-taste-frontend-v1` instead.
|
|
33
|
+
|
|
34
|
+
## Execution order
|
|
35
|
+
|
|
36
|
+
Do these in order; each one constrains the next, and doing them out of order produces a busy page with generous padding rather than a quiet one.
|
|
37
|
+
|
|
38
|
+
1. **Macro whitespace first.** Set the band rhythm before any component exists: the world's band clamp between chapters, and inside a chapter one step of `spacing`. A section that needs a divider to be legible has too little air, not too few rules.
|
|
39
|
+
2. **Constrain the measure.** Body text sits at 60 to 70 characters. The container is the world's `spacing.container`; a reading column inside it is narrower still, and it is centred only if the page has no rail.
|
|
40
|
+
3. **The type scale and the tokens.** Every size from the world's scale, every colour a `var()` or a `color-mix()` of the fifteen. This is where the look is decided; nothing below adds a colour.
|
|
41
|
+
4. **The hairline rule on every border.** See below. Do this as one pass over the whole sheet, not per component.
|
|
42
|
+
5. **Depth, then entry motion.** Both last, both optional, both removable without the page changing meaning.
|
|
43
|
+
|
|
44
|
+
## Typography
|
|
45
|
+
|
|
46
|
+
- **One display job, one body job.** The pack carries two stacks; a minimalist surface uses `display` for the h1 and h2 only and `body` for everything else. Where the pack's two stacks are the same family (the chrome world's are), the contrast is **weight and tracking**, not family: display at 300 with tight tracking, body at 400 at the world's default tracking.
|
|
47
|
+
- **Tight display, generous body.** Display headings take negative tracking (about -0.02em) and a line height near 1.05; body takes 1.6 to 1.7. The contrast between the two is the whole look.
|
|
48
|
+
- **Mono for metadata and code**, from the pack's `links` (the chrome world loads JetBrains Mono). Numerals in a table or a stat are tabular; a number that changes in place never reflows its neighbours.
|
|
49
|
+
- **Off-black body ink**, which is what `ink` already is; the softer tiers (`ink-soft`, `ink-faint`) carry captions and labels. Pure black as body ink is a different look.
|
|
50
|
+
- **No orphan.** A headline's last word never sits alone on its own line; fix it by widening the container or reducing the clamped size, never by rewriting the sentence for the layout's convenience.
|
|
51
|
+
|
|
52
|
+
## Colour and the hairline rule
|
|
53
|
+
|
|
54
|
+
- **The canvas is the pack's `bg`; a card is `bg-card`;** a recessed well is `bg-deep`. Three grounds, no more, and the step between them is small enough that the page reads as one sheet of paper.
|
|
55
|
+
- **Every border is a hairline**: 1px, `rule-soft` at rest, `rule` where it separates two live regions. There is no second border weight anywhere on the surface. A border that needs to be heavier is a background change.
|
|
56
|
+
- **Accents are washed, and each carries its own ink.** A badge, a tag or a quiet highlight is `color-mix(in srgb, var(--accent) 22%, var(--bg-card))` with `accent-ink` on it; the same shape in the `sea` or the `terracotta` field uses that field's ink. Measure the pair with `node modules/ui-design/kit/contrast.js` — a washed pastel is exactly where AA gets lost.
|
|
57
|
+
- **One solid call to action**, the accent as the fill with `accent-on` as the ink, and **no shadow on it**. The rest of the page's buttons are hairline-bordered and transparent.
|
|
58
|
+
- **Banned by default** (an instance's `DESIGN.md` may overrule any of these): gradients as a surface treatment, neon, a heavy or untinted drop shadow, 3D glass, a hero band painted in the accent, and a fully rounded pill on a large container. Where the instance's world has a signature that breaks one of these, the world wins and the sheet says so in a comment.
|
|
59
|
+
|
|
60
|
+
## Components
|
|
61
|
+
|
|
62
|
+
- **Tile cards**: a hairline border, the world's small radius (never the panel radius), padding at `spacing.md` or more, and no shadow. Cards sit in a flat grid with a real gutter; nothing lifts at rest.
|
|
63
|
+
- **Badges**: uppercase, the world's label size, tracked out, the washed field with its own ink, a small radius. Never a full pill, never the accent at full strength.
|
|
64
|
+
- **Accordions**: no box. Two rows are separated by one hairline and nothing else; the open row is marked by its own weight, not by a container appearing around it.
|
|
65
|
+
- **Keycaps**: mono, the world's smallest size, a hairline border, a 3px radius, `bg-deep` as the fill. A shortcut is written as keys, not as prose.
|
|
66
|
+
- **Tables**: hairlines between rows, none between columns; the header row is `ink-soft` at label size, tracked out, uppercase.
|
|
67
|
+
|
|
68
|
+
## Copy
|
|
69
|
+
|
|
70
|
+
Realistic and specific, always. Banned: emoji, placeholder person and company names, lorem, invented round numbers, and the marketing verbs that mean nothing (`unleash`, `supercharge`, `revolutionize`, `elevate`). If the real content is not known yet, write the shortest true sentence and mark the gap in a comment — a page full of plausible fiction is harder to fix than a page with a hole in it.
|
|
71
|
+
|
|
72
|
+
## Quiet depth
|
|
73
|
+
|
|
74
|
+
A band must not read as a flat empty strip. Give it one of these, and only one:
|
|
75
|
+
|
|
76
|
+
- a faint radial of the pack's own warm token, `radial-gradient(circle at 30% 0%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 60%)`, sitting behind the content;
|
|
77
|
+
- a minimal line pattern from a `repeating-linear-gradient` of `rule-soft` at low alpha, on a pointer-inert pseudo-element;
|
|
78
|
+
- a **text-free hero plate** from the instance's world, as a background token on `body` with a provenance sidecar beside it, never an `<img>` and never on more than one band per page.
|
|
79
|
+
|
|
80
|
+
That is the whole list. Depth is a background job; it never touches the content layer, and it is invisible in a screenshot taken at 320.
|
|
81
|
+
|
|
82
|
+
## Motion, inside the Kill Switch
|
|
83
|
+
|
|
84
|
+
Felt, not noticed. All CSS, on the world's curves, nothing authored in JavaScript.
|
|
85
|
+
|
|
86
|
+
- **Entry**: a fade with a 12 to 16px rise on the entrance curve, about 500ms. Prefer `animation-timeline: view()` behind `@supports`; where that is unsupported, one `IntersectionObserver` may add a single class and the motion stays in CSS. The rest frame is the finished page, so a browser with no script shows a complete composition.
|
|
87
|
+
- **Cascade**: across a list or a grid, `animation-delay` stepped from an `--i` in the markup, capped at about 6 steps so the last card is never noticeably late.
|
|
88
|
+
- **Hover**: an ultra-subtle lift on a card, `translateY(-2px)` and a hairline that goes from `rule-soft` to `rule`. No shadow appears on hover that was not there at rest.
|
|
89
|
+
- **Press**: `scale(.98)` on `transform` only.
|
|
90
|
+
- **`@media (prefers-reduced-motion: reduce)` at the foot of the token layer kills all of it**, and the page is complete without it.
|
|
91
|
+
|
|
92
|
+
## Pre-flight, measured by the kit
|
|
93
|
+
|
|
94
|
+
Render the real surface — `node modules/ui-design/kit/render.js --page <path>` (add `--style expedition` to see it under the library's warm paper) — and read the shots, not the source.
|
|
95
|
+
|
|
96
|
+
- [ ] One display job, one body job; the contrast is weight and tracking where the pack has one family.
|
|
97
|
+
- [ ] Measure between 60 and 70 characters on the reading column at 1440.
|
|
98
|
+
- [ ] **One** border weight in the whole sheet, and it is a hairline.
|
|
99
|
+
- [ ] Every washed accent pair measured with `contrast.js`, AA in both modes.
|
|
100
|
+
- [ ] Exactly one depth treatment per band, none on the content layer.
|
|
101
|
+
- [ ] Every band that looked empty at 1440 still reads at 390 and 320.
|
|
102
|
+
- [ ] Reduced-motion pass identical to the rest frame.
|
|
103
|
+
- [ ] `node modules/ui-design/kit/check-mock.js` clean, including the craft tells; a tell you mean to keep is waived in the file with its reason.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stitch-design-taste
|
|
3
|
+
description: Writes a design contract document for an external screen-generating tool such as Google Stitch, in the platform's own frontmatter shape. Four dials (creativity, density, variance, motion intent) with named bands and stated defaults, then eight fixed sections (atmosphere, colour roles, typography, components, layout, responsive rules, motion intent, banned patterns) written as descriptive language paired with exact values. The thirteen colours are transcribed from the instance's pack and diffed, never invented; motion is an intent brief inside the Kill Switch; the screens that come back are a reference, not a source of truth. Triggers on "write a design brief for Stitch", "a design contract document", "a prompt-to-screen tool". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
|
|
7
|
+
> **Platform preamble — read before any rule below.** This skill ships with the `ui-design` module (vendored from a scanned origin, or rebuilt in-house from a spec) and runs INSIDE an instance's world, which it reads first and never overrides.
|
|
8
|
+
>
|
|
9
|
+
> 1. **Load the world before any taste rule.** `config/branding.json` over `config/branding.neutral.json` (`theme.ui` is the fifteen tokens: <redacted> colours and two font stacks), `config/design-tokens.json` over `config/design-tokens.neutral.json`, and `DESIGN.md` at the repo root: the instance's palette and derived tiers, faces, materials, motion grammar, components, and its Do's and Don'ts. Where an instance has no `DESIGN.md`, the neutral pack is the world and the platform floors in item 6 are the whole rulebook; say so and build to them. Never invent a palette.
|
|
10
|
+
> 2. **The fifteen-token contract is the only colour source.** Every colour is a `var()` or a `color-mix()` of the fifteen; the only legal literal is pure black or white with alpha as a scrim, mask or halo. No page `:root` (page tokens live on `body`), and every dark rule is written twice: `:root[data-mode="dark"]` and its `prefers-color-scheme` twin.
|
|
11
|
+
> 3. **A style skill produces variants INSIDE that world, never a second world.** The look this skill carries (its palette, faces, materials, mood boards) is a reference for composition and craft; the instance's tokens and faces replace it. Changing the world is the owner's decision and an ADR, not a session's taste.
|
|
12
|
+
> 4. **Images are for hero plates and hero objects only.** UI is built from templates and the instance's world, not painted; any image-generation step below applies to text-free plates and hero objects, never to controls, cards, text, or a screenshot of a screen.
|
|
13
|
+
> 5. **The instance's taste bans apply and win.** Read them from its `DESIGN.md`. Cloud Bongos's own (no em dash or en dash in visible copy, no emoji, no gradient text, no three-equal-card row, no fake screenshot built from divs) are that instance's, not the module's; where this skill's rules conflict with an instance's bans, the instance wins.
|
|
14
|
+
> 6. **The platform floors hold on every instance** and a `DESIGN.md` may tighten them, never loosen them: every control clears 24px on both sides (primary pills 46–48px); AA in both modes (4.5:1 body text, 3:1 large text and non-text) measured from the real stylesheet; and the Kill Switch, `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, no motion authored in JavaScript, and a rest frame that is a complete composition on its own.
|
|
15
|
+
> 7. **Look before you ship.** Render the real surface through the module's kit before and after (`/design` Step 2 and `docs/recipes/ui-look-before-you-ship.md`: every state at 1440 / 390 / 320 in dark and light, audited against the floors); a page with no states file gets one first, and new interactive code ships with a pin in the page's own test file.
|
|
16
|
+
>
|
|
17
|
+
> The rules below are the skill's own. A vendored copy carries a `PROVENANCE.md` beside this file naming the origin, the pinned commit, the licence and the scan verdict it was vendored under; an in-house rebuild names its rebuild spec and its ADR in the section right after this block.
|
|
18
|
+
<!-- END PLATFORM PREAMBLE -->
|
|
19
|
+
|
|
20
|
+
## Rebuilt from
|
|
21
|
+
|
|
22
|
+
This skill is an **in-house rebuild**, not a vendored copy. Its documented functionality comes from `Leonxlnx/taste-skill` @ `<redacted>` (MIT; the `stitch-skill` entry and its `DESIGN.md`), which `/scan-before-install` reduced to `dangerous` at the default tier on 2026-08-28, so under [ADR 0198](../../../../docs/adr/<redacted>.md) it could land only as a REBUILD-SPEC. The spec is the body of [task 1003325](https://cloudbongos.com/builders#/task/1003325) — the `[stitch-design-taste]` requirement group — and this file is its port under [task 1003327](https://cloudbongos.com/builders#/task/1003327); it implements those requirements from scratch, **no upstream bytes consulted or copied**. The landing shape is [ADR 0220](../../../../docs/adr/<redacted>.md); what a *look* skill is allowed to be is [ADR 0230](../../../../docs/adr/<redacted>.md).
|
|
23
|
+
|
|
24
|
+
What changed in translation, on purpose: the upstream **authored a palette** for the downstream tool — a descriptive name, a hex and a role per token, invented to taste. Here the colour contract is **transcribed, not invented**: it is the instance's thirteen with their real hex values and the roles the instance's `DESIGN.md` already gives them, so the external tool and the built surface cannot diverge. The document it writes uses the platform's own frontmatter shape — the one every look in `modules/ui-design/styles/` carries — rather than a shape of its own, so a document produced here can be read back as a style. Its universal bans (no pure black, no serif in a dashboard, the default UI sans banned) become **defaults the instance's `DESIGN.md` overrules**: Cloud Bongos's own world is a black void, and a document that told the tool otherwise would describe a different product. Its "signature move" of small contextual images set inline between headline words survives as a **hero object** under the owner's rule: a text-free cutout with its provenance sidecar, never a photograph of a screen. Its motion section stays what it always was, an intent brief for whoever writes the code afterwards, and every parameter in it is written to live inside the Kill Switch.
|
|
25
|
+
|
|
26
|
+
## Where this look lives
|
|
27
|
+
|
|
28
|
+
This skill writes a **document**, not code. The palette it transcribes is the instance's pack; the recipes it transcribes are the instance's `DESIGN.md`; a variant is a look from the style library at `modules/ui-design/styles/`, named by its directory so the reader can render it (`chrome-world`, `expedition` and `grove` today). It never invents a fifteen-token world and it never edits the instance's.
|
|
29
|
+
|
|
30
|
+
## Fires on
|
|
31
|
+
|
|
32
|
+
The deliverable is a **design contract document for an external screen-generating tool** — Google Stitch, a comparable prompt-to-screen product, or any collaborator who will produce screens from prose rather than from a repository. It does not fire when the deliverable is code (that is `design-taste-frontend` or `impeccable shape`), and it does not fire when the deliverable is a new look for the library (that is the `/style` session).
|
|
33
|
+
|
|
34
|
+
## Step 1 — the dials
|
|
35
|
+
|
|
36
|
+
Open the document with four dials, each with named bands and a stated default, set from the owner's description of the vibe and written as one line each.
|
|
37
|
+
|
|
38
|
+
| Dial | Bands | Default | What it moves |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| **Creativity** | conventional · considered · expressive | considered | how far a screen may depart from the archetype the surface implies |
|
|
41
|
+
| **Density** | airy · balanced · dense | balanced | information per screen; `dense` switches numerals to mono and tightens the scale |
|
|
42
|
+
| **Variance** | uniform · varied · asymmetric | varied | how much two sibling screens may differ in structure |
|
|
43
|
+
| **Motion intent** | still · restrained · alive | restrained | what the downstream coder is asked to build, all of it inside the Kill Switch |
|
|
44
|
+
|
|
45
|
+
State the default and the chosen value. A dial the owner did not speak to keeps its default and says so.
|
|
46
|
+
|
|
47
|
+
## Step 2 — the document, in this order
|
|
48
|
+
|
|
49
|
+
The section order is fixed. The reader is a tool that interprets **visual description**, so every rule is written as descriptive natural language **paired with an exact value** — "a warm salmon, the pack's `accent` hex verbatim, used only on what is pressable or live", never `bg-accent-500`. Utility class names mean nothing to it.
|
|
50
|
+
|
|
51
|
+
1. **Atmosphere** — three or four sentences on what the surface feels like and what it is not. This is the only prose section; everything below is rules with values.
|
|
52
|
+
2. **Colour roles** — the instance's thirteen, transcribed: descriptive name, hex, functional role, one row each. One palette for the whole output. One accent. Where the instance's world uses a colour the upstream would have banned (a pure black ground, a saturated accent), transcribe it and note that it is the instance's decision, not a default.
|
|
53
|
+
3. **Typography** — the pack's two stacks by name with their real fallbacks, the scale as values, tracking and measure specified per role, and which roles use mono numerals (always, above `density: dense`).
|
|
54
|
+
4. **Components** — the vocabulary the instance already has, described: tactile press feedback with no outer glow; cards only where elevation carries hierarchy; label-above-input forms with the error below the field; skeleton loaders shaped like the content, never a spinner; composed empty states with an action in them; errors inline rather than in a toast.
|
|
55
|
+
5. **Layout** — grid over flexbox arithmetic; no overlapping content layers; no three-equal-card feature row; a max-width container with its value; dynamic viewport height (`100dvh`) for any full-height section.
|
|
56
|
+
6. **Responsive rules**, a hard requirement, not advice — single-column collapse below the tablet breakpoint; zero horizontal overflow; headline sizes clamped with a stated body-size floor; 44px minimum touch targets (the platform floor is 24px of clearance on both sides of every control, and this is the tighter of the two); and the named test viewports, 1440, 390 and 320.
|
|
57
|
+
7. **Motion intent** — written for the coding agent, because the generating tool emits static screens: the curve and duration per interaction, perpetual micro-loops only where the world has one, staggered mounts with their step, `transform` and `opacity` only, and the statement that every rule here sits inside `@media (prefers-reduced-motion: reduce)` at the foot of the token layer with a complete rest frame.
|
|
58
|
+
8. **Banned patterns** — the closing list, below.
|
|
59
|
+
|
|
60
|
+
## The hero contract
|
|
61
|
+
|
|
62
|
+
- **One primary call to action.** A second competing action on the same screen is a defect.
|
|
63
|
+
- **Small contextual images set inline between headline words** are this contract's signature move, and under the owner's rule they are **hero objects**: text-free cutouts with a provenance sidecar, sized to the cap height, never a photograph of an interface.
|
|
64
|
+
- **No overlapping layers** in the hero, no scroll-cue filler text, and above `variance: varied` the structure is asymmetric.
|
|
65
|
+
- Full-page **hero plates** are text-free by definition; the document says so explicitly so the downstream tool does not letter one.
|
|
66
|
+
|
|
67
|
+
## Banned patterns (the closing list)
|
|
68
|
+
|
|
69
|
+
Emoji · the default UI sans as a display face where the pack has a real one · pure black as a *ground the instance did not choose* · neon glows · custom cursors · placeholder person and company names · fake round numbers · cliché marketing verbs · unstyled component-library defaults · arbitrary `z-index` values · circular spinners · a three-equal-card feature row · gradient text · a screenshot of an interface built out of divs.
|
|
70
|
+
|
|
71
|
+
Each line is a default. The document states plainly that the instance's `DESIGN.md` is the authority and that where it disagrees with this list, it wins — otherwise the external tool is being told a different product exists.
|
|
72
|
+
|
|
73
|
+
## Step 3 — prove it before it is sent
|
|
74
|
+
|
|
75
|
+
A document that cannot be built is a wish list.
|
|
76
|
+
|
|
77
|
+
- **Transcription check**: the thirteen hexes in the document equal the instance's pack, value for value. Diff them; do not retype them.
|
|
78
|
+
- **Contrast**: `node modules/ui-design/kit/contrast.js` over the pack, and every pair the document names as a component's colour must be in the clean list.
|
|
79
|
+
- **Reference render**: render the nearest existing surface — `node modules/ui-design/kit/render.js --page <path>` (add `--style <name>` for a look from the library) — and attach the 1440, 390 and 320 shots to the document. The tool composes better against a real specimen than against prose alone, and a rule the specimen contradicts is a rule that was wrong.
|
|
80
|
+
- **Round trip**: when the screens come back, they are a reference, not a source of truth. `image-to-code` reads them, the instance's world overrides them, and what ships goes through the kit like anything else.
|
|
81
|
+
|
|
82
|
+
## Pre-flight
|
|
83
|
+
|
|
84
|
+
- [ ] Four dials, each with its bands, its default and its chosen value.
|
|
85
|
+
- [ ] The eight sections, in order, every rule paired with an exact value.
|
|
86
|
+
- [ ] The thirteen transcribed from the pack and diffed, not retyped.
|
|
87
|
+
- [ ] Every colour pair the document names cleared by `contrast.js`.
|
|
88
|
+
- [ ] The responsive section names 1440, 390 and 320 and states the overflow rule.
|
|
89
|
+
- [ ] The motion section states the Kill Switch and a complete rest frame.
|
|
90
|
+
- [ ] The banned list closes with the instance-wins clause.
|
|
91
|
+
- [ ] The reference shots are attached, and the document names the look they were rendered under.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.479",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.479",
|
|
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.
|
|
3
|
+
"version": "1.19.479",
|
|
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
|
@@ -34,7 +34,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
34
34
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
35
35
|
// the entry to that file. Look for a version's history there, not here.
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
|
-
const CORE_VERSION = '1.19.
|
|
37
|
+
const CORE_VERSION = '1.19.479'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
38
38
|
|
|
39
39
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
40
40
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/closeness.mjs
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
// 3. a hidden builder appears in no graph.
|
|
10
10
|
//
|
|
11
11
|
// The scorer is pure, so 1 and 2 are driven with real inputs rather than asserted
|
|
12
|
-
// over source. 3 is a consent property of the read, and
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// over source. 3 is a consent property of the read, and since task 1003175 (C05)
|
|
13
|
+
// it is driven through the REAL graph read against a stub pool — the leak tests
|
|
14
|
+
// need a graph that was actually assembled, not a gate called in isolation —
|
|
15
|
+
// including the failure modes, which are the ones that actually matter.
|
|
15
16
|
import { strict as assert } from 'node:assert';
|
|
16
17
|
import { test } from 'node:test';
|
|
17
18
|
import { createRequire } from 'node:module';
|
|
@@ -152,55 +153,174 @@ test('effective weights always sum to 1 over the available subset', () => {
|
|
|
152
153
|
assert.ok(Math.abs(summed - 1) < 1e-3, `effective weights sum to ${summed}`);
|
|
153
154
|
});
|
|
154
155
|
|
|
155
|
-
// ── 3. CONSENT
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
// ── 3. CONSENT IS THE READ PATH (task 1003175 / C05) ────────────────────────
|
|
157
|
+
//
|
|
158
|
+
// C04 gated the graph by filtering assembled rows. C05's criterion says that is
|
|
159
|
+
// not enough: "the hide cannot be a post-filter over an already-assembled graph —
|
|
160
|
+
// an assembled-then-filtered graph leaks through counts, ranks and 'you have 4
|
|
161
|
+
// close collaborators' totals even when the node itself is hidden. Test the leak,
|
|
162
|
+
// not just the absence of the row."
|
|
163
|
+
//
|
|
164
|
+
// So these drive the REAL closenessFor against a stub pool and assert on the four
|
|
165
|
+
// things a post-filter gets wrong — the total, the identity read, the ranking and
|
|
166
|
+
// the page — rather than only on the missing row. Every one of them fails if the
|
|
167
|
+
// gate is moved back downstream of assembly.
|
|
168
|
+
|
|
169
|
+
// A pool that answers the three signal queries and the hydrate, keyed off the SQL
|
|
170
|
+
// each read actually sends. It also RECORDS which ids were hydrated, which is how
|
|
171
|
+
// "a hidden builder's identity is never read" becomes an observation rather than a
|
|
172
|
+
// claim about source order.
|
|
173
|
+
function fakePool({ coClaim = {}, coSession = {}, shipped = {} } = {}) {
|
|
174
|
+
const seen = { hydrated: null };
|
|
175
|
+
const toRows = (obj) => Object.entries(obj).map(([other_id, n]) => ({ other_id, n }));
|
|
176
|
+
return {
|
|
177
|
+
seen,
|
|
178
|
+
async query(sql, params) {
|
|
179
|
+
if (/FROM claims mine/.test(sql)) return { rows: toRows(coClaim) };
|
|
180
|
+
if (/FROM session_logs mine/.test(sql)) return { rows: toRows(coSession) };
|
|
181
|
+
if (/FROM tasks t_mine/.test(sql)) return { rows: toRows(shipped) };
|
|
182
|
+
if (/FROM builders WHERE id/.test(sql)) {
|
|
183
|
+
seen.hydrated = (params[0] || []).map(String);
|
|
184
|
+
return {
|
|
185
|
+
rows: seen.hydrated.map((id) => ({
|
|
186
|
+
id, github_login: `u${id}`, display_name: `B${id}`, avatar_url: null,
|
|
187
|
+
})),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`unexpected query: ${sql.slice(0, 70)}`);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Four collaborators of clearly separated strength. '40' is the STRONGEST, so
|
|
196
|
+
// hiding them is the case that moves the top of the ranking.
|
|
197
|
+
const GRAPH = { coClaim: { 10: 1, 20: 2, 30: 3, 40: 9 } };
|
|
198
|
+
const hides = (...ids) => async () => new Set(ids.map(String));
|
|
199
|
+
|
|
200
|
+
test('THE CONSENT RULE: a hidden builder is no node in anyone else\'s graph', async () => {
|
|
201
|
+
const pool = fakePool(GRAPH);
|
|
202
|
+
const { entries } = await closeness.closenessFor(1, { hiddenIdsFor: hides(40) }, { pool });
|
|
203
|
+
assert.deepEqual(entries.map((e) => e.builder_id).sort(), ['10', '20', '30']);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('THE COUNTS LEAK: the total counts the CONSENTED graph, not the assembled one', async () => {
|
|
207
|
+
// This is the criterion's own example. Four candidates, one hidden — a surface
|
|
208
|
+
// that says "you have 4 close collaborators" over a list of 3 has leaked the
|
|
209
|
+
// hidden builder by subtraction, without ever showing their row.
|
|
210
|
+
const pool = fakePool(GRAPH);
|
|
211
|
+
const { entries, total } = await closeness.closenessFor(1, { hiddenIdsFor: hides(40) }, { pool });
|
|
212
|
+
assert.equal(total, 3, 'the total is counted after the gate, not before it');
|
|
213
|
+
assert.equal(entries.length, 3);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('THE IDENTITY LEAK: a hidden builder\'s name is never even fetched', async () => {
|
|
217
|
+
// Not fetched and then dropped — never asked for. A post-filter that ran after
|
|
218
|
+
// hydrate would still have read the row.
|
|
219
|
+
const pool = fakePool(GRAPH);
|
|
220
|
+
await closeness.closenessFor(1, { hiddenIdsFor: hides(40) }, { pool });
|
|
221
|
+
assert.ok(pool.seen.hydrated, 'the graph was hydrated');
|
|
222
|
+
assert.ok(!pool.seen.hydrated.includes('40'), 'the hidden builder was never looked up');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('THE RANK LEAK: hiding the top scorer leaves no gap at the top', async () => {
|
|
226
|
+
// Ranking runs over the consented set, so the #1 slot is the strongest VISIBLE
|
|
227
|
+
// collaborator. Ranked before the gate, the top slot belongs to the hidden
|
|
228
|
+
// builder and is then removed — so "who am I closest to" comes back EMPTY for a
|
|
229
|
+
// builder who has three collaborators, which tells them precisely that someone
|
|
230
|
+
// was taken out from above them. Asked for one, you get the real one.
|
|
231
|
+
const pool = fakePool(GRAPH);
|
|
232
|
+
const { entries, total } = await closeness.closenessFor(1, { limit: 1, hiddenIdsFor: hides(40) }, { pool });
|
|
233
|
+
assert.equal(entries.length, 1, 'the #1 slot is filled, not vacated by the hidden builder');
|
|
234
|
+
assert.equal(entries[0].builder_id, '30', 'and it is the top VISIBLE builder');
|
|
235
|
+
assert.equal(total, 3);
|
|
236
|
+
|
|
237
|
+
// Ordering over the whole consented graph is dense and monotonic — no rank a
|
|
238
|
+
// reader could subtract from.
|
|
239
|
+
const all = await closeness.closenessFor(1, { hiddenIdsFor: hides(40) }, { pool });
|
|
240
|
+
const scores = all.entries.map((e) => e.score);
|
|
241
|
+
assert.deepEqual(scores, [...scores].sort((a, b) => b - a));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('THE PAGE LEAK: a page is refilled from the rest, never returned short', async () => {
|
|
245
|
+
// Gating after the slice would hand back one row for a limit of two, and a page
|
|
246
|
+
// that comes back short announces a removal as loudly as the row would have.
|
|
247
|
+
const pool = fakePool(GRAPH);
|
|
248
|
+
const { entries, total } = await closeness.closenessFor(1, { limit: 2, hiddenIdsFor: hides(40) }, { pool });
|
|
249
|
+
assert.equal(entries.length, 2, 'the page is full');
|
|
250
|
+
assert.deepEqual(entries.map((e) => e.builder_id), ['30', '20']);
|
|
251
|
+
assert.equal(total, 3, 'and the total still describes the whole consented graph, not the page');
|
|
166
252
|
});
|
|
167
253
|
|
|
168
254
|
test('FAIL CLOSED: a consent read that THROWS empties the graph', async () => {
|
|
169
255
|
// An unreadable consent state is not permission. This is the direction that
|
|
170
256
|
// matters: the opposite default would serve a hidden builder on any hiccup.
|
|
171
|
-
const
|
|
172
|
-
|
|
257
|
+
const pool = fakePool(GRAPH);
|
|
258
|
+
const { entries, total } = await closeness.closenessFor(1, {
|
|
259
|
+
hiddenIdsFor: async () => { throw new Error('hub down'); },
|
|
260
|
+
}, { pool });
|
|
261
|
+
assert.deepEqual(entries, []);
|
|
262
|
+
assert.equal(total, 0);
|
|
263
|
+
assert.equal(pool.seen.hydrated, null, 'and nobody was looked up on the way out');
|
|
173
264
|
});
|
|
174
265
|
|
|
175
266
|
test('FAIL CLOSED: a consent read returning null empties the graph too', async () => {
|
|
176
267
|
// null is not "nobody is hidden".
|
|
177
|
-
const
|
|
178
|
-
|
|
268
|
+
const pool = fakePool(GRAPH);
|
|
269
|
+
const { entries, total } = await closeness.closenessFor(1, { hiddenIdsFor: async () => null }, { pool });
|
|
270
|
+
assert.deepEqual(entries, []);
|
|
271
|
+
assert.equal(total, 0);
|
|
179
272
|
});
|
|
180
273
|
|
|
181
274
|
test('no consent reader at all leaves the graph intact — that is not a hole', async () => {
|
|
182
275
|
// Where the hub module is off there is no hub-level hide flag to honour,
|
|
183
276
|
// because nobody on that instance has been offered the switch. Suppressing
|
|
184
277
|
// everyone would not be consent, it would be an outage.
|
|
185
|
-
const
|
|
186
|
-
|
|
278
|
+
const pool = fakePool(GRAPH);
|
|
279
|
+
const { entries, total } = await closeness.closenessFor(1, {}, { pool });
|
|
280
|
+
assert.equal(entries.length, 4);
|
|
281
|
+
assert.equal(total, 4);
|
|
187
282
|
});
|
|
188
283
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
284
|
+
// ── the gate's own shape ────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
test('THE GATE TAKES IDS, NEVER ASSEMBLED ROWS — that is what makes it the read path', () => {
|
|
287
|
+
// The whole of C05 is this signature. A gate over ids can only be applied before
|
|
288
|
+
// there is anything to derive; a gate over rows invites being applied after.
|
|
289
|
+
const fn = SRC.slice(SRC.indexOf('async function consentedIds'));
|
|
290
|
+
const body = fn.slice(0, fn.indexOf('\n}'));
|
|
291
|
+
assert.ok(!/\.score|\.builder_id|entries/.test(body),
|
|
292
|
+
'the gate never touches a scored row');
|
|
195
293
|
});
|
|
196
294
|
|
|
197
|
-
test('
|
|
198
|
-
//
|
|
199
|
-
// not
|
|
295
|
+
test('consent is applied to the SIGNALS, so the graph is built from consented data', async () => {
|
|
296
|
+
// readConsentedSignals is the single structural place. The maps it returns do
|
|
297
|
+
// not contain a hidden builder, so nothing assembled from them can.
|
|
298
|
+
const pool = fakePool(GRAPH);
|
|
299
|
+
const signals = await closeness.readConsentedSignals(1, { hiddenIdsFor: hides(40) }, { pool });
|
|
300
|
+
assert.deepEqual([...signals.ids].sort(), ['10', '20', '30']);
|
|
301
|
+
assert.ok(!signals.co_claim.has('40'), 'the hidden builder is not a key in the signal map');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('consent is ONE gate, and the graph read never re-implements it', () => {
|
|
305
|
+
assert.equal((SRC.match(/await consentedIds\(/g) || []).length, 1,
|
|
306
|
+
'exactly one call site applies the gate');
|
|
307
|
+
const fn = SRC.slice(SRC.indexOf('async function closenessFor'));
|
|
308
|
+
const body = fn.slice(0, fn.indexOf('\n}'));
|
|
309
|
+
assert.ok(!/consentedIds|\.filter\([^)]*hidden/i.test(body),
|
|
310
|
+
'closenessFor does not filter on consent itself — it reads already-consented data');
|
|
311
|
+
assert.match(body, /await readConsentedSignals\(/);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('everything derived comes AFTER the gate', () => {
|
|
315
|
+
// Source order is the backstop for the behavioural tests above: the read, then
|
|
316
|
+
// the scoring, then the total. A future aggregate added below this line inherits
|
|
317
|
+
// the guarantee; one added above it would not.
|
|
200
318
|
const fn = SRC.slice(SRC.indexOf('async function closenessFor'));
|
|
201
319
|
const body = fn.slice(0, fn.indexOf('\n}'));
|
|
202
|
-
|
|
203
|
-
|
|
320
|
+
const gate = body.indexOf('readConsentedSignals');
|
|
321
|
+
for (const derived of ['scoreCloseness(', 'entries.sort(', 'const total', 'hydrate(']) {
|
|
322
|
+
assert.ok(gate < body.indexOf(derived), `${derived} is computed after the gate`);
|
|
323
|
+
}
|
|
204
324
|
});
|
|
205
325
|
|
|
206
326
|
test('the pair read cannot be used to detect that someone hid themselves', () => {
|