@clearlysh/beehaven 0.8.27 → 0.8.29
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/dist/build-channel.js +2 -2
- package/dist/clearly.js +5 -4
- package/dist/cli.js +1 -1
- package/dist/commands/workspace.js +22 -5
- package/dist/relay-presets.js +3 -4
- package/package.json +1 -1
- package/skills/beehaven-cli/SKILL.md +12 -0
- package/skills/brand-identity/SKILL.md +15 -9
- package/skills/clearly-agent/SKILL.md +13 -0
- package/skills/clearly-canvas/SKILL.md +58 -7
- package/skills/clearly-canvas/authoring.md +23 -0
- package/skills/clearly-canvas/review.mjs +106 -12
- package/skills/design-craft/SKILL.md +5 -0
- package/skills/design-craft/references.md +95 -0
- package/skills/index.json +7 -6
package/dist/build-channel.js
CHANGED
|
@@ -53,9 +53,9 @@ export const PUBLIC_BUILD = true;
|
|
|
53
53
|
* ⚠ REWRITTEN IN `dist/` AT BUILD TIME, like `PUBLIC_BUILD`. `unstamped` is what a source checkout
|
|
54
54
|
* carries and what `tsx`/`vitest` see, and it is honest: nothing has stamped them.
|
|
55
55
|
*/
|
|
56
|
-
export const BUILD_SHA = '
|
|
56
|
+
export const BUILD_SHA = 'd733c0004e58-dirty';
|
|
57
57
|
/** ISO-8601 minute precision, UTC. Rewritten in `dist/` by `scripts/stamp-build-channel.mjs`. */
|
|
58
|
-
export const BUILD_TIME = '2026-10-
|
|
58
|
+
export const BUILD_TIME = '2026-10-10T21:21Z';
|
|
59
59
|
/**
|
|
60
60
|
* The build identity as one short string, for `--version`.
|
|
61
61
|
*
|
package/dist/clearly.js
CHANGED
|
@@ -47,11 +47,12 @@ const c = {
|
|
|
47
47
|
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
50
|
-
* ⚠ THE AGENT TURN IS THE SLOW PART
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* ⚠ THE AGENT TURN IS THE SLOW PART. The server's `RESPOND_TIMEOUT_MS` is 300s (clearly CDS-90, was
|
|
51
|
+
* 180s); this waits that plus the report's tail, so a turn that finishes on time is never abandoned
|
|
52
|
+
* by its caller and a timeout here still means the turn itself ran out. The relay-forward window
|
|
53
|
+
* follows the `_timeout` sent with the call.
|
|
53
54
|
*/
|
|
54
|
-
const TURN_MS =
|
|
55
|
+
export const TURN_MS = 330_000;
|
|
55
56
|
/**
|
|
56
57
|
* ⚠ STDERR IS CAPTURED, NOT INHERITED. A failing probe is normal here — `HEAD~30` does not exist in
|
|
57
58
|
* a repo with two commits — and with inherited stderr git printed
|
package/dist/cli.js
CHANGED
|
@@ -3917,7 +3917,7 @@ complete -F _beehaven_completions beehaven`);
|
|
|
3917
3917
|
console.log(` ${r.url}`);
|
|
3918
3918
|
// "reached" not "navigated": the frame goes to every connected surface but only the
|
|
3919
3919
|
// VISIBLE one acts on it, so a count of sockets is not a count of things that moved.
|
|
3920
|
-
console.log(color.dim(` ${r.delivered > 0 ? `
|
|
3920
|
+
console.log(color.dim(` ${r.delivered > 0 ? `navigation requested (${r.delivered} connected surface(s)); editor display is not yet verified` : 'no navigation recipient connected'}`));
|
|
3921
3921
|
// Only reach for the local browser when asked, or when nothing was
|
|
3922
3922
|
// delivered — opening a window under someone who already has the tab
|
|
3923
3923
|
// focused is worse than doing nothing.
|
|
@@ -26,7 +26,10 @@ import { resolveActiveAgent } from '../agent-state.js';
|
|
|
26
26
|
import { isAgentRuntime } from '../agent-scope.js';
|
|
27
27
|
import { getTarget } from '../cli-state.js';
|
|
28
28
|
import { attachShellCwd, absorbShellCwd, shellTargetKey } from '../shell-session.js';
|
|
29
|
-
|
|
29
|
+
/** Workspace-agent turns wait for the server's turn ceiling (300s) plus the report's tail. */
|
|
30
|
+
export const AGENT_TURN_TIMEOUT_MS = 330_000;
|
|
31
|
+
const AGENT_TURN_ACTIONS = new Set(['agent-prompt', 'agent-turn']);
|
|
32
|
+
export function getTimeout(values, action) {
|
|
30
33
|
// Precedence: explicit --timeout flag → BEEHAVEN_RPC_TIMEOUT_MS env → default.
|
|
31
34
|
// The default is generous because real jobs (sticker-sheet gen, renders,
|
|
32
35
|
// long agent turns) routinely run well past the old 30s and shouldn't need a
|
|
@@ -37,7 +40,8 @@ export function getTimeout(values) {
|
|
|
37
40
|
const env = process.env.BEEHAVEN_RPC_TIMEOUT_MS;
|
|
38
41
|
if (env && Number.isFinite(Number(env)) && Number(env) > 0)
|
|
39
42
|
return parseInt(env, 10);
|
|
40
|
-
|
|
43
|
+
// An agent turn may run to the server's 300s ceiling (clearly CDS-90); waiting less abandons it.
|
|
44
|
+
return action && AGENT_TURN_ACTIONS.has(action) ? AGENT_TURN_TIMEOUT_MS : 180_000;
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
47
|
* True when `msg` says `action` isn't available on the target DO.
|
|
@@ -618,11 +622,24 @@ export async function cmdActions(values, positional) {
|
|
|
618
622
|
grouped.set(cat, []);
|
|
619
623
|
grouped.get(cat).push(action);
|
|
620
624
|
}
|
|
625
|
+
if (!values.verbose && !values.category && !values.search) {
|
|
626
|
+
for (const [category, entries] of grouped) {
|
|
627
|
+
console.log(` ${category.padEnd(18)} ${String(entries.length).padStart(3)} actions · ${entries.slice(0, 3).map(a => a.name).join(', ')}${entries.length > 3 ? ', …' : ''}`);
|
|
628
|
+
}
|
|
629
|
+
console.log('');
|
|
630
|
+
console.log(' Find an action: beehaven actions --search <term>');
|
|
631
|
+
console.log(' List a category: beehaven actions --category <name>');
|
|
632
|
+
console.log(' Exact schema: beehaven explain <action>');
|
|
633
|
+
console.log(' Complete inventory: beehaven actions --verbose or --format full --json');
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
621
636
|
for (const [category, catActions] of grouped) {
|
|
622
637
|
const catLabel = category.charAt(0).toUpperCase() + category.slice(1);
|
|
623
638
|
console.log(` \x1b[36m${catLabel}\x1b[0m (${catActions.length})`);
|
|
624
639
|
for (const a of catActions) {
|
|
625
|
-
const
|
|
640
|
+
const description = String(a.description || '');
|
|
641
|
+
const display = values.verbose || description.length <= 160 ? description : `${description.slice(0, 157)}…`;
|
|
642
|
+
const desc = display ? ` \x1b[90m${display}\x1b[0m` : '';
|
|
626
643
|
const aliasLabel = Array.isArray(a.aliases) && a.aliases.length > 0
|
|
627
644
|
? ` \x1b[2m(also: ${a.aliases.join(', ')})\x1b[0m`
|
|
628
645
|
: '';
|
|
@@ -1088,7 +1105,7 @@ export async function cmdCall(action, rawInput, values) {
|
|
|
1088
1105
|
console.error(`\x1b[2m→ ${target} ${action}${who}\x1b[0m`);
|
|
1089
1106
|
}
|
|
1090
1107
|
try {
|
|
1091
|
-
const raw = await rpcOnce(resolved.action, resolved.input, getTimeout(values));
|
|
1108
|
+
const raw = await rpcOnce(resolved.action, resolved.input, getTimeout(values, action));
|
|
1092
1109
|
// Unwrap proxy envelope: admin:*-rpc wraps results in { type: 'ack', result: ... }
|
|
1093
1110
|
const result = raw?.type === 'ack' && raw?.result !== undefined
|
|
1094
1111
|
? raw.result
|
|
@@ -3090,7 +3107,7 @@ export async function cmdAgent(prompt, values) {
|
|
|
3090
3107
|
: {}),
|
|
3091
3108
|
},
|
|
3092
3109
|
// Omni runs tools; the CLI default is far too short for a real turn.
|
|
3093
|
-
getTimeout(values
|
|
3110
|
+
getTimeout(values, 'agent-turn'));
|
|
3094
3111
|
/**
|
|
3095
3112
|
* Print the REPLY, not the envelope. The whole response is still available
|
|
3096
3113
|
* with `--json` — but a verb whose entire purpose is "ask a question and read
|
package/dist/relay-presets.js
CHANGED
|
@@ -67,9 +67,8 @@ export function resolveRelaySelection(cfg) {
|
|
|
67
67
|
* `https://localhost:3000` (canvas.ts's `--dev` branch). They are not interchangeable: the apex
|
|
68
68
|
* 301s to www, so the apex spelling handed people a link with a redirect in it.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* is `www.clearly.sh`. Inventing a `staging.clearly.sh` here would be the dead-link bug again.
|
|
70
|
+
* Staging uses the local HTTPS frontend (Clearly AGENTS.md, 2026-10-08). Its workspace IDs
|
|
71
|
+
* belong to staging and must never be linked into the production frontend.
|
|
73
72
|
*
|
|
74
73
|
* ⚠ `custom` RETURNS undefined, DELIBERATELY. A custom relay says nothing about where its frontend
|
|
75
74
|
* is, and guessing production for it is how you hand someone a link into the wrong deployment.
|
|
@@ -78,10 +77,10 @@ export function resolveRelaySelection(cfg) {
|
|
|
78
77
|
export function appOriginForEnv(envName) {
|
|
79
78
|
switch (envName) {
|
|
80
79
|
case 'prod':
|
|
81
|
-
case 'staging':
|
|
82
80
|
return 'https://www.clearly.sh';
|
|
83
81
|
// `npm run dev` is `next dev --experimental-https`, so the local app is HTTPS, not HTTP.
|
|
84
82
|
case 'dev':
|
|
83
|
+
case 'staging':
|
|
85
84
|
return 'https://localhost:3000';
|
|
86
85
|
default:
|
|
87
86
|
return undefined;
|
package/package.json
CHANGED
|
@@ -25,6 +25,18 @@ beehaven cd home # back to the default workspace
|
|
|
25
25
|
beehaven disconnect # end the session
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
Start discovery narrowly: `beehaven explain <action>` gives one schema; `beehaven actions
|
|
29
|
+
--search <term>` or `--category <name>` bounds the inventory. The default inventory is compact;
|
|
30
|
+
`--verbose` or `--format full --json` requests full detail. Save large JSON once and print a
|
|
31
|
+
small summary rather than repeating a call after output truncation.
|
|
32
|
+
|
|
33
|
+
For ticket text search use `ticket-list {text:"…",projectId?,fields?,limit?}` (`query` and `q`
|
|
34
|
+
are aliases). For a design setup, `design-start {title,projectId?,boardId?,idempotencyKey}` creates or
|
|
35
|
+
resumes the linked artifacts using the normal permissions. Preserve idempotencyKey across retries;
|
|
36
|
+
requestId is reserved by the transport.
|
|
37
|
+
For supported canvas mutations inspect actual `updated` IDs, `outcome`, ignored/rejected fields
|
|
38
|
+
and warnings. A navigation request reaching a socket is not a verified editor display.
|
|
39
|
+
|
|
28
40
|
Once `cd`'d, **every** verb auto-routes to that target: `actions`, `call`, `sql`, `ls`, `cat`, `tree`, `grep`, `watch`, `logs`, `workspace`, `ask`. Switch by `cd`-ing again.
|
|
29
41
|
|
|
30
42
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brand-identity
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
4
|
+
Design the requested logo or brand identity on a Clearly canvas —
|
|
5
5
|
brief, mark, wordmark, lockups and clear space, palette, type system,
|
|
6
6
|
applications, and a spec board someone else can build from. Use when asked
|
|
7
7
|
for a brand, a logo, an identity, a rebrand, a visual system, brand
|
|
@@ -13,10 +13,14 @@ description: >-
|
|
|
13
13
|
|
|
14
14
|
# brand-identity — a system, not a picture of a logo
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Match the user's scope. For a logo concept, deliver the editable mark/wordmark, primary and
|
|
17
|
+
one-color use, a small-size check, and the requested application. A logo plus landing page does
|
|
18
|
+
not require merchandise, storefronts or a complete guideline deck. Add those when requested or
|
|
19
|
+
when a concrete use in the brief requires them.
|
|
20
|
+
|
|
21
|
+
For a complete identity request, the deliverable is a system someone else can build with:
|
|
22
|
+
symbol, wordmark, palette, typography, applications and guidelines. Sections 6–8 describe that
|
|
23
|
+
larger scope; they are optional for a narrow concept.
|
|
20
24
|
|
|
21
25
|
Read **`design-craft`** first or alongside — grid, type scale, colour roles and the studio
|
|
22
26
|
pass are assumed here.
|
|
@@ -93,7 +97,9 @@ good. If it does not work in flat black, the shape is wrong and the gradient is
|
|
|
93
97
|
- ⚠ **A wordmark is always tracked tighter than body text.** At display size, `letterSpacing`
|
|
94
98
|
−1 to −4. Then fix the specific pairs the font gets wrong (an uppercase A next to a V, a
|
|
95
99
|
T next to a lowercase o).
|
|
96
|
-
- **
|
|
100
|
+
- **For a full identity, define this fixed set.** For a concept, deliver the requested lockup
|
|
101
|
+
plus the mark-only and one-color forms needed for its smallest use. Additional lockups are
|
|
102
|
+
optional unless the brief asks for them:
|
|
97
103
|
|
|
98
104
|
```
|
|
99
105
|
primary mark + wordmark, horizontal ← the default
|
|
@@ -225,11 +231,11 @@ Run `design-craft`'s studio pass, plus these identity-specific ones:
|
|
|
225
231
|
```
|
|
226
232
|
□ Rendered the mark at 24px and LOOKED at it
|
|
227
233
|
□ Mark works in flat black, flat white, and knocked out
|
|
228
|
-
□ Every lockup has stated clear space and minimum size
|
|
234
|
+
□ Every delivered lockup has stated clear space and minimum size
|
|
229
235
|
□ Palette has a dark-ground variant and role sentences
|
|
230
236
|
□ Type scale has NUMBERS, not just face names
|
|
231
|
-
□
|
|
232
|
-
□ Misuse section exists
|
|
237
|
+
□ Requested applications at real proportions (4–6 for a full identity; the requested surface for a concept)
|
|
238
|
+
□ Misuse section exists for a full identity; a concise use note is enough for a logo concept
|
|
233
239
|
□ Put the mark beside the competitor named in UNLIKE — still distinct?
|
|
234
240
|
□ Can someone build a new asset from this board without asking a question?
|
|
235
241
|
```
|
|
@@ -190,6 +190,19 @@ task changes a product's UI, the composition comes FIRST: build the screen and i
|
|
|
190
190
|
`canvas.review` it, link it to the ticket (`work-link {ticketId, kind:"design", refKind:"composition",
|
|
191
191
|
refId}`), then implement. `clearly-canvas` §0 has the details.
|
|
192
192
|
|
|
193
|
+
No brand yet (`brand-tokens` answers `brand-not-found`) for a real company? Ask the person for their
|
|
194
|
+
website rather than inventing a palette, then:
|
|
195
|
+
|
|
196
|
+
1. `beehaven call brand-import '{"url":"theirsite.com","dryRun":true}'` — colours by role, faces, logos,
|
|
197
|
+
voice and shape, each with its reason in `provenance`. Tell the person what you read.
|
|
198
|
+
2. Ask what to call the brand (`suggestedName` is what the site calls itself — a suggestion, not a default).
|
|
199
|
+
3. `beehaven call brand-import '{"url":"theirsite.com","name":"<their answer>"}'` saves it (the read is
|
|
200
|
+
reused; the first brand becomes the workspace default). Without `name` nothing is saved:
|
|
201
|
+
`brand-name-required`.
|
|
202
|
+
|
|
203
|
+
`brandId` + `apply:["colors","type"]` refreshes an existing brand as a new version instead. Licensed faces
|
|
204
|
+
come back set in the closest free one — `faces` says which.
|
|
205
|
+
|
|
193
206
|
## When a write is refused
|
|
194
207
|
|
|
195
208
|
`agent-denied-create` / `-update` / `-delete` mean your **capability matrix** said no. It covers the
|
|
@@ -22,6 +22,10 @@ stored. A rendered image you have inspected is the evidence for visual quality.
|
|
|
22
22
|
For a small correction, keep the existing direction and make the smallest useful edit.
|
|
23
23
|
For a new composition or substantial redesign, use the workflow below.
|
|
24
24
|
|
|
25
|
+
Keep the requested scope. A landing concept with a logo needs its page layouts and a usable
|
|
26
|
+
logo; add application boards or a full guideline system only when the brief calls for them.
|
|
27
|
+
Read only relevant references. Save large tool responses once and print compact summaries.
|
|
28
|
+
|
|
25
29
|
## The studio bar — what "done" means, every time
|
|
26
30
|
|
|
27
31
|
Load **`design-craft`** before any new visual work: grid, type scale, colour roles,
|
|
@@ -33,8 +37,9 @@ A composition is done when every requested artboard meets all four conditions:
|
|
|
33
37
|
1. **`canvas.review` verdict is `clean` or `look`.** `fix` means a probable defect
|
|
34
38
|
remains: text colliding or covered, contrast under WCAG (4.5:1 body, 3:1 display),
|
|
35
39
|
type below the artboard's legible floor, missing media, text overflowing its box,
|
|
36
|
-
placeholder copy. Fix it.
|
|
37
|
-
`
|
|
40
|
+
placeholder copy. Fix it. For an intentional finding, use
|
|
41
|
+
`accept:[{check,ids:[node IDs],reason}]`. Accepted findings stay visible and return `look`.
|
|
42
|
+
Legacy `ignore` can suppress multiple checks and is not an unconditional pass.
|
|
38
43
|
2. **Every `look` finding was looked at**: type-size and palette sprawl, near-miss
|
|
39
44
|
alignment, copy against the trim, text over imagery. Act on it or say why not.
|
|
40
45
|
3. **You inspected the rendered PNG** against design-craft's studio pass, covering what no
|
|
@@ -54,8 +59,9 @@ build from them — redrawing a button the library already has is how designs dr
|
|
|
54
59
|
product they describe.
|
|
55
60
|
|
|
56
61
|
```sh
|
|
57
|
-
beehaven call brand-tokens '{}'
|
|
58
|
-
beehaven call
|
|
62
|
+
beehaven call brand-tokens '{"compact":true}' # roles without repeated CSS/prose
|
|
63
|
+
beehaven call brand-import '{"url":"theirsite.com","dryRun":true}' # no brand yet? read it off their website
|
|
64
|
+
beehaven call component-list '{"query":"<kind>","compact":true}' # IDs, dimensions and prop names
|
|
59
65
|
beehaven call component-instantiate '{"compositionId":"<id>","instances":[
|
|
60
66
|
{"id":"<component>","x":96,"y":200,"variant":"Light"},
|
|
61
67
|
{"id":"<component>","x":96,"y":280,"variant":"Light","props":{"label":"Save changes"}}]}'
|
|
@@ -68,8 +74,9 @@ beehaven call component-instantiate '{"compositionId":"<id>","instances":[
|
|
|
68
74
|
`component-create` (or `{ fromCompositionId, nodeId }` to save one you drew).
|
|
69
75
|
- Component node coordinates are relative to the component ROOT, even under an inner
|
|
70
76
|
frame — the opposite of `canvas.create-node`, where x/y follow `parentId`.
|
|
71
|
-
-
|
|
72
|
-
and
|
|
77
|
+
- For Clearly product UI, use its brand kit. For a separate client, use that client's kit
|
|
78
|
+
and supplied assets. A requested new client identity is scoped to that composition;
|
|
79
|
+
it does not change the workspace's default brand.
|
|
73
80
|
|
|
74
81
|
**UX design before implementation.** When the work is a change to a product's UI, the
|
|
75
82
|
composition is the design the code will answer to: show the real states (empty, loading,
|
|
@@ -107,6 +114,33 @@ beehaven call canvas-perceive '{"compositionId":"<id>","format":"json"}'
|
|
|
107
114
|
beehaven call canvas-catalog '{"query":"create-node"}'
|
|
108
115
|
```
|
|
109
116
|
|
|
117
|
+
A canvas is planned like a git repository: its own plan board, a WORK TICKET for the request, and
|
|
118
|
+
COMMITS — one ticket per coherent layer or section of a larger change.
|
|
119
|
+
`design-start {title, brandId?}` makes a PRIVATE DRAFT canvas, bound to the brand, with its private
|
|
120
|
+
plan board and the work ticket, and returns `{ compositionId, ticketId, ticketKey, boardId, brand }`.
|
|
121
|
+
Same title again returns the same draft. Nothing is shared until it is filed:
|
|
122
|
+
`design-file {compositionId, projectId | projectTitle}` moves the canvas — plan board and every
|
|
123
|
+
ticket with it — into the project the person chose and sends the work ticket to in-review. Ask which
|
|
124
|
+
project unless they named one; a close-but-different title answers `similar-projects` with
|
|
125
|
+
candidates. Repeat-safe.
|
|
126
|
+
Plan a larger change before drawing, the way you would split a big code change into commits:
|
|
127
|
+
`design-plan {compositionId, commits: ["Grid, type and colour styles", "Nav", "Hero", …]}` (drafts
|
|
128
|
+
and filed canvases alike) adds ordered commit tickets under the work ticket. `sections: true` uses
|
|
129
|
+
the landing-page set (set up frames and styles, nav bar, hero, highlights, offer, story, call to
|
|
130
|
+
action, footer, mobile, states); `phases: true` and a `brief` add the studio method (brief, research,
|
|
131
|
+
structure, direction, design, review) — load the `design-process` skill. Order commits so each
|
|
132
|
+
builds on the last and can be reviewed alone. Build ONE commit per canvas-act batch that ends with
|
|
133
|
+
`canvas.review` of the artboard and carries
|
|
134
|
+
`"ticket":{"id":"<key>","status":"done","resolution":"<what changed>"}` — it lands only if that
|
|
135
|
+
review is clean or look; `result.ticket` says whether it did, and the ticket logs the canvas
|
|
136
|
+
revision and the layers the commit changed. Auto-layout, gradients, shadows and SVG placeholders:
|
|
137
|
+
see "Technique" in [authoring.md](authoring.md).
|
|
138
|
+
|
|
139
|
+
`composition-board-get {compositionId}` reads the plan back. `design-start` with a project, ticket or
|
|
140
|
+
`phases` still sets up planned work directly (kept for one release).
|
|
141
|
+
For an existing task, reuse its ticket/project. Check one unknown action with
|
|
142
|
+
`beehaven explain <action>` or a filtered catalog; do not download the entire registry.
|
|
143
|
+
|
|
110
144
|
Use the composition supplied by the user. If none is named, inspect recent titles and
|
|
111
145
|
context; create a clearly titled composition for new work instead of modifying an
|
|
112
146
|
arbitrary recent canvas. File shared work into its project.
|
|
@@ -188,6 +222,10 @@ For a single page, establish the complete hierarchy before polishing small detai
|
|
|
188
222
|
Use a batch for each coherent build or revision, with descriptive unique layer names.
|
|
189
223
|
Parent content to its artboard. Inspect every operation result and rollback status.
|
|
190
224
|
Save your builder and returned IDs so revisions are targeted and reproducible.
|
|
225
|
+
Check `ignoredFields`, `rejectedFields`, `warnings`, `outcome` and `canvasChanges` as well as
|
|
226
|
+
`ok/count`. `matched` is not `updated`. An unchanged or partly ignored request does not prove
|
|
227
|
+
the intended edit happened. Read `composition-detail {includeScene:true}` from its top-level
|
|
228
|
+
`nodes`; never treat a missing response field as an empty scene. Keep executed payloads immutable.
|
|
191
229
|
|
|
192
230
|
⚠ **Batch, never loop.** One `canvas-act` batch of hundreds of steps is one scene read,
|
|
193
231
|
persisted every 50 steps, and the workspace stays responsive for everyone in it while it
|
|
@@ -225,6 +263,12 @@ It invokes `canvas.review` on one persisted snapshot and returns:
|
|
|
225
263
|
type and palette sprawl, near-miss edges, trim crowding, text over imagery;
|
|
226
264
|
- the placeholder/overflow audit, measured text fit, and a preview PNG.
|
|
227
265
|
|
|
266
|
+
For several artboards, write their IDs/names as a JSON array and use:
|
|
267
|
+
`node <skill-directory>/review.mjs --many <composition-id> <ids.json> <output-directory>`.
|
|
268
|
+
This uses `canvas.review-many`: one scene read, separate findings per artboard and compact
|
|
269
|
+
preview handles. Review only artboards changed since their verified snapshot. The helper versions
|
|
270
|
+
existing output paths so before/after evidence is retained.
|
|
271
|
+
|
|
228
272
|
The helper prints the verdict and every finding. Work until the verdict is `clean` or
|
|
229
273
|
`look`, as described in the studio bar above. If the installed worker lacks `canvas.review`,
|
|
230
274
|
use a real frame ID with `--perceive` for a preview-only check and run the text measurement
|
|
@@ -232,9 +276,16 @@ and audit separately. A failed or missing render means visual verification is in
|
|
|
232
276
|
|
|
233
277
|
```sh
|
|
234
278
|
# A deliberate finding — say why in the handoff:
|
|
235
|
-
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame>"],"
|
|
279
|
+
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame>"],"accept":[{"check":"text-over-image","ids":["<text-id>","<image-id>"],"reason":"Intentional type over this photograph; rendered contrast was inspected."}]}}'
|
|
236
280
|
```
|
|
237
281
|
|
|
282
|
+
Asset storage is a reference-aware pipeline: an SVG may have `svgRef` instead of inline markup.
|
|
283
|
+
Do not shrink or recreate it just because the inline field is absent. Replace it with
|
|
284
|
+
`canvas.update-nodes {ids, patch:{svg:<complete markup>}}`; references are managed by the action.
|
|
285
|
+
Use `canvas.fonts` before choosing an unfamiliar family/weight. Fallback metrics are not exact.
|
|
286
|
+
Use `canvas.editor-state {requireLive:true,includePixels:true}` to verify the responding editor.
|
|
287
|
+
An agent camera and a navigation delivery count do not prove the person's view was framed.
|
|
288
|
+
|
|
238
289
|
Inspect the actual image and identify concrete defects, not generic praise:
|
|
239
290
|
|
|
240
291
|
| Lens | What to judge |
|
|
@@ -49,6 +49,12 @@ do not blindly resend only skipped operations. Mixed live/headless actions may h
|
|
|
49
49
|
different rollback behavior. Use deterministic layer names and returned IDs to avoid
|
|
50
50
|
duplicate content during retries.
|
|
51
51
|
|
|
52
|
+
Also check ignored/rejected fields, warnings, mutation outcome and actual changed IDs. A matched
|
|
53
|
+
node or `ok:true` receipt alone is not proof of an update. The bundled `assertReceipt` helper can
|
|
54
|
+
reject partial drops; `requireChange:true` rejects an unexpected no-op. A deliberate replay may
|
|
55
|
+
be unchanged and should reuse its previous evidence instead of recreating nodes.
|
|
56
|
+
Keep executed payloads and returned-ID manifests separate from regenerated plans.
|
|
57
|
+
|
|
52
58
|
## Node shape and coordinates
|
|
53
59
|
|
|
54
60
|
Fields are flat: `{name,type,x,y,w,h,text,size,family,fontWeight,fill,...}`.
|
|
@@ -71,6 +77,13 @@ Create normalizes hex colours; raw stored scenes use RGBA arrays in 0..1.
|
|
|
71
77
|
moves/resizes use the catalog's frame/arrangement/scale actions; do not assume a patch
|
|
72
78
|
reflows children.
|
|
73
79
|
- Build backgrounds before foregrounds. Use `canvas.order` to change stacking.
|
|
80
|
+
|
|
81
|
+
For create/update geometry, `coordinateSpace:"world"` or `"parent"` makes the intent explicit.
|
|
82
|
+
Default behavior remains compatible with the rules above. Receipts include normalized world
|
|
83
|
+
bounds and parent offsets where available. Template coordinates remain relative to their root.
|
|
84
|
+
An SVG's bytes may be stored in `svgRef` once it exceeds the inline limit; this is valid artwork,
|
|
85
|
+
not a missing node. Replacing `svg` clears and rebuilds its reference automatically. Never patch
|
|
86
|
+
`svgRef` manually or guess its storage URL. Image `fit` is explicitly `contain` or `cover`.
|
|
74
87
|
- Name nodes uniquely; use returned IDs where names collide. Parent content to its
|
|
75
88
|
frame so movement, selection, clipping and reviews can treat it as one artifact.
|
|
76
89
|
- Give an intended stroke both `stroke` and `strokeWidth`. Zero explicitly removes it.
|
|
@@ -106,6 +119,16 @@ and tokens when relevant. Constraints reflow children on frame resize; they do n
|
|
|
106
119
|
a static canvas into a responsive website. Design explicit mobile artboards when the
|
|
107
120
|
deliverable requires them, with content order and hierarchy reconsidered for the width.
|
|
108
121
|
|
|
122
|
+
## Technique
|
|
123
|
+
|
|
124
|
+
The same rules the in-app assistant works to (canvas-catalog returns them as `technique`).
|
|
125
|
+
|
|
126
|
+
- AUTO-LAYOUT: rows and stacks (nav links, button groups, card rows, form fields) are a frame whose children you create in reading order, then canvas.auto-layout { id, direction, gap, padding, align, justify, hug }. hug:false keeps the frame size, which a full-width bar needs with justify:"space-between"; set a child to layoutSizingH/V "fill" | "hug" | "fixed" with canvas.update-nodes.
|
|
127
|
+
- GRADIENTS: gradient { kind, angle, stops: [{ offset, color }] } replaces the solid fill. angle is the direction the colour travels: 0 = left to right, 90 = top to bottom, 45 = top-left to bottom-right (not CSS degrees). Use two or three stops from the brand palette, and keep text off a busy gradient or check its contrast in canvas.review.
|
|
128
|
+
- SHADOWS AND BLUR: effects on any node. A soft card shadow is { type: "drop-shadow", x: 0, y: 4, blur: 16, spread: 0, color: "#0000001F" }; frosted glass is { type: "background-blur", blur: 16 }. Never fake a shadow with an extra rectangle.
|
|
129
|
+
- SVG PLACEHOLDERS: until real imagery exists, draw it as an svg node at its final size in a muted tone from the brand palette, never a flat accent block, e.g. for a 400×240 slot svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 240"><rect width="400" height="240" rx="12" fill="#EDE6DF"/><circle cx="170" cy="96" r="14" fill="#CDBFB2"/><path d="M150 168l40-48 30 34 22-20 40 34z" fill="#CDBFB2"/></svg>' — <svg viewBox="0 0 W H"> always matches the node w and h. Put a label in its own text node and name the svg "<section> image placeholder". Hand-write only simple marks like this (placeholders, dividers, UI icons); logos, illustrations and finished artwork come from svg-make.
|
|
130
|
+
- SECTIONS: one frame per page section, named exactly as its ticket says, inside the page artboard. Build a section in one canvas-act batch that ends with canvas.review of the artboard and carries ticket: { id: "<its key>", status: "done", resolution: "<what changed>" }. The ticket moves only when that review is clean or look; result.ticket says whether it did. Then start the next section.
|
|
131
|
+
|
|
109
132
|
Use `arrow.create {from,to,routing,label}` for connected diagrams, so arrows remain bound
|
|
110
133
|
when nodes move. Use native shapes and SVG for custom vector details. Keep labels as
|
|
111
134
|
native text so they remain editable and measurable.
|
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Save review pixels outside the model context. No shell interpolation, no workspace mutations. */
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
5
6
|
import { dirname, resolve } from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
|
-
export function
|
|
9
|
+
export function assertReceipt(result, { requireChange = false } = {}) {
|
|
10
|
+
if (result?.ok === false || result?.error || result?.results?.some(r => r.ok === false)) {
|
|
11
|
+
throw new Error(result.error || result.results?.find(r => !r.ok)?.error || 'Canvas request failed');
|
|
12
|
+
}
|
|
13
|
+
for (const row of result?.results ?? [result]) {
|
|
14
|
+
const value = row.value ?? row;
|
|
15
|
+
if (value.ignoredFields?.length || value.rejectedFields?.length) {
|
|
16
|
+
throw new Error(`Fields were not applied: ${[...(value.ignoredFields ?? []), ...(value.rejectedFields ?? [])].join(', ')}. Some work may have landed; inspect the saved scene before retrying.`);
|
|
17
|
+
}
|
|
18
|
+
if (requireChange) {
|
|
19
|
+
const delta = value.canvasChanges;
|
|
20
|
+
const changed = ['changed', 'partial'].includes(value.outcome) || (Array.isArray(delta) ? delta.length > 0 : delta && typeof delta === 'object'
|
|
21
|
+
? Number(delta.added || 0) + Number(delta.changed || 0) + Number(delta.removed || 0) > 0
|
|
22
|
+
: value.placed === true || /\.create(-node)?$/.test(String(row.action || '')) && !!value.id && !value.duplicate);
|
|
23
|
+
if (!changed) throw new Error('No nodes changed, or the receipt does not prove a change. Inspect the saved scene; do not treat a match as an update.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function rpc(action, payload, options) {
|
|
9
30
|
const stdout = execFileSync('beehaven', ['call', action, '-'], {
|
|
10
31
|
input: JSON.stringify(payload), encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
|
11
32
|
stdio: ['pipe', 'pipe', 'pipe'], timeout: 120_000,
|
|
12
33
|
});
|
|
13
34
|
const result = JSON.parse(stdout);
|
|
14
|
-
|
|
15
|
-
|
|
35
|
+
return assertReceipt(result, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sceneNodes(result) {
|
|
39
|
+
if (!Array.isArray(result?.nodes)) throw new Error('Expected composition-detail includeScene:true to return top-level nodes. Refusing to interpret a missing field as an empty canvas.');
|
|
40
|
+
return result.nodes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Resume creates only. Existing layers are preserved, including the person's later edits. */
|
|
44
|
+
export function resumeCanvasPlan(plan, snapshot, idManifest = {}) {
|
|
45
|
+
const nodes = sceneNodes(snapshot), byId = new Map(nodes.map(n => [n.id, n]));
|
|
46
|
+
const names = new Map();
|
|
47
|
+
for (const n of nodes) { const list = names.get(n.name) ?? []; list.push(n); names.set(n.name, list); }
|
|
48
|
+
const roots = new Map(), batch = [], skipped = [];
|
|
49
|
+
for (const step of plan.batch ?? []) {
|
|
50
|
+
const args = { ...(step.args ?? {}) };
|
|
51
|
+
if (!['frame.create', 'canvas.create-node'].includes(step.action)) { skipped.push({ action: step.action, reason: 'Resume preserves existing mutations; revise explicitly.' }); continue; }
|
|
52
|
+
const type = step.action === 'frame.create' ? 'frame' : args.type;
|
|
53
|
+
const pinned = args.id || idManifest[args.name];
|
|
54
|
+
const matches = names.get(args.name) ?? [];
|
|
55
|
+
if (!pinned && matches.length > 1) throw new Error(`Ambiguous layer name ${args.name}; use its recorded ID.`);
|
|
56
|
+
const existing = pinned ? byId.get(pinned) : matches[0];
|
|
57
|
+
if (existing) {
|
|
58
|
+
if (existing.type !== type) throw new Error(`Layer ${args.name} has type ${existing.type}, expected ${type}. No automatic replacement.`);
|
|
59
|
+
roots.set(args.name, existing.id); skipped.push({ id: existing.id, name: args.name, reason: 'Already exists; preserved.' }); continue;
|
|
60
|
+
}
|
|
61
|
+
args.id = pinned || `plan-${createHash('sha256').update(`${plan.compositionId}:${args.name}`).digest('hex').slice(0, 24)}`;
|
|
62
|
+
if (args.parentId && roots.has(args.parentId)) args.parentId = roots.get(args.parentId);
|
|
63
|
+
roots.set(args.name, args.id);
|
|
64
|
+
batch.push({ ...step, args });
|
|
16
65
|
}
|
|
17
|
-
return
|
|
66
|
+
return { compositionId: plan.compositionId, expectedCanvasNodesRev: snapshot.canvasNodesRev, batch, skipped };
|
|
18
67
|
}
|
|
19
68
|
|
|
20
69
|
export function saveReview(report, prefix) {
|
|
@@ -24,7 +73,21 @@ export function saveReview(report, prefix) {
|
|
|
24
73
|
}
|
|
25
74
|
const bytes = Buffer.from(preview.dataUrl.slice('data:image/png;base64,'.length), 'base64');
|
|
26
75
|
if (!bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) throw new Error('Invalid PNG response');
|
|
27
|
-
|
|
76
|
+
if (bytes.length < 45 || bytes.readUInt32BE(8) !== 13 || bytes.subarray(12,16).toString() !== 'IHDR'
|
|
77
|
+
|| !bytes.readUInt32BE(16) || !bytes.readUInt32BE(20)) throw new Error('Invalid PNG response: missing image header/body');
|
|
78
|
+
let offset = 8, imageData = false, end = false;
|
|
79
|
+
while (offset + 12 <= bytes.length) {
|
|
80
|
+
const length = bytes.readUInt32BE(offset), tag = bytes.subarray(offset+4,offset+8).toString();
|
|
81
|
+
if (offset + length + 12 > bytes.length) throw new Error('Invalid PNG response: truncated image chunk');
|
|
82
|
+
if (tag === 'IDAT' && length > 0) imageData = true;
|
|
83
|
+
offset += length + 12;
|
|
84
|
+
if (tag === 'IEND') { end = length === 0; break; }
|
|
85
|
+
}
|
|
86
|
+
if (!imageData || !end) throw new Error('Invalid PNG response: missing image data/end');
|
|
87
|
+
let base = resolve(prefix);
|
|
88
|
+
const original = base;
|
|
89
|
+
let version = 0;
|
|
90
|
+
while (existsSync(`${base}.png`) || existsSync(`${base}.json`)) base = `${original}-r${report.canvasNodesRev ?? 'unknown'}-${++version}`;
|
|
28
91
|
mkdirSync(dirname(base), { recursive: true });
|
|
29
92
|
const { dataUrl, url, ...metadata } = preview;
|
|
30
93
|
const saved = { ...report, preview: { ...metadata, path: `${base}.png` } };
|
|
@@ -34,16 +97,47 @@ export function saveReview(report, prefix) {
|
|
|
34
97
|
return { png: `${base}.png`, report: `${base}.json`,
|
|
35
98
|
// `fix` = a probable defect remains: fix it, or pass its check/id in `ignore` with a reason.
|
|
36
99
|
// `look` = only things worth a second look. `clean` = nothing mechanical found — NOT "good".
|
|
37
|
-
verdict: report.verdict ?? 'unknown',
|
|
100
|
+
verdict: report.verdict ?? 'unknown', acceptanceStatus: report.acceptanceStatus,
|
|
101
|
+
acceptedFindings: report.acceptedFindings ?? [], blocking: report.blocking,
|
|
38
102
|
craft: craft.map((f) => `${f.severity} ${f.check}: ${f.detail}`),
|
|
103
|
+
audit: (report.audit?.violations ?? []).map(v => ({check:v.kind,layer:v.name || v.id,detail:v.detail || v.message || v.reason || `${v.kind} on ${v.name || v.id}`})),
|
|
104
|
+
scopeWarnings: report.scopeWarnings ?? [],
|
|
105
|
+
assets: (report.assets ?? []).filter(a => a.status !== 'ready'),
|
|
106
|
+
typography: (report.typography?.text ?? []).filter(t => t.issue || t.horizontalOverflow > 1 || !t.autoHeight && t.verticalOverflow > 1 || t.exact === false)
|
|
107
|
+
.map(t => ({layer:t.name || t.id,issue:t.issue,horizontalOverflow:t.horizontalOverflow,verticalOverflow:t.verticalOverflow,
|
|
108
|
+
requestedFont:t.font?.requestedFamily,requestedWeight:t.font?.requestedWeight,resolvedFile:t.font?.file,exact:t.exact})),
|
|
39
109
|
nodes: report.scope?.nodeCount, findings: report.audit?.total, textChecked: report.typography?.checked,
|
|
40
110
|
textOmitted: report.typography?.omitted, visualReviewRequired: true,
|
|
41
111
|
note: report.verdict === 'fix'
|
|
42
|
-
? 'Fix every warn (or
|
|
112
|
+
? 'Fix every warn (or accept an exact finding with a reason), then review changed artboards. Then open the PNG and critique it.'
|
|
43
113
|
: 'Open the PNG with your image-viewing tool, critique it against the studio bar, revise and render again.' };
|
|
44
114
|
}
|
|
45
115
|
|
|
46
|
-
export function
|
|
116
|
+
export async function saveReviewPixels(report, prefix, fetchImage = fetch) {
|
|
117
|
+
if (report.preview?.dataUrl) return saveReview(report, prefix);
|
|
118
|
+
const url = report.preview?.url;
|
|
119
|
+
if (!url || !/^https?:\/\//.test(url)) throw new Error(report.previewError || 'No downloadable preview. Visual verification is incomplete.');
|
|
120
|
+
const response = await fetchImage(url);
|
|
121
|
+
if (!response.ok) throw new Error(`Preview download failed (HTTP ${response.status}); request a fresh review.`);
|
|
122
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
123
|
+
return saveReview({ ...report, preview: { ...report.preview, dataUrl: `data:image/png;base64,${bytes.toString('base64')}` } }, prefix);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
127
|
+
if (argv.includes('--many')) {
|
|
128
|
+
const args = argv.filter(a => a !== '--many');
|
|
129
|
+
if (args.length !== 3) throw new Error('Usage: node review.mjs --many <compositionId> <artboard-ids.json> <output-directory>');
|
|
130
|
+
const [compositionId, manifest, directory] = args;
|
|
131
|
+
const ids = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
132
|
+
if (!Array.isArray(ids) || !ids.length) throw new Error('The manifest must be a non-empty artboard ID/name array.');
|
|
133
|
+
const response = rpc('canvas-act', { compositionId, action: 'canvas.review-many', args: { ids, preview: 'files' } });
|
|
134
|
+
const reports = response.results?.[0]?.value?.reviews;
|
|
135
|
+
if (!Array.isArray(reports) || reports.length !== ids.length) throw new Error('Incomplete review-many result; do not claim all artboards passed.');
|
|
136
|
+
const saved = [];
|
|
137
|
+
for (let i = 0; i < reports.length; i++) saved.push(await saveReviewPixels(reports[i], resolve(directory, `${String(i + 1).padStart(2, '0')}-${String(ids[i]).replace(/[^a-z0-9_-]/gi, '-')}`)));
|
|
138
|
+
process.stdout.write(JSON.stringify(saved, null, 2) + '\n');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
47
141
|
const legacy = argv.includes('--perceive');
|
|
48
142
|
const args = argv.filter((a) => a !== '--perceive');
|
|
49
143
|
if (args.length !== 3) throw new Error('Usage: node review.mjs <compositionId> <frame-id-or-name> <output-prefix> [--perceive]\n--perceive: older workers, preview only; pass a frame ID.');
|
|
@@ -60,9 +154,9 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
60
154
|
report = response.results?.[0]?.value;
|
|
61
155
|
if (!report?.scope) throw new Error('canvas.review unavailable. Check canvas-catalog; use --perceive for an explicitly preview-only check on an older worker.');
|
|
62
156
|
}
|
|
63
|
-
process.stdout.write(JSON.stringify(
|
|
157
|
+
process.stdout.write(JSON.stringify(await saveReviewPixels(report, prefix), null, 2) + '\n');
|
|
64
158
|
}
|
|
65
159
|
|
|
66
|
-
if (process.argv[1] &&
|
|
67
|
-
|
|
160
|
+
if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
161
|
+
main().catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
|
|
68
162
|
}
|
|
@@ -298,6 +298,11 @@ fewer elements, tighter type, one accent*. Every ornament added is a step away f
|
|
|
298
298
|
|
|
299
299
|
## Sources
|
|
300
300
|
|
|
301
|
+
The full shelf — about fifty books across grid, type, print production, colour, graphic form,
|
|
302
|
+
brand identity, information and interface design, each reduced to one rule — is in
|
|
303
|
+
[references.md](references.md). In Research pick two or three whose lens fits the brief, and name
|
|
304
|
+
the one behind each direction in its rationale.
|
|
305
|
+
|
|
301
306
|
Josef Müller-Brockmann, *Grid Systems in Graphic Design* (1981) · Massimo Vignelli, *The
|
|
302
307
|
Vignelli Canon* (semantics / syntactics / pragmatics; "Design is one") · Michael Bierut,
|
|
303
308
|
*How to* (Pentagram; brief → rationale → trade-offs) · Paula Scher / Pentagram, The Public
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# The design shelf
|
|
2
|
+
|
|
3
|
+
<!-- Generated from apps/cloudflare/src/agent/design-references.ts (the design-references and brand-references house skills).
|
|
4
|
+
Edit the list there, then run: WRITE_DESIGN_REFERENCES=1 npx vitest run src/__tests__/verify-design-references.test.ts -->
|
|
5
|
+
|
|
6
|
+
Books a studio designer works from, each reduced to one rule you can act on. The in-app
|
|
7
|
+
assistant gets the same list as the `design-references` and `brand-references` workspace skills.
|
|
8
|
+
|
|
9
|
+
## How to use it
|
|
10
|
+
|
|
11
|
+
- In Research, pick two or three references whose lens fits the brief: a print piece or poster → grid, type, print; an identity → brand; a chart or report → information; a screen or flow → interface. Write what each one changes in this design.
|
|
12
|
+
- In Direction, name the reference behind each direction's idea in one line ("a Müller-Brockmann modular grid, type as the image after Scher").
|
|
13
|
+
- In Review, critique against the same rules you chose.
|
|
14
|
+
- These are paraphrased principles, not quotations. Never invent a quote or page number; cite author and title only.
|
|
15
|
+
|
|
16
|
+
## Method — deciding before drawing
|
|
17
|
+
|
|
18
|
+
- Massimo Vignelli, *The Vignelli Canon* (2010): Answer semantics (what it means, to whom), syntactics (its visual grammar) and pragmatics (will they get it) before placing a node; few typefaces, a grid, discipline.
|
|
19
|
+
- Michael Bierut, *How to* (2015): Present brief → idea → trade-offs; the idea comes out of the problem, and the rationale is part of the deliverable.
|
|
20
|
+
- Paul Rand, *Thoughts on Design* (1947): Form and content are one: simplify until the idea is legible, and allow wit — play is a way of making it memorable.
|
|
21
|
+
- Bruno Munari, *Design as Art* (1966): Solve the problem with the simplest means; decoration that solves nothing is noise.
|
|
22
|
+
- Karl Gerstner, *Designing Programmes* (1964): Design the rules, not one layout: define the variables (size, position, colour) and generate the set from them.
|
|
23
|
+
- Dieter Rams, *Less but Better* (1995): As little design as possible: every element must earn its place by use; remove what does not.
|
|
24
|
+
- Alan Fletcher, *The Art of Looking Sideways* (2001): Look for the idea sideways — a visual pun or found connection beats a literal illustration of the brief.
|
|
25
|
+
|
|
26
|
+
## Grid and layout
|
|
27
|
+
|
|
28
|
+
- Josef Müller-Brockmann, *Grid Systems in Graphic Design* (1981): Construct margins, columns, gutter and baseline first; every element lands on the grid; break it once, on purpose.
|
|
29
|
+
- Timothy Samara, *Making and Breaking the Grid* (2002): Pick the grid for the content (manuscript, column, modular, hierarchical); a deliberate break needs a grid to break.
|
|
30
|
+
- Kimberly Elam, *Geometry of Design* (2001): Place the focal point on a proportional line (golden section, root rectangles, thirds), not at dead centre.
|
|
31
|
+
- Beth Tondreau, *Layout Essentials* (2009): Grids by job: a single column for reading, multi-column for mixed content, modular for catalogues and dashboards.
|
|
32
|
+
|
|
33
|
+
## Typography
|
|
34
|
+
|
|
35
|
+
- Robert Bringhurst, *The Elements of Typographic Style* (1992): Measure 45–75 characters, leading to suit the measure, honour the content: type exists to be read.
|
|
36
|
+
- Ellen Lupton, *Thinking with Type* (2004): Build hierarchy by changing one variable at a time — size, weight, colour or space — and track capitals open.
|
|
37
|
+
- Jan Tschichold, *The New Typography* (1928): Asymmetric, functional layout: hierarchy by size, weight and position, never ornament.
|
|
38
|
+
- Emil Ruder, *Typographie* (1967): White space is active form: the counter, the gap and the margin are composed like the type itself.
|
|
39
|
+
- Jost Hochuli, *Detail in Typography* (1987): Letter, word and line spacing decide readability: tighten display type, keep word spaces even, open the leading for long lines.
|
|
40
|
+
- Erik Spiekermann and E. M. Ginger, *Stop Stealing Sheep & Find Out How Type Works* (1993): A typeface is a voice: choose it for the message and audience, then set it with care before anything else.
|
|
41
|
+
- Ellen Lupton, *Type on Screen* (2014): On screen, type is motion and interaction too: sizes, scales and states are a system, not one setting.
|
|
42
|
+
|
|
43
|
+
## Print and editorial production
|
|
44
|
+
|
|
45
|
+
- Jan Tschichold, *The Form of the Book*: Classical page proportions: inner margin smallest, foot margin largest; the text block sits high and toward the spine.
|
|
46
|
+
- Richard Hendel, *On Book Design* (1998): A book design serves the reading; decide trim, margins, type and running heads together, and stay invisible.
|
|
47
|
+
- Andrew Haslam, *Book Design* (2006): Plan the whole object — format, grid, pagination, binding — before designing a spread.
|
|
48
|
+
- Jan V. White, *Editing by Design* (1974): Editorial layout starts from what the story says; headline, picture and caption are one unit.
|
|
49
|
+
- International Paper, *Pocket Pal*: Production rules: CMYK for press, 300 dpi images at final size, 3 mm (0.125 in) bleed, a safe area inside the trim.
|
|
50
|
+
- Alan Pipes, *Production for Graphic Designers*: Specify for the press: paper stock, ink coverage, rich black for large areas only, overprint and trap small type.
|
|
51
|
+
|
|
52
|
+
## Colour
|
|
53
|
+
|
|
54
|
+
- Josef Albers, *Interaction of Color* (1963): Colour is relative: judge it beside its neighbours on the real ground, never as a swatch alone.
|
|
55
|
+
- Johannes Itten, *The Art of Color* (1961): Choose the one contrast that leads (light–dark, warm–cool, complementary, saturation, extent) and let the rest support it.
|
|
56
|
+
|
|
57
|
+
## Graphic form and visual perception
|
|
58
|
+
|
|
59
|
+
- Armin Hofmann, *Graphic Design Manual* (1965): Build with elementary contrasts — point, line, plane, black and white, large and small — before colour.
|
|
60
|
+
- Ellen Lupton and Jennifer Cole Phillips, *Graphic Design: The New Basics* (2008): Point, line, plane, rhythm, scale, texture, figure/ground and framing are variables; change one deliberately.
|
|
61
|
+
- Wucius Wong, *Principles of Two-Dimensional Design* (1972): Repetition, gradation and radiation give a field structure; an anomaly in that structure is the focal point.
|
|
62
|
+
- Rudolf Arnheim, *Art and Visual Perception* (1954): Visual weight balances on the frame's hidden axes; size, value, isolation and position all add weight.
|
|
63
|
+
- Robin Williams, *The Non-Designer's Design Book* (1994): Contrast, repetition, alignment, proximity: if two things differ, make them clearly different.
|
|
64
|
+
- Alex W. White, *The Elements of Graphic Design* (2002): Treat space as an element: emptiness gives the content its shape and its importance.
|
|
65
|
+
- Kenya Hara, *Designing Design* (2007): Emptiness invites the viewer in: leave room for the reader's imagination instead of filling the frame.
|
|
66
|
+
- Paula Scher, *Make It Bigger* (2002): Type can be the image: scale and crop a word until it carries the identity on its own.
|
|
67
|
+
- Philip B. Meggs, *A History of Graphic Design* (1983): Know the lineage of a style so a reference is chosen, not a pastiche of the first thing found.
|
|
68
|
+
- Adrian Frutiger, *Signs and Symbols*: A sign reads by its simplest form; test a mark small, at a distance and in one colour.
|
|
69
|
+
|
|
70
|
+
## Brand and identity
|
|
71
|
+
|
|
72
|
+
- Alina Wheeler, *Designing Brand Identity* (2003): Identity is a process — research, strategy, design, touchpoints, asset management; design the touchpoints, not just the logo.
|
|
73
|
+
- Marty Neumeier, *The Brand Gap* (2003): A brand is the customer's gut feeling; close the gap between strategy and the look by testing what people actually perceive.
|
|
74
|
+
- Marty Neumeier, *Zag* (2006): When everyone zigs, zag: state what makes this the only one, then design to that difference.
|
|
75
|
+
- Wally Olins, *On Brand* (2003): A brand shows in product, environment, communication and behaviour; keep all four saying the same thing.
|
|
76
|
+
- Byron Sharp, *How Brands Grow* (2010): Growth comes from being easy to notice and buy: protect distinctive assets and stay consistent.
|
|
77
|
+
- Jenni Romaniuk, *Building Distinctive Brand Assets* (2018): An asset (colour, shape, character) works once it is famous and unique to the brand; use it everywhere, change it rarely.
|
|
78
|
+
- Tom Geismar, Ivan Chermayeff and Sagi Haviv, *Identify* (2011): A mark must be appropriate, distinctive and simple; it earns meaning through use over time.
|
|
79
|
+
- David Airey, *Logo Design Love* (2009): Test a logo at 16 px, in one colour and in reverse; if it needs detail or colour to survive, simplify it.
|
|
80
|
+
- Danne & Blackburn, *NASA Graphics Standards Manual* (1975): A standards manual fixes the mark, clear space, colours, type, grid and applications so anyone can apply the system.
|
|
81
|
+
- Unimark International (Vignelli, Noorda), *New York City Transit Authority Graphics Standards Manual* (1970): A system scales by rules: one typeface, fixed modules, colour coding and consistent placement.
|
|
82
|
+
|
|
83
|
+
## Information design
|
|
84
|
+
|
|
85
|
+
- Edward R. Tufte, *The Visual Display of Quantitative Information* (1983): Maximise data-ink: remove chart junk, label directly, show the comparison; use small multiples for change.
|
|
86
|
+
- Edward R. Tufte, *Envisioning Information* (1990): Layer and separate: quiet the grid and background so the data reads first.
|
|
87
|
+
- Otto Neurath, *International Picture Language* (1936): Isotype: count with repeated identical pictograms, never by enlarging one.
|
|
88
|
+
|
|
89
|
+
## Interface and product design
|
|
90
|
+
|
|
91
|
+
- Don Norman, *The Design of Everyday Things* (1988): Make the action visible: signifiers, feedback, mapping and constraints; a control must say how it is used.
|
|
92
|
+
- Steve Krug, *Don't Make Me Think* (2000): People scan, they do not read: obvious hierarchy, conventions, one clear next action per page.
|
|
93
|
+
- Adam Wathan and Steve Schoger, *Refactoring UI* (2018): Hierarchy by weight and colour before size, de-emphasise secondary text, use a spacing and shadow scale.
|
|
94
|
+
- Jenifer Tidwell, *Designing Interfaces* (2005): Use the known pattern for the job (navigation, forms, lists, dashboards) before inventing one.
|
|
95
|
+
- William Lidwell, Kritina Holden and Jill Butler, *Universal Principles of Design* (2003): Check the design against named principles (Fitts, Hick, proximity, progressive disclosure) and say which one a choice serves.
|
package/skills/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"files": [
|
|
5
5
|
"SKILL.md"
|
|
6
6
|
],
|
|
7
|
-
"sha": "
|
|
7
|
+
"sha": "6233057bd1a44b51"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"name": "clearly-canvas",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"project-plans.md",
|
|
18
18
|
"review.mjs"
|
|
19
19
|
],
|
|
20
|
-
"sha": "
|
|
20
|
+
"sha": "7d79406b37f611a8"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"name": "agent-home",
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
{
|
|
37
37
|
"name": "design-craft",
|
|
38
38
|
"files": [
|
|
39
|
-
"SKILL.md"
|
|
39
|
+
"SKILL.md",
|
|
40
|
+
"references.md"
|
|
40
41
|
],
|
|
41
|
-
"sha": "
|
|
42
|
+
"sha": "bf2f4fac6f57ea46"
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
"name": "layout-systems",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"files": [
|
|
53
54
|
"SKILL.md"
|
|
54
55
|
],
|
|
55
|
-
"sha": "
|
|
56
|
+
"sha": "cd891499599ccb7b"
|
|
56
57
|
},
|
|
57
58
|
{
|
|
58
59
|
"name": "pair-on-canvas",
|
|
@@ -81,6 +82,6 @@
|
|
|
81
82
|
"files": [
|
|
82
83
|
"SKILL.md"
|
|
83
84
|
],
|
|
84
|
-
"sha": "
|
|
85
|
+
"sha": "b3f47988cb9235b9"
|
|
85
86
|
}
|
|
86
87
|
]
|