You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ce0e3c1
Browse filesBrowse the repository at this point in the historyBrowse files
description: The words and the voice for every piece of text a person reads in OpenBot — screen headings and descriptions, buttons, empty states, notices, chat cards, and the server's error and refusal messages that reach the browser. Holds the glossary (Bot, app, admin, OpenBot) and the style rules. Use when writing or changing any text that is shown to a person, naming a screen, a section, a button or a state, writing an error or refusal message, or reviewing a diff that adds user-facing strings under app/src or server/src. Don't use for text written for the model (tool descriptions, standing instructions, system prompts), for code comments, or for the docs under docs/.
4
+
---
5
+
6
+
# OpenBot Copy
7
+
8
+
## When To Use
9
+
10
+
Any string a person reads: a heading, a description, a button, a placeholder, an empty state, a
11
+
notice, a chat card, a server error that the browser shows. Server messages count: a 409 that says
12
+
what to do next is read by a person as much as any screen is.
13
+
14
+
Not text written for the model. Tool descriptions, standing instructions and prompts steer how a Bot
15
+
behaves, and changing their wording is a behaviour change with its own review. Not code comments,
16
+
and not `docs/`, which has its own register.
17
+
18
+
## The Glossary
19
+
20
+
One word for one thing, everywhere a person can see it.
21
+
22
+
| Say | For | Not |
23
+
|---|---|---|
24
+
|**Bot**, **Bots** (capitalised) | What a person creates, talks to and gives work to. | agent, coworker, assistant, AI |
25
+
|**agent**| Only an outside framework's program, in the places that are about connecting one: "an agent you already run", "AG-UI agent". | Bot, when the thing is a Bot |
26
+
|**app**| A service a Bot uses: Gmail, Google Drive, Notion. | connector, integration, plugin (outside Admin) |
27
+
|**Connections**| The apps and accounts a Bot uses as you, and the page that lists them. | connected accounts, linked accounts |
28
+
|**connect / disconnect / reconnect**| What a person does to their own account. | link (except GitHub and Slack identity, where "link" is the vendor's own word) |
29
+
|**Capabilities**| Your skills and your Bots' memory of you, and the page with both. | plugins |
30
+
|**skill**| A named instruction invoked with `/`. | command, prompt |
31
+
|**admin**| The person who runs OpenBot for everybody. | administrator, owner, operator |
32
+
|**OpenBot**| The product the person is using. | this deployment, the server, the instance |
33
+
|**turn on / turn off**| An admin switching an app or a feature. | enable / disable (in sentences; a switch may still read "Enabled") |
34
+
|**conversation**| A thread with one or more Bots. | channel, chat (in sentences) |
35
+
36
+
**Admin is allowed exact names.** An admin page may name `COMPOSIO_API_KEY`, OAuth and "deployment",
37
+
because the admin is the one who sets them. A page anybody can open may not.
38
+
39
+
## The Style
40
+
41
+
1.**Say what happened, then what to do.** "Gmail isn't connected. Connect it to let Bots read your
42
+
mail." Not why the screen is built the way it is.
43
+
2.**Use complete sentences for explanations.** Page descriptions, helper text, notices, and empty-state bodies need a subject and a verb, or a direct instruction ("Connect your accounts"). Avoid fragments such as "The apps your Bots use" or "What this Bot can do." Headings, labels, buttons, and short status labels may be fragments.
44
+
**Keep only useful explanation.** State the action, outcome, or constraint the person needs. Remove copy that merely repeats the heading. Use natural sentence lengths; do not chop prose into fragments to meet a word count.
45
+
3.**Plain words.** "use", not "hold a grant on". "can't", not "is unable to". Contractions are fine.
46
+
4.**No "rather than", no em dashes, no "which is why", no "so that nothing … that may be wrong".**
47
+
These are the marks of text that is explaining itself. Cut the clause or make it its own sentence.
deployment, broker, endpoint: say what the person sees instead ("not set up yet", "your account",
50
+
"this Bot can use it").
51
+
6.**Second person.** "Your apps", "you can". The Bot is "it".
52
+
7.**Sentence case** for headings and buttons: "New Bot", "Your connections".
53
+
8.**Buttons are verbs**: Connect, Request access, Create, Save. A button never says "Click here" or
54
+
"OK" when it does something specific.
55
+
9.**Errors**: what failed, in the person's terms, and one next step. "Couldn't load your
56
+
connections. Reload the page." No stack of reasons, no blame.
57
+
10.**Empty states** say what will appear and how: "No skills yet. Write one with New skill."
58
+
59
+
## Before And After
60
+
61
+
| Before | After |
62
+
|---|---|
63
+
| The apps and accounts your Bots use as you. Open one to connect it or change it. | Connect your accounts so Bots can use apps on your behalf. |
64
+
| Your connected accounts could not be loaded, so nothing is listed here rather than a list that may be wrong. Reload the page, and tell an administrator if it persists. | Couldn't load your connections. Reload the page, or ask an admin if it keeps happening. |
65
+
| Sent to the coworker it is for. Type @ to choose one yourself. | OpenBot picks the right Bot. Type @ to choose one. |
66
+
| Gmail is a Composio app, and this deployment has no Composio key. (to a non-admin) | Gmail isn't set up yet. An admin has to turn it on. |
67
+
| The agent is still working on another request. Please wait or stop that task first. | This Bot is still working on something. Wait for it, or stop it first. |
68
+
69
+
## Red Flags
70
+
71
+
| Signal | Do instead |
72
+
|---|---|
73
+
| "agent" or "coworker" for a Bot | Bot |
74
+
| "administrator" | admin |
75
+
| "this deployment" on a page anybody can open | OpenBot, or say what is missing |
76
+
| A sentence over ~25 words | Split it, or cut the clause that justifies the screen |
77
+
| "rather than", an em dash, "which is" in UI text | Rewrite as two short sentences |
78
+
| A description beginning "The apps…", "What…", "Who…", or "Where…" without a main verb | State the action or behavior in a complete sentence; remove redundant explanation |
79
+
| An env var, OAuth, Composio or "grant" outside Admin | Say what the person sees |
80
+
| A test asserting long explanatory copy | Assert the short sentence, or the role and the key words |
| Controls stacked inside a section | 12px | the screen, with `gap-3`|
75
+
| A row in `PageRows`| 65px with a description, never under 56px |`Item` (`px-4 py-3.5 gap-3`), `PageRows` (`min-h-14`) |
76
+
| Divider between rows | inset 16px from both edges |`PageRows`, on every horizontal `Separator` inside it |
77
+
78
+
The 32px under the title is deliberately tighter than the 48px between sections: the title belongs
79
+
to the page it introduces. Those `PageShell` selectors outrank the first child's own `mt-*`, so a
80
+
section or notice keeps its usual margin everywhere else and loses it only at the top.
81
+
82
+
A page with one section gives that section no `title`: the page title already names it, and a second
83
+
heading 32px below the first says the same thing twice. Put the description on `PageShell` instead
84
+
(`connections.index.tsx`). A count or filter that belongs to a search sits on the search's row, not
85
+
on a line of its own.
86
+
87
+
`PageEmpty` is shadcn's empty state: a bordered box where the rows would be, with an `icon` for
88
+
the kind of thing, a `title` ("No rules yet", "No matches", "Background research is off"), one
89
+
sentence of description saying how the first one arrives, and an `action` — the same Add button the
90
+
page already has — when there is one to offer. Don't repeat "Add one with X" in the description when
91
+
the action is right there. A search with no results uses `IconSearch` and "No matches".
61
92
62
93
## Procedures
63
94
@@ -78,8 +109,10 @@ here yet" is a fact, and the section heading already said what the section is fo
78
109
79
110
### Procedure 2: Compose a row
80
111
81
-
1.`ItemMedia variant="icon"` holds a leading icon from `@tabler/icons-react`. It is what makes a
82
-
card of rows scannable; a row without one reads as a paragraph.
112
+
1.`ItemMedia variant="icon"` holds a leading icon from `@tabler/icons-react`, drawn as a 16px glyph
113
+
on a 32px muted tile. It is what makes a card of rows scannable; a row without one reads as a
114
+
paragraph. Pass the bare icon — the variant sizes and colours it — and use plain `ItemMedia` only
115
+
for something that is its own picture, such as a Bot's avatar.
83
116
2.`ItemContent` holds `ItemTitle` and, where the row needs a second line, `ItemDescription`.
84
117
3.`ItemActions` holds the control, or the value when the row is read-only.
85
118
4.`ItemHeader` and `ItemFooter` are `basis-full`, so they wrap onto a line of their own inside the
@@ -126,7 +159,19 @@ itself when the dialog changes something.
126
159
4.`bg-card` is invisible inside a dialog. `--card` and `--popover` resolve to the same colour in
127
160
both themes, so a card-coloured row against a popover is no row at all. Use `muted`.
128
161
129
-
### Procedure 5: Lay out a dialog
162
+
### Procedure 5: Add something from a list
163
+
164
+
1. Creating a thing that takes several fields — a credential, a key — is its own screen, not a dialog
165
+
over the list. Put it at `<list>_.new.tsx` (the `_` keeps it from nesting inside the list's route),
166
+
and make the list's `action` a `Button` rendering a `Link` to it.
167
+
2.`PageShell` with `backButton` pointing at the list, a title that is the verb ("Add credential"),
168
+
and a description saying what happens on save.
169
+
3. The fields go in one `PageCard` inside a `<form>`, stacked in a single column, each a `Field`
170
+
with `FieldLabel`, an optional `FieldDescription`, then the control. `PageCard`'s `footer` takes
171
+
Cancel (`variant="outline"`, a `Link` back) and the submit button; on success, navigate to the list.
172
+
4.`admin/credentials_.new.tsx` is the reference.
173
+
174
+
### Procedure 6: Lay out a dialog
130
175
131
176
1.`DialogContent` defaults to `max-w-lg` and `max-h-[85svh]` with `p-5`. Header, body and footer go
132
177
in as `DialogHeader` / `DialogBody className="mt-4"` / `DialogFooter className="mt-4"`.
@@ -141,34 +186,34 @@ itself when the dialog changes something.
141
186
its own backing — the built-in one is a ghost button in the foreground colour and disappears over
142
187
a pale image.
143
188
144
-
## The rem Trap
145
-
146
-
`app/src/styles.css` sets `html { font-size: 15px }`. Every rem-based Tailwind token is therefore
147
-
**0.9375×** its documented pixel value:
189
+
## Units
148
190
149
-
| Token | Documented | Actual here |
150
-
|-------|-----------|-------------|
151
-
|`max-w-2xl`| 672px |**630px**|
152
-
|`max-w-3xl`| 768px |**720px**|
153
-
|`max-w-5xl`| 1024px |**960px**|
191
+
The root is the browser's 16px, so Tailwind's spacing, sizes and widths are their documented values
192
+
(`p-4` is 16px, `max-w-3xl` is 768px) and land on its 4px grid. Text is the exception: `styles.css`
193
+
sets every `--text-*` to Tailwind's default times 15/16, so `text-sm` is 13.125px and body text is
194
+
15px. That is the scale this app has always read at; do not size text in px or rem by hand to match
195
+
the docs.
154
196
155
-
Never hardcode a pixel constant derived from a Tailwind rem token, and never compute a ratio from
156
-
one. Measure the element — `offsetWidth`, `clientWidth`, a `ResizeObserver` — and scale from what is
157
-
actually on screen. A constant taken from the Tailwind docs is wrong by 6.25% here, which is enough
158
-
to sit a centred element visibly off centre or clip a card's corners against its container.
197
+
Even so, never hardcode a pixel constant derived from a Tailwind token, and never compute a ratio
198
+
from one. Measure the element — `offsetWidth`, `clientWidth`, a `ResizeObserver` — and scale from what
199
+
is actually on screen. The root was once 15px, and every constant taken from the docs sat 6.25% off.
159
200
160
201
## Decision Tree
161
202
162
203
- Adding a screen → Procedure 1, then Procedure 2 for each row.
163
204
- Adding a row to an existing screen → Procedure 2, then Procedure 3 to pick its kind.
164
205
- The row toggles one thing → Procedure 3, kind 1: a `Switch`, no save.
165
206
- The row edits text, or a set of grants, or more than one value → Procedure 3, kind 2: a summary, a
166
-
chevron, and a dialog. Then Procedure 5.
207
+
chevron, and a dialog. Then Procedure 6.
208
+
- Adding a new thing that takes several fields → Procedure 5: its own screen with a back button.
167
209
- The row only states something → Procedure 3, kind 3: a value and no chevron.
168
210
- Unsure which `Item` size, `Button` size, or `variant` → Procedure 4.
169
-
- A width, ratio or scale needs a number in pixels → the rem Trap section. Measure, do not assume.
211
+
- A width, ratio or scale needs a number in pixels → the Units section. Measure, do not assume.
212
+
- Choosing a margin or gap between blocks → the Rhythm table. If the frame already gives it, set none.
170
213
- The screen genuinely does not fit this shape → say so in the response and comment the reason in the
171
-
code. A table is the one accepted precedent (`admin/audit.tsx`, `width="wide"`).
214
+
code. There are two accepted precedents: a table (`admin/audit.tsx`, `width="wide"`), and the
215
+
Connections tiles (`connections.index.tsx`), a grid of separate `variant="outline"` cards, 56px
216
+
tall and 12px apart, because apps are picked out by their mark rather than read down as a list.
172
217
- The question is which states the screen renders while loading or failing → not this skill;
173
218
`openbot-data-access`, Procedure 3.
174
219
@@ -183,7 +228,9 @@ to sit a centred element visibly off centre or clip a card's corners against its
183
228
| Children on an element passed to `Item`'s `render`| They replace the row's own children; the row draws empty | Pass a childless element |
184
229
|`bg-card` on a row inside a dialog |`--card` equals `--popover`; the row is invisible |`variant="muted"`|
185
230
|`Item variant="outline"` and `variant="muted"` on one screen | Reads as two kinds of list and invites a distinction that is not there | One variant per screen |
186
-
| A pixel constant matching a Tailwind rem token (672, 768, 1024) | Assumes a 16px root; the root here is 15px | Measure the element |
231
+
| A pixel constant matching a Tailwind rem token (672, 768, 1024) | Breaks the day a width or the root changes | Measure the element |
232
+
| An `mt-*` on a screen's first block, or a gap between blocks not in the Rhythm table | A second spacing scale, which is what made every screen feel slightly off | Let `PageShell` and `PageSection` space it |
233
+
| A one-section page whose section has a `title`| The heading repeats the page title | Drop the section title; description on `PageShell`|
187
234
| A `Switch` on a row that needs two or more values | Half the change happens in place and half in a dialog | A summary, a chevron, and a dialog |
188
235
| A chevron on a row that opens nothing | Announces a destination that does not exist | Value in `ItemActions`, no chevron |
189
236
| A set of chips or toggles crammed into `ItemActions`| It competes with the label for width and wraps badly |`ItemFooter`, which is `basis-full`|
@@ -194,8 +241,8 @@ to sit a centred element visibly off centre or clip a card's corners against its
194
241
-**A row renders empty — media, title and actions all missing**: children were passed on the
195
242
element given to `Item`'s `render`. Remove them.
196
243
-**A row's content sits visibly off centre, or a card's corners are clipped by its container**: a
197
-
pixel constant was taken from the Tailwind docs and the root font size is 15px. Measure the drawn
198
-
element instead of the box it was given.
244
+
pixel constant was taken from the Tailwind docs rather than measured. Measure the drawn element
245
+
instead of the box it was given.
199
246
-**A dialog's footer has body content painted over it**: `DialogBody` is missing
200
247
`overflow-y-auto`. It has `flex-1 min-h-0` and no overflow of its own.
201
248
-**A dialog stops scrolling after a refactor**: `DialogBody` is no longer a direct child of
0 commit comments