@clearlysh/beehaven 0.8.16
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/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/__tests__/_cli-switch.js +105 -0
- package/dist/__tests__/agent-wrap-probe.js +136 -0
- package/dist/agent-attribution.js +192 -0
- package/dist/agent-identity-daemon.js +135 -0
- package/dist/agent-onboard.js +156 -0
- package/dist/agent-scope.js +79 -0
- package/dist/agent-sessions.js +370 -0
- package/dist/agent-state.js +482 -0
- package/dist/app-manifest.js +221 -0
- package/dist/app-signer.js +54 -0
- package/dist/app-signing.js +127 -0
- package/dist/audio-play.js +98 -0
- package/dist/avatar-protocol.js +25 -0
- package/dist/avatar-route.js +79 -0
- package/dist/bee/cloud-proxy.js +130 -0
- package/dist/blocked-actions.js +120 -0
- package/dist/build-channel.js +101 -0
- package/dist/buildings/daemon-workspace.js +475 -0
- package/dist/claude-bridge.js +310 -0
- package/dist/claude-credential-store.js +633 -0
- package/dist/clearly-sync.js +518 -0
- package/dist/clearly.js +616 -0
- package/dist/cli-state.js +334 -0
- package/dist/cli.js +6973 -0
- package/dist/commands/agent-skills.js +302 -0
- package/dist/commands/agent.js +1310 -0
- package/dist/commands/app.js +1123 -0
- package/dist/commands/auth.js +171 -0
- package/dist/commands/building-shell.js +392 -0
- package/dist/commands/canvas.js +932 -0
- package/dist/commands/claude.js +218 -0
- package/dist/commands/compliance.js +387 -0
- package/dist/commands/compose.js +507 -0
- package/dist/commands/composition.js +95 -0
- package/dist/commands/connect-session.js +409 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/costs.js +443 -0
- package/dist/commands/d1.js +253 -0
- package/dist/commands/daemon-cmd.js +162 -0
- package/dist/commands/deploy.js +164 -0
- package/dist/commands/design.js +617 -0
- package/dist/commands/dev-generate.js +125 -0
- package/dist/commands/dev.js +109 -0
- package/dist/commands/devices.js +135 -0
- package/dist/commands/explain-router.js +24 -0
- package/dist/commands/explain.js +882 -0
- package/dist/commands/explore.js +264 -0
- package/dist/commands/help.js +2418 -0
- package/dist/commands/init.js +408 -0
- package/dist/commands/install.js +129 -0
- package/dist/commands/library.js +229 -0
- package/dist/commands/marketplace.js +138 -0
- package/dist/commands/mcp-install.js +476 -0
- package/dist/commands/mention.js +257 -0
- package/dist/commands/omni.js +529 -0
- package/dist/commands/secrets.js +323 -0
- package/dist/commands/self-update.js +206 -0
- package/dist/commands/sheet.js +264 -0
- package/dist/commands/shell-output.js +309 -0
- package/dist/commands/shell.js +1048 -0
- package/dist/commands/shop.js +368 -0
- package/dist/commands/skills.js +311 -0
- package/dist/commands/slides.js +418 -0
- package/dist/commands/swarm.js +341 -0
- package/dist/commands/ticket.js +1301 -0
- package/dist/commands/tool.js +312 -0
- package/dist/commands/trash.js +106 -0
- package/dist/commands/treehouse.js +120 -0
- package/dist/commands/user.js +240 -0
- package/dist/commands/voice-tts.js +319 -0
- package/dist/commands/voice.js +166 -0
- package/dist/commands/watch-git.js +401 -0
- package/dist/commands/webhook.js +159 -0
- package/dist/commands/workspace.js +3354 -0
- package/dist/composition-repos.js +91 -0
- package/dist/daemon-avatar.js +130 -0
- package/dist/daemon-claude-code-auto-install.js +52 -0
- package/dist/daemon-client.js +363 -0
- package/dist/daemon-codex.js +349 -0
- package/dist/daemon-entry.js +101 -0
- package/dist/daemon-fetch-allowlist.js +194 -0
- package/dist/daemon-gemini.js +224 -0
- package/dist/daemon-mcp-clearly.js +353 -0
- package/dist/daemon-mcp-sandbox.js +127 -0
- package/dist/daemon-omni-company.js +338 -0
- package/dist/daemon-omni-hooks.js +375 -0
- package/dist/daemon-omni-identity.js +229 -0
- package/dist/daemon-omni-pipeline.js +251 -0
- package/dist/daemon-omni-policies.js +346 -0
- package/dist/daemon-omni-principal.js +580 -0
- package/dist/daemon-omni-tool-gate.js +116 -0
- package/dist/daemon-omni.js +1442 -0
- package/dist/daemon-path-transform.js +120 -0
- package/dist/daemon-write-sandbox.js +191 -0
- package/dist/daemon.js +4471 -0
- package/dist/dashboard.js +846 -0
- package/dist/dep-installer.js +286 -0
- package/dist/dev-mode.js +67 -0
- package/dist/doc-indexer.js +169 -0
- package/dist/e2e-crypto.js +94 -0
- package/dist/feature-flags.js +154 -0
- package/dist/file-browser.js +88 -0
- package/dist/first-party-hives.js +211 -0
- package/dist/folder-sync.js +226 -0
- package/dist/health.js +56 -0
- package/dist/ipc-auth.js +299 -0
- package/dist/ipc-client.js +495 -0
- package/dist/ipc-server.js +684 -0
- package/dist/keychain.js +224 -0
- package/dist/lib/admin-client.js +73 -0
- package/dist/lib/mobile-build-parser.js +156 -0
- package/dist/local-app-manager.js +660 -0
- package/dist/log-redaction.js +89 -0
- package/dist/logger.js +108 -0
- package/dist/manifest.js +143 -0
- package/dist/marketplace.js +793 -0
- package/dist/min-version-copy.js +51 -0
- package/dist/min-version.js +214 -0
- package/dist/public-surface.js +23 -0
- package/dist/relay-presets.js +89 -0
- package/dist/relay.js +2723 -0
- package/dist/repl.js +619 -0
- package/dist/setup-relay.js +363 -0
- package/dist/setup-service.js +536 -0
- package/dist/shared/bee-memory/index.js +6 -0
- package/dist/shared/bee-memory/types.js +682 -0
- package/dist/shared/claude-code/daemon-types.js +112 -0
- package/dist/shared/claude-code/index.js +2 -0
- package/dist/shared/claude-code/types.js +1 -0
- package/dist/shared/status/schema.js +133 -0
- package/dist/shell-session.js +198 -0
- package/dist/speak-lock.js +97 -0
- package/dist/spinner.js +57 -0
- package/dist/telemetry.js +166 -0
- package/dist/terminal/block-parser.js +207 -0
- package/dist/terminal/shell-integration.js +108 -0
- package/dist/transcript-tailer.js +215 -0
- package/dist/tts-qwen3.js +547 -0
- package/dist/types.js +4 -0
- package/dist/utm.js +38 -0
- package/dist/verb-surface.js +331 -0
- package/dist/version-utils.js +85 -0
- package/dist/watcher.js +207 -0
- package/package.json +88 -0
- package/python/qwen3_tts.py +230 -0
- package/scripts/stamp-build-channel.mjs +115 -0
- package/scripts/sync-installed.mjs +159 -0
- package/scripts/sync-skills.mjs +112 -0
- package/skills/README.md +24 -0
- package/skills/agent-doodle-space/SKILL.md +118 -0
- package/skills/agent-home/SKILL.md +98 -0
- package/skills/beehaven-cli/SKILL.md +144 -0
- package/skills/claude-shopper/README.md +90 -0
- package/skills/claude-shopper/SKILL.md +151 -0
- package/skills/clearly-agent/SKILL.md +183 -0
- package/skills/clearly-canvas/SKILL.md +222 -0
- package/skills/clearly-canvas/authoring.md +160 -0
- package/skills/clearly-canvas/communications.md +32 -0
- package/skills/clearly-canvas/composition-patterns.md +84 -0
- package/skills/clearly-canvas/example.mjs +78 -0
- package/skills/clearly-canvas/project-plans.md +156 -0
- package/skills/clearly-canvas/review.mjs +68 -0
- package/skills/clearly-workspace/SKILL.md +79 -0
- package/skills/clearly-workspace/survey.mjs +101 -0
- package/skills/codebase-map/SKILL.md +142 -0
- package/skills/design-craft/SKILL.md +300 -0
- package/skills/index.json +79 -0
- package/skills/layout-systems/SKILL.md +184 -0
- package/skills/pair-on-canvas/SKILL.md +92 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-workspace
|
|
3
|
+
description: Understand and audit a Clearly workspace from the terminal — what it holds (documents, projects, boards, tickets, thoughts, files, agents), who authored what, and which known data defects are present. Run `survey.mjs` for the whole picture in one command. TRIGGER when the user asks "what is in this workspace", "how big is this workspace", "audit / survey / check the workspace", "what have my agents made", "is anything broken in here", "who created these", or before any cleanup, migration or backfill — the survey is what tells you whether a repair is needed and whether it worked. NOT for editing documents (use `document-*`) or drawing on a canvas (use clearly-canvas).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# clearly-workspace — know what you are standing in
|
|
7
|
+
|
|
8
|
+
## ⚠ FIRST: sign in as an agent
|
|
9
|
+
|
|
10
|
+
Every `beehaven` verb except a short bootstrap list is refused without an agent identity.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
beehaven agent login <name> # mints it if it does not exist
|
|
14
|
+
beehaven status # daemon + relay + which env
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
⚠ **Check `beehaven env` before reading anything.** `prod` and `staging` are different
|
|
18
|
+
databases, and every number below is meaningless if you are on the one you did not mean.
|
|
19
|
+
|
|
20
|
+
## The whole workspace in one command
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node ~/.claude/skills/clearly-workspace/survey.mjs # human-readable
|
|
24
|
+
node ~/.claude/skills/clearly-workspace/survey.mjs --json # for further processing
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It prints the workspace name, its key prefix, counts of every content kind, a
|
|
28
|
+
**needs-attention** block, and documents-by-author.
|
|
29
|
+
|
|
30
|
+
⚠⚠ **PREFER THIS OVER HAND-WRITTEN SQL.** The same survey by hand is ~15 `workspace-sql`
|
|
31
|
+
round trips, and three of the coercions are traps that fail *silently*:
|
|
32
|
+
|
|
33
|
+
- **SQLite folder booleans are TEXT.** `is_archived != 1` misses every row storing the
|
|
34
|
+
string `'true'`, so "live projects" quietly includes archived ones.
|
|
35
|
+
- **Archived rows still satisfy most defect queries.** Counting body-less documents without
|
|
36
|
+
a live filter reports problems that were already resolved by archiving.
|
|
37
|
+
- **An unreadable table and an empty table both return 0** unless you check for the error.
|
|
38
|
+
The script prints `?` plus the reason instead.
|
|
39
|
+
|
|
40
|
+
Every one of those produced a wrong number on this workspace before the script existed.
|
|
41
|
+
|
|
42
|
+
## What the needs-attention flags mean
|
|
43
|
+
|
|
44
|
+
| flag | what it is | fix |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| documents with no body | no `note-content` row → **invisible to `document-search` forever** | archive them, or seed a body; the create path is fixed forward |
|
|
47
|
+
| boards with no project | reachable from nothing in the UI — a board is found through its project | file them, or archive if empty |
|
|
48
|
+
| many 'active' agent sessions | sessions are reaped after 6h idle, so a high count means the sweep is not running | call `agent-session-list` once (it sweeps on read) |
|
|
49
|
+
|
|
50
|
+
## Reading it deeper
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# who authored what — `agent:<id>` is an AGENT principal, not a person
|
|
54
|
+
beehaven call workspace-sql '{"query":"SELECT COALESCE(created_by,'\''(unstamped)'\'') AS who, COUNT(*) AS n FROM notes GROUP BY who ORDER BY n DESC"}'
|
|
55
|
+
|
|
56
|
+
# what a specific agent has been doing
|
|
57
|
+
beehaven call recent-list '{"limit":20}' # self-scoped: YOUR recents
|
|
58
|
+
beehaven call agent-brief '{}' # your session brief, incl. recents + unfiled drafts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
⚠ **`(unstamped)` is the ordinary case, not a defect.** `created_by` was added long after most
|
|
62
|
+
rows, and an unstamped row is treated as legacy-visible by design.
|
|
63
|
+
|
|
64
|
+
⚠ **`agent:<id>` in `created_by` is an AGENT.** Its work is visible to its owner and invisible
|
|
65
|
+
to a SIBLING agent unless filed into a shared project — filing IS the sharing mechanism.
|
|
66
|
+
|
|
67
|
+
## Repairs — all operator verbs, all `dryRun` by default
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
beehaven call keys-repair-prefix '{}' # workspace renamed but keys still say the old prefix
|
|
71
|
+
beehaven call boards-repair-authors '{}' # boards authored by a workspace id instead of a person
|
|
72
|
+
beehaven call notes-backfill-keys '{}' # documents with no CLR-42 style key
|
|
73
|
+
beehaven call thought-vacuum '{}' # thought vectors whose map is gone
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
⚠⚠ **EVERY ONE IS BOUNDED AND REPORTS `remaining` — LOOP UNTIL IT READS 0.** A single call is
|
|
77
|
+
not a finished job. `keys-repair-prefix` once flipped a counter after re-keying only 500 of
|
|
78
|
+
1,219 artifacts and then reported "nothing to do" while 719 rows were stranded. Re-run the
|
|
79
|
+
survey afterwards; that is what tells you the repair actually landed.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* survey.mjs — what is IN this workspace, in one command.
|
|
4
|
+
*
|
|
5
|
+
* `node survey.mjs [--json]`
|
|
6
|
+
*
|
|
7
|
+
* ── WHY A SCRIPT AND NOT PROSE ───────────────────────────────────────────────
|
|
8
|
+
* Answering "what is in here" by hand is ~15 `workspace-sql` round trips, each
|
|
9
|
+
* one a chance to mistype a table name or forget that a SQLite boolean comes
|
|
10
|
+
* back as the STRING 'true'. Every one of those mistakes is silent: you get a
|
|
11
|
+
* number, it is just the wrong number. A script makes the sequence one call and
|
|
12
|
+
* the coercions one decision.
|
|
13
|
+
*
|
|
14
|
+
* ⚠ IT REPORTS WHAT IT CANNOT SEE. A query that fails prints as `?` with its
|
|
15
|
+
* error rather than as 0 — an unreadable table and an empty table are opposite
|
|
16
|
+
* facts, and a survey that renders them alike is worse than no survey.
|
|
17
|
+
*
|
|
18
|
+
* ⚠ READ-ONLY BY CONSTRUCTION. Everything goes through `workspace-sql`, which
|
|
19
|
+
* refuses anything that is not a single SELECT/WITH. This cannot mutate.
|
|
20
|
+
*/
|
|
21
|
+
import { execFileSync } from 'node:child_process';
|
|
22
|
+
|
|
23
|
+
const JSON_OUT = process.argv.includes('--json');
|
|
24
|
+
|
|
25
|
+
/** One guarded SELECT through the CLI. Returns rows, or `{ error }`. */
|
|
26
|
+
function sql(query) {
|
|
27
|
+
try {
|
|
28
|
+
const out = execFileSync('beehaven', ['call', 'workspace-sql', JSON.stringify({ query })], {
|
|
29
|
+
encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'],
|
|
30
|
+
});
|
|
31
|
+
const m = out.match(/\{[\s\S]*\}/);
|
|
32
|
+
if (!m) return { error: 'no JSON in response — is the daemon running? `beehaven status`' };
|
|
33
|
+
const body = JSON.parse(m[0]);
|
|
34
|
+
if (body.error) return { error: body.error };
|
|
35
|
+
return { rows: body.rows ?? [] };
|
|
36
|
+
} catch (e) {
|
|
37
|
+
const msg = String(e.stderr || e.message || e);
|
|
38
|
+
// ⚠ The two failures a caller can actually act on, named rather than dumped.
|
|
39
|
+
if (/agent login|Not an agent/i.test(msg)) return { error: 'no agent identity — run `beehaven agent login <name>`' };
|
|
40
|
+
if (/daemon|IPC/i.test(msg)) return { error: 'daemon not reachable — `beehaven status`' };
|
|
41
|
+
return { error: msg.split('\n')[0].slice(0, 160) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const one = (query, col = 'n') => {
|
|
46
|
+
const r = sql(query);
|
|
47
|
+
if (r.error) return { v: null, error: r.error };
|
|
48
|
+
return { v: Number(r.rows?.[0]?.[col] ?? 0) };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** ⚠ SQLite folder booleans store as TEXT. `is_archived != 1` misses every 'true'. */
|
|
52
|
+
const LIVE = "(is_archived IS NULL OR is_archived NOT IN (1,'true','1'))";
|
|
53
|
+
|
|
54
|
+
const counts = {
|
|
55
|
+
documents: one(`SELECT COUNT(*) AS n FROM notes WHERE ${LIVE}`),
|
|
56
|
+
// ⚠ LIVE ONLY. Counting archived rows too made this flag lie: 16 body-less documents were
|
|
57
|
+
// resolved by ARCHIVING them, and the health line still reported all 16 as a problem.
|
|
58
|
+
// A health check that cannot see its own fix is one nobody trusts twice.
|
|
59
|
+
bodyless: one(`SELECT COUNT(*) AS n FROM notes WHERE ${LIVE} AND id NOT IN (SELECT id FROM "note-content")`),
|
|
60
|
+
projects: one(`SELECT COUNT(*) AS n FROM projects WHERE ${LIVE}`),
|
|
61
|
+
compositions: one(`SELECT COUNT(*) AS n FROM compositions WHERE ${LIVE} AND (is_system IS NULL OR is_system != 'true')`),
|
|
62
|
+
boards: one(`SELECT COUNT(*) AS n FROM boards WHERE ${LIVE}`),
|
|
63
|
+
orphanBoards: one(`SELECT COUNT(*) AS n FROM boards WHERE project_id IS NULL OR project_id = ''`),
|
|
64
|
+
tickets: one(`SELECT COUNT(*) AS n FROM tickets`),
|
|
65
|
+
openTickets: one(`SELECT COUNT(*) AS n FROM tickets WHERE status NOT IN ('done','closed','resolved')`),
|
|
66
|
+
thoughts: one(`SELECT COUNT(*) AS n FROM thoughts`),
|
|
67
|
+
files: one(`SELECT COUNT(*) AS n FROM files WHERE deleted_at IS NULL OR deleted_at = 0`),
|
|
68
|
+
agents: one(`SELECT COUNT(*) AS n FROM agents WHERE archived IS NULL OR archived NOT IN ('true','1')`),
|
|
69
|
+
liveSessions: one(`SELECT COUNT(*) AS n FROM agent_sessions WHERE state = 'active'`),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const name = sql(`SELECT value FROM "team-settings" WHERE key = 'name'`);
|
|
73
|
+
const prefix = sql(`SELECT prefix, last_number FROM project_counters WHERE id = 'unfiled'`);
|
|
74
|
+
const authors = sql(`SELECT COALESCE(created_by,'(unstamped)') AS who, COUNT(*) AS n FROM notes WHERE ${LIVE} GROUP BY who ORDER BY n DESC LIMIT 6`);
|
|
75
|
+
|
|
76
|
+
if (JSON_OUT) {
|
|
77
|
+
console.log(JSON.stringify({ name: name.rows?.[0]?.value ?? null, counts, prefix: prefix.rows?.[0] ?? null, authors: authors.rows ?? [] }, null, 2));
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const n = (c) => (c.error ? `? (${c.error})` : String(c.v));
|
|
82
|
+
console.log(`\n ${name.rows?.[0]?.value ?? '(unnamed workspace)'}`);
|
|
83
|
+
if (prefix.rows?.[0]) console.log(` keys: ${prefix.rows[0].prefix}-* (${prefix.rows[0].last_number} issued)`);
|
|
84
|
+
console.log(' ' + '─'.repeat(46));
|
|
85
|
+
for (const [k, v] of Object.entries(counts)) console.log(` ${k.padEnd(16)} ${n(v)}`);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ⚠ THE HEALTH LINES ARE THE POINT. A raw count tells you the size; these tell
|
|
89
|
+
* you what is WRONG, and each one is a defect this workspace has actually had.
|
|
90
|
+
*/
|
|
91
|
+
const flags = [];
|
|
92
|
+
if (counts.bodyless.v > 0) flags.push(`${counts.bodyless.v} document(s) have no body — invisible to document-search forever`);
|
|
93
|
+
if (counts.orphanBoards.v > 0) flags.push(`${counts.orphanBoards.v} board(s) have no project — reachable from nothing in the UI`);
|
|
94
|
+
if (counts.liveSessions.v > 20) flags.push(`${counts.liveSessions.v} 'active' agent sessions — sessions are reaped after 6h idle, so this is high`);
|
|
95
|
+
if (flags.length) { console.log('\n needs attention'); for (const f of flags) console.log(` ⚠ ${f}`); }
|
|
96
|
+
|
|
97
|
+
if (authors.rows?.length) {
|
|
98
|
+
console.log('\n documents by author');
|
|
99
|
+
for (const a of authors.rows) console.log(` ${String(a.who).padEnd(34)} ${a.n}`);
|
|
100
|
+
}
|
|
101
|
+
console.log('');
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codebase-map
|
|
3
|
+
description: Walk a code repository and draw a living ARCHITECTURE MAP of it on a Clearly spatial canvas — modules as frames (sized by LOC, colored by layer), key files as nodes, imports/dependencies as arrows — so the big pieces, how they depend on each other, and where the complexity sits all read at a glance. Load this when the user says "map / diagram / visualize this codebase / repo / architecture", "how is this project structured", "draw the dependency graph", "give me an architecture overview", or when you're onboarding to an unfamiliar repo and need to see its shape fast.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **⚠ CANVAS WORK GOES THROUGH THE CLI, NOT AN MCP TOOL.** Anything written
|
|
7
|
+
> `canvas-act { … }` / `canvas-perceive { … }` below is an ACTION, run as
|
|
8
|
+
> `beehaven call canvas-act '<the JSON>'`. The payloads are exactly as shown. The MCP
|
|
9
|
+
> surface is seven tools split by operation, and a single tool that dispatches 230
|
|
10
|
+
> caller-named canvas actions spans reads, writes and deletes at once — which is why the
|
|
11
|
+
> generic door is the shell instead. `clearly_read { target, type: "composition" }` still
|
|
12
|
+
> reads a canvas as an artifact.
|
|
13
|
+
|
|
14
|
+
# Codebase map — repo → spatial architecture diagram
|
|
15
|
+
|
|
16
|
+
A `tree` dump is 400 files of noise. The thing a dev actually wants is the **shape**: ~8 boxes,
|
|
17
|
+
sized by weight, wired by who-imports-whom, with the hot, gnarly module obvious. Use the host shell
|
|
18
|
+
to inspect the repo and the `beehaven` CLI to draw it. Sign in as an agent and connect the intended
|
|
19
|
+
workspace first; `clearly-init` handles setup. Read `clearly-canvas` for the primitives and the
|
|
20
|
+
locked tool contract — this skill builds on it (perceive → act → revise; `frame.create`;
|
|
21
|
+
`canvas.create-node`; frame-relative nesting; bound `arrow.create`; always `compositionId`).
|
|
22
|
+
|
|
23
|
+
## 1. Discover the structure (host shell)
|
|
24
|
+
|
|
25
|
+
Survey before you draw. Run these from the repo root:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# top-level shape + package boundaries
|
|
29
|
+
ls -d */ ; git ls-files | sed 's|/.*||' | sort | uniq -c | sort -rn | head -20
|
|
30
|
+
find . -name package.json -o -name go.mod -o -name Cargo.toml -o -name pyproject.toml | grep -v node_modules
|
|
31
|
+
|
|
32
|
+
# size per module (the LOC that sizes each frame) — cloc if present, else wc
|
|
33
|
+
cloc --quiet --json src 2>/dev/null \
|
|
34
|
+
|| git ls-files | grep -vE 'node_modules|dist|build|\.lock' | xargs wc -l 2>/dev/null | sort -rn | head
|
|
35
|
+
|
|
36
|
+
# hotspots: most-churned files (complexity lives where the diffs pile up)
|
|
37
|
+
git log --format= --name-only | grep -vE '^$|node_modules' | sort | uniq -c | sort -rn | head -20
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Dependencies** (the arrows). Either read declared deps from manifests, or grep the cross-module edges directly:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# JS/TS — who imports whom across top-level dirs
|
|
44
|
+
grep -rhoE "from ['\"](\.\./)+[a-zA-Z0-9_-]+" src | sort | uniq -c | sort -rn | head -30
|
|
45
|
+
grep -rhoE "import .* from ['\"]@/[a-zA-Z0-9_-]+" src | sort | uniq -c | sort -rn
|
|
46
|
+
# Python: ^import|^from · Go: read go.mod requires · Rust: [dependencies] in Cargo.toml
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You want, per module: an LOC count, a file count, and its 1–3 heaviest outbound dependencies. That's enough to draw the map.
|
|
50
|
+
|
|
51
|
+
## 2. Cluster into domains
|
|
52
|
+
|
|
53
|
+
Collapse dirs into a handful of **logical layers** — the same lens regardless of stack:
|
|
54
|
+
|
|
55
|
+
| Layer | typical dirs | color |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **ui** | components, pages, app, views | `#3b82f6` blue |
|
|
58
|
+
| **api / logic** | routes, handlers, services, lib | `#8b5cf6` violet |
|
|
59
|
+
| **data** | db, models, store, repositories | `#10b981` green |
|
|
60
|
+
| **infra** | workers, deploy, config, scripts | `#f59e0b` amber |
|
|
61
|
+
| **shared** | utils, types, common | `#6b7280` slate |
|
|
62
|
+
| **hotspot** | the biggest / most-churned module | `#ef4444` red |
|
|
63
|
+
|
|
64
|
+
Pick the **~6–12 modules that matter** — never every file. A 40-file `components/` dir is ONE frame, not 40 nodes. Optionally surface 2–4 marquee files *inside* a frame (the entrypoint, the fattest file, the one everything imports).
|
|
65
|
+
|
|
66
|
+
## 3. Draw it
|
|
67
|
+
|
|
68
|
+
Get a canvas, then **perceive the background first** so you paint with it (dark board → light ink), and place the map in the user's `gaze`:
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
beehaven call composition-create '{"title":"Architecture map · clearly"}' // → { id: "c_…" }
|
|
72
|
+
canvas-perceive { "compositionId": "c_…", "format": "text" } // read backgroundColor + visibleWorldRect
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then batch the whole map in one `canvas-act`. Capture the frame ids the creates return so you can nest + connect. **Size each frame ~proportional to LOC** (e.g. `w ≈ 160 + LOC/40`, clamp 200–420), **fill by layer color**, lay out on a grid with 40px gutters:
|
|
76
|
+
|
|
77
|
+
```jsonc
|
|
78
|
+
canvas-act {
|
|
79
|
+
"compositionId": "c_…",
|
|
80
|
+
"batch": [
|
|
81
|
+
{ "action": "canvas.create-node", "args": { "type": "text", "name": "map-title",
|
|
82
|
+
"text": "clearly · architecture (12k LOC, 9 modules) — arrows = imports, red = hotspot",
|
|
83
|
+
"x": 0, "y": -56, "size": 22, "fontWeight": 700, "fill": "#e8edf5" } },
|
|
84
|
+
|
|
85
|
+
{ "action": "frame.create", "args": { "name": "ui/components", "x": 0, "y": 0, "w": 360, "h": 220, "fill": "#16223a" } },
|
|
86
|
+
{ "action": "frame.create", "args": { "name": "lib/services", "x": 440, "y": 0, "w": 300, "h": 200, "fill": "#241a3a" } },
|
|
87
|
+
{ "action": "frame.create", "args": { "name": "data/store", "x": 440, "y": 280, "w": 240, "h": 150, "fill": "#10291f" } },
|
|
88
|
+
{ "action": "frame.create", "args": { "name": "infra/worker", "x": 0, "y": 300, "w": 280, "h": 160, "fill": "#2a2110" } }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then a second `canvas-act` once you hold the frame ids — **titles + stats inside each frame** (coords are frame-relative via `parentId`), **key files as nodes**, and **dependency arrows** spanning the gap between frames (use bounds from perceive):
|
|
94
|
+
|
|
95
|
+
```jsonc
|
|
96
|
+
canvas-act {
|
|
97
|
+
"compositionId": "c_…",
|
|
98
|
+
"batch": [
|
|
99
|
+
{ "action": "canvas.create-node", "args": { "type": "text", "name": "ui-title",
|
|
100
|
+
"text": "ui/components", "x": 16, "y": 12, "size": 16, "fontWeight": 700, "fill": "#cfe0ff", "parentId": "<uiFrameId>" } },
|
|
101
|
+
{ "action": "canvas.create-node", "args": { "type": "text", "name": "ui-stat",
|
|
102
|
+
"text": "84 files · 4.1k LOC", "x": 16, "y": 36, "size": 12, "fontWeight": 400, "fill": "#8aa0c0", "parentId": "<uiFrameId>" } },
|
|
103
|
+
{ "action": "canvas.create-node", "args": { "type": "rect", "name": "ui-key-canvas",
|
|
104
|
+
"text": "DocumentCanvas.tsx", "x": 16, "y": 64, "w": 180, "h": 30, "fill": "#1e2d4d", "stroke": "#3b82f6", "radius": 6, "parentId": "<uiFrameId>" } },
|
|
105
|
+
|
|
106
|
+
{ "action": "arrow.create", "args": { "from":"ui", "to":"lib", "routing":"elbow" } }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Arrow weight = coupling**: `stroke-width:3` for a heavy edge (dozens of imports), `1.5` for a thin one. Curve overlapping arrows with a `<path d=…>` instead of `<line>`. Drop a tiny `text` at an edge's midpoint only when the relationship needs a name.
|
|
112
|
+
|
|
113
|
+
Finish with a **legend frame** (one colored `rect` + label per layer) tucked in a corner, and confirm the **one-line summary** text sits at the top.
|
|
114
|
+
|
|
115
|
+
## 4. Reveal, don't dump
|
|
116
|
+
|
|
117
|
+
The map earns its keep by making structure *jump out*:
|
|
118
|
+
|
|
119
|
+
- **Hotspot in red.** The biggest or most-churned module gets `fill:"#ef4444"`-family — "here be dragons" at a glance.
|
|
120
|
+
- **Size encodes weight.** A 6k-LOC frame should dwarf a 400-LOC one. Don't draw equal boxes.
|
|
121
|
+
- **Thicker arrows = heavier coupling.** A glance shows what's load-bearing.
|
|
122
|
+
- **Whitespace.** 40px gutters, aligned edges, the legend out of the way. A breathing board reads as designed; a crowded one reads as a dump.
|
|
123
|
+
|
|
124
|
+
Good map answers three questions instantly: *what are the big pieces, how do they depend on each other, and where's the complexity?*
|
|
125
|
+
|
|
126
|
+
## 5. Keep it live
|
|
127
|
+
|
|
128
|
+
This isn't a one-shot render — it's a living diagram. After the repo changes, re-run §1 and `canvas.update-nodes` the frames whose LOC/coupling shifted (resize, recolor, re-arrow) instead of redrawing — the map stays current as the codebase moves. A `canvas.add-diff` block can land the current change next to the architecture map, and **`beehaven watch git`** can re-tune the map as commits land (⚠ CLI-only; on MCP, re-run §1 when you know the repo moved).
|
|
129
|
+
|
|
130
|
+
## The viral bit
|
|
131
|
+
|
|
132
|
+
A beautiful, auto-generated architecture map — modules sized by weight, layers color-coded, dependency arrows, the hotspot glowing red — is *exactly* the thing devs screenshot and post: "Claude mapped my entire codebase in 30 seconds." Make every map look like that. Clean frames, honest arrows, an obvious "this is the scary part." The screenshot sells the next install.
|
|
133
|
+
|
|
134
|
+
## Rules of thumb
|
|
135
|
+
|
|
136
|
+
- **Always pass `compositionId`** — that's what persists the map with no tab open.
|
|
137
|
+
- **Perceive the background first**; paint with it (dark board → light ink), drop the map in the user's `gaze`.
|
|
138
|
+
- **Draw ~6–12 modules, not every file** — collapse a dir into one frame; surface only marquee files inside.
|
|
139
|
+
- **Size by LOC, color by layer**, one module red as the hotspot.
|
|
140
|
+
- **Arrows only for REAL dependencies** (A imports B → A→B); thicker = heavier coupling. Don't decorate, inform.
|
|
141
|
+
- **`name` is required on every create**; capture returned frame ids to nest + connect.
|
|
142
|
+
- **Need an action not listed?** `canvas-catalog { surface: "all" }` describes every one.
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-craft
|
|
3
|
+
description: >-
|
|
4
|
+
The craft layer for visual work on a Clearly canvas — how to make something
|
|
5
|
+
that looks like a design studio made it, not like a first draft. Grid
|
|
6
|
+
construction, type scale, colour systems, spacing, hierarchy, optical
|
|
7
|
+
correction, and the pass to run before calling anything finished. Use whenever visual
|
|
8
|
+
quality is the point rather than an afterthought, and load it BEFORE building any poster, deck, identity, landing page, social post, cover,
|
|
9
|
+
report or diagram; clearly-canvas tells you HOW to place a node, this tells
|
|
10
|
+
you WHERE it goes and WHY. Triggers: "design", "make it look good", "make it
|
|
11
|
+
beautiful", "lay this out", "poster", "deck", "landing page", "brand", "this
|
|
12
|
+
looks off", "make it more premium", "art direct this", "clean this up".
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# design-craft — the difference between placed and designed
|
|
16
|
+
|
|
17
|
+
`clearly-canvas` teaches the mechanics: node shape, coordinates, verbs. This teaches the
|
|
18
|
+
part that makes the result worth looking at.
|
|
19
|
+
|
|
20
|
+
⚠ **Everything here is expressed as NUMBERS you can put on a node.** A rule you cannot act
|
|
21
|
+
on is a rule you will not follow. Where a value is a judgement call, this says so.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 0. The method: decide before you draw
|
|
26
|
+
|
|
27
|
+
Pentagram — the largest independent design consultancy, partner-led with no house style —
|
|
28
|
+
is **idea-led, not style-led**: each partner runs their own team, and the through-line is
|
|
29
|
+
that the work answers a brief rather than expressing a signature. Michael Bierut's *How to*
|
|
30
|
+
presents 35 case studies as **brief → rationale → trade-offs**, which is the useful shape:
|
|
31
|
+
the reasoning is the deliverable, not the artefact.
|
|
32
|
+
|
|
33
|
+
Massimo Vignelli's *Canon* gives the three questions, in order. Answer them in one line each
|
|
34
|
+
before you place a single node:
|
|
35
|
+
|
|
36
|
+
| | question | if you skip it |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Semantics** | What does this MEAN, to whom? | a beautiful thing about nothing |
|
|
39
|
+
| **Syntactics** | What visual grammar suits it? | borrowed style, arbitrary choices |
|
|
40
|
+
| **Pragmatics** | Will they understand it? | clever, unread |
|
|
41
|
+
|
|
42
|
+
⚠⚠ **THE SINGLE MOST COMMON AGENT FAILURE IS SKIPPING SEMANTICS.** Asked for "a poster for a
|
|
43
|
+
jazz night", a weak agent reaches for a typeface and a gradient. A studio asks what the night
|
|
44
|
+
IS — basement or concert hall, 40 people or 400, regulars or first-timers — and the answer
|
|
45
|
+
picks the type, the palette and the crop. **You can ask the user one sentence, or state your
|
|
46
|
+
reading and proceed; you cannot skip it.**
|
|
47
|
+
|
|
48
|
+
⚠ **Vignelli: "Design is one."** The discipline is above style. A deck, a poster and an app
|
|
49
|
+
screen are the same problem — order, hierarchy, restraint — wearing different aspect ratios.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 1. The grid — build it, don't eyeball it
|
|
54
|
+
|
|
55
|
+
Josef Müller-Brockmann systematised this over two decades of Tonhalle Zürich posters and
|
|
56
|
+
codified it in *Grid Systems in Graphic Design* (1981). The grid is not a technical
|
|
57
|
+
convenience; it is what makes a composition read as ordered rather than arranged.
|
|
58
|
+
|
|
59
|
+
**Construct it in this order, every time:**
|
|
60
|
+
|
|
61
|
+
1. **Canvas** — pick the real output size. 1080×1350 (social 4:5) · 1080×1920 (story 9:16) ·
|
|
62
|
+
1920×1080 (deck/desktop) · 1440×1024 (web) · 2480×3508 (A4 at 300dpi).
|
|
63
|
+
2. **Margin** — the single biggest lever on whether it looks professional. Start at
|
|
64
|
+
**6–8% of the short edge**. On 1080 wide that is **72px**. Go bigger for editorial (10%),
|
|
65
|
+
never smaller than 4% except for a deliberate full-bleed.
|
|
66
|
+
3. **Columns** — 12 divides by 2, 3, 4 and 6, which is why it is the default. Use 6 for
|
|
67
|
+
simple work, 12 for anything with mixed content, 3–4 for a poster.
|
|
68
|
+
4. **Gutter** — 2–3% of the content width. On a 936px content area, **24px**.
|
|
69
|
+
5. **Baseline** — an 8pt vertical rhythm. Every y is a multiple of 8; every height is a
|
|
70
|
+
multiple of 4.
|
|
71
|
+
|
|
72
|
+
**The arithmetic, done for you (1080 canvas):**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
margin 72 · content 936 · 12 columns · gutter 24
|
|
76
|
+
column = (936 − 11×24) / 12 = 56px
|
|
77
|
+
column + gutter = 80px ← every x lands on 72 + n×80
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
⚠ **A column count is a decision about CONTENT, not a default.** Three columns of body copy
|
|
81
|
+
at 1080 wide gives a 96-character measure, which is unreadable. Pick the grid the content
|
|
82
|
+
needs, then hold it.
|
|
83
|
+
|
|
84
|
+
⚠⚠ **THE GRID IS A SKELETON, NOT A CAGE.** Müller-Brockmann's own posters break it — one
|
|
85
|
+
element crossing the grid is what creates tension. **Break it once, deliberately, and hold
|
|
86
|
+
everything else.** Two breaks and there was no grid.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 2. Type — the whole game
|
|
91
|
+
|
|
92
|
+
**Use a modular scale.** Pick a base and a ratio; every size is the base multiplied or
|
|
93
|
+
divided by the ratio. Sizes that share a ratio look related; sizes picked by hand do not.
|
|
94
|
+
|
|
95
|
+
| ratio | name | use for |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| 1.125 | Major second | dense UI, documentation, dashboards |
|
|
98
|
+
| 1.250 | Major third | product UI, reports, general purpose |
|
|
99
|
+
| 1.333 | Perfect fourth | editorial, marketing pages — the safe default |
|
|
100
|
+
| 1.500 | Perfect fifth | posters, covers, decks |
|
|
101
|
+
| 1.618 | Golden | display work with very few sizes |
|
|
102
|
+
|
|
103
|
+
**A perfect-fourth scale from a 16px base** (round to whole pixels, then round to the 4-grid):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
16 · 21 · 28 · 38 · 50 · 67 · 89 · 119 · 158 · 211
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Rules that survive contact with real work:**
|
|
110
|
+
|
|
111
|
+
- ⚠⚠ **THREE SIZES PER SURFACE.** Display, body, caption. A fourth is almost always a
|
|
112
|
+
hierarchy failure you are papering over. Count them before you finish.
|
|
113
|
+
- **ONE typeface, two weights, is a stronger position than two typefaces.** If you use two,
|
|
114
|
+
make them obviously different in class (a grotesque with a serif), never two neighbours.
|
|
115
|
+
- **Measure: 45–75 characters.** At 16px in a normal grotesque that is roughly **360–600px**.
|
|
116
|
+
Set the text node's `w` to hold that, not to fill the column.
|
|
117
|
+
- **Line height moves INVERSELY to size.** Body 1.4–1.6. Subheads 1.25–1.35. Display
|
|
118
|
+
**0.95–1.05** — big type needs tight leading or it falls apart into stripes.
|
|
119
|
+
- ⚠⚠ **TRACK DISPLAY TYPE NEGATIVE.** Type designed at 16px is far too loose at 120px.
|
|
120
|
+
Above ~40px use `letterSpacing: -1` to `-4`; above ~120px, −3 to −6. **Uppercase and small
|
|
121
|
+
labels go the other way: +1 to +3.** This one adjustment does more for perceived quality
|
|
122
|
+
than any other single change.
|
|
123
|
+
- **Never centre more than three lines.** Centred text has two ragged edges; the eye needs
|
|
124
|
+
one hard edge to return to.
|
|
125
|
+
|
|
126
|
+
⚠ **MEASURE, DO NOT GUESS.** `canvas.measure-text {text, size, fontWeight, letterSpacing,
|
|
127
|
+
maxWidth}` returns `{width, height, lineCount, fits, overflowBy}` from real font metrics.
|
|
128
|
+
Guessing a per-character ratio is how a headline ships bleeding past its margin — a 182px
|
|
129
|
+
headline estimated at 867px measured **990**.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 3. Colour — a system, not a mood board
|
|
134
|
+
|
|
135
|
+
**Build the palette from a role list, not from colours you like:**
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
ground the surface everything sits on 1 colour
|
|
139
|
+
ink primary text on that ground 1
|
|
140
|
+
ink-muted secondary text 1
|
|
141
|
+
accent the brand / the one thing that shouts 1
|
|
142
|
+
support 2–3 tones for charts, states, depth
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- ⚠⚠ **ONE ACCENT.** Two accents means neither is one. If the work needs a second bright
|
|
146
|
+
colour, it is a *support* tone and must be visibly quieter.
|
|
147
|
+
- **60 / 30 / 10** — ground, secondary surface, accent. If the accent is over ~10% of the
|
|
148
|
+
area it has stopped being emphasis.
|
|
149
|
+
- **Contrast is a measurement, not an opinion.** Body text ≥ **4.5:1**, large/display ≥ **3:1**.
|
|
150
|
+
Check it; do not eyeball dark grey on mid grey.
|
|
151
|
+
- ⚠ **Avoid pure `#000` on pure `#FFF` for large areas** — the contrast is harsh and reads
|
|
152
|
+
cheap in print and on OLED. `#111` on `#FAFAF8` is the same legibility, better texture.
|
|
153
|
+
- ⚠ **A dark UI needs a LIGHTER accent than a light one.** The same green that sings on white
|
|
154
|
+
goes muddy on near-black; lift lightness ~10% for dark grounds.
|
|
155
|
+
- **Colour last, or colour first — never in the middle.** Compose in greyscale to prove the
|
|
156
|
+
hierarchy works on VALUE alone, then add colour. If it only works in colour, it does not work.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 4. Space — the cheapest quality signal there is
|
|
161
|
+
|
|
162
|
+
- **Spacing is a scale**, same as type: `4 · 8 · 12 · 16 · 24 · 32 · 48 · 64 · 96 · 128`.
|
|
163
|
+
Never an arbitrary 27.
|
|
164
|
+
- ⚠⚠ **PROXIMITY BEATS EVERY OTHER GROUPING DEVICE.** A label 8px from its value and 48px
|
|
165
|
+
from the next pair needs no line, no box and no colour to be understood. **Reach for space
|
|
166
|
+
before you reach for a border.**
|
|
167
|
+
- **Space between groups must be visibly larger than space within them** — aim for ≥2×. If
|
|
168
|
+
a card's internal padding is 24, the gap between cards is 48+, not 32.
|
|
169
|
+
- ⚠ **Padding is not symmetrical when type is involved.** Text sits optically high in its
|
|
170
|
+
box because of descender space, so a visually centred label usually needs 2–4px more
|
|
171
|
+
padding on top than bottom at body sizes, more at display.
|
|
172
|
+
- **Whitespace is not "empty".** The most common studio-vs-draft difference is that the draft
|
|
173
|
+
fills the canvas. **Cutting content is a design move.**
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 5. Hierarchy — decide what wins
|
|
178
|
+
|
|
179
|
+
Rank every element **before** you style it: primary / secondary / tertiary. Then:
|
|
180
|
+
|
|
181
|
+
- ⚠⚠ **CHANGE ONE VARIABLE AT A TIME.** Size, weight, colour and space each signal
|
|
182
|
+
importance. Using all four at once on the same element is the signature of amateur work —
|
|
183
|
+
a heading that is bigger AND bolder AND coloured AND boxed. **Pick one, maybe two.**
|
|
184
|
+
- **One focal point per surface.** Where does the eye land first? If you cannot answer in
|
|
185
|
+
one second, neither can the reader.
|
|
186
|
+
- **Scale contrast should be decisive.** A 28px heading over 24px body reads as a mistake;
|
|
187
|
+
38 or 50 over 16 reads as a decision. If two things are nearly the same size, make them
|
|
188
|
+
the same size.
|
|
189
|
+
- **Alignment creates order for free.** Every element should align to something else. A
|
|
190
|
+
single unaligned element is read as an error, not as freedom.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 6. Optical correction — where the numbers stop being right
|
|
195
|
+
|
|
196
|
+
The renderer is mathematically correct and the eye is not. These are the adjustments that
|
|
197
|
+
separate work that is *measured* from work that *looks* right.
|
|
198
|
+
|
|
199
|
+
- **Optical centring**: a thing centred in a frame looks low. Raise it ~2% of the frame
|
|
200
|
+
height. Always true for a mark inside a container.
|
|
201
|
+
- **Circles and pointed shapes overshoot**: a circle the same height as a square looks
|
|
202
|
+
smaller. Make round and pointed forms ~2–3% larger to match a flat-sided neighbour.
|
|
203
|
+
- **Align on the INK, not the box.** A text node's box includes ascender/descender space.
|
|
204
|
+
To align a heading's cap-height with a rule or an icon, expect to nudge 2–6px.
|
|
205
|
+
- **Punctuation hangs.** A quote mark or bullet starting a line should sit slightly OUTSIDE
|
|
206
|
+
the margin, or the text edge reads as ragged.
|
|
207
|
+
- ⚠ **Stroke weight does not scale with the box.** Scaling a 2px-stroked icon to 3× gives a
|
|
208
|
+
6px stroke that no longer matches its neighbours. `canvas.scale` scales strokes and fonts
|
|
209
|
+
as a unit — that is right for a whole card, wrong for an icon that must match a set.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 7. THE STUDIO PASS — run this before you say it is done
|
|
214
|
+
|
|
215
|
+
⚠⚠ **A check reads the DATA. It cannot see that the work is ugly.** Run
|
|
216
|
+
`canvas.review {ids:[<artboard>]}` (or clearly-canvas's `review.mjs`). Its `verdict` and
|
|
217
|
+
`craft` findings measure the boxes marked **✓** below. Then open the PNG it returns and
|
|
218
|
+
judge the rest by eye. On a real build, looking caught four defects that every automated
|
|
219
|
+
check called clean.
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
✓ MARGINS Is anything closer to an edge than the margin, unintentionally? (edge-crowding)
|
|
223
|
+
□ MEASURE Any line of body copy over ~75 characters?
|
|
224
|
+
✓ SIZES Count distinct type sizes. More than 3–4? Collapse them. (type-sprawl flags >6 and near-duplicates; aim lower)
|
|
225
|
+
□ TRACKING Is display type still at default spacing? Tighten it.
|
|
226
|
+
✓ ALIGNMENT Pick any element — what is it aligned to? If nothing, fix it. (near-miss-alignment catches 1–3px misses only)
|
|
227
|
+
□ SPACE Is between-group space ≥ 2× within-group space?
|
|
228
|
+
□ FOCAL Where does the eye land first? Is that the most important thing?
|
|
229
|
+
□ GREYSCALE Does the hierarchy survive with colour removed?
|
|
230
|
+
✓ CONTRAST Body ≥ 4.5:1, display ≥ 3:1 — measured, not guessed. (contrast; text-over-image means look)
|
|
231
|
+
□ ACCENT Exactly one? Under ~10% of the area? (palette-sprawl flags a scatter)
|
|
232
|
+
□ ORPHANS Any single word alone on the last line of a heading?
|
|
233
|
+
□ CONSISTENCY Same corner radius, same stroke weight, same gap, everywhere?
|
|
234
|
+
✓ EDGE Zoom to 100%. Does anything touch, overlap or clip? (text-collision, text-covered, overflow)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A **✓** check that comes back clean only means the numbers passed. Still look at the pixels.
|
|
238
|
+
|
|
239
|
+
A first draft almost always has something this pass should change, so if you changed nothing,
|
|
240
|
+
run it again on the thumbnail. If it is still clean, say what you checked. Never make a change
|
|
241
|
+
only to show that a review happened.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 8. Canvas recipes — the numbers, ready to use
|
|
246
|
+
|
|
247
|
+
**A 1080×1350 social post**
|
|
248
|
+
```
|
|
249
|
+
margin 72 · display 96–140 (weight 800, tracking −3, lineHeight 1.0)
|
|
250
|
+
body 20–24 (weight 500, lineHeight 1.5, w ≤ 560)
|
|
251
|
+
caption 16 (weight 600, tracking +1.5, uppercase)
|
|
252
|
+
rule 2–3px accent · footer block bottom-aligned at 1350 − 72 − blockHeight
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**A 1920×1080 deck slide**
|
|
256
|
+
```
|
|
257
|
+
margin 120 · title 72–96 · body 28–32 · caption 20
|
|
258
|
+
Never more than ~40 words. A slide is a headline with evidence, not a document.
|
|
259
|
+
Content column ≤ 1200 wide even though the slide is 1920 — full width is unreadable.
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**A 1440 web hero**
|
|
263
|
+
```
|
|
264
|
+
margin 96 · content max 1200 · headline 64–88 (tracking −2, lineHeight 1.05)
|
|
265
|
+
sub 20–24 (muted ink, w ≤ 640) · CTA 48–56 tall, 24–32 horizontal padding, radius 8 or full
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**A card**
|
|
269
|
+
```
|
|
270
|
+
padding 24–32 · radius 10–16 · gap between cards ≥ 2× padding
|
|
271
|
+
title 20–24/600 · body 15–16/400 muted · one accent element maximum
|
|
272
|
+
Background first, THEN the label — later nodes draw over earlier ones.
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 9. What to do when asked to "make it better"
|
|
278
|
+
|
|
279
|
+
In order, because the earlier ones are cheaper and do more:
|
|
280
|
+
|
|
281
|
+
1. **Increase the margins.** Nine times in ten, it is too tight.
|
|
282
|
+
2. **Remove a type size.** Collapse the two closest into one.
|
|
283
|
+
3. **Track the display type negative.**
|
|
284
|
+
4. **Double the space between groups.**
|
|
285
|
+
5. **Remove the second accent colour.**
|
|
286
|
+
6. **Align one stray element** to the nearest grid line.
|
|
287
|
+
7. **Cut 20% of the words.**
|
|
288
|
+
8. Only now consider a new typeface, a texture or an effect.
|
|
289
|
+
|
|
290
|
+
⚠ **Do not answer "make it more premium" with more ornament.** Premium reads as *more space,
|
|
291
|
+
fewer elements, tighter type, one accent*. Every ornament added is a step away from it.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Sources
|
|
296
|
+
|
|
297
|
+
Josef Müller-Brockmann, *Grid Systems in Graphic Design* (1981) · Massimo Vignelli, *The
|
|
298
|
+
Vignelli Canon* (semantics / syntactics / pragmatics; "Design is one") · Michael Bierut,
|
|
299
|
+
*How to* (Pentagram; brief → rationale → trade-offs) · Paula Scher / Pentagram, The Public
|
|
300
|
+
Theater identity (typography as the whole system) · Pentagram's partner-led, idea-led model.
|