beehaven 0.8.29 → 0.8.34
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/agent-attribution.js +20 -2
- package/dist/agent-scope.js +55 -10
- package/dist/agent-state.js +44 -0
- package/dist/agents-md-text.js +64 -0
- package/dist/browser-login.js +14 -4
- package/dist/build-channel.js +2 -2
- package/dist/claude-bridge.js +12 -9
- package/dist/clearly.js +4 -3
- package/dist/cli-state.js +45 -3
- package/dist/cli.js +82 -53
- package/dist/commands/agent-skills.js +12 -1
- package/dist/commands/agent.js +235 -28
- package/dist/commands/agents-md.js +118 -0
- package/dist/commands/daemon-cmd.js +2 -0
- package/dist/commands/help.js +21 -0
- package/dist/commands/self-update.js +32 -5
- package/dist/commands/workspace.js +22 -6
- package/dist/daemon-client.js +10 -6
- package/dist/daemon-entry.js +1 -0
- package/dist/daemon-process.js +125 -0
- package/dist/daemon.js +131 -62
- package/dist/dashboard.js +118 -6
- package/dist/ipc-auth.js +3 -1
- package/dist/ipc-client.js +7 -6
- package/dist/ipc-path.js +92 -0
- package/dist/ipc-server.js +33 -33
- package/dist/npm-update.js +138 -0
- package/dist/session-binding.js +135 -0
- package/dist/session-target.js +91 -0
- package/dist/setup-service.js +9 -4
- package/dist/verb-surface.js +1 -0
- package/dist/watcher.js +9 -23
- package/dist/windows-hide.js +77 -0
- package/package.json +2 -2
- package/scripts/sync-skills.mjs +6 -0
- package/skills/agent-doodle-space/SKILL.md +1 -1
- package/skills/agent-home/SKILL.md +1 -1
- package/skills/brand-identity/SKILL.md +22 -5
- package/skills/clearly-agent/SKILL.md +38 -14
- package/skills/clearly-canvas/SKILL.md +31 -5
- package/skills/clearly-canvas/authoring.md +41 -12
- package/skills/clearly-canvas/example.mjs +72 -69
- package/skills/clearly-docs/SKILL.md +151 -0
- package/skills/clearly-init/SKILL.md +135 -0
- package/skills/clearly-workflows/SKILL.md +90 -0
- package/skills/clearly-workspace/SKILL.md +1 -1
- package/skills/codebase-map/SKILL.md +1 -1
- package/skills/design-craft/SKILL.md +9 -6
- package/skills/index.json +45 -10
- package/skills/layout-systems/SKILL.md +14 -11
- package/skills/pair-on-canvas/SKILL.md +1 -1
- package/skills/sticker-pack/SKILL.md +132 -0
- package/skills/visualize/SKILL.md +188 -0
|
@@ -1,78 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
2
|
+
/** Three editable page states. Writes JSON only; it never calls a workspace. */
|
|
3
3
|
import { writeFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
const [compositionId, output = '/tmp/fieldwork-build.json'] = process.argv.slice(2);
|
|
6
6
|
if (!compositionId) throw new Error('Usage: node example.mjs <composition-id> [output.json]');
|
|
7
|
-
const grid = { w: 1440, h: 900, margin: 88, gutter: 80 };
|
|
8
|
-
const ink = '#172824', paper = '#F2F0E7', orange = '#C84A2E', muted = '#58685E';
|
|
9
7
|
const batch = [];
|
|
10
8
|
const op = (action, args) => batch.push({ action, args });
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const rule = (p, y, color) => rect(p, `rule-${y}`, grid.margin, y, grid.w - grid.margin * 2, 1, color);
|
|
23
|
-
const masthead = (p, index, color) => {
|
|
24
|
-
text(p, 'brand', 'FIELDWORK', grid.margin, 52, 400, 20, color, 700, { letterSpacing: 2 });
|
|
25
|
-
text(p, 'series', 'DESIGN NOTES / VOL. 01', 912, 54, 440, 14, color, 400, { letterSpacing: 1 });
|
|
26
|
-
text(p, 'folio', 'STUDIO PRACTICE', grid.margin, 828, 900, 14, color, 400, { letterSpacing: 1 });
|
|
27
|
-
text(p, 'page', String(index).padStart(2, '0'), 1304, 828, 48, 14, color, 400, { align: 'right' });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Brief: show how one simple visual idea becomes a coherent editorial system.
|
|
31
|
-
// Hierarchy: statement → geometric proof → compact practical explanation.
|
|
32
|
-
// Motif: two fields meet to make a third; repeated with a different function on each slide.
|
|
33
|
-
const cover = 'fieldwork-01';
|
|
34
|
-
frame(cover, 0, paper); masthead(cover, 1, ink); rule(cover, 104, ink);
|
|
35
|
-
text(cover, 'eyebrow', 'A NOTE ON MAKING', 88, 168, 560, 16, orange, 700, { letterSpacing: 2 });
|
|
36
|
-
text(cover, 'title', 'Make room\nfor the idea.', 80, 228, 810, 108, ink, 700, { letterSpacing: -4, lineHeight: 1.02 });
|
|
37
|
-
text(cover, 'intro', 'Good design begins with a point of view.\nThe system gives it room to grow.', 88, 538, 740, 25, muted);
|
|
38
|
-
circle(cover, 'disc', 977, 233, 350, orange);
|
|
39
|
-
rect(cover, 'field', 916, 385, 270, 270, ink);
|
|
40
|
-
rect(cover, 'intersection', 977, 385, 209, 198, paper, { radius: 0 });
|
|
41
|
-
text(cover, 'motif-caption', '01 / TWO FIELDS, ONE IDEA', 920, 696, 420, 14, muted, 400, { letterSpacing: 1 });
|
|
42
|
-
rule(cover, 782, ink);
|
|
43
|
-
|
|
44
|
-
const method = 'fieldwork-02';
|
|
45
|
-
frame(method, 1, ink); masthead(method, 2, paper); rule(method, 104, '#607369');
|
|
46
|
-
text(method, 'title', 'One idea.\nMany expressions.', 80, 164, 1120, 92, paper, 700, { letterSpacing: -3, lineHeight: 1.04 });
|
|
47
|
-
text(method, 'intro', 'Keep the visual logic.\nChange what the content needs.', 952, 380, 400, 24, '#C7CFC4');
|
|
48
|
-
const concepts = [
|
|
49
|
-
{ x: 88, title: '01 / EMPHASIS', body: 'Let one thing lead.', kind: 0 },
|
|
50
|
-
{ x: 520, title: '02 / RELATIONSHIP', body: 'Make the connection visible.', kind: 1 },
|
|
51
|
-
{ x: 952, title: '03 / RHYTHM', body: 'Repeat with a reason.', kind: 2 },
|
|
52
|
-
];
|
|
53
|
-
for (const c of concepts) {
|
|
54
|
-
const y = 518;
|
|
55
|
-
if (c.kind === 0) { circle(method, 'emphasis', c.x, y, 152, orange); circle(method, 'quiet', c.x + 192, y + 92, 60, paper); }
|
|
56
|
-
if (c.kind === 1) { rect(method, 'relation-field', c.x, y + 20, 148, 120, paper); circle(method, 'relation-disc', c.x + 80, y, 152, orange); }
|
|
57
|
-
if (c.kind === 2) for (let i = 0; i < 4; i++) rect(method, `rhythm-${i}`, c.x + i * 78, y + i * 20, 48, 152 - i * 20, i === 3 ? orange : paper);
|
|
58
|
-
text(method, `label-${c.kind}`, c.title, c.x, 714, 380, 16, paper, 700, { letterSpacing: 1 });
|
|
59
|
-
text(method, `body-${c.kind}`, c.body, c.x, 748, 380, 20, '#C7CFC4');
|
|
9
|
+
const fillWidth = id => op('canvas.update-nodes', { ids: [id], patch: { layoutSizingH: 'fill' } });
|
|
10
|
+
// Page, section, row, card and button share this container contract.
|
|
11
|
+
function flow(id, parentId, box, layout, children) {
|
|
12
|
+
// Enable flow before adding children so reading order is creation order.
|
|
13
|
+
op('frame.create', { id, name: id, parentId, fill: 'transparent', ...box,
|
|
14
|
+
autoLayout: { direction: 'v', gap: 24, padding: 0, ...layout },
|
|
15
|
+
layoutSizingH: layout.sizingH ?? 'fixed', layoutSizingV: layout.sizingV ?? 'hug' });
|
|
16
|
+
children(id);
|
|
17
|
+
op('canvas.auto-layout', { id, direction: 'v', gap: 24, padding: 0,
|
|
18
|
+
sizingH: 'fixed', sizingV: 'hug', ...layout });
|
|
19
|
+
return id;
|
|
60
20
|
}
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
21
|
+
function text(parentId, name, copy, w, size, fill, extra = {}) {
|
|
22
|
+
const id = parentId + '/' + name;
|
|
23
|
+
op('canvas.create-node', { id, name, parentId, type: 'text', w, text: copy,
|
|
24
|
+
size, family: 'Inter', fill, lineHeight: 'auto', ...extra });
|
|
25
|
+
fillWidth(id);
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
function page(id, x, mobile = false, dark = false) {
|
|
29
|
+
const w = mobile ? 390 : 1440, pad = mobile ? 24 : 80, inner = w - pad * 2;
|
|
30
|
+
const ink = dark ? '#F2F0E7' : '#172824', ground = dark ? '#172824' : '#F2F0E7';
|
|
31
|
+
flow(id, undefined, { x, y: 0, w, h: 1000, fill: ground }, { gap: 64, padding: pad }, p => {
|
|
32
|
+
flow(p + '/navigation', p, { w: inner, h: 48 }, { direction: 'h', align: 'center', justify: 'space-between' }, nav => {
|
|
33
|
+
text(nav, 'brand', 'FIELDWORK', inner - 140, 20, ink, { fontWeight: 700 });
|
|
34
|
+
text(nav, 'edition', 'STUDIO NOTES', 140, 14, ink);
|
|
35
|
+
op('canvas.update-nodes', { ids: [nav + '/edition'], patch: { layoutSizingH: 'fixed' } });
|
|
36
|
+
});
|
|
37
|
+
fillWidth(p + '/navigation');
|
|
38
|
+
flow(p + '/hero', p, { w: inner, h: 420 }, { direction: mobile ? 'v' : 'h', gap: 48 }, hero => {
|
|
39
|
+
const copyWidth = mobile ? inner : 740;
|
|
40
|
+
flow(hero + '/copy', hero, { w: copyWidth, h: 400 }, { gap: 24 }, copy => {
|
|
41
|
+
text(copy, 'eyebrow', 'A NOTE ON MAKING', copyWidth, 14, ink, { letterSpacing: 1.5 });
|
|
42
|
+
text(copy, 'title', 'Make room for the idea.', copyWidth, mobile ? 48 : 88, ink,
|
|
43
|
+
{ fontWeight: 700, letterSpacing: -2, lineHeight: '105%' });
|
|
44
|
+
text(copy, 'description', 'A shared direction. An editable system. Room for the next idea.', copyWidth, 22, ink, { lineHeight: '145%' });
|
|
45
|
+
flow(copy + '/action', copy, { w: 240, h: 56, fill: ink, radius: 8 },
|
|
46
|
+
{ direction: 'h', padding: 16, align: 'center' }, button => {
|
|
47
|
+
text(button, 'label', 'Explore the studio', 208, 16, ground, { align: 'center' });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
fillWidth(hero + '/copy');
|
|
51
|
+
// Art-directed coordinates belong inside an owning artwork frame.
|
|
52
|
+
const art = hero + '/artwork', aw = mobile ? inner : 400;
|
|
53
|
+
op('frame.create', { id: art, name: 'Artwork / two fields', parentId: hero, w: aw, h: 320, fill: '#C84A2E' });
|
|
54
|
+
op('canvas.create-node', { id: art + '/disc', name: 'Disc', type: 'ellipse', parentId: art, x: 24, y: 24, w: 220, h: 220, fill: '#F2F0E7' });
|
|
55
|
+
op('canvas.create-node', { id: art + '/field', name: 'Field', type: 'rect', parentId: art, x: 116, y: 136, w: 196, h: 160, fill: '#172824' });
|
|
56
|
+
});
|
|
57
|
+
fillWidth(p + '/hero');
|
|
58
|
+
flow(p + '/features', p, { w: inner, h: 220 }, { direction: 'grid', columns: mobile ? 1 : 3,
|
|
59
|
+
rows: mobile ? 3 : 1, rowSizes: mobile ? ['hug', 'hug', 'hug'] : ['hug'], gap: 24, rowGap: 24 }, grid => {
|
|
60
|
+
for (const [name, title, body] of [
|
|
61
|
+
['type', 'Give ideas a voice.', 'Type scales and wraps with the available space.'],
|
|
62
|
+
['space', 'Keep room to grow.', 'Content owns its height. Related layers move together.'],
|
|
63
|
+
['review', 'Make the next edit.', 'Resize a card and extend its copy to check the system.'],
|
|
64
|
+
]) {
|
|
65
|
+
const card = grid + '/' + name, cw = mobile ? inner : (inner - 48) / 3;
|
|
66
|
+
flow(card, grid, { w: cw, h: 180, fill: dark ? '#294337' : '#FFFFFF', radius: 12 }, { gap: 16, padding: 24 }, c => {
|
|
67
|
+
text(c, 'title', title, cw - 48, 24, ink, { fontWeight: 600 });
|
|
68
|
+
text(c, 'body', body, cw - 48, 18, ink, { lineHeight: '145%' });
|
|
69
|
+
});
|
|
70
|
+
fillWidth(card);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
fillWidth(p + '/features');
|
|
74
|
+
text(p, 'footer', 'FIELDWORK / A SYSTEM READY FOR CHANGE', inner, 14, ink, { letterSpacing: 1 });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
page('01-desktop', 0);
|
|
78
|
+
page('02-mobile', 1520, true);
|
|
79
|
+
page('03-dark', 1990, false, true);
|
|
77
80
|
writeFileSync(output, JSON.stringify({ compositionId, batch }, null, 2) + '\n');
|
|
78
|
-
process.stdout.write(
|
|
81
|
+
process.stdout.write(output + ': ' + batch.length + ' operations\n');
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-docs
|
|
3
|
+
description: >-
|
|
4
|
+
Read, search, edit and recover a company's documents in a Clearly workspace.
|
|
5
|
+
Use the seven-tool MCP filesystem for ordinary work; use the `beehaven` CLI
|
|
6
|
+
only for document history, diffs, and restore. Load this whenever the answer
|
|
7
|
+
depends on what the company has written down, or when you are about to write
|
|
8
|
+
something down for it. Triggers: "what do our docs say", "find the doc about
|
|
9
|
+
X", "update the spec", "write this up", "add it to the docs", "has this
|
|
10
|
+
changed", "what did they edit", "search the wiki", "company knowledge", "our
|
|
11
|
+
documentation", "the PRD", "the runbook".
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
> **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may expose them
|
|
15
|
+
> with a server prefix — e.g. `mcp__plugin_clearly_clearly__clearly_read`. **Match by suffix,
|
|
16
|
+
> not by exact name**: a skill written against the bare name resolves to nothing otherwise, and
|
|
17
|
+
> the failure looks like "the tool doesn't exist" rather than "the name is decorated".
|
|
18
|
+
>
|
|
19
|
+
> **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
|
|
20
|
+
> session — these skills still LIST when it isn't, so you find out by firing a dead call.
|
|
21
|
+
> Authorise it (`/mcp`, or `claude mcp`).
|
|
22
|
+
|
|
23
|
+
## What this is
|
|
24
|
+
|
|
25
|
+
The workspace is a **filesystem**, not a chat surface with a search box.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
~/ every document
|
|
29
|
+
~/clearly.md what is settled for the whole workspace
|
|
30
|
+
~/<Folder>/ one directory per project
|
|
31
|
+
~/<Folder>/clearly.md what is settled for that folder
|
|
32
|
+
~/.archive/ put away — hidden, readable, read-only, still searched
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Folders are projects. Documents are `.md` files named `<KEY> <Title>.md` — e.g.
|
|
36
|
+
`CLR-42 Pricing.md`. The key is an address: `cat ~/CLR-42.md` works from anywhere, and so does
|
|
37
|
+
the full name and the bare title.
|
|
38
|
+
|
|
39
|
+
## The loop
|
|
40
|
+
|
|
41
|
+
**Find it, then open it.** `clearly_glob` answers "what is it called", `clearly_grep` answers
|
|
42
|
+
"where is this mentioned", and `clearly_read` opens what either one returned.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
clearly_glob { pattern: "~/**/*.md" }
|
|
46
|
+
clearly_grep { pattern: "pricing", limit: 5 }
|
|
47
|
+
clearly_read { target: "~/Prepress/CLR-42 Pricing.md" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
⚠ **`clearly_grep` takes a REGEX, unparsed** — `|`, `(`, `$` and quotes mean what they mean in a
|
|
51
|
+
regular expression. Nothing shells out, so nothing eats them as syntax first.
|
|
52
|
+
|
|
53
|
+
⚠ **Archived documents are excluded from both.** When any matched, the result carries
|
|
54
|
+
`archivedHidden` saying how many — mention that rather than reporting a clean miss. Search the
|
|
55
|
+
archive by naming it: `clearly_grep { pattern: …, path: "~/.archive" }`.
|
|
56
|
+
|
|
57
|
+
**Structural questions — a date range, a null body, counts — have no keyword to search for**, so
|
|
58
|
+
they go to SQL over the workspace's own tables. That is a CLI job, not an MCP tool:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
beehaven call bash '{"command":"sql SELECT title, updated_at FROM notes WHERE project_id IS NULL LIMIT 20"}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Read-only, one statement, credential columns redacted.
|
|
65
|
+
|
|
66
|
+
## ⚠⚠ Read before you write, and check what changed
|
|
67
|
+
|
|
68
|
+
This is the part that makes documentation stay true, and it is the part agents skip.
|
|
69
|
+
|
|
70
|
+
The MCP read/edit/replace tools cover the current body. Document history is a separate CLI surface:
|
|
71
|
+
|
|
72
|
+
- **`beehaven call document-status '{"limit":20}'`** lists recent changes. `mine: true` is the
|
|
73
|
+
certain signal that the active agent made the latest change. `by: "unknown"` means the log has
|
|
74
|
+
no author; it may be a browser edit or an older unstamped agent write, so do not claim a person
|
|
75
|
+
made it.
|
|
76
|
+
- **`beehaven call document-diff '{"id":"<documentId>"}'`** follows up with added and removed
|
|
77
|
+
lines. Pass a `versionId` from status to compare against a specific checkpoint.
|
|
78
|
+
|
|
79
|
+
Run status when you resume a conversation, and whenever they say something implying the document
|
|
80
|
+
has moved — *"I rewrote that"*, *"take another look"*.
|
|
81
|
+
|
|
82
|
+
⚠ **Their edit wins, always.** If a document now disagrees with something you concluded
|
|
83
|
+
earlier, your conclusion is the stale one. You are keeping their documents, not defending your
|
|
84
|
+
own record. Never quietly restore what they took out.
|
|
85
|
+
|
|
86
|
+
## Editing
|
|
87
|
+
|
|
88
|
+
**`clearly_edit` (find/replace) is the default. `clearly_replace` REPLACES the whole document.**
|
|
89
|
+
|
|
90
|
+
To change one line of a 12,000-character document, `replace` makes you reproduce all 12,000 — and
|
|
91
|
+
anything you fail to retype is gone with no error. It is also a lost-update machine: it writes
|
|
92
|
+
whatever you last read, overwriting anything the human changed in between.
|
|
93
|
+
|
|
94
|
+
- `clearly_edit` requires the `find` text to match **exactly once**. Ambiguity is REFUSED with a
|
|
95
|
+
count, never guessed — an agent that edits the first of three matches has changed something
|
|
96
|
+
nobody looked at.
|
|
97
|
+
- Several independent document edits can be sent in one tool call with `batch`, but each batch
|
|
98
|
+
row is still one exact `{ target, find, replace, all? }` operation. There is no `multi_edit`
|
|
99
|
+
field and the batch is not transactional; inspect the per-item results and retry only failures.
|
|
100
|
+
- `clearly_replace` does not accept `expected_content` or a commit-message field. If you must replace
|
|
101
|
+
a whole existing document, re-read it immediately before writing and verify the returned body.
|
|
102
|
+
Prefer `clearly_edit` whenever a precise passage change can express the job.
|
|
103
|
+
|
|
104
|
+
## Comments and @mentions — say it beside the work, not instead of it
|
|
105
|
+
|
|
106
|
+
A comment is ABOUT something; an edit changes it. When you have a question or a note for a person,
|
|
107
|
+
leave it where they will see it, on the thing itself:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
clearly_comment { target: "CLR-42", content: "Is this still the Q3 number? @sam" }
|
|
111
|
+
clearly_comment { target: "<sheetId>", content: "Double-counts refunds", fields: { cell: "Plan!D12" } }
|
|
112
|
+
clearly_comment { target: "<deckId>", content: "Tighten this headline", fields: { slide: 3 } }
|
|
113
|
+
clearly_read { type: "comment", target: "SS-41" } # the thread, on any item
|
|
114
|
+
clearly_read { type: "comment", target: "@me" } # where YOU were @mentioned
|
|
115
|
+
clearly_edit { type: "comment", target: "<mentionId>", changes: { acknowledged: true } }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`target` is the item — a document, sheet, deck, ticket, board, thought or canvas, by id, key or exact
|
|
119
|
+
title. Anchor with `fields.quote` (document), `slide` (deck, required), `cell` (sheet), `column`
|
|
120
|
+
(board) or `pinId` / `x`,`y` (canvas); an anchor the item cannot carry is refused, never dropped.
|
|
121
|
+
|
|
122
|
+
`@handle` mentions a person (notified) or an agent (queued in its inbox). Get real handles with
|
|
123
|
+
`clearly_read { type: "comment", target, includeMentionables: true }`. The write's result lists
|
|
124
|
+
`mentions`, and `unmatchedMentions` for any handle that reached **no one** — say so, do not assume.
|
|
125
|
+
Acknowledge a mention once you have acted on it, or it comes back in every session.
|
|
126
|
+
|
|
127
|
+
## `clearly.md` — what is settled
|
|
128
|
+
|
|
129
|
+
Not a document like the others. It holds what this place IS (the work, the constraints, the
|
|
130
|
+
vocabulary, the decisions that stopped moving) and **how this person wants to be worked with** —
|
|
131
|
+
length, tone, format, and what irritates them, which is said once in passing and worth more than
|
|
132
|
+
anything they praise.
|
|
133
|
+
|
|
134
|
+
Fold a conclusion into it once it has stopped moving. Reach for `edit`, never `replace`.
|
|
135
|
+
|
|
136
|
+
## Report what MOVED
|
|
137
|
+
|
|
138
|
+
*"You added a paragraph about the 12-hour window to Pricing and removed the line about three
|
|
139
|
+
credits"* is a report. *"I've updated the note"* is not — it costs a turn and says nothing they
|
|
140
|
+
could not have assumed.
|
|
141
|
+
|
|
142
|
+
## What not to do
|
|
143
|
+
|
|
144
|
+
- **Do not tidy unasked.** A document that looks redundant or superseded is still theirs. If two
|
|
145
|
+
say the same thing, say so and let them choose.
|
|
146
|
+
- **`delete` archives, it does not erase** — the document moves to `~/.archive/`, stays readable
|
|
147
|
+
and comes back. Report it as **archived**; "deleted" makes someone believe something is gone
|
|
148
|
+
that is not.
|
|
149
|
+
- **Do not invent a media line.** `` renders a photo, audio or video by file
|
|
150
|
+
extension. You cannot upload, so never write one pointing at a URL you guessed — it renders
|
|
151
|
+
broken, which reads as their photo being lost. Move existing lines; never fabricate one.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-init
|
|
3
|
+
description: >-
|
|
4
|
+
Connect a Clearly workspace to Claude Code or Codex and prove both the MCP and
|
|
5
|
+
CLI paths work. Prefer browser OAuth for remote HTTP clients; when an agent-bound
|
|
6
|
+
bearer credential is required, mint and install it with the active `beehaven`
|
|
7
|
+
identity. Use FIRST whenever a Clearly tool is refused for auth, returns nothing
|
|
8
|
+
on a workspace that should have content, or the user has never connected.
|
|
9
|
+
Triggers: "connect Clearly", "set up Clearly", "sign in to Clearly",
|
|
10
|
+
"authenticate", "install the MCP", "add the MCP token", "it says unauthorized",
|
|
11
|
+
"the tools are not showing up", "/mcp shows nothing", "which workspace am I in".
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
> **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may
|
|
15
|
+
> expose them with a server prefix — e.g.
|
|
16
|
+
> `mcp__plugin_clearly_clearly-staging__clearly_read`. **Match by suffix, not by
|
|
17
|
+
> exact name**: a skill written against the bare name resolves to nothing otherwise, and
|
|
18
|
+
> the failure looks like "the tool doesn't exist" rather than "the name is decorated".
|
|
19
|
+
>
|
|
20
|
+
> **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
|
|
21
|
+
> session — note that these skills still LIST when it isn't, so you find out by firing a
|
|
22
|
+
> dead call. Authorise it (`/mcp`, or `claude mcp`), or if you have a shell, use the
|
|
23
|
+
> `beehaven` CLI and its own `clearly-canvas` skill instead.
|
|
24
|
+
|
|
25
|
+
# `/clearly:init` — connect a Clearly workspace to Claude Code or Codex
|
|
26
|
+
|
|
27
|
+
Run this once per machine. The plugin's `.mcp.json` points at
|
|
28
|
+
`https://relay.clearly.sh/mcp`; complete authentication and verify the connection before doing
|
|
29
|
+
workspace work.
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
|
|
33
|
+
### 1. Authenticate (OAuth — browser sign-in)
|
|
34
|
+
|
|
35
|
+
The plugin already wires the MCP server. Prefer browser OAuth:
|
|
36
|
+
|
|
37
|
+
- **In Claude Code (recommended)**: run `/mcp`, select `clearly`, and choose **Authenticate**. A browser window opens for sign-in; approve and it closes itself. No token.
|
|
38
|
+
- **From the shell**: `claude mcp login clearly` — same browser sign-in flow.
|
|
39
|
+
- **In Codex**: `codex mcp login clearly`. If the server was added outside the plugin first, run
|
|
40
|
+
`codex mcp add clearly --url https://relay.clearly.sh/mcp`, then log in.
|
|
41
|
+
|
|
42
|
+
The sign-in grants scoped access (`rpc:read` for search and read; add `rpc:write` to let the
|
|
43
|
+
agent write documents and create boards or tickets). Sign out or revoke with the client's MCP
|
|
44
|
+
logout command, or from Settings → **Developers** in the app.
|
|
45
|
+
|
|
46
|
+
### Agent-bound token path (CLI and automation)
|
|
47
|
+
|
|
48
|
+
Use this when the client cannot complete OAuth or when the credential must act as a specific agent
|
|
49
|
+
identity. Sign in and select the identity first:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
beehaven agent login <agent-name>
|
|
53
|
+
beehaven connect home
|
|
54
|
+
beehaven agent whoami
|
|
55
|
+
beehaven mcp install --client codex
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The installer writes through the client's own config command and never prints the token. Restart
|
|
59
|
+
the client afterward. A static token normally carries `rpc:read` and `rpc:write`; `agent:ask` is
|
|
60
|
+
OAuth-only and `rpc:admin` is reserved for configured platform admins. Never paste a bearer token
|
|
61
|
+
into a document, ticket, canvas, log, or chat response.
|
|
62
|
+
|
|
63
|
+
> **Working in more than one workspace?** A client stores one credential per server entry, so two
|
|
64
|
+
> workspaces behind a single entry collide. Use `https://relay.clearly.sh/mcp/w/<workspaceId>` as a
|
|
65
|
+
> second entry — see [the MCP docs](https://clearly.sh/docs/mcp).
|
|
66
|
+
|
|
67
|
+
### 2. Verify
|
|
68
|
+
|
|
69
|
+
Run `/mcp` in Claude Code, or `codex mcp list` in Codex. You should see `clearly` authenticated
|
|
70
|
+
with **7 tools**:
|
|
71
|
+
|
|
72
|
+
**Seven MCP tools, split by what they DO rather than by what they act on.** The four CRUD tools
|
|
73
|
+
cover every artifact type — document, canvas, sheet, deck, board, ticket, project — so the tool fixes
|
|
74
|
+
whether it reads or writes and `type` says what it touches.
|
|
75
|
+
|
|
76
|
+
- **`clearly_catalog`** — optional, lazy machine-readable schemas. Use `{ type, operation }` before a type-specific write/edit; omit both for the compact capability overview.
|
|
77
|
+
- **`clearly_read` / `clearly_create` / `clearly_edit` / `clearly_replace` / `clearly_delete`** — open one, make a new one, change part of one, overwrite one's whole body, archive one. Each takes a uniform same-operation `batch`.
|
|
78
|
+
- **`clearly_comment`** — comment on any item (with @mentions), or send and reply to messages.
|
|
79
|
+
- **`clearly_grep` / `clearly_glob`** — find by content, find by name. Both span notes and code.
|
|
80
|
+
|
|
81
|
+
Addressed by `target`: a path (`~/Q1/Plan.md`), a key (`CLR-42`) or an id. Folders are projects,
|
|
82
|
+
documents are `.md`, canvases are `.scene.json` — the extension names the type, and you pass `type`
|
|
83
|
+
when it does not.
|
|
84
|
+
|
|
85
|
+
**Everything else is the `beehaven` CLI**, by action name — the canvas, the Company Brain, skills,
|
|
86
|
+
scheduling, stickers, billing. `beehaven actions` lists them; `beehaven call <action> '<json>'` runs
|
|
87
|
+
one:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
beehaven call context-search '{"query":"per-seat billing","scope":"org"}'
|
|
91
|
+
beehaven call canvas-act '{"compositionId":"c_…","batch":[…]}'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
⚠ **That split is deliberate, not a gap.** A single tool that runs whatever action the caller names
|
|
95
|
+
spans safe and unsafe operations at once — a connector-directory rejection criterion, and one the
|
|
96
|
+
criteria explicitly refuse to let a description paper over. So the generic door is the shell, where
|
|
97
|
+
Claude Code already lives and where every call is attributed to your agent.
|
|
98
|
+
|
|
99
|
+
> ⚠ **Everything requires authentication.** There is no anonymous surface: as of 2026-09-11 both
|
|
100
|
+
> `tools/list` and `tools/call` resolve a credential first, and every credential is bound to an
|
|
101
|
+
> agent. A 401 carries an OAuth challenge, so a capable client can recover from it by itself —
|
|
102
|
+
> but a caller with no token sees no catalog, by design.
|
|
103
|
+
|
|
104
|
+
## Install the CLI — the other half of the surface
|
|
105
|
+
|
|
106
|
+
The seven MCP tools cover reading, writing and searching artifacts. **Everything else — the canvas,
|
|
107
|
+
the Company Brain, skills, stickers — is reached with `beehaven`**, and the skills in this plugin
|
|
108
|
+
assume it is on PATH. Claude Code has a shell, so this is one command:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl -fsSL https://clearly.sh/install.sh | sh
|
|
112
|
+
beehaven login # once, opens a browser
|
|
113
|
+
beehaven status # daemon + relay, both should read UP
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
⚠ **`beehaven: command not found` is the failure to expect**, and it does not look like a missing
|
|
117
|
+
install — it looks like the skill being wrong. If a `beehaven call …` line fails that way, install
|
|
118
|
+
it rather than reaching for an MCP tool that does not exist.
|
|
119
|
+
|
|
120
|
+
⚠ **Do not restart the user's daemon to fix something.** It is theirs; `beehaven status` tells you
|
|
121
|
+
what is wrong and the message says which command fixes it.
|
|
122
|
+
|
|
123
|
+
Then load the usage skill: it teaches the Company Brain + self-prompting loops — see `clearly-workflows`.
|
|
124
|
+
|
|
125
|
+
A quick functional test:
|
|
126
|
+
> "Map my Clearly workspace's brain using the clearly MCP."
|
|
127
|
+
|
|
128
|
+
If Claude calls `clearly_catalog` and gets back the artifact types with their capabilities, the connection is live. It is a connection check and schema lookup, not a required preflight for ordinary reads/deletes.
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
- **`Missing scope "rpc:write"`** — the sign-in didn't grant write access. Run `claude mcp logout clearly`, then re-authenticate (`/mcp` → **Authenticate**) and approve the write scope.
|
|
133
|
+
- **`Not authenticated` / calls rejected** — the session expired or was revoked. Re-authenticate with `claude mcp login clearly` (or `/mcp` → **Authenticate**).
|
|
134
|
+
- **`clearly` shows in `/mcp` but as unauthenticated** — the endpoint is preconfigured by the plugin; you still need to complete the browser sign-in. Select `clearly` → **Authenticate**.
|
|
135
|
+
- **`Network error reaching ...`** — relay.clearly.sh might be down (rare); check https://clearly.sh status page.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clearly-workflows
|
|
3
|
+
description: >-
|
|
4
|
+
Best with the beehaven CLI (semantic search and write-back); with only the Clearly connector, search with clearly_grep and record decisions with clearly_create.
|
|
5
|
+
Use the Clearly workspace as a COMPANY BRAIN — search the org's accumulated
|
|
6
|
+
context (decisions, facts, prompts, docs), write conclusions back so the next
|
|
7
|
+
agent inherits them instead of re-deriving them, and inherit the team's
|
|
8
|
+
skills. Use when a question is about what this company already decided or
|
|
9
|
+
knows, and after you work something out that the next person would otherwise
|
|
10
|
+
rediscover. Triggers: "what did we decide", "why did we do it this way", "has
|
|
11
|
+
anyone looked at this", "remember this", "write that down", "what do we know
|
|
12
|
+
about X", "company context", "our decisions", "prior art", "institutional
|
|
13
|
+
knowledge", "so nobody has to work this out again".
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
> **Tool names below are written UNPREFIXED** (`clearly_read`). Your runtime may
|
|
17
|
+
> expose them with a server prefix — e.g.
|
|
18
|
+
> `mcp__plugin_clearly_clearly-staging__clearly_read`. **Match by suffix, not by
|
|
19
|
+
> exact name**: a skill written against the bare name resolves to nothing otherwise, and
|
|
20
|
+
> the failure looks like "the tool doesn't exist" rather than "the name is decorated".
|
|
21
|
+
>
|
|
22
|
+
> **If no such tool is callable at all**, the Clearly MCP server isn't authorised in this
|
|
23
|
+
> session — note that these skills still LIST when it isn't, so you find out by firing a
|
|
24
|
+
> dead call. Authorise it (`/mcp`, or `claude mcp`), or if you have a shell, use the
|
|
25
|
+
> `beehaven` CLI and its own `clearly-canvas` skill instead.
|
|
26
|
+
|
|
27
|
+
# Working a Clearly workspace (Company Brain)
|
|
28
|
+
|
|
29
|
+
Clearly is a **company brain over MCP**: every prompt + agent response is auto-cataloged into a searchable, ranked archive, with documents, decisions, and durable facts alongside. Treat it the way you'd treat a repo — read before you write, and write so the next agent inherits your work.
|
|
30
|
+
|
|
31
|
+
If the `clearly` server isn't connected yet, run the `clearly-init` skill first.
|
|
32
|
+
|
|
33
|
+
## 1. Orient before you act
|
|
34
|
+
|
|
35
|
+
Run `beehaven call context-map` once at the start. It returns the brain's size (prompts / responses / facts), the recurring topics, and the org/teams it can federate across. Use it to decide whether the answer already exists.
|
|
36
|
+
|
|
37
|
+
## 2. Retrieve context (don't re-derive it)
|
|
38
|
+
|
|
39
|
+
`beehaven call context-search '{ "query": …, "scope"?, "kinds"?, "limit"? }'` is ONE ranked search across prompts, agent responses, documents/PRDs, and facts — use it instead of guessing or asking the user what they already decided.
|
|
40
|
+
|
|
41
|
+
- `scope: "workspace"` (default) searches this workspace.
|
|
42
|
+
- `scope: "org"` **federates across every workspace in the org**, merges + re-ranks by relevance, and excludes private artifacts + personal facts. Use it for "what has anyone on the team decided/built about X".
|
|
43
|
+
- `kinds`: narrow to `["document"]`, `["prompt"]`, etc.
|
|
44
|
+
|
|
45
|
+
Each hit carries `kind`, `ref` (the id to fetch in full), `title`, `snippet`, `score`, and `meta` (intent, tags, `sourceWorkspace`).
|
|
46
|
+
|
|
47
|
+
**Always search before writing** — it prevents duplicate PRDs/decisions and surfaces prior context you'd otherwise miss.
|
|
48
|
+
|
|
49
|
+
## 3. Write context BACK so it compounds
|
|
50
|
+
|
|
51
|
+
When you produce something durable — a PRD, a decision, a spec, a research summary — file it with `context-write`:
|
|
52
|
+
|
|
53
|
+
```jsonc
|
|
54
|
+
beehaven call context-write '{
|
|
55
|
+
"title": "Per-seat billing decision",
|
|
56
|
+
"body": "We charge per seat; usage is per-seat with shared top-up overflow. Rationale: …",
|
|
57
|
+
"kind": "decision", // doc | note | decision | prd
|
|
58
|
+
"tags": ["billing","pricing"],
|
|
59
|
+
"fact": "Billing is per-seat with shared top-up", // optional one-line takeaway
|
|
60
|
+
"private": false // true → workspace-local, never federated across the org
|
|
61
|
+
}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
It creates the document AND files a searchable catalog entry, so the very next `context-search` finds it. Mark anything sensitive `private: true`.
|
|
65
|
+
|
|
66
|
+
⚠ **Why these are CLI calls and not MCP tools.** The MCP surface is seven tools split by what they do to an artifact (`clearly_catalog · _read · _write · _edit · _delete · _grep · _glob`); the Company Brain's verbs are workspace ACTIONS, reached by name through the shell. Once a document exists, `clearly_grep` and `clearly_read` open it like any other.
|
|
67
|
+
|
|
68
|
+
## 4. Inherit the team's skills
|
|
69
|
+
|
|
70
|
+
The workspace has its own skills (procedures the team grew). Before improvising a multi-step task, check whether one exists:
|
|
71
|
+
|
|
72
|
+
- `beehaven call skill-list '{"includeWorkspace":true}'` → the skill cards (name + when-to-use).
|
|
73
|
+
- `beehaven call skill-get '{"id":"…"}'` → the full step-by-step instructions for the one whose trigger matches.
|
|
74
|
+
|
|
75
|
+
Follow the loaded procedure rather than inventing your own — that's how you work *the way this workspace works*.
|
|
76
|
+
|
|
77
|
+
## Anything else
|
|
78
|
+
|
|
79
|
+
`beehaven actions` lists every workspace action with its description; `beehaven call <action> '<json>'` runs one. Reach for these when the seven MCP tools don't cover what you need — which is most things beyond reading, writing and searching artifacts.
|
|
80
|
+
|
|
81
|
+
## Rules of thumb
|
|
82
|
+
|
|
83
|
+
- **Read → act → write back.** Search first; file durable outcomes so they persist.
|
|
84
|
+
- **`scope:"org"`** for cross-team questions; **`private:true`** for sensitive writes.
|
|
85
|
+
|
|
86
|
+
<!-- A "Schedule your future self" section documenting `clearly_schedule_wake` was removed on
|
|
87
|
+
2026-08-02: the tool is in ZERO entries of mcp-server.ts's tool list and `schedule-wake` is
|
|
88
|
+
not a registered RPC, so every example here failed. Restore only when it is in tools/list. -->
|
|
89
|
+
|
|
90
|
+
- **Check `beehaven call skill-list`** before hand-rolling a known procedure.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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).
|
|
3
|
+
description: Needs a terminal with the beehaven CLI (Claude Code, Codex, Gemini CLI); in a chat with only the Clearly connector, say so and use the clearly_* tools instead. 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
4
|
---
|
|
5
5
|
|
|
6
6
|
# clearly-workspace — know what you are standing in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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.
|
|
3
|
+
description: Needs a terminal with the beehaven CLI (Claude Code, Codex, Gemini CLI); in a chat with only the Clearly connector, say so and use the clearly_* tools instead. 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
4
|
---
|
|
5
5
|
|
|
6
6
|
> **⚠ CANVAS WORK GOES THROUGH THE CLI, NOT AN MCP TOOL.** Anything written
|
|
@@ -114,8 +114,9 @@ divided by the ratio. Sizes that share a ratio look related; sizes picked by han
|
|
|
114
114
|
make them obviously different in class (a grotesque with a serif), never two neighbours.
|
|
115
115
|
- **Measure: 45–75 characters.** At 16px in a normal grotesque that is roughly **360–600px**.
|
|
116
116
|
Set the text node's `w` to hold that, not to fill the column.
|
|
117
|
-
- **Line height
|
|
118
|
-
**
|
|
117
|
+
- **Line height defaults to Auto**, using the font's metrics. When tuning it deliberately,
|
|
118
|
+
use **140–160%** for body, **125–135%** for subheads, and **95–105%** for display type.
|
|
119
|
+
Author explicit units (`lineHeight:"140%"` or `"24px"`); percentage leading follows font size.
|
|
119
120
|
- ⚠⚠ **TRACK DISPLAY TYPE NEGATIVE.** Type designed at 16px is far too loose at 120px.
|
|
120
121
|
Above ~40px use `letterSpacing: -1` to `-4`; above ~120px, −3 to −6. **Uppercase and small
|
|
121
122
|
labels go the other way: +1 to +3.** This one adjustment does more for perceived quality
|
|
@@ -250,8 +251,8 @@ only to show that a review happened.
|
|
|
250
251
|
|
|
251
252
|
**A 1080×1350 social post**
|
|
252
253
|
```
|
|
253
|
-
margin 72 · display 96–140 (weight 800, tracking −3, lineHeight
|
|
254
|
-
body 20–24 (weight 500, lineHeight
|
|
254
|
+
margin 72 · display 96–140 (weight 800, tracking −3, lineHeight "100%")
|
|
255
|
+
body 20–24 (weight 500, lineHeight "150%", w ≤ 560)
|
|
255
256
|
caption 16 (weight 600, tracking +1.5, uppercase)
|
|
256
257
|
rule 2–3px accent · footer block bottom-aligned at 1350 − 72 − blockHeight
|
|
257
258
|
```
|
|
@@ -265,7 +266,7 @@ Content column ≤ 1200 wide even though the slide is 1920 — full width is unr
|
|
|
265
266
|
|
|
266
267
|
**A 1440 web hero**
|
|
267
268
|
```
|
|
268
|
-
margin 96 · content max 1200 · headline 64–88 (tracking −2, lineHeight
|
|
269
|
+
margin 96 · content max 1200 · headline 64–88 (tracking −2, lineHeight "105%")
|
|
269
270
|
sub 20–24 (muted ink, w ≤ 640) · CTA 48–56 tall, 24–32 horizontal padding, radius 8 or full
|
|
270
271
|
```
|
|
271
272
|
|
|
@@ -273,7 +274,9 @@ sub 20–24 (muted ink, w ≤ 640) · CTA 48–56 tall, 24–32 horizontal paddi
|
|
|
273
274
|
```
|
|
274
275
|
padding 24–32 · radius 10–16 · gap between cards ≥ 2× padding
|
|
275
276
|
title 20–24/600 · body 15–16/400 muted · one accent element maximum
|
|
276
|
-
|
|
277
|
+
Use a filled frame that owns the card's contents. Apply auto layout with this padding
|
|
278
|
+
and a deliberate content gap; let text hug its height. Rectangles serve artwork,
|
|
279
|
+
swatches and rules. A background rectangle with sibling text does not form a card.
|
|
277
280
|
```
|
|
278
281
|
|
|
279
282
|
---
|