vApps
vApps
A vApp is a small portal you build in an afternoon and throw away when you are done with it: a grid of components wired to each other, over rows kept with it. A form and a table for the invoices you are chasing. A picker, a button and a tree for poking at a set of records. Whatever a spreadsheet would have been, if a spreadsheet could hold a real data grid and a dropdown fed from what you last ran.
The point is that they are cheap. Creating one costs a title, deleting one takes the rows with it, and nothing in the app is worse off for either.
The list
vApps sits next to Home in the top bar. The rail beside the list opens with a search field, which narrows every list to the vApps whose title, id, description or category holds each word you type; collapsed, its magnifier opens the rail and puts the caret there. Below it come All, Recent (the ones that have actually been run), one row per category you have filed vApps under, with how many each holds, Hidden, and Components, which is the reference for what a layout can hold.
New vApp asks for a title and an id. The id is what links and agents name it by, so it is lowercase letters, digits and hyphens; it is suggested from the title and you can leave it alone.
Above your own, All lists Built-in: pages of the app itself that live here rather than in the top bar. Bedos is the first; press its card to open it. Ctrl+P finds them by name too.
Under them come vTools: the vApps mechabedo ships for its own behaviours and tools, such as Turn actions (Enhance) and Second opinion. Their cards have Reset where yours have delete, because a vTool cannot be deleted, only put back the way it ships.
Each vApp is a card, the same kind the Home dashboard pins projects as. Press one to run it; the buttons in its title band pin it, hide it, open the architect, copy a migration prompt (see Bringing an older vApp up to date), customize the card, or delete it. Pinned vApps come first in All, in the order you pinned them. A hidden vApp leaves every list but Hidden, where the same button brings it back. The category is set in Customize, which suggests the ones already in use; clear the field to take a vApp out of its category. Pins, hiding and categories belong to this installation, so none of them travel with an export.
Customize is the card's own look: a title and an icon, a cover color, the ornament that runs behind the title, and the cell it takes on the grid, so a board you open every morning can be twice as wide and impossible to miss. It is part of the vApp, so it travels with an export or a share.
Second opinion
Second opinion is a vTool that is there from the first start: you pick a technical decision you took lately, add your own words, and ask one or more models what they make of it.
- The list at the top is your recent work: each project you touched in the last three weeks, the busiest and most recent first, with its sessions, commits and branch. It reads the sessions of every harness on the machine, not only the ones you ran through mechabedo, plus your own git commits and the projects your editors opened lately, so it finds work done elsewhere.
- Open a project and the fast tier reads its sessions and commits for the technical decisions they hold. Each decision is a row saying what was chosen; pick it to see why, the alternatives, the tradeoffs and what is still open. Decisions you read are kept, so the list shows them the next time. Read decisions again reads afresh, and text in the search field steers it.
- Describe it instead. Type what you remember into the search field and press Enter. The fast tier searches your sessions and opens what matches.
- Sendoff is your own words: what you want looked at, and what you suspect. It goes to every model exactly as you wrote it.
- Start asks every model in Models. Each slot starts on the think tier and can be any model from any gateway you have set up. Options chooses the lens (the default asks for an honest second opinion that weighs each decision's tradeoffs; Tradeoffs reviews every decision against its alternatives in a table, and Devil's advocate, Pre-mortem, Simpler way, Fresh eyes and Next step are the others), the depth, and whether the think tier writes a summary. Keep as default saves them.
- Depth. Deep opens one read-only session per model in the hub, so each model reads the code itself; the card's open button shows you the session. Quick sends what mechabedo gathered about the topic straight to each model and answers in seconds. Either way each model gets the decision you picked and the passages of your sessions that argued it, not only their last turns.
The answers land on the board as cards, with the summary on top, which sets the models' calls on each decision side by side. They stay in Runs to read again. Lenses are rows you can edit or add to under the board. Any step can be bound to a tool of your own in the backstage, and a tool that publishes topics in the same shape can feed the list beside the shipped one. The word cloud the vTool first shipped with is still a component, Topic cloud, if you prefer it. A Second opinion installed before the list came gets it with Reset.
Face input
Face input lets somebody drive mechabedo without hands or a voice. An iPhone with Face ID, running mechabedo's own iPhone app and propped up facing them, streams their face to the desktop, a Mac or a Windows PC, over the local network, or through the beam host when the phone cannot reach it directly. The window then shows a pointer that follows their eyes, or their head, and a facial movement clicks. It is a vTool, under Enhance > vTools.
| Tile | What it does |
|---|---|
| Face control | Turns the pointer on, switches between pointing and navigating, pauses and resumes it, picks eyes or head, and sets dwell, reach, steadiness, zones and sound. For the eyes, how far a look must move before the pointer follows and how much the head nudges it. Navigation, with a live reading of how far out you are looking against the line a step needs, Screen, with which fit points, the screen's size, whether sizes follow your distance and which side the dock sits on, Zoom, Escape hatches and Calibration pace, with which routine Calibrate runs, are below. Lists the phones streaming, with a live reading of the head and Recentre |
| Face calibration | One button per routine: Quick, Eyes, Head, Movements, Resting face, Check, Phone moved and Everything. For the eyes and the head says when each was last fitted, how far off it lands in degrees through the angles and through the fitted screen, and how far the last check landed in pixels; draws every dot on a map, coloured by how far off it landed, with where the fit put it, where the check landed and where you are aiming right now; lists each dot's frames, wander, miss and check; draws the Fitted screen from the side and from above, with the phone, where your eyes were and where they are now; lists each movement with what it does while pointing and while navigating and how long it is held; and for each movement how far the face goes on its own and on purpose. Switches between pointing with the head and with the eyes |
| Face switches | Each facial movement as a live bar with the lines where it presses and lets go, shaded as far as the face goes on its own, its timings, which are where a hold is changed, and what it does while pointing and while navigating: click, pause, zones, scroll, keyboard, Escape, Back, a step in one direction and more. Every movement lists all of the movements the phone reads, with the highest each reached lately; + makes one a switch |
With the pointer on, the window outlines what the pointer would click and reaches for the nearest button when it lands close by. Resting on something fills the ring and clicks it. The eyes point by default: the pointer holds still while they rest, even though the phone reads them a little unsteadily, and jumps when they look somewhere else. Turning the head while looking at a target moves the pointer the last few pixels, and a blink does not move it. The eyes need a calibration to land well; until there is one, Calibrate in the dock is lit.
While a facial movement with an action is held, a label by the pointer names it and fills toward the click, and the pointer stays where it is. Whatever the face did is written by the pointer for a moment, such as Click · Send, with a tick, or two notes when a mode turns on or off. The window plays sound only once somebody has clicked or typed in it since it opened. A column of large buttons at the right edge, or the left one if chosen under Screen, pauses the pointer, turns dwell off, splits the window into zones, zooms, scrolls by head, opens the keyboard, arms a right or double click, goes back, presses Escape, opens the command bar, recentres and calibrates. Those buttons answer a stare even with dwell turned off, and Recentre first shows a dot in the middle to look at.
Clicking into a text field opens the keyboard, which fills the window. What you type goes into the box at its top in large letters, with the next word to pick from words already on the page and Ideas for whole phrases from the Fast tier. Nothing reaches the field until Insert, which writes the box into the field, or Send, which writes it and sends it. Hiding the keyboard keeps the draft, and Earlier draft brings back one written for another field.
Zoom magnifies the window around where the pointer last was on the page, so a small button becomes a large one for the next look; a click on the page zooms back out, and so does Escape. Under Zoom in Face control, how much it magnifies, Click zoom, which makes every click on the page zoom first and the next one act, and Sticky zoom, which stays zoomed after a click. The pointer's own buttons stay their size while the page is magnified.
Navigating
Navigating steers without aiming. Choose Navigate in the dock, or Navigating in Face control or on the phone. Look toward an edge of the window and hold it there, or turn your head if navigation is set to the head, and a highlight steps that way. A look a little above or below straight still counts, and a blink does not start the hold over. Held on, it keeps stepping. An arrow runs from the middle of the window to where your look is read, four dashed lines show how far out a look must go, and a ring at the arrow's tip fills toward the step. Four pads at the edges fill too and name what the step would reach. Look arrow and Edge pads under Navigation turn either off.
- Raising the eyebrows selects what is highlighted, as a click would.
- Puffing the cheeks goes back out. Both can be changed per movement in Face switches, under Navigating.
- On the workbench and the chat, left and right move between the sessions, the conversation, the prompt and the side pane, and up and down move between sessions, messages or tabs. Selecting the prompt opens the keyboard; selecting another region steps inside it.
- Everywhere else, and in a dialog, a menu or the command bar, the highlight steps between buttons, fields and links. Past the last one, the page scrolls.
- The highlight goes to what matters first: on Home the directory browser, the harnesses, the loadouts, the launch button, Use hub directory and the project cards; on the workbench the sessions, the new session button and the prompt. Other controls are reached when nothing that matters lies that way, and small chrome such as the top bar's tools and the status bar is never stepped onto.
- Going back from the outermost level opens the face menu: the keyboard, the command bar, every page, the open tabs, the whole page, scrolling, zoom, the pointer, pause, recentre and calibrate. Whole page steps over every control there is, the skipped chrome included, until you choose it again.
Paused, looking up past the top edge and holding it resumes. The phone shows where the highlight is, and has Left, Up, Right, Down, Select, Back and Face menu buttons for a helper. If steps come too easily or too hard, change how far out a look must go with Eyes reach under Navigation: the dashed lines move with it, and the reading below it shows how far out you are looking right now.
One window at a time
Only the window in front acts on the face, and only on the desktop the phone streams to. Switching the phone to another installation in its menu moves a running stream there. Moving the mouse or pressing a key hands the window to whoever did it for a few seconds.
On the phone, open the menu's Face input, press Start face input, and Start when the app opens so it streams again after a restart without anyone touching it. While it streams the phone shows what the desktop is doing with the face, how clicking works with the settings in use, and each facial movement as a bar against the line where it clicks. Whoever holds the phone can turn the pointer on, switch between pointing and navigating, pause and resume it, zoom in and out, choose eyes or head, change the dwell time, switch sound, and start any calibration routine or Recentre on the desktop from there; during a calibration its Pause pauses the run, and Stop the calibration ends it. Setup shows how the phone sits: its distance from your eyes, how far off its camera's middle your face is, how it leans, and one thing to change. When a phone starts streaming while the pointer is off, the desktop's window says so at the top, with Turn on pointer and Open Face input.
Calibrating
A calibration is one of a few routines, because the eyes drift from their fit within a sitting while the movements do not. Quick is the one for every sitting: five dots for whatever you point with, then a check, under a minute. Eyes and Head redo one source over the full set of dots, and each keeps the other's fit. Movements watches the resting face and asks for each movement; Resting face watches only that; Check shows five dots and measures where the pointer lands, changing nothing; Phone moved shows three dots after the phone was knocked or moved and keeps the rest of the calibration; Everything is all of it. The dock's Calibrate, a movement mapped to it and the phone's Calibrate run the routine chosen under Calibration pace, Quick unless changed; the Face calibration tile and the phone offer every one.
Every step waits for the person rather than for a clock. First it says what will happen and counts down. A routine with dots then checks the phone: how far it is from your eyes, how far off its camera's middle your face sits and how it leans, with one thing to change, such as Move the phone closer. It goes on by itself once the phone sees your face well, or after a few seconds, so a helper can adjust it meanwhile. The resting face is nothing to do but look at the screen, and whatever the face does on its own is exactly what it wants to see. Each facial movement that has an action is asked for a couple of times. Before each one, the screen says what to do and how long to hold it, then a tone marks when it is held long enough. For closing both eyes it says so before you close them: keep them closed until the tone, then open them. Nothing needs reading while they are shut, and that tone sounds even with the pointer's sound off. There is no line to reach: the upper bar is scaled to what your face makes, so any clear movement counts, and the lower bar fills while you hold it. Between tries there is a pause to relax. The dots glide slowly from place to place, followed with the eyes and the head still, or pointed at with the head; each one waits until the eyes have held still on it, and there is a rest every few dots. With Head turns on under Calibration pace, the eyes then hold the middle dot twice more while the head turns a little to the left and to the right, which sharpens the fitted screen for anyone who can turn their head. The check shows the dots once more with a small ring where the pointer now lands beside each, so the miss is seen as it is measured. Space pauses and Escape stops; the pause movement pauses it too, except while the face is being watched or asked for a movement.
At the end it says what it found and changed, with Keep and Undo large enough to press with the face, or to reach by navigating, where Back keeps: how far off the eyes or the head land and at which dot it was worst, how much they wandered while holding and how far they swept, and how far the check landed against the pointer's reach. Each movement's press line is moved between what the face does on its own and what it does on purpose, however small that is, and its hold is fitted to how long you held it without matching anything the face holds by itself. A movement the face also makes on its own is named as such, with the advice to choose another. Every routine's results end with a table of each movement, what it does while pointing and while navigating, and how long it is held, with the old time where the run changed it. Raised eyebrows start at 0.15 s. Change any of them on the Face switches tile. Calibration pace under Face control sets every timing: Gentle, the default, Steady and Quick, and the resting face or the movements can be left out of every routine.
The dots are fitted twice. The angles map how far the eyes or the head turned straight onto the window. The fitted screen works out where the screen stands in front of the phone, how large it is and how far from you, and points where the line of your gaze meets it. The fitted screen keeps the pointer where you look when you lean in, sit back or slide sideways, and the results say which of the two missed the dots less; that one points unless Fit under Screen says otherwise. Both remember where the window was, so moving or resizing the window keeps the calibration. Choosing the screen's size under Screen makes its distance exact rather than estimated. With Follow distance on, the reach, the fixation radius and the zoom grow as you sit back. When the eyes land clearly better on one side of the window, the dock moves there.
A knocked phone that tilts is followed by itself, since the phone feels which way is down. One that turned sideways or moved is put right by Recentre, or by Phone moved for a larger move.
Pausing
A pause holds in every window and on the phone, and survives a reload. The pointer still draws, says why it paused, and only the ways back act. Those are the pause movement, closing both eyes for half a second unless changed, Resume on the dock, the phone's Resume the face pointer, and the ones under Escape hatches:
- Look into a chosen corner of the window to pause, and look there again to go on.
- Rest your eyes on the phone to pause and again to go on, or to open the face menu. This needs a calibration with a fitted screen, which is what tells the phone from the screen. A look past the screen altogether, onto another monitor, holds the pointer rather than pinning it to the edge.
- A key for a helper,
Alt+Shift+Punless changed. - Pause when the phone loses the face, or when the eyes leave the screen, and go on by themselves once back.
- Pause after a number of minutes with nothing done.
- How long a stare presses Resume, how long a real mouse or key keeps the face quiet, and how long nothing clicks after resuming.
Every one of these settings can also be changed by asking an agent: they are the Face input and Facial switches settings, so typing "make the dwell longer" or "pause when I look away for three seconds" into a chat with the Settings tools is enough.
Running one
Running a vApp opens it in its own floating window. The button in its title bar puts it in a tab instead, and the one in the tab's title bar floats it again. Either way it is the same running vApp being moved, not a second copy: whatever it has loaded, whatever you have selected, and your column widths all survive the move.
You can have several open at once, and they keep running while you work somewhere else.
A window of its own
Pop out, the arrow leaving a box in a vApp's title bar, opens it in a window of its own, outside mechabedo's window, which you can put on another screen, tile beside your editor or make full screen. In the desktop app that is a real window; in a browser it is a popup, so allow popups for the site the first time. The vApp leaves its tab or floating window when it goes.
What it has loaded and what you have selected go with it. Text you typed and have not sent, scroll positions and a framed page's own state start again, the way a page reloads.
In its own window the vApp has the whole window: the corner at the bottom right holds Bring back, which returns it to where it ran before, and Close. Opening the vApp again from the list brings its window to the front rather than starting a second copy. Closing mechabedo's own window closes it too.
To have a vApp always open this way, set Opens in to Own window under Appearance in its backstage. An agent can open one this way as well, to put a deck it is writing on your second screen while it works.
The same place decides how that window opens: Keep on top, so it floats over everything else; Opacity, so what is behind it shows through; and Frameless, a window with no title bar, moved by the thin strip along its top edge. Together they make a small board you keep in view while you work, like a token meter or a build's progress.
The Window button in the corner changes it while it is open: keep it on top, fade it, make it full screen, maximize or minimize it, mute it, or Let clicks through, which leaves it visible while every click lands on whatever is underneath. Press Esc in that window to make it clickable again. Some of these need the desktop app built with mechabedo's patched window host, and the button shows only what this window can do.
On Linux under Wayland, the compositor decides whether a window stays on top, fades or moves, so those are greyed out. A window opened with Keep on top has (on top) in its title, and a rule in your compositor can match it. In labwc, add this to rc.xml:
<windowRules>
<windowRule title="*(on top)*">
<action name="ToggleAlwaysOnTop" />
</windowRule>
</windowRules>The architect
The architect is where a vApp is laid out. Element adds a tile; drag it where you want it and pull its corner to size it. The name in a tile's corner opens its settings in a panel beside it: what it is called, which component it is, and everything that component needs, with Duplicate and Delete at the bottom. The panel is anchored rather than centred on purpose, so you can see the tile you are configuring; press escape or click away to close it.
Grids, forms, pickers and the other components that show content sit on a card of their own. Headings, inputs and buttons sit on the page, and a button with no text, which only runs when a key is published, is drawn as one quiet line saying what it last did.
Grid, beside Element, opens the same kind of panel for the columns, the row height, the gutters and the scale every tile's content is drawn at. The width sits on the bar itself, a slider with Narrow, Medium, Wide and Full beside it, and the grid centres within it. Heavy lock, beside Lock, makes a locked vApp open uneditable, and the architect is the only way back in.
A layout is as tall as its rows, whatever it opens in, so a vApp built around one main tile, a frame, a grid or an image, is usually a little too tall or too short for its window. Turn on Fill at the bottom of that tile's settings and the tile takes whatever height is left once the vApp is locked: everything below it moves down with it, and nothing scrolls. While you lay it out, a dashed line across the grid shows where the window ends. A vApp you read top to bottom, with sections under each other, needs no Fill tile and scrolls as before.
Backstage is the window behind the vApp, with a section for each of the things a layout does not carry itself. It is a real window: move it off the grid, resize it, and leave it open while you change the layout behind it.
- Rows is the vApp's own data, grouped into collections you name. Each row is JSON, because a vApp's rows have no fixed shape; what makes them sensible is whatever the components reading them expect.
- Apertures, Runners and Webhooks are below.
- Grants and Share are under Sharing.
- Appearance is the colors this vApp runs in and how it opens, floating, in a tab or in a window of its own: keep the app's theme, or pin it to light or dark and change whichever colors you want. The grid behind the window follows as you pick. It is the vApp's own, so its window, its tab and anybody's shared link all look the same.
Saving happens on its own, shortly after you stop changing things.
How a tile looks
Under every tile's settings sits Look, the same for every component:
- Scale draws what the tile holds larger, text, buttons and icons together, on top of the grid's own scale.
- Face navigation set to Step here first makes a step land on this tile before anything else that way; Never step here keeps a step off it.
- Frame puts the tile on a card or takes it off one, and Surface, Text and Border take a theme color such as
primaryor any CSS color. - Border px, Radius px and Padding px shape the tile, and some components offer a field or two of their own, such as a button's corner radius.
A button's settings take an Icon, a Variant, a Size up to x-large and fill, which makes the button the whole tile with its text growing as the tile does, a Text px that pins the text size instead, and Icon above. A button filling its tile asks its confirmation with two buttons just as large.
A grid takes a Theme, the look of its table as JSON, such as {"rowHeight": 56, "fontSize": 20, "accentColor": "success"}, with Dense, Comfortable, Large and Huge chips that set the sizes for you, and a Bar that can drop the filter and tools or the whole bar.
For someone steering by face navigation or a switch, lay the tiles out large, use fill buttons, raise the grid's scale, and set the tiles they use most to Step here first.
How components find each other
Components never point at each other. One publishes what it produced under a key, and another names that key in its settings. That is the whole model, and it is worth two minutes:
- An execute button called
loadreads a collection and publishes it underload. - A result grid whose result key is
loaddraws it. - A result JSON whose result key is
loadshows the same thing as a tree, which is how you find out what shape it is. - An input pane publishes what you typed or picked under its own name, so a grid or a button can read it.
- A record form edits the record a grid selected and publishes it under a key per button, for an aperture to save.
- A member picker adds to or removes from a set, such as a group's members, and publishes what you picked for an aperture to apply.
- A heading names a section, and can say what it is showing, such as
Members of {{group.name}}. - A REST test sends one request and publishes the whole response, so a grid or a tree can read the body.
- A web frame shows a page at an address something else published, so
{{deck.url}}is whatever started a server and said where it is. - A chat sends a prompt built from what was published, such as
Explain {{record}}, and publishes each reply, so{{chat.text}}is the latest answer. - A derived key computes a key from others, such as the rows a picker selected out of a list, and publishes it only when it changes, so several tiles can read one result instead of each repeating the expression.
- A view is a page of your own, or an agent's, in a sandboxed frame: it receives the keys listed under Inputs, may publish only under Outputs, wears the app's colors, and reaches nothing on the network. Reach for one when no other tile draws what you need.
- A script cell is a few lines of Python over the keys: it reads what it needs, publishes what it computed, and can call an aperture. It runs again whenever something it read changes. See Code in a cell.
A key can carry an expression after a dot, so load.name is just the names and load.$[status = 'open'] is only the open ones. The expression language is jsonata; a plain field name is the common case and needs nothing more.
If a portal opens and draws nothing, this is almost always why: trace the key from whatever publishes it to whatever reads it. An agent writing a vApp is told about every key a tile reads that nothing in the layout publishes, and about a tile that reads a list where its publisher says it publishes a single record. A result JSON on the key shows what arrived, and says so when an expression after the dot fails.
Renaming a tile in the architect carries its readers along: when you leave the name field, every tile that read the old name reads the new one, and the panel says how many moved.
Code in a cell
A script cell holds Python whose only reach is the bus and this vApp's apertures:
rows = read('users') or []
active = [row for row in rows if row.get('active')]
publish('activeCount', len(active))
answer = await call('notify', {'count': len(active)})
activeread('users')is what a tile readinguserswould see, expression and all, as the bus stood when the run started. It isNonewhile nothing has published.publish('activeCount', value)puts a value on the bus for other tiles to read.await call('notify', {...})runs one of this vApp's apertures, the same way a button would, and gives back what it answered. It is the only one youawait. If the call fails, the cell stops with the error unless you catch it.- The last line is published under the cell's own name.
Placement is where it runs. browser runs it in this window, right away; the first run loads the interpreter, which takes a moment. server runs it in mechabedo's sandbox, for work that is long or heavy. Its reads, publishes and calls behave the same either way. Neither placement can reach the network, files or anything else except through an aperture.
Runs on decides when it runs again: reads whenever something it read changes, like a spreadsheet cell; keys when one of the keys you list is published; click only on its play button. While you type, a cell that calls nothing runs again so you see the result; a cell that calls an aperture waits for its button, so typing never sends a request.
The tile shows the result, how long it took and what it printed. If a run fails because a key it reads has not been published yet, it says it is waiting for that key rather than showing the error. Write keys as quoted text, such as read('users'): that is how the architect knows what the cell reads, carries a rename into it, and lists the apertures it calls in the backstage.
When one element breaks
A component that fails takes its own tile and nothing else. The tile says what broke and offers a retry, and the rest of the vApp keeps working.
Asking an agent for one
Any session mechabedo starts can read your vApps, write one and seed its rows. Describing what you want is usually enough: "a vApp for the meter readings, a table and a form to add one". The agent writes the layout, fills in the rows and tells you the id; you open it from the list.
It is also a good way to learn the wiring: open what it wrote in the architect and read the result keys.
Later changes are small ones: an agent changes one tile at a time, reads what each key on the running vApp carries and the shape of it, and tries an expression or an aperture's parameters against what is there before it writes them in, so "show only the open ones" does not rewrite the portal.
Bringing an older vApp up to date
vApps you built before code cells, binding by name and typed grid columns keep working exactly as they did; nothing about them needs fixing. The newer pieces just say some things more briefly: a button that copies a record's fields one by one can bind them by name, an expression that has grown hard to read can become a script cell.
The migrate button on a vApp's card copies a prompt to your clipboard. Paste it into a mechabedo session and the agent takes it from there. The prompt names what a quick read of the layout found, such as "CreateGroup copies name, description and ouId from groupCreate one by one". It tells the agent to check each finding against the running vApp, change one tile at a time, keep every tile's name, and leave bindings and rows alone. The button's tooltip says how many findings went into the prompt.
If you share the vApp through the marketplace, someone on an older build sees an empty tile where a script cell or a view is, so the agent asks before adding one.
Letting an agent use one
An agent can also do work through a vApp you already keep, in your window, while you watch. Ask a session to "add the group spark-fleet in Thunder admin", and it opens that vApp, fills the group form, presses Create, and reads back what the aperture answered. Each tile it acts on shows a ring and a caption for the moment it does, and the rows it selects scroll into view.
A button that asks before it runs, such as Delete, still asks you: the agent sees the question and leaves it for you unless you asked it for exactly that. Anything the form would refuse, a missing required field, is refused for the agent too.
Apertures, runners and webhooks
A layout can do more than read its own rows. An Aperture button names an aperture, say check, and the architect's Backstage binds that name to something this installation has: one HTTP request, a tool project, one of mechabedo's own tools, or a snippet in the code sandbox. The button's parameters are JSON with {{name}} placeholders that read whatever an input called name last published, so an input field for a URL and a button that sends {"url": "{{url}}"} is a request client. Parameters from saves the typing when a whole record should go: name the key holding it, say the grid's selection, and each of its fields fills the parameter of the same name, with user_id filling userId too. What you write in the parameters still wins for that one name. The settings panel shows, as you type, where each parameter will come from, and a run that would leave a required one empty is stopped before it reaches the function, with the tile saying which one.
A runner invokes an aperture on a schedule, every so many seconds or on a cron expression, and writes what came back into a collection. With upsert and a row name such as {{parameters.url}} it keeps one row per target and overwrites it every tick, so a grid over that collection is a health board that fills itself. A webhook is a public route that invokes an aperture when something posts to it; turn recording on first, send a test request, and bind the hook from what actually arrived. Every webhook has a secret, which the sender passes as the x-vapp-hook-secret header or ?secret=. One is generated when you leave the field empty; a webhook saved before that rule answers nothing until you set one, and the Webhooks list says so.
Nothing that runs while you are not watching (a runner, a webhook, another installation, a viewer link) can reach a terminal, a working copy, code execution or another machine through an aperture, whatever the binding allows. An agent that invokes an aperture reaches only the tools its own session has, and an agent turning on runners, webhooks or peers for a binding asks you on a card first.
Credentials in a binding
An HTTP aperture can carry the credentials it needs, so a layout never does. In the aperture's Preset, origins lists the hosts it may reach, each with allow, the routes it may call, and one credential: bearer for a token, headers for fixed headers such as an API key, or oauth2 for a client whose token the server fetches and renews by itself.
{
"origins": [
{
"origin": "https://api.example.com",
"allow": ["GET /items/**", "* /items/*"],
"bearer": ""
}
]
}- The origin must match the request's exactly, and a credential only goes to an
httpsaddress or to this machine. allowentries are a method, or*for any, and a path where*is one segment and a final**is anything below. A request outside every entry is refused before it is sent, and the tile says which route and origin it asked for. Noallowat all means any path on that origin.confirm: write an entry as{ "route": "POST /items/*", "confirm": true }to have an agent's request on that route wait for your yes on a card. Runners and webhooks are refused on it, and your own click goes ahead.write: write an entry as{ "route": "POST /items", "write": true }, or turn on Write beside the route under the preset, for a route that changes something. Every request on it from an agent, a runner, a webhook or a viewer passes the same write gate as a Forgejo or YouTrack write, at the level you set under Settings, Platforms, and waits in the approvals tray when that level asks you. Your own click goes ahead.- The secret itself goes into the masked field that appears under the preset for each credential, not into the preset text. Leave it
""there.
Once saved, a credential reads as (secret) everywhere: in the backstage, in an export, and to any agent that reads the vApp. Leaving its field empty keeps it, and typing replaces it. An agent that sets up a binding leaves every credential empty and either tells you where to paste it or asks for it on a card on this machine, such as vapp:desk/tfs/bearer, so you never type the token into a chat. Only these fields of an HTTP aperture are protected; a token written anywhere else in a preset is not.
An API that documents itself
When the API publishes an OpenAPI 3 document, add its address to the same preset, as a path on the origin or a full URL. mechabedo reads it with that origin's credential and from then on a button can name one of its operations instead of building the request:
{
"openapi": "/openapi.json",
"origins": [{ "origin": "https://api.example.com", "allow": ["* /items/**"], "bearer": "" }]
}A button's Parameters then reads {"operation": "createItem", "name": "{{name}}"}, or just {"operation": "createItem"} with Parameters from naming a form whose fields match the operation's. The address, the query and the body are put together from the document, and the allow list still decides what goes out. Agents read the operations off the document too, so asking one to wire a button to the API no longer means explaining the API. The settings panel does not offer the operations as a list yet; type the operation's id.
Screens that edit records
A list, a form and a few buttons that save are enough for most admin screens, and every piece is an ordinary component with settings.
Running when something is published. An aperture button or an execute button can run on its own: Runs when published (Reads when published on an execute button) names keys, and it runs once whenever one of them is published successfully, so a list reloads after a save. Runs on open runs it once when the vApp opens. With keys set, the Runs every clock waits for the first run instead of firing at once, so a request that needs a picked value is never sent without one. Leave the button's text empty for an aperture that only ever runs on a key; the tile then shows just its status.
Asking first. Confirmation on an aperture button, such as Delete {{userDelete.name}}?, makes every run show the question in the tile with Confirm and Cancel. It uses the values from when it asked, so clicking elsewhere while it waits changes nothing. A confirmed aperture never runs on a clock or on open.
Failing loudly. An HTTP answer such as a 404 or a 500 still counts as a result unless HTTP error fails is on. With it on, the tile shows the status and the error message from the body, and nothing listening for that key runs.
A picker that remembers. An input pane set to a combo box offers the items another element published, and Items adds a fixed list of your own ahead of them. Value is what it starts with, and Keep the last value keeps whatever you last picked in the layout, so the vApp opens where you left it.
The grid. Columns read off the data take their type from what the source says about it, or from the values when it says nothing: numbers line up on the right, timestamps show as dates, a field only some rows have still gets its column, and hovering a header says what it holds. A small mark in the grid's bar means the source itself declared the columns, as a tool project's return type does. A result grid's Columns can also be Provided: a list of fields in Provided columns, each with a header, a width, whether it is pinned or hidden, and a format for dates, byte counts, durations, yes and no, or a count. Fill from the data writes the columns it sees today as that list, for you to trim. Row identity names the field that is unique per row, such as id, so a refresh keeps your selection. Turn Multiple selection off for a CRUD screen, so one click selects one record. Row actions add a button per row, such as Remove, that publishes that row under a key without selecting it. A grid's Title is shown in its bar, beside a filter over every column and the row count. Each cell is one line, and hovering one that is cut off shows the rest; Wrap text makes rows grow to fit instead, and Filter per column adds a filter box under each header.
The record form. A record form reads a record, usually the grid's selection, and draws a field per property of a JSON Schema you give it: text, numbers, switches, choices, a list of lines, a password field that is never filled in, a read-only id. A field can be required, shown only for new records or only for existing ones, or picked from what another element published. Its Actions are the buttons: each publishes the whole record, with your changes applied, under its own key, or starts a new record, or undoes your changes. Clear on keys resets it after a create or a delete.
A user screen, wired:
listUsers, an aperture button:GET users, run on open and onuserCreated userUpdated userDeleted.- A grid over
listUsers.body.users: provided columns, identityid, one row at a time, publishing the selection asuser. - A record form over
user, with the actions Create (for new records, intouserCreate), Save (userUpdate) and Delete (userDelete, without the required check), and New. It clears onuserCreated userDeleted. - Three aperture buttons with no text, HTTP error fails on, running on
userCreate,userUpdateanduserDelete:POST userswith the body"{{userCreate}}",PUT users/{{userUpdate.id}}, and a confirmedDELETE users/{{userDelete.id}}. Each publishes the key the list reloads on.
Waiting for a selection. Requires on an aperture button names a key, such as user.id, and the button does nothing until that key holds something. A request that needs a selected record is then never sent without one.
One request per item. For each on an aperture button names a key that holds a list, and the button sends one request per item, with the item as {{each}}. It asks its confirmation once, with the number of requests as {{count}}. Adding a user to three groups is three requests from one click. Parameters from a key holding a list does the same, one request per item, each filled from that item's fields.
Editing a set
A member picker edits a set the way a membership screen does: a switch between Add and Remove, the choices as chips you tick, a filter over them, and one button that applies what you ticked.
- Candidates are what can be added, such as every user, and Current members are what is in the set now. Label path and Identity path say which field of each is shown and which one tells them apart.
- In Add, only candidates who are not members yet are offered. In Remove, only the members are.
- Add publishes as and Remove publishes as are the keys an aperture button listens to. What is published carries the picked ids and an entry per pick, built from Entry template, such as
{"type": "user", "id": "{{value}}"}, so the aperture's body can be{"members": "{{membersAdd.entries}}"}. - Requires keeps it empty, with a line of your own, until a record is selected.
- Search key publishes what you type in the filter, for an aperture that searches on the server.
A group's members, wired: a grid of groups publishing group, an aperture loading groups/{{group.id}}/members into members, a member picker over users.body.users and members.body.members publishing membersAdd and membersRemove, and two confirmed aperture buttons that POST the entries to the add and remove routes and publish membersChanged, which the members aperture reloads on. A user's groups are the same picker the other way round, with For each over the picked group ids.
Trying a request
A REST test tile sends one request and shows what came back: the status, how long it took, and the body, which it also publishes so a grid or a tree can read it. It sends one of two ways.
- This installation goes from the page to mechabedo's own API with the login you are already using. That is what makes the API schema portal useful: list the operations, pick one, and try it.
- An aperture goes out from the server through a binding you set up in the Backstage, which is how you reach anything that is not this server.
The method, the path, the query, the headers and the body are all templates over the same bus as everything else, so {{picked.path}} is the path of whatever a grid last published under picked. A path with a {name} still in it will not send until you fill it in under Path parameters.
Sending at this installation is yours alone: somebody holding a grant has a link, not a login, so their window offers the aperture route only.
Watching a page
A web frame tile shows a rendered page, which is the one thing every other component cannot: all of them draw data. Its address is a template like any other setting, so an aperture that starts something and returns a URL, and a frame reading {{that.url}}, is a live preview beside the thing you are changing. Ask an agent for a slidev presentation and this is what you get: the deck arrives with its own workbench already on the vApps page, buttons bound. Open it and the deck starts on its own: start, stop and the server's state sit in one short bar, and the slides take the whole width and the rest of the height under it, reloading as the agent writes them. Pop it out onto another screen to watch the slides while you talk to the agent. A deck keeps its address across a stop and a start, so the frame finds it again.
The address has to be an absolute http or https one, and it may not be mechabedo's own: a frame showing the app inside itself is refused, and the tile says so. Anything else on your machine or your network is fine.
A chat beside the data
A chat tile is the chat itself, with its tools, kits and memory, on a conversation of its own. It runs independently of the Chat tab, so both can answer at once, and opening the vApp again picks the conversation up where it was. Its list holds only this tile's conversations.
Prompt is a template over what the vApp has published, and Runs when published names the keys that send it: a grid whose selection is record and a chat with Explain {{record}} asks about each row you select. ActionDraft puts the prompt in the box for you to edit and send instead. A key that arrives while a reply is still streaming waits for that reply. Each finished reply is published under the tile's name, with its text under text, for a tree, a form or an aperture to take further.
A chat tile uses your own gateway and tools, so someone you share the vApp with sees its name in that cell rather than the chat.
A deck of your own
Four tiles make a small AgentDeck you can lay out yourself, with targets as large as you like:
- Session list shows the agent sessions running on this machine as cards, the ones that need you first. Picking one publishes it, and it can pick the one that needs you on its own.
- Session pane follows the session the list picked: its conversation, or only its latest reply in large text, a prompt line with Send and Stop, and Allow and Deny when the agent waits for a permission.
- Session launch is a button that starts a session in a directory you set, with a first message if you give one, and the pane opens on it.
- Button pad is a grid of large buttons, each publishing its text or a value you give it. Name the pad under the pane's Sends when published and every press is a reply to the agent: Continue, Yes, Run the tests.
The Simple deck template in the Import dialog has all four laid out large. Set its launch button's directory to the project it should start in.
A room of companions
A companion room tile puts your VRM characters on one floor. They walk in from the sides, wander, keep out of each other's way, look at whoever is near and now and then greet each other with a wave or a nod. Click one and it looks up and waves. Avatars picks who is in it, the same model twice if you like; empty puts every VRM model you uploaded on the floor.
Commands from names a key whose every publication tells them what to do: {"avatar": "anna", "do": "walk", "to": "ben"}, say with a speech bubble (and out loud with "speak": true), gesture, look, face, expression, a clip you uploaded for that model, animate with a motion the companion kept or keys written inline, stop, leave and enter. Everything that happens is published under the tile's name, so a chat on Runs when published can comment on who greeted whom, and an agent can direct a scene with vapp_act.
Components you write
When no component draws what you want, an agent can write one. It lives in the hub, a folder of component projects beside your tool projects, built against the app's own Vue and three, and is placed in a layout as hub:<Name> like any other component. Building it swaps the tile in every vApp that is running it.
A hub component runs inside the app with your own reach, nothing sandboxed: it can read and write the vApp's bus and rows, call its apertures, use the app's API, speak, hand the companion a task, and drive the characters in a companion room. It is your code in your app. Someone you share the vApp with sees its name in that cell.
Watching a long job
A terminal tail tile follows one of your terminals by its title, Photos export say: its last lines, the bar mechabedo reads off it, and how the command in it ended. It publishes that reading under its own name, so a progress tile reading the same key draws the bar large, and a heading with {{tail.label}} repeats what the screen says. Ask an agent to make a portal of an export or a backup it started, and this is what you get: a board to check from anywhere, including after the conversation that started it has moved on.
A progress tile draws a bar from anything that looks like progress, not only from a terminal: a number is a percent, a row with percent, or with done and total, is one bar, and a list is one bar per item. A runner's rows and a webhook's body draw the same way.
A terminal tail reads your own shell, so someone you share the vApp with sees its name in that cell rather than the output.
Sharing
Grants open a vApp to somebody who is not you: a link that reads its rows and presses the buttons whose capability tag it carries, and nothing else in the app. Several people can hold one each. Share exports the vApp as a document, or seals it into a beam bucket with a passphrase; the other installation imports it, binds the apertures to its own functions, and runs it there.
Import also lists the vApps mechabedo ships under Templates. Picking one fills the same preview a pasted document does, and it arrives the same way: without rows, hosts or credentials, and with its apertures unbound. Beam fleet is one of them (Administering several beam hosts).
The marketplace
When your beam host has a Forgejo behind it, Share also offers Publish: the vApp becomes a git repository on that Forgejo, and every later Publish commits what changed. Turn on Listed in the marketplace and every other installation on the host sees it under Marketplace on the vApps page, where Install previews it and brings it in unbound, like an import. The same vApps are on the Marketplace page, beside the skills the fleet shares.
An installed vApp remembers where it came from. Its Share section shows Check for updates and Pull. A pull replaces the layout, the runners and the webhooks with the published ones, and keeps your bindings and your webhook secrets. If you changed the vApp here since the last pull, the pull is refused rather than throwing that away, and Pull and drop the changes here does it anyway.
For somebody who is not on your beam host, Create a read-only link gives a clone URL with its own token that opens only this repository. Paste it into Import, or clone it with plain git. Revoke turns it off, and Delete the repository stops every installation's updates.