diff --git a/.claude/skills/openbot-screen-layout/SKILL.md b/.claude/skills/openbot-screen-layout/SKILL.md index 20cd104d6f..7025349711 100644 --- a/.claude/skills/openbot-screen-layout/SKILL.md +++ b/.claude/skills/openbot-screen-layout/SKILL.md @@ -64,7 +64,7 @@ components above; a screen that sets its own is drifting. | Between | Gap | Where it lives | |---------|-----|----------------| -| Page edge (top / sides / bottom) | 48 / 16 / 64px | `PageShell` column (`pt-12` or `pt-8` under the bar, `px-4`, `pb-16`) | +| Page edge (top / sides / bottom) | 48 / 16 / 64px | `PageShell` column (`pt-12`, or `pt-22` under the bar: its 56px plus 32px, `px-4`, `pb-16`) | | Title → description | 8px | `PageShell` header | | Title block → first content | 32px | `PageShell`: `[&>header+*]:mt-8` | | Title → tabs, tabs → content | 24px, then 32px | `PageShell`: `[&>header+nav]`, `[&>header+nav+*]` | @@ -96,7 +96,8 @@ the action is right there. A search with no results uses `IconSearch` and "No ma 1. Open with `PageShell`, giving it `title` and `description`. Leave `width` alone. 2. On a detail screen reached from a list, pass `backButton={{ label, linkProps }}` — it draws a - chevron-left bar above the header. Do not put a Back button in `action`; `action` is for the + chevron-left button floating in the pane's top-left corner, only as wide as itself, so the page + scrolls under it rather than under a full-width band. Do not put a Back button in `action`; `action` is for the page's one primary verb, on the title's baseline. 3. Group the page's decisions into `PageSection`s, each with a `title` and, where the grouping is not self-evident, a `description`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f9d3f16f4..fae183b0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,15 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. `COMPUTER_MEMORY_HIGH_PERCENT=0` and `COMPUTER_TAB_IDLE_MS=0` (Helm: `computers.memoryHighPercent` and `computers.tabIdleMs`) keep the old behaviour. +### Writing a skill is a screen of its own, and lists keep their place + +New skill and each skill's page open as screens instead of dialogs, laid out a section at a time: +details, instructions, tools, the Bots it is on, and on an existing skill, Delete. Tools are picked +one app at a time, and a new skill can be put on Bots before it is created. Each skill in the list +is a card that opens it and shows how many Bots it is on and how many tools it needs. Coming back +from any detail screen returns a list to where it was scrolled, and the back button no longer +covers the top of the page. + ### A computer closes browsers nobody is using when its memory runs high A computer runs one browser for each Bot that is browsing, and the only limits were a count and half diff --git a/app/src/components/layout/page-parts.tsx b/app/src/components/layout/page-parts.tsx index 7fa01f8dba..d1fbee7065 100644 --- a/app/src/components/layout/page-parts.tsx +++ b/app/src/components/layout/page-parts.tsx @@ -28,6 +28,9 @@ export type ShellWidth = "prose" | "full"; * bar needs the router, so `PageShell` draws it and passes `bar` to say whether it did; this file * imports nothing from the router so the extension can render the same column. */ +/** The name the router restores a page's scroll position by. See `router.tsx`. */ +export const PAGE_SCROLLER = "page"; + export function PageColumn({ bar, children, @@ -58,7 +61,10 @@ export function PageColumn({ * Separate from the centred column below so the scrollbar rides the pane's edge instead of * appearing inside the measure with content either side of it. */} -
+ {error.message} +
+ ) : null} + +- Couldn't load the tools. Reload the page. -
- ) : servers.length === 0 ? ( -- No app offers tools yet. You can still write the skill. Most skills - are only instructions. -
- ) : ( -{server.title}
-Public-web research
-Not available here
-
- This skill asks for these, but no app here offers them. They do
- nothing until an admin adds the app. Click one to remove it.
+/**
+ * The apps a skill can take tools from, each opening a step with that app's tools. One card of
+ * divided rows, like every other list of settings, with no label of its own: the section around it
+ * has the heading and the description.
+ */
+export function SkillToolApps({
+ selected,
+ onOpen,
+}: {
+ selected: string[];
+ onOpen: (source: ToolSource) => void;
+}) {
+ const { plugins, groups } = useToolGroups(selected);
+ const held = new Set(selected);
+
+ const rows = groups.map((group, index) => (
+
+ Couldn't load the tools. Reload the page. +
+ ); + } + return+ This app no longer offers any tools. +
+ ); + } + + const query = searchable(search.trim()); + const shown = query + ? group.choices.filter( + (choice) => + searchable(choice.name).includes(query) || + searchable(choice.description).includes(query), + ) + : group.choices; + const picked = group.choices.filter((choice) => held.has(choice.ref)).length; + + return ( +{group.explanation}
++ {picked} picked · {group.choices.length} in all
- Using this skill loads these tools for that reply. It doesn't give - access. A Bot can only use tools it already has. -
- + {shown.length === 0 ? ( ++ Nothing matches “{search.trim()}”. +
+ ) : ( +
- {error}
+
+ Couldn't load your skills. Reload the page.
- Couldn't load your skills. Reload the page.
- / and a skill's name to give a Bot that
+ instruction. Only you see yours, and they work on Bots you own.
+ >
+ }
+ title="Your skills"
+ >
+ {/*
+ * Nothing while the two queries are still in flight. The alternative is the empty state
+ * standing there saying this person has written no skills, which is a claim the page has
+ * not yet earned.
+ *
+ * And not after a read that failed, for the same reason: `isPending` goes false on a
+ * failed fetch too, and no list is no evidence of no skills. A list already held stays on
+ * screen through a failed refetch, because `data` is still there.
+ */}
+ {loading ? null : !data ? (
+ / and a skill's name to give a Bot that
- instruction. Only you see yours, and they work on Bots you own.
- >
- }
- title="Your skills"
+ description="An admin writes these for everyone and picks which Bots use them."
+ title="Workspace skills"
>
- {/*
- * Nothing while the two queries are still in flight. The alternative is the empty state
- * standing there saying this person has written no skills, which is a claim the page has
- * not yet earned.
- *
- * And not after a read that failed, for the same reason: `isPending` goes false on a
- * failed fetch too, and no list is no evidence of no skills. A list already held stays on
- * screen through a failed refetch, because `data` is still there.
- */}
- {loading ? null : !data ? (
-
- /{skill.slug}
-
- {skill.summary ? ` · ${skill.summary}` : null}
-
- /{skill.slug}
-
- {skill.summary ? ` · ${skill.summary}` : null}
-
+ /{skill.slug}
+
+ + Couldn't load this skill. Reload the page. +
+ ) : ( ++ This skill was deleted, or it isn't yours to edit. +
+ )} +/{skill.slug}.
+ Bots that have it keep it.
+ >
+ }
+ title={skill.title}
+ >
+ /{skill.slug} stops working on every Bot it is
+ on.
+ >
+ }
+ title="Delete"
+ >
+ /. Only you see it, and it
+ works on Bots you own.
+ >
+ }
+ title="New skill"
+ >
+