Skip to content

Commit ce0e3c1

Browse files
committed
Merge remote-tracking branch 'upstream/main' into dustywusty/fix-leftover-volumes
# Conflicts: # desktop/src-tauri/src/main.rs
2 parents 3e675dc + 72a0b1f commit ce0e3c1

1,074 files changed

Lines changed: 296043 additions & 12335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
name: openbot-copy
3+
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.
48+
5. **No internals outside Admin.** Composio, OAuth client, credential, token, grant, scope,
49+
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 |

‎.claude/skills/openbot-screen-layout/SKILL.md‎

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,44 @@ PageShell the page: header (title, description, action, backButton) then
5151
PageEmpty what a section says when it has nothing to list
5252
```
5353

54-
`PageShell` takes a `width`: `prose` (`max-w-2xl`) is the default because configuration is mostly
55-
reading, and a row of label-and-control has no business being wider than the sentence explaining it.
54+
`PageShell` takes a `width`: `prose` (`max-w-3xl`, 768px) is the default, and every
55+
configuration screen uses it so no screen is wider than another. Sentences inside it are held to
56+
`max-w-prose`, so the column has room for a label beside its control without stretching the text.
5657
`wide` (`max-w-5xl`) exists for one screen — `admin/audit.tsx`, because an audit log is a table to be
5758
scanned and prose width would wrap every row. It is not a licence for anything else to be wide.
5859

59-
`PageEmpty` is a sentence, not an illustration with a heading. On a configuration screen "nothing
60-
here yet" is a fact, and the section heading already said what the section is for.
60+
## The Rhythm
61+
62+
Spacing comes from the frame, not from the screen. These are the gaps every screen gets by using the
63+
components above; a screen that sets its own is drifting.
64+
65+
| Between | Gap | Where it lives |
66+
|---------|-----|----------------|
67+
| Page edge (top / sides / bottom) | 48 / 16 / 64px | `PageShell` column (`pt-12` or `pt-8` under the bar, `px-4`, `pb-16`) |
68+
| Title → description | 8px | `PageShell` header |
69+
| Title block → first content | 32px | `PageShell`: `[&>header+*]:mt-8` |
70+
| Title → tabs, tabs → content | 24px, then 32px | `PageShell`: `[&>header+nav]`, `[&>header+nav+*]` |
71+
| Section → section | 48px | `PageSection` (`mt-12`) |
72+
| Section heading → description | 4px | `PageSection` |
73+
| Heading block → section content | 16px | `PageRows` / `PageEmpty` (`mt-4`) |
74+
| 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".
6192

6293
## Procedures
6394

@@ -78,8 +109,10 @@ here yet" is a fact, and the section heading already said what the section is fo
78109

79110
### Procedure 2: Compose a row
80111

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.
83116
2. `ItemContent` holds `ItemTitle` and, where the row needs a second line, `ItemDescription`.
84117
3. `ItemActions` holds the control, or the value when the row is read-only.
85118
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.
126159
4. `bg-card` is invisible inside a dialog. `--card` and `--popover` resolve to the same colour in
127160
both themes, so a card-coloured row against a popover is no row at all. Use `muted`.
128161

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
130175

131176
1. `DialogContent` defaults to `max-w-lg` and `max-h-[85svh]` with `p-5`. Header, body and footer go
132177
in as `DialogHeader` / `DialogBody className="mt-4"` / `DialogFooter className="mt-4"`.
@@ -141,34 +186,34 @@ itself when the dialog changes something.
141186
its own backing — the built-in one is a ghost button in the foreground colour and disappears over
142187
a pale image.
143188

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
148190

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.
154196

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.
159200

160201
## Decision Tree
161202

162203
- Adding a screen → Procedure 1, then Procedure 2 for each row.
163204
- Adding a row to an existing screen → Procedure 2, then Procedure 3 to pick its kind.
164205
- The row toggles one thing → Procedure 3, kind 1: a `Switch`, no save.
165206
- 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.
167209
- The row only states something → Procedure 3, kind 3: a value and no chevron.
168210
- 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.
170213
- 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.
172217
- The question is which states the screen renders while loading or failing → not this skill;
173218
`openbot-data-access`, Procedure 3.
174219

@@ -183,7 +228,9 @@ to sit a centred element visibly off centre or clip a card's corners against its
183228
| Children on an element passed to `Item`'s `render` | They replace the row's own children; the row draws empty | Pass a childless element |
184229
| `bg-card` on a row inside a dialog | `--card` equals `--popover`; the row is invisible | `variant="muted"` |
185230
| `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` |
187234
| 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 |
188235
| A chevron on a row that opens nothing | Announces a destination that does not exist | Value in `ItemActions`, no chevron |
189236
| 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
194241
- **A row renders empty — media, title and actions all missing**: children were passed on the
195242
element given to `Item`'s `render`. Remove them.
196243
- **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.
199246
- **A dialog's footer has body content painted over it**: `DialogBody` is missing
200247
`overflow-y-auto`. It has `flex-1 min-h-0` and no overflow of its own.
201248
- **A dialog stops scrolling after a refactor**: `DialogBody` is no longer a direct child of

0 commit comments

Comments
 (0)