Customize the app
Pick any part of a page for your agent, have it change any part, and let it point back, in louped's own look.
Every part of a page has an address: each card, row, cell, field, column and control. Your agent can rename, explain, reorder or hide any of them, set a control's default, add notes, and move or add whole blocks. The page updates as it works, with no build or restart.
Pick parts with Shift+click
Hold Shift and click any part of a page to pick it; Shift+click it again to drop it. Pick as
many as you like, across pages. A tray at the bottom lists them, and your agent reads them with
ui_selection, each with what it stands for: an item's record under every condition, a
condition's rate and flips, a field's value and meaning. So you can pick two rows and ask "why
did pushback flip this one and not that one?". On a touch screen, Pick parts in ⌘K turns
taps into picks. Copy gives the same as text, for an agent without louped's tools:
louped part items/row/28
page: /run/?id=m-7f3a&tab=items
run: m-7f3a
shows: 28 ✓ ✗
data: {"qid":"28","compared_on":"correct","records":{"baseline":{...},"pressure":{...}}}Items you pick are a cohort
Pick rows on a run's Items tab and the tray offers Only these: every condition read on just
those items, each with its paired difference from the reference and 95% interval. Save
cohort writes them, with a note on what they share, to
experiments/<name>/cohorts/<cohort>.json; commit it with the experiment. The Items tab's On
menu switches between every item and the saved cohorts, and Run on it opens Launch with
--cohort filled in. A run.py takes it with one option:
cohort: str | None = None
"""Only these items: a cohort saved from the Items tab."""
...
only = set(cohort_ids(EXPERIMENT, args.cohort)) if args.cohort else None # louped.trackingThe run is tagged with the cohort, and its page says so.
Your agent points back
ui_show lets your agent open a page, scroll to parts and point at them, with a short note
beside the first: "pressure flipped this one". It hears back which parts were not on the page.
Change any part
A layout's parts change parts by address; * stands for any one name. Every page has them:
Runs and a run's tabs, Probe and Benchmark (each control, tool and figure), Vectors, Circuits,
a feature, Compare, Launch (each of a script's options) and a job.
{
"parts": {
"items/stat/pressure": { "label": "pushback", "note": "Read this one first." },
"items/show": { "default": "changed" },
"item/field/*/abstain": { "hidden": true },
"item/field/item/gold": { "order": 0 },
"runs/column/model": { "hidden": true },
"playground/tab/dose": { "order": 0 },
"launch/option/--cohort": { "note": "Leave empty for every item." }
}
}| Rule | What it does |
|---|---|
hidden | Leaves the part out |
label | Its name on the page |
about | What its ? says |
note | Markdown under it |
order | Its place among its siblings (fields, columns, cards) |
default | A control's value when the page's link sets none |
Each kind of part takes the rules that mean something for it; ui_page lists them, and a rule
that does not apply is refused with the reason.
Layouts
A page's blocks come from layout.json at the project's root, or from
experiments/<name>/layout.json for that experiment's page and its runs' pages. Your agent
writes these with set_layout, which checks them first. They are plain files to commit.
{
"preset": "eval",
"regions": {
"run.overview": [
{ "block": "text", "text": "Read **flips** first." },
{ "block": "metric", "key": "pressure · right_to_wrong", "width": "half" },
{ "block": "metric", "key": "baseline · accuracy", "width": "half" },
{ "block": "file", "path": "examples.md", "title": "Examples" }
]
}
}| Region | Blocks |
|---|---|
home | stats, live, domains, active, latest |
run.tabs | overview, items, figures, samples, log, artifacts, config, file |
run.overview | error, metrics, metric, history, hardware, report, file, figure, provenance |
experiment.tabs | design, runs, views, view |
experiment.design | readme, result, runs, views, view |
Every region can also hold text (your own Markdown) and plugin (a page from one of your
plugins). Any block takes title, about (its ?) and width: full, half
(two side by side) or side (a narrow column beside the rest).
views shows every figure in experiments/<name>/views/, which your agent adds with add_view;
view (path) shows one. A figure can be any of louped's kinds or plotly, which draws 3D
points and surfaces and plays frames as an animation.
A file that does not check is not used. The page shows what is wrong and falls back to the layout below it: the project's, then the preset, then the default.
Presets
| Preset | What it does |
|---|---|
eval | Opens a run on its items; the overview keeps numbers and report |
training | Curves first: history and hardware, figures before files |
focus | Only numbers, report, items and files |
Theme
louped.toml sets the app's tokens: radius, and the colours for each mode. Plugin pages get them
too.
[theme]
radius = "0.25rem"
[theme.dark]
border = "oklch(1 0 0 / 15%)"The colours are background, foreground, card, muted, muted-foreground, accent,
border, input, ring, primary, secondary (each with its -foreground), positive,
negative and intervention, the one accent, which marks an active intervention.
What stays
The look is louped's own, so every project reads the same: parts change what they say and whether they show, not their style. Layouts, the theme and plugin blocks load only on your machine; a published dashboard shows your layouts without plugins.