Repository navigation
Name the frameworks in the New Bot wizard, ask visibility last, and share one harness catalogue - #799
Merged
Merged
Conversation
A floor of 70% of the viewport, under the 85% ceiling the popup already carried, so three of the four states land on one height instead of the dialog resizing between every question. The buttons pin to the bottom, and the pane keeps slack under it so the animated frame never shaves the last line of a step while the spring settles. "Role" beside "Title" read as a second job title when it is the standing instruction the Bot carries into every channel. It is now "What it does", and the per-channel promise moved from above all three fields onto the one field it is true of. The validation messages and the Bot's own screens follow the same name.
The twelve frameworks and the address row lived in Rust, reachable only through the Tauri command the desktop setup window calls. The web app's New Bot dialog needs the same names, summaries and marks to say "the thing you already run is one of these", and a second copy would drift — which matters more here than for most lists, because this one carries other companies' trade marks and the rules about what may be drawn for them. The desktop is not in the root bun workspace, so Rust takes the JSON by path with include_str! rather than importing the module beside it. The file is camelCase, which is what the web app reads; `Harness` stays snake_case, which is what the Tauri command has always sent HarnessPicker.tsx, so a private row struct does the renaming and the wire format is unchanged. Two fields are new and the desktop ignores both. `webCreate` is false for Mastra alone: the server reaches it as remote_mastra through the @ag-ui/mastra bridge and needs a roster id, and the web create API can send neither, so a Mastra row in that dialog would make a Bot that appears, accepts a grant, and answers nothing. `webSummary` overrides the installer's sentence on the two rows where it is false on a surface that installs nothing.
The marks sat under desktop/src, reachable only by the setup window. The web app's New Bot dialog draws the same eleven, and copying the artwork would have meant two sets of another company's trade mark to keep in step. They now live in shared/marks/, globbed from both sides, with the README that carries the rules moved with them. Both globs resolve a file to its id by stripping everything up to the marks directory rather than a fixed prefix, so neither breaks again if the path moves. The Rust guard that every named mark is vendored follows to the new location. A glob that matches nothing fails silently here: every row would simply show its name, which is also what a brand with no mark legitimately looks like. `every named mark is vendored where both apps look for it` is the assertion that tells those two apart, and it was proved to fail by removing a mark before it was trusted.
Visibility was the second question, which put it before the person had
decided anything that makes private or public mean something. It is the one
answer that does not change what they are making, so it goes last.
The counter goes with the reorder rather than despite it. The wizard is three
questions for a Bot that runs here and four for one at an address somebody
hosts, and a counter whose total changes under the reader mid-way reads as
the thing having got longer. Each step already says what it is asking in its
own heading, which is what was carrying the reader.
Two tests used the counter as their signal that a step had moved; they now
read the heading, which says the same thing and survives the next reorder.
The first version of the test forbidding the counter passed against the
counter it was written to forbid — `Step {n} of {total}` renders as four text
nodes and a regex matcher walks them one at a time — so it asserts against
the popup's whole text instead.
Two radios asked a person who had just described a role to tell Built-in from Managed. Both are a kind of Bot and neither word says which one is theirs. The step now leads with the Bot this deployment runs itself and lists the frameworks somebody might already have running, by name and by their own mark, so the answer is recognised instead of deduced. The press is the answer, so there is no Continue beside them: a button that confirmed a decision already taken was the second press this path used to cost. Three frameworks show and the rest open behind a disclosure, because twelve names at once is a wall on the step somebody reaches knowing only that they want a Bot. The address moves onto a step of its own, reached only by the frameworks, so a Bot that runs here is never shown a field it has no use for. It is generic: no heading names the framework that led to it, because nothing about it differs by framework. That is also the honest limit of the list — picking Google ADK rather than CrewAI changes nothing and is stored nowhere. Mastra is absent, and shared/harnesses.json says why: the server reaches it through its own bridge and needs a roster id this create path cannot send. Tasks 4 and 5 of the plan are one commit because they could not be two. Click-to-advance leaves the step the moment a framework is pressed, so the endpoint fields could not stay inline for one commit and move in the next without shipping a state where a hosted Bot had nowhere to type an address. biome.json now skips .superpowers, the git-ignored scratch directory these plans and their mockups live in; HTML written to be thrown away was failing `bun run lint`.
"Show 9 more" made somebody who did not find their own in the first three press a control before they could even look. The list is open, and scrolls: scrolling is the gesture people already try on a list, and the scroller is bounded so it is the list that moves and not the step — the question above it and the Back button below it stay where they were drawn. "Run it here" was describing the deployment rather than the person's choice: "On this deployment's own Bot. Nothing to host or connect." Neither "deployment" nor "host or connect" means anything to somebody who has just written down what they want a Bot to do. It now says OpenBot runs it for you and there is nothing to set up. The unavailable case stops naming the plumbing too: it says the Bot can be switched on and that whoever set OpenBot up is the one to do it, which is true and is addressed to the right person.
Four server tests still asserted "Role description must be text between 1 and 1000 characters.", which the rename changed two weeks of commits ago. CI runs the repo-wide suite, so the branch was red. This is the second half of a miss already recorded once: the app half was fixed when it was found, the server half was never looked for, because both times the rename was verified against a subset of the suite rather than all of it. `app/tests/composing-enter.test.tsx` and `create-agent-steps.test.tsx` passed only because a `mock.module` in a different file leaked process-wide. Both failed when run alone, and the plan's own step command for one of them was broken. The stub moves to app/tests/preload.ts, which exists for exactly this — its comment is about a test that "passed on its own and failed in the suite", and this was the same bug pointing the other way. A file that needs a mark drawn still overrides it. The desktop CI filter watched desktop/** only, and the catalogue and the marks it guards now live in shared/. A row with a bad `credential` would have passed lint, typecheck and the TS suite and then panicked the setup window, because `catalogue()` parses that file at runtime. The filter now watches shared/harnesses.json and shared/marks/**. Unmarked rows no longer collapse their tile. Four of the twelve have no mark, and their names began a tile's width left of the rest, which read as a ragged list rather than four brands without artwork. Reserving the column draws nothing and invents nothing; it only keeps the names in line.
`overflow-y-auto` computes `overflow-x` to `auto` as well, so the box clipped sideways as much as it did downward — and the inset was on the right only. The row under the pointer had its left border shaved off against that edge, which is what made the component look cut off. The padding is now on both sides, and the negative margin hands it back so the rows keep the width of the button above them. The height was a constant, chosen before seeing it in a dialog that is itself 70% of the viewport: the list stopped at 240px and left 139px of the step empty below it. In `svh` it tracks the popup it sits in — 336px of list on a laptop, and the gap down to Back is 45px instead of 139px.
The real cause of the cut-off left edge, which the last commit guessed at and missed. The frame that animates each step's height hides its overflow on both axes. The negative margin added to keep a row's border off the clip edge made the scroller three pixels wider than that frame, and focusing the step scrolled the frame four pixels sideways to bring them into view. Every child then drew four pixels left of where it belonged — which is why the heading lost the left of its W and each row lost its left border, and why insetting on both sides did not help: the inset was the thing causing it. The scroller is flush now, and nothing inside the step is wider than the frame. The rows keep their border off the clip edge by having a focus ring drawn inside rather than outside — which they needed anyway, since they had no focus style at all and keyboard focus fell back to an outline the scroller would have clipped. The test asserts the frame's scrollWidth against its clientWidth, which is the property that actually broke. The one it replaces asserted a Tailwind class on a wrapper two levels up, and would have passed throughout.
guidovizoso
requested review from
MikeRyanDev,
davidmckayv,
mxmzb and
tylerslaton
as code owners
October 17, 2026 00:47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The New Bot wizard asked a person who had just decided what they wanted a Bot
to do to choose between two radio buttons labelled "Built-in" and "Managed".
Both are a kind of Bot and neither word says which one is theirs. This rebuilds
that step as a list they can recognise themselves in, moves visibility to the
end, and gives the dialog a steadier shape.
What changed
Where does it run is buttons now, led by the Bot this deployment runs
itself, followed by the frameworks somebody might already have running — by
name, by their own mark, and with the one-line summary each already had. The
whole list is open and scrolls; the question above it and Back below it stay
put. Pressing one both answers and moves on, so there is no Continue confirming
a decision already taken.
The address moved to its own step, reached only by the frameworks, so a Bot
that runs here is never shown a field it has no use for.
Visibility is last. It is the one answer that does not change what the
person is making, and asked second it arrived before they had anything to go on.
The step counter is gone. The wizard is three questions for a Bot that runs
here and four for one at an address, and a counter whose total changes under
the reader reads as the thing having got longer.
"Role" became "What it does". Beside "Title" it read as a second job title
when it is the standing instruction the Bot carries into every channel. The
per-channel promise moved from above all three fields onto the one field it is
true of. The dialog also holds a floor of 70% of the viewport so it stops
resizing between questions.
The structural part
The twelve frameworks and their marks lived in
desktop/src-tauri/src/harness.rsand
desktop/src/marks/, reachable only through the Tauri command the setupwindow calls. The web app needs the same names, summaries and artwork, and a
second copy would drift — which matters more than for most lists, because this
one carries other companies' trade marks and the rules about what may be drawn
for them.
They now live in
shared/harnesses.jsonandshared/marks/, following theshared/model-providers.jsonprecedent. Rust reads the JSON withinclude_str!(the desktop is not in the bun workspace, so it takes the file by path); the web
app imports the module.
Harnesskeeps its snake_case wire format forHarnessPicker.tsx— a private camelCase row struct does the renaming — so theTauri contract is unchanged, and all 24 Rust harness tests and 139 desktop TS
tests pass against the moved files.
Two things to decide before merge
1. Twelve vendor marks now appear in the hosted web app.
shared/marks/README.mdscopes those vendored SVGs to "a picker" in a locallyrun installer. This is the same identifying use, but it is a different surface —
multi-tenant and served to signed-in customers. Worth a look from whoever owns
that call; reversing it after launch is worse than delaying it.
2. The framework choice is recognition only. Picking Google ADK rather than
CrewAI changes nothing: both lead to the same generic endpoint field and are
stored identically, so "which did I pick" is unanswerable after the dialog
closes. That follows from keeping the API unchanged, and it was raised and
accepted deliberately — but it is the thing most likely to want revisiting.
Mastra is deliberately absent from the web list. The server reaches it as
remote_mastrathrough the@ag-ui/mastrabridge and needs a roster id to saywhich agent it means; the create endpoint can send neither, so a Mastra row here
would make a Bot that appears, accepts a grant, and answers nothing. The
catalogue carries that as
webCreate: false, which also lets the web andinstaller lists diverge later without a second file.
Three frameworks still show their name alone — Agno, AG2 and Langroid have
no mark in any maintained set (all 404 on
@lobehub/icons-static-svg), and theREADME forbids inventing one. Their rows reserve the tile's width so the list
does not read ragged.
Also in here
New Boton/botsis a small ghost button rather than a large filled one.Not in the original scope; flagging it because it is now the least prominent
control on a page whose purpose is making Bots.
.github/workflows/desktop.ymlwatchesshared/harnesses.jsonandshared/marks/**.catalogue()parses that JSON at runtime with anexpect,so a bad row would pass lint, typecheck and the TS suite and then panic the
setup window — and the desktop jobs are the only thing that would catch it.
biome.jsonskips.superpowers, the git-ignored scratch directory.Known gaps
comes back selected but off-screen. One
scrollIntoViewif it matters.aria-pressedannounces "toggle button" for a control that navigates, and theset-of-N semantics the radiogroup gave screen readers are gone.
shared/harnesses.tsasserts the JSON's shape rather than validating thecredential/maintainerunions, so a typo there surfaces as a Rust panicrather than a type error.
Testing
1221 app tests, 242 shared, 149 desktop TS, 24 Rust harness tests, and
server/tests/agent-routes.test.tsall pass;lint,format:check,typecheckand
buildare clean. A repo-widebun testadditionally needs a provisionedopenbot_testdatabase — the failures without one are environmental andunrelated to this branch, whose entire server footprint is a single changed line.
Design and plan:
docs/superpowers/specs/2026-10-16-bot-creation-dialog-design.md,docs/superpowers/plans/2026-10-16-bot-creation-dialog.md.