@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
|
@@ -24,13 +24,18 @@
|
|
|
24
24
|
// dropped and the remaining weights are renormalised, and the breakdown names what
|
|
25
25
|
// was excluded and why.
|
|
26
26
|
//
|
|
27
|
-
// CONSENT. ADR 0171's posture is
|
|
28
|
-
// enforced in the READ path (task
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
27
|
+
// CONSENT (task 1003175 / C05). ADR 0171's posture is INHERITED, not reinvented:
|
|
28
|
+
// fail-closed account state, the hide flag enforced in the READ path (task
|
|
29
|
+
// 1002277) — a builder who hid their profile must not reappear as a node in
|
|
30
|
+
// someone else's graph. What C05 adds is WHERE the gate sits. `consentedIds` gates
|
|
31
|
+
// candidate IDS, and `readConsentedSignals` applies it to the signal maps
|
|
32
|
+
// themselves, so the graph is ASSEMBLED FROM CONSENTED DATA rather than assembled
|
|
33
|
+
// and then filtered. Nothing downstream — score, rank, total, page, hydrate — can
|
|
34
|
+
// see a hidden builder, because they were never in the set those are derived from.
|
|
35
|
+
// That is the difference the criterion asks for: an assembled-then-filtered graph
|
|
36
|
+
// still leaks through counts, ranks and totals even when the row itself is gone.
|
|
37
|
+
// The gate FAILS CLOSED: a consent read that throws or returns null yields the
|
|
38
|
+
// empty set, and so the empty graph.
|
|
34
39
|
//
|
|
35
40
|
// A KNOWN PERFORMANCE GAP, measured rather than assumed. The signal joins key on
|
|
36
41
|
// tasks.goal_id (NO index), claims.builder_id and claims.task_id (indexed only
|
|
@@ -243,11 +248,21 @@ function toCountMap(rows) {
|
|
|
243
248
|
}
|
|
244
249
|
|
|
245
250
|
// ---------------------------------------------------------------------------
|
|
246
|
-
// Consent — ONE gate, fail-closed
|
|
251
|
+
// Consent — ONE gate, fail-closed, and UPSTREAM of everything derived
|
|
247
252
|
// ---------------------------------------------------------------------------
|
|
248
253
|
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
254
|
+
// C05 (task 1003175) is "consent is the READ PATH, not a filter bolted on after",
|
|
255
|
+
// and the distinction is structural rather than stylistic. The gate takes a set of
|
|
256
|
+
// candidate builder IDS and returns the ones that may be seen — it never takes
|
|
257
|
+
// assembled rows. Consent is therefore resolved before a score, a rank, a total or
|
|
258
|
+
// a page exists, so there is no derived quantity that could have been computed
|
|
259
|
+
// while a hidden builder was still in the set.
|
|
260
|
+
//
|
|
261
|
+
// A gate that filtered assembled entries leaves every present and future aggregate
|
|
262
|
+
// one forgetful line away from leaking: "you have 4 close collaborators" over a
|
|
263
|
+
// list of 3 is a leak by subtraction even though the node itself is absent, and so
|
|
264
|
+
// is a page that comes back one short, and so is a rank with a gap in it. Removing
|
|
265
|
+
// the row is the easy half; never having counted it is the guarantee.
|
|
251
266
|
//
|
|
252
267
|
// `hiddenIdsFor` is injected: the platform-identity hub owns the hide flag
|
|
253
268
|
// (ADR 0171 D2/D4) and lifecycle must not reach across a module boundary to read
|
|
@@ -255,19 +270,49 @@ function toCountMap(rows) {
|
|
|
255
270
|
// is enabled there is no hub-level hide flag to honour, and NOTHING is suppressed
|
|
256
271
|
// by this mechanism — which is correct, not a hole: on such an instance nobody has
|
|
257
272
|
// been offered the switch. What is NOT tolerated is an ERROR being read as
|
|
258
|
-
// "nobody is hidden", so a throwing reader
|
|
259
|
-
async function
|
|
260
|
-
|
|
273
|
+
// "nobody is hidden", so a throwing reader yields the empty set.
|
|
274
|
+
async function consentedIds(candidateIds, hiddenIdsFor) {
|
|
275
|
+
const ids = [...candidateIds].map(String);
|
|
276
|
+
if (typeof hiddenIdsFor !== 'function') return new Set(ids);
|
|
277
|
+
if (!ids.length) return new Set();
|
|
261
278
|
let hidden;
|
|
262
279
|
try {
|
|
263
|
-
hidden = await hiddenIdsFor(
|
|
280
|
+
hidden = await hiddenIdsFor(ids);
|
|
264
281
|
} catch (_) {
|
|
265
282
|
// FAIL CLOSED. An unreadable consent state is not permission.
|
|
266
|
-
return
|
|
283
|
+
return new Set();
|
|
267
284
|
}
|
|
268
|
-
if (!hidden) return
|
|
285
|
+
if (!hidden) return new Set(); // ditto: null is not "nobody"
|
|
269
286
|
const set = hidden instanceof Set ? hidden : new Set((hidden || []).map(String));
|
|
270
|
-
return
|
|
287
|
+
return new Set(ids.filter((id) => !set.has(id)));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// THE READ PATH. Reads the three signals and hands back count maps that have
|
|
291
|
+
// ALREADY passed the consent gate — a hidden builder is not a key in any of them.
|
|
292
|
+
// Every graph read goes through this one function, so consent is not a step a
|
|
293
|
+
// caller performs but a property of the data a caller is given.
|
|
294
|
+
async function readConsentedSignals(builderId, { otherId = null, hiddenIdsFor } = {}, deps = {}) {
|
|
295
|
+
const activePool = deps.pool || pool;
|
|
296
|
+
const [coClaim, coSession, shipped] = await Promise.all([
|
|
297
|
+
coClaimCounts(builderId, { otherId }, { pool: activePool }),
|
|
298
|
+
coSessionCounts(builderId, { otherId }, { pool: activePool }),
|
|
299
|
+
shippedTogetherCounts(builderId, { otherId }, { pool: activePool }),
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
const candidates = new Set([...coClaim.keys(), ...coSession.keys(), ...shipped.keys()]);
|
|
303
|
+
const allowed = await consentedIds(candidates, hiddenIdsFor);
|
|
304
|
+
return {
|
|
305
|
+
ids: allowed,
|
|
306
|
+
co_claim: keepOnly(coClaim, allowed),
|
|
307
|
+
co_build_session: keepOnly(coSession, allowed),
|
|
308
|
+
shipped_together_goal: keepOnly(shipped, allowed),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function keepOnly(counts, allowed) {
|
|
313
|
+
const m = new Map();
|
|
314
|
+
for (const id of allowed) if (counts.has(id)) m.set(id, counts.get(id));
|
|
315
|
+
return m;
|
|
271
316
|
}
|
|
272
317
|
|
|
273
318
|
// ---------------------------------------------------------------------------
|
|
@@ -275,28 +320,30 @@ async function applyConsent(entries, hiddenIdsFor) {
|
|
|
275
320
|
// ---------------------------------------------------------------------------
|
|
276
321
|
|
|
277
322
|
// Everyone this builder has any measurable closeness to, scored and explained,
|
|
278
|
-
// strongest first. `
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
323
|
+
// strongest first. `otherId` narrows every signal query to one pair at the
|
|
324
|
+
// DATABASE, so the pair read does not fetch a hundred rows to keep one.
|
|
325
|
+
//
|
|
326
|
+
// Returns `{ entries, total }`. `total` is the size of the CONSENTED graph before
|
|
327
|
+
// paging — the honest answer to "how many people am I close to", counted here,
|
|
328
|
+
// downstream of the gate. It is returned rather than left to the caller because
|
|
329
|
+
// both ways a caller could work it out are wrong: counting pre-gate rows is the
|
|
330
|
+
// "you have 4 close collaborators" leak itself, and counting the page answers 100
|
|
331
|
+
// for a builder with 300.
|
|
282
332
|
async function closenessFor(builderId, { limit = MAX_ROWS, hiddenIdsFor, otherId = null } = {}, deps = {}) {
|
|
283
333
|
const activePool = deps.pool || pool;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
shippedTogetherCounts(builderId, { otherId }, { pool: activePool }),
|
|
288
|
-
]);
|
|
334
|
+
// Nothing below this line can see a builder who hid: they are not a key in any
|
|
335
|
+
// of these maps, so no score, rank, total or page is ever computed with them in.
|
|
336
|
+
const signals = await readConsentedSignals(builderId, { otherId, hiddenIdsFor }, { pool: activePool });
|
|
289
337
|
|
|
290
|
-
const
|
|
291
|
-
let entries = [...ids].map((otherId) => {
|
|
338
|
+
const entries = [...signals.ids].map((id) => {
|
|
292
339
|
const counts = {
|
|
293
|
-
co_claim:
|
|
294
|
-
co_build_session:
|
|
295
|
-
shipped_together_goal:
|
|
340
|
+
co_claim: signals.co_claim.get(id) || 0,
|
|
341
|
+
co_build_session: signals.co_build_session.get(id) || 0,
|
|
342
|
+
shipped_together_goal: signals.shipped_together_goal.get(id) || 0,
|
|
296
343
|
};
|
|
297
344
|
const scored = scoreCloseness(counts);
|
|
298
345
|
return {
|
|
299
|
-
builder_id:
|
|
346
|
+
builder_id: id,
|
|
300
347
|
score: scored.score,
|
|
301
348
|
// The explanation travels WITH the score, on every surface, rather than
|
|
302
349
|
// behind a second call a caller may not make. That is the decision.
|
|
@@ -304,11 +351,14 @@ async function closenessFor(builderId, { limit = MAX_ROWS, hiddenIdsFor, otherId
|
|
|
304
351
|
};
|
|
305
352
|
}).filter((e) => e.score > 0);
|
|
306
353
|
|
|
307
|
-
|
|
354
|
+
// Ranking and paging run over the consented set, so positions are dense — a
|
|
355
|
+
// hidden builder leaves no gap at the top of someone's graph — and the page is
|
|
356
|
+
// filled from the rest rather than coming back short. A short page announces a
|
|
357
|
+
// removal as loudly as the row would have.
|
|
308
358
|
entries.sort((a, b) => b.score - a.score || String(a.builder_id).localeCompare(String(b.builder_id)));
|
|
309
|
-
|
|
359
|
+
const total = entries.length;
|
|
310
360
|
|
|
311
|
-
return hydrate(entries, activePool);
|
|
361
|
+
return { entries: await hydrate(entries.slice(0, capRows(limit)), activePool), total };
|
|
312
362
|
}
|
|
313
363
|
|
|
314
364
|
// One pair, with the same breakdown the graph carries. Separate from closenessFor
|
|
@@ -323,7 +373,7 @@ async function closenessBetween(builderId, otherId, { hiddenIdsFor } = {}, deps
|
|
|
323
373
|
// into the same queries, so there is still exactly one definition of each
|
|
324
374
|
// signal and the pair read costs a pair.
|
|
325
375
|
const graph = await closenessFor(builderId, { limit: 1, hiddenIdsFor, otherId }, { pool: activePool });
|
|
326
|
-
const found = graph.find((e) => String(e.builder_id) === String(otherId));
|
|
376
|
+
const found = graph.entries.find((e) => String(e.builder_id) === String(otherId));
|
|
327
377
|
if (found) return found;
|
|
328
378
|
|
|
329
379
|
// Not in the graph. That is either "no measurable closeness" or "consent says
|
|
@@ -359,5 +409,5 @@ module.exports = {
|
|
|
359
409
|
weightsModel, availableSignals, unavailableSignals,
|
|
360
410
|
scoreCloseness, explainInWords,
|
|
361
411
|
coClaimCounts, coSessionCounts, shippedTogetherCounts,
|
|
362
|
-
|
|
412
|
+
consentedIds, readConsentedSignals, closenessFor, closenessBetween,
|
|
363
413
|
};
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
// a caller may simply not make is how a score ends up on a page with nothing to
|
|
16
16
|
// point at. So `why` rides in the same payload as the number, always.
|
|
17
17
|
//
|
|
18
|
-
// CONSENT is resolved here and passed INTO the read as a gate (
|
|
19
|
-
//
|
|
20
|
-
// the
|
|
21
|
-
//
|
|
18
|
+
// CONSENT is resolved here and passed INTO the read as a gate (closeness.js
|
|
19
|
+
// `consentedIds`, applied by `readConsentedSignals` to the signal maps), never to
|
|
20
|
+
// the output afterwards — these handlers receive a graph that was assembled from
|
|
21
|
+
// consented data, and so have no filtering left to forget. That is C05 (task
|
|
22
|
+
// 1003175). The hub owns the hide flag (ADR 0171 D2/D4) and lifecycle must not
|
|
23
|
+
// read another module's table (ADR 0083), so the port is resolved optionally and a
|
|
24
|
+
// reader is handed down.
|
|
22
25
|
//
|
|
23
26
|
// Reaches core only through the doorway (ADR 0083).
|
|
24
27
|
|
|
@@ -36,8 +39,9 @@ const { asyncHandler, parseId } = api;
|
|
|
36
39
|
// hub-level hide flag to honour, because nobody on that instance has been offered
|
|
37
40
|
// the switch — suppressing everyone would not be consent, it would be an outage.
|
|
38
41
|
// What must never happen is an ERROR being read as "nobody is hidden", and that is
|
|
39
|
-
// why the reader THROWS rather than swallowing:
|
|
40
|
-
// the
|
|
42
|
+
// why the reader THROWS rather than swallowing: consentedIds catches and yields
|
|
43
|
+
// the empty set, so the graph is empty — the fail-closed direction ADR 0171 asks
|
|
44
|
+
// for.
|
|
41
45
|
function consentReader(pool) {
|
|
42
46
|
const hub = api.resolveOptional('platform-identity.hub');
|
|
43
47
|
if (!hub || typeof hub.hiddenGithubIds !== 'function') return null;
|
|
@@ -85,10 +89,15 @@ module.exports = function buildClosenessRouter() {
|
|
|
85
89
|
// Reading another builder's graph would expose who THEY are close to, which is
|
|
86
90
|
// a fact about third parties they did not consent to share.
|
|
87
91
|
router.get('/closeness/for-me', api.requireBuilder, asyncHandler('GET /closeness/for-me', async (req, res) => {
|
|
88
|
-
const
|
|
92
|
+
const { entries, total } = await closeness.closenessFor(req.builder.id, {
|
|
89
93
|
hiddenIdsFor: consentReader(api.pool),
|
|
90
94
|
});
|
|
91
|
-
|
|
95
|
+
// `count` is the CONSENTED total the read counted for us, never `entries.length`.
|
|
96
|
+
// Counting the page here would be wrong twice over: it is the surface that says
|
|
97
|
+
// "you have N close collaborators", so counting rows that were assembled before
|
|
98
|
+
// the gate is the leak this criterion is about, and counting the page after it
|
|
99
|
+
// answers 100 for a builder with 300.
|
|
100
|
+
return res.json({ closeness: entries, count: total, returned: entries.length });
|
|
92
101
|
}, { errorCode: 'closeness_failed' }));
|
|
93
102
|
|
|
94
103
|
// One pair — "why am I close to THEM", asked directly rather than by paging the
|
|
@@ -886,7 +886,7 @@ async function getPublicProfileExtras(githubId, { pool = defaultPool } = {}) {
|
|
|
886
886
|
// FAIL-CLOSED is the CALLER's job here, and deliberately so: this function
|
|
887
887
|
// THROWS on a bad read rather than returning an empty set, because an empty set
|
|
888
888
|
// means "nobody is hidden" and a database error must never be able to say that.
|
|
889
|
-
// modules/lifecycle/closeness.js
|
|
889
|
+
// modules/lifecycle/closeness.js consentedIds catches and empties the graph.
|
|
890
890
|
async function hiddenGithubIds(githubIds, { pool = defaultPool } = {}) {
|
|
891
891
|
const ids = (Array.isArray(githubIds) ? githubIds : []).filter(isNumericGithubId).map(String);
|
|
892
892
|
if (!ids.length) return new Set();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"coreVersion": "^1.19.0",
|
|
6
6
|
"default": true,
|
|
7
7
|
"maintenance": { "status": "core-maintained" },
|
|
8
|
-
"contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
|
|
8
|
+
"contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "minimalist-ui", "industrial-brutalist-ui", "gpt-taste", "stitch-design-taste", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
|
|
9
9
|
"provides": [],
|
|
10
10
|
"consumes": []
|
|
11
11
|
}
|
|
@@ -27,7 +27,7 @@ The eleven taste skills are single-file prose; the floor's one critical is a fal
|
|
|
27
27
|
|
|
28
28
|
## The in-house rebuilds (2026-09-01)
|
|
29
29
|
|
|
30
|
-
A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` segment, so the ratchets count it; no `PROVENANCE.md` or `LICENSE`, because an in-house skill claims no upstream; the same PLATFORM PREAMBLE first after the frontmatter, byte-identical; then a **`## Rebuilt from`** section naming the origin the *spec* was derived from (documentation only), the scan verdict, the spec task, the statement that no upstream bytes were consulted or copied, and what changed in translation ([ADR 0220](../../../docs/adr/<redacted>.md)). Declared in `module.json` like any other; `claude-materialize.js` step 1b lands it through its own-dir branch, which no sidecar gate touches. `tests/ui_design_skills.mjs` pins all of it. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)).
|
|
30
|
+
A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` segment, so the ratchets count it; no `PROVENANCE.md` or `LICENSE`, because an in-house skill claims no upstream; the same PLATFORM PREAMBLE first after the frontmatter, byte-identical; then a **`## Rebuilt from`** section naming the origin the *spec* was derived from (documentation only), the scan verdict, the spec task, the statement that no upstream bytes were consulted or copied, and what changed in translation ([ADR 0220](../../../docs/adr/<redacted>.md)). Declared in `module.json` like any other; `claude-materialize.js` step 1b lands it through its own-dir branch, which no sidecar gate touches. `tests/ui_design_skills.mjs` pins all of it. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)). A rebuild whose upstream shipped a **look** carries a composition grammar and no colours of its own — the palette is the instance's pack and the texture is a named entry in [`../styles/`](../styles/README.md) ([ADR 0230](../../../docs/adr/<redacted>.md)).
|
|
31
31
|
|
|
32
32
|
| Skill | Rebuilt from (spec) | Task | What it is |
|
|
33
33
|
|---|---|---|---|
|
|
@@ -35,13 +35,17 @@ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` seg
|
|
|
35
35
|
| `design-taste-frontend-v1` | `Leonxlnx/taste-skill` `taste-skill-v1` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the earlier generation, kept distinct: a fixed baseline triple adapted in conversation, stack conventions for a served surface, the layout bans, full interaction cycles, the pattern vocabulary, the tile-grid archetypes |
|
|
36
36
|
| `high-end-visual-design` | `Leonxlnx/taste-skill` `soft-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the agency-build polish pass: one texture archetype (the style library's looks) and one layout archetype per project, the nested container anatomy where the world allows a container, the pill with its nested arrow disc, rhythm, choreography and scroll entry inside the Kill Switch |
|
|
37
37
|
| `redesign-existing-projects` | `Leonxlnx/taste-skill` `redesign-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the in-place upgrade: scan the stack, diagnose against the seven-group audit with the instance's world as the answer key, fix in impact-per-risk order, one group per commit; keeps the stack, never migrates frameworks |
|
|
38
|
+
| `minimalist-ui` | `Leonxlnx/taste-skill` `minimalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the quiet editorial look as a grammar and no colours: macro whitespace first, a capped measure, one display job and one body job, exactly one hairline weight in the sheet, washed accents measured against their own ink, flat tile cards, box-less accordions, keycaps, one depth treatment per band; declines dense surfaces |
|
|
39
|
+
| `industrial-brutalist-ui` | `Leonxlnx/taste-skill` `brutalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the technical-document look: one archetype per project (Swiss print or dark terminal, never mixed) with the other mode still complete, clamped uppercase display against fixed mono metadata, terracotta as the one hazard accent and sea as one terminal green, the hairline as a grid gap, zero rounding, static degradation |
|
|
40
|
+
| `gpt-taste` | `Leonxlnx/taste-skill` `gpt-tasteskill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the anti-default pass: a plan block before any markup with a selection derived from the route and bumped against siblings, the four-stage structure, the ultra-wide hero at two or three lines, a dense grid with zero empty cells, scroll-driven motion in CSS; pack-agnostic |
|
|
41
|
+
| `stitch-design-taste` | `Leonxlnx/taste-skill` `stitch-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the design contract document for an external screen-generating tool: four dials, eight fixed sections, descriptive language paired with exact values, the thirteen transcribed from the pack and diffed, motion as an intent brief inside the Kill Switch |
|
|
38
42
|
| `impeccable` | `pbakaus/impeccable` @ `<redacted>` (skill v4.1.x) | [task 1003326](https://cloudbongos.com/builders#/task/1003326) | the design-craft method as one skill with sub-commands, one playbook loaded per request from `impeccable/reference/`: shape, critique, audit, polish, harden, onboard, animate, colorize, typeset, layout, adapt, distill, clarify, optimize, document, extract, bolder, quieter; the craft floor and the bounded finish live in its `SKILL.md`; the three persisted homes are the instance's own files; no hooks, no bypass agent, no telemetry, no live server (the playwright plugin and the kit are the loops) |
|
|
39
43
|
| `image-to-code` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the analyse-then-implement loop with the owner's references and the world as the source of truth; the only image it may cause is the hero plate; a structured per-image analysis, the extraction spec, a faithful translation, drift named as the failure mode, a clarity checklist the kit measures |
|
|
40
44
|
| `imagegen-frontend-web` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the plate-making skill: text-free hero plates, hero objects, backdrops and orbs for the slots a composition already has, a composition anchor per plate, one family per surface, the pack as the palette lock, the instance's art pipeline key, the pair-and-sidecar output contract; never a section image, a comp or a screen |
|
|
41
45
|
| `imagegen-frontend-mobile` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | portrait plates for the 390 and 320 compositions, clear of the regions a phone reserves, the first view held to one focal point, a scrim under text over a plate; a screen is the states file rendered by the kit; no phone mockups, no flows of screens; native declines |
|
|
42
46
|
| `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the identity system with the strategy inferred first and the logo standard kept: the mark drawn as SVG, the colour system as the pack, the type specimen as the faces, the applications as the kit's renders, the image direction as a hero-object family, the board as a contact sheet; never a generated logo, board or mockup |
|
|
43
47
|
|
|
44
|
-
What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key.
|
|
48
|
+
What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. The four looks (task 1003327, [ADR 0230](../../../docs/adr/<redacted>.md)) carry the other rewrite: a look skill ships structure, rhythm, type roles, component anatomy, motion and bans and **no colours**, so a substrate becomes an archetype that still ships both modes, a prompt-seeded roll becomes a deterministic selection derived from the route, a design document transcribes the world instead of authoring one, and the upstream's seeded placeholder photography is dropped rather than softened. The whole scoped set of thirteen is now rebuilt in-house; nothing from either origin is still waiting on a spec.
|
|
45
49
|
|
|
46
50
|
**In the core checkout itself**, where these skills are written, Claude Code reads only `.claude/skills/` at the repo root and the materialiser's self-copy guard leaves it empty of module skills. `node scripts/gds/claude-materialize.js --module-skills-only` lands every enabled module's skills there as untracked, self-ignored copies (a generated `.claude/skills/.module-skills.json` manifest and a `.gitignore` beside them; `--clean` removes them; the file map skips them by the manifest) so a builder can invoke them here too ([task 1003330](https://cloudbongos.com/builders#/task/1003330), [ADR 0224](../../../docs/adr/<redacted>.md)). Edit the module's `skills/<name>/`, never the copy, and re-run to refresh.
|
|
47
51
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gpt-taste
|
|
3
|
+
description: Anti-default enforcement for a persuasive scrolling surface when the last attempt came out looking like every other one. A written plan block before any markup whose selection is derived deterministically from the route (a hero architecture, three component architectures, two CSS motion paradigms, checked against what sibling surfaces already used), the four-stage attention-interest-desire-action structure, an ultra-wide hero container that holds the headline to two or three lines, a dense tile grid with zero empty cells, real scroll-driven motion inside the Kill Switch, and a measured contrast sweep. Triggers on "it looks generic", "award-tier", "stop making the same page", "anti-default". 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 `gpt-tasteskill` 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 `[gpt-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 asked the agent to **simulate a random number generator** seeded from the prompt and let the roll pick the design. A roll nobody can reproduce is not a decision, and the same roll on a sibling surface is a coincidence rather than a system — so the selection here is **deterministic and written down**: derived from the surface's own route, recorded in the plan block, and checked against what its siblings already used. Its "type stack" selection is gone; the pack carries two stacks and this skill does not add a face. Its ambient graded backgrounds are `color-mix()` radials of the fifteen, never a new colour. Its **filtered seeded placeholder photography is dropped entirely** — the only rasters this skill may ask for are text-free hero plates under the owner's rule, and a surface with no plate ships without one. Its scroll-motion paradigms survive only as CSS scroll-driven animation inside the Kill Switch: pinned titles, scrubbed reveals and stacking cards are available that way; pointer-tracked physics and shared-element transitions are named *not available* rather than faked in script.
|
|
25
|
+
|
|
26
|
+
## Where this look lives
|
|
27
|
+
|
|
28
|
+
This skill is **not a texture**. It is an anti-default enforcement layer that runs over whatever look is on: the palette is the instance's pack, and a variant comes from the style library at `modules/ui-design/styles/` — it composes the same way on `chrome-world`, `expedition` or `grove`, and the plan block below names which one is in force. Where it disagrees with the instance's `DESIGN.md`, the instance wins.
|
|
29
|
+
|
|
30
|
+
## Fires on
|
|
31
|
+
|
|
32
|
+
A persuasive, scrolling surface — a landing page, a launch page, a product marketing page — **and specifically when the previous attempt came out looking like every other one**: a centred hero, three equal cards, a two-column feature row, a footer. It does not fire on an application surface; density and repetition are virtues in a dashboard, and this skill's whole method is to break repetition.
|
|
33
|
+
|
|
34
|
+
## Step 1 — the plan block, before any UI code
|
|
35
|
+
|
|
36
|
+
Write this block first, in the task's notes and as a comment at the head of the sheet. No markup until it exists.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
SURFACE /pricing (the seed: the route)
|
|
40
|
+
PACK expedition (the look in force)
|
|
41
|
+
HERO off-axis split (of the five below; siblings used: full-bleed type, stage)
|
|
42
|
+
COMPONENTS marquee band / inline-image headline / horizontal accordion
|
|
43
|
+
MOTION pinned title + scrubbed reveal (two paradigms, both CSS)
|
|
44
|
+
HERO MATH container 1180px, clamp(44px, 5.6vw, 88px), headline 47 chars -> 2 lines
|
|
45
|
+
GRID PROOF 9 items, dense auto-flow, spans 2/1/1/3/1/2/1/2/1 -> 0 empty cells
|
|
46
|
+
CONTRAST CTA accent-on over --accent = 8.1:1 · label ink-faint on bg-card = 4.9:1
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**The selection is deterministic, not random.** Take the surface's route, sum its character codes, and index the lists below. Then check the sibling surfaces of this instance: if the route's index lands on a hero architecture a sibling already uses, take the next one. Record both the index and the bump. The same route re-derives the same page a year later; two different routes cannot collide silently.
|
|
50
|
+
|
|
51
|
+
**Hero architectures** (pick one): full-bleed type · off-axis split · the stage (one object centred, type wrapped around it) · the ledger (type in a left rail, content scrolling beside it) · the masthead (a text-free hero plate behind the words, with a scrim under the text).
|
|
52
|
+
|
|
53
|
+
**Component architectures** (pick three, never the same three as a sibling): the marquee band · the inline-image headline · the horizontal accordion · the stacking card sequence · the offset testimonial rail · the numbered chapter list · the comparison lattice.
|
|
54
|
+
|
|
55
|
+
**Motion paradigms** (pick two, both CSS): pinned section title beside a scrolling gallery · scrubbed word-by-word reveal · scale-in and fade-out on exit · stacking cards · hover physics on a clickable card.
|
|
56
|
+
|
|
57
|
+
## Step 2 — the four-stage structure
|
|
58
|
+
|
|
59
|
+
Always opening with a navigation bar that is **not** a centred logo with links either side.
|
|
60
|
+
|
|
61
|
+
1. **Attention** — the hero, built to the architecture chosen above.
|
|
62
|
+
2. **Interest** — the tile grid (below).
|
|
63
|
+
3. **Desire** — the scroll sequence, carrying both motion paradigms.
|
|
64
|
+
4. **Action** — the closer: one CTA, restated, with the single strongest reason beside it.
|
|
65
|
+
|
|
66
|
+
Sections read as chapters: the vertical padding between them is at or above twice the world's band clamp, and each one is either the widest thing on the page or the narrowest. Two adjacent bands never share a width.
|
|
67
|
+
|
|
68
|
+
## The hero rule
|
|
69
|
+
|
|
70
|
+
The headline container is **ultra-wide** so the headline flows in **two to three lines**. Four or more lines is a failure with exactly two fixes: widen the container, or reduce the clamped font size. **Never rewrite the copy to fit the layout** — the sentence is the product's, the container is yours. Do the arithmetic in the plan block, then confirm it in the render.
|
|
71
|
+
|
|
72
|
+
Banned in the hero: a floating stamp badge over the headline, a row of pill tags beneath it, and raw statistics inside it. Statistics belong to the interest stage where they have room to be believed.
|
|
73
|
+
|
|
74
|
+
## The tile grid
|
|
75
|
+
|
|
76
|
+
- `grid-auto-flow: dense` with interlocking spans and **mathematically zero empty cells** — prove the count in the plan block.
|
|
77
|
+
- Three to five intentional cards, not eight thin ones. A card that exists because the grid had a hole is deleted, and the spans are redistributed.
|
|
78
|
+
- Never three equal cards in a row. Never a grid whose every cell is the same size.
|
|
79
|
+
|
|
80
|
+
## Motion, inside the Kill Switch
|
|
81
|
+
|
|
82
|
+
Everything below is CSS, on the world's curves, and dead under `@media (prefers-reduced-motion: reduce)` with the rest frame a complete composition.
|
|
83
|
+
|
|
84
|
+
- **Real scroll-driven motion** via `animation-timeline: view()` and `scroll()` behind `@supports`: the pinned section title, the image that scales in and fades on exit, the word-by-word scrubbed reveal (one span per word, stepped `animation-range`), the stacking card sequence (`position: sticky` plus a view timeline).
|
|
85
|
+
- **Hover physics** on a clickable card: a small lift and a hairline change on `transform` and border colour only, on the state curve.
|
|
86
|
+
- **Not available here**, and named as such rather than approximated: spring physics, magnetic or pointer-tracked hover, shared-element transitions, and any continuous value read from a scroll listener. If the composition depends on one of them, the composition changes.
|
|
87
|
+
- Nothing moves in JavaScript. At most one `IntersectionObserver` adding a single class, and only where a view timeline is unsupported.
|
|
88
|
+
|
|
89
|
+
## The bans
|
|
90
|
+
|
|
91
|
+
Defaults an instance's `DESIGN.md` may only tighten:
|
|
92
|
+
|
|
93
|
+
- **Enumerated meta labels above sections** (`01 — FEATURES`). The chapter is legible from its own content.
|
|
94
|
+
- **Emoji**, anywhere: markup, copy, alt text, comments, commit message.
|
|
95
|
+
- **Low-contrast button text.** Every CTA is measured with `node modules/ui-design/kit/contrast.js` and pasted into the plan block; a CTA that fails is not a style choice.
|
|
96
|
+
- **Flat colour on a full band** where the world has an ambient tier: use a `color-mix()` radial of the fifteen instead, at low strength, behind the content layer.
|
|
97
|
+
- **Horizontal overflow, page-wide.** A guard on the page container, and the 320 shot is the proof.
|
|
98
|
+
|
|
99
|
+
## Pre-flight, measured by the kit
|
|
100
|
+
|
|
101
|
+
Render the real surface — `node modules/ui-design/kit/render.js --page <path> --style <pack>` — and audit the shots against the plan block. The anti-pattern detector (`node modules/ui-design/kit/tells.js`, or `check-mock.js` which runs it) catches the mechanical half of this list; the rest you read.
|
|
102
|
+
|
|
103
|
+
- [ ] The plan block exists, its selection is derived from the route, and the sibling bump is recorded.
|
|
104
|
+
- [ ] Headline at two or three lines at 1440, and still at 390.
|
|
105
|
+
- [ ] Grid proof holds in the render: zero empty cells, no three-equal row.
|
|
106
|
+
- [ ] Two adjacent bands never share a width; section padding at or above twice the band clamp.
|
|
107
|
+
- [ ] Both motion paradigms are CSS; the reduced-motion shots are the rest frame and it is complete.
|
|
108
|
+
- [ ] Every CTA pair measured and pasted; no emoji anywhere in the diff.
|
|
109
|
+
- [ ] Zero horizontal overflow at 1440, 390 and 320.
|
|
110
|
+
- [ ] Where a **hero plate** is used it is text-free, a background token with its provenance sidecar, and it is the only raster on the page.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: industrial-brutalist-ui
|
|
3
|
+
description: Raw mechanical interfaces that read like a technical document, inside the instance's world. One archetype per project (Swiss industrial print or dark tactical terminal, never mixed), massive uppercase clamped display against fixed tracked-out mono metadata, the pack's terracotta as the single hazard accent and its sea as one terminal green, strict grid anchoring with the hairline as a one-pixel grid gap, zero corner rounding, bracket and crosshair framing glyphs, and static halftone, scanline and noise degradation inside the Kill Switch. Triggers on "brutalist", "industrial", "terminal", "make it raw", "like a technical document". 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 `brutalist-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 `[industrial-brutalist-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's central rule was **one substrate per project** — off-white document paper *or* near-black terminal, never both. A platform surface here ships **both modes on every page** (the preamble's dark twins), so the substrate choice becomes an **archetype** that decides which mode the surface is *composed for* while the other mode is still complete and still measured. Its hazard red and optional terminal green are not new colours: red is the pack's `terracotta`, green is the pack's `sea`, and neither may appear a third time. Its "zero corner rounding" is legal because a radius is a **derived tier**, not one of the fifteen — a brutalist sheet sets its own radii to 0 and says so in the header comment; where the instance's `DESIGN.md` names a radius as a signature, the instance wins. Its degradation effects (halftone, dithering, scanlines, noise) survive as CSS built from the one legal literal, pure black or white with alpha, and every one of them is static: a scanline that crawls is an animation, and it dies under the Kill Switch with everything else. Its "high-contrast serif after heavy degradation" is dropped unless the pack already carries a serif; this skill does not add a face.
|
|
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 two archetypes map onto the library like this — the **terminal** archetype is at home on `chrome-world` (a black void, one warm accent, mono metadata), and the **print** archetype is at home on `expedition` (warm paper, halftone, heavy uppercase display). `grove` is too soft for either and this skill says so rather than trying. Render under one with `--style <name>` before committing.
|
|
29
|
+
|
|
30
|
+
## Fires on
|
|
31
|
+
|
|
32
|
+
Data-heavy dashboards, telemetry and status surfaces, engineering portfolios, technical editorial — anything meant to read like a document produced by a machine rather than a brochure. It does **not** fire on a sign-up flow, a marketing landing page or an onboarding surface: the grammar reads as hostile where the user's job is to be persuaded or reassured.
|
|
33
|
+
|
|
34
|
+
## Commit to one archetype, and write it down
|
|
35
|
+
|
|
36
|
+
Pick **one** before anything is drawn, name it in the sheet's header comment and in the ship notes. The two are never mixed inside one interface.
|
|
37
|
+
|
|
38
|
+
| Archetype | Composed for | The substrate | The metadata voice |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| **Swiss industrial print** | light | the pack's `bg` as document paper, `bg-frame` as the plate | mono, `ink-soft`, tracked out |
|
|
41
|
+
| **Dark tactical terminal** | dark | the sheet's black tier, `bg-card` as the compartment | mono, the pack's `sea` at label size |
|
|
42
|
+
|
|
43
|
+
The mode the archetype is *not* composed for still ships, still clears AA, and still gets its own shots. It is the same composition in the other tier, not a second design: the print archetype in dark is the same grid on the black tier with the rules inverted; the terminal archetype in light is the same grid on paper.
|
|
44
|
+
|
|
45
|
+
## Typography
|
|
46
|
+
|
|
47
|
+
- **Macro: massive uppercase display.** The pack's `display` stack at its heaviest available weight, uppercase, negative tracking (about -0.03em), leading compressed to 0.9, and `clamp()`ed. Clamped sizing is **reserved for macro type**; nothing else on the page fluid-scales.
|
|
48
|
+
- **Micro: fixed mono, tracked out.** Every piece of metadata, every nav item, every unit identifier, coordinate, timestamp and count is mono at a fixed small size (12 to 13px), uppercase, `letter-spacing: .12em`. It never scales with the viewport; a technical readout that grows on a wide screen stops reading as a readout.
|
|
49
|
+
- **Nothing in between is decorative.** Body copy is the pack's `body` stack at the world's scale. There is no third voice.
|
|
50
|
+
- **A serif appears only if the pack has one.** This skill adds no face and loads no font; the two stacks the pack carries are the whole type system.
|
|
51
|
+
|
|
52
|
+
## Colour
|
|
53
|
+
|
|
54
|
+
- **One substrate, per the archetype.** Everything else is drawn on it.
|
|
55
|
+
- **One hazard accent: the pack's `terracotta`.** It marks what is dangerous, live or out of tolerance. It is never a decoration and never a second heading colour.
|
|
56
|
+
- **One optional terminal green: the pack's `sea`, on exactly one element** across the surface (a live indicator, a cursor, an in-range readout). A second use is a bug.
|
|
57
|
+
- **The pack's `accent` stays where the world put it** — on what is pressable — and does not join the hazard vocabulary.
|
|
58
|
+
- **Prohibited by default**: gradients as a surface, soft shadows, translucency as a material. Where the instance's world is built on glass (the chrome world is), this skill uses the world's glass token for the fixed layers the world already puts it on and adds none of its own; the world wins.
|
|
59
|
+
|
|
60
|
+
## Layout
|
|
61
|
+
|
|
62
|
+
- **Strict grid anchoring.** One grid for the whole surface, declared once; every compartment starts and ends on a line. Nothing is positioned by eye.
|
|
63
|
+
- **Visible compartments.** A region is bounded by a rule on all four sides or by a full-bleed rule above and below it. The rule is `rule`, 1px, at full strength — this look does not use the soft tier.
|
|
64
|
+
- **The hairline is a grid gap.** A one-pixel `gap` on a grid over a parent painted in `rule` produces a true hairline lattice that never doubles at a junction and never rounds. Prefer it to per-cell borders.
|
|
65
|
+
- **Oscillation.** A dense cluster of readouts sits directly against a band of vast negative space. Even rhythm is the failure mode; if every band has the same weight, the look has not landed.
|
|
66
|
+
- **Zero corner rounding.** Every radius on this surface is `0`, set once in the sheet's own derived tier with a comment naming this skill. A world whose signature shape is a pill keeps it and the sheet says which one it kept.
|
|
67
|
+
- **Full-width rules** between chapters, edge to edge, not inset to the container.
|
|
68
|
+
|
|
69
|
+
## The component vocabulary
|
|
70
|
+
|
|
71
|
+
Standard web chrome is replaced, not restyled:
|
|
72
|
+
|
|
73
|
+
- **Bracket and slash framing** on labels and actions: `[ DEPLOY ]`, `// SECTION 04`, `< BACK`. The glyphs are text, in the mono voice, never an icon font.
|
|
74
|
+
- **Registration and trademark marks used structurally** — a `®` or `™` at the corner of a compartment as a registration tick, at label size in `ink-faint`.
|
|
75
|
+
- **Crosshairs at grid intersections**: a 9px cross drawn with two pseudo-element rules in `rule`, on a pointer-inert layer, at the corners the grid actually has.
|
|
76
|
+
- **Barcode rules**: a `repeating-linear-gradient` of `ink` at varying stops as a band separator, at low alpha, never as a background for text.
|
|
77
|
+
- **Warning stripes**: a 45-degree `repeating-linear-gradient` of the `terracotta` and the substrate, reserved for a genuinely hazardous state and used on a bar no taller than 6px.
|
|
78
|
+
|
|
79
|
+
Every one of these is drawn from tokens and CSS. None of them is an image, and none of them carries meaning a screen reader cannot reach — a bracket is decoration around a real label, never the label itself.
|
|
80
|
+
|
|
81
|
+
## Simulated analog degradation
|
|
82
|
+
|
|
83
|
+
Three treatments, at most two per surface, all static:
|
|
84
|
+
|
|
85
|
+
- **Halftone or one-bit dithering** over a plate: a `repeating-radial-gradient` mask at low alpha over a background token, or a `background-blend-mode` against a dot pattern. The plate underneath is one of the instance's own **text-free hero plates** with its provenance sidecar; this skill never asks for a new one.
|
|
86
|
+
- **Scanlines**: a `repeating-linear-gradient` of transparent and pure black at 4 to 6 percent alpha, 3px period, on a fixed `pointer-events: none` pseudo-element. Static. A scanline that moves is an animation and dies under the switch anyway.
|
|
87
|
+
- **One global noise layer**: a single fixed, pointer-inert pseudo-element at very low alpha. One per page, never per component.
|
|
88
|
+
|
|
89
|
+
All three are built from the one legal literal (pure black or white with alpha). None of them reduces the contrast of text measured underneath it — check with `node modules/ui-design/kit/contrast.js` after the layer is on, not before.
|
|
90
|
+
|
|
91
|
+
## Engineering directives
|
|
92
|
+
|
|
93
|
+
- **Semantic technical elements** for readouts: `<data value>`, `<time datetime>`, `<meter>`, `<output>`, `<abbr title>`. A telemetry number that is only a `<span>` is a defect.
|
|
94
|
+
- **Clamped sizing only on macro type**, as above.
|
|
95
|
+
- **`z-index` on a named systemic scale** declared on `body`; the degradation layers are the top of it, the content never competes.
|
|
96
|
+
- **No layout property animates**; `transform` and `opacity` only.
|
|
97
|
+
- Motion is minimal by nature here, and what exists lives inside the **Kill Switch** — `@media (prefers-reduced-motion: reduce)` at the foot of the token layer killing every animation and transition, with the rest frame complete.
|
|
98
|
+
|
|
99
|
+
## Pre-flight, measured by the kit
|
|
100
|
+
|
|
101
|
+
Render the real surface — `node modules/ui-design/kit/render.js --page <path>`, and `--style chrome-world` or `--style expedition` for the archetype you committed to — and read the shots.
|
|
102
|
+
|
|
103
|
+
- [ ] The archetype is named in the sheet header and the ship notes, and the interface carries exactly one.
|
|
104
|
+
- [ ] The mode the archetype was **not** composed for is complete and clears AA in its own shots.
|
|
105
|
+
- [ ] The hazard accent appears only on hazard; the terminal green appears exactly once.
|
|
106
|
+
- [ ] Every radius on the surface is 0, or the one the world kept is named.
|
|
107
|
+
- [ ] The lattice is a grid gap, not per-cell borders; no doubled or rounded junctions at 1440, 390 or 320.
|
|
108
|
+
- [ ] At most two degradation treatments, all static, all pointer-inert, and text under them still measures AA.
|
|
109
|
+
- [ ] Mono metadata does not scale with the viewport at any of the three widths.
|
|
110
|
+
- [ ] Reduced-motion pass identical to the rest frame; `check-mock.js` clean, tells waived in the file with a reason where kept.
|