Selection: S.sel stays the object picked last; S.selIds holds everything
selected.
- In the object list, Ctrl+click adds or removes an object and
Shift+click selects a range.
- On the map, Ctrl/Shift+click toggles an object and Shift+drag draws a
box that selects what lies wholly inside it (locked and hidden objects
are left out).
- Ctrl+A selects everything that is not locked; Esc selects nothing.
- Clicking a wall or fence a second time selects every piece of the same
type joined to it; another click goes back to that one piece.
- Dragging one selected object moves them all, together with the windows
and doors on them, as one undo step. Arrow keys, R (turns the group
about its middle), Ctrl+D, Delete, Lock and Hide all work on the whole
selection.
- When all selected objects share a type, the property form edits them
all; fields where they differ show "mixed".
- The agent's system prompt lists the selected objects.
The viewer outlines several objects with selectObj([ids]) and passes the
click event to onPick. A batch op now answers `ids`, one per operation,
so the copies from a group duplicate get selected.
Pond (Nature): natural, round or square, with a stone or grass edge,
fish, lily pads, spreading ripples and reed clumps. The fish come from a
new Swim primitive (script: s.swim). One matrix maps the object's frame
to the screen. Each fish turns round its ellipse with a CSS rotation,
so off-screen pausing and reduced motion apply as for the other
animations. The house demo gets a koi pond in the back garden.
Tests: ponds in every shape and rotation have the right number of fish,
no NaN and no depth-sort cycles, and stay inside their rectangle.
check_studio covers list and map multi-select, a group drag undone in
one step, joined-fence selection, a mixed-value form applied to all, box
select, delete and undo, Ctrl+A, Esc, and a new pond's fish.
Found while recording a demo with Qwen3.8 on the inference server:
- When the model wrote its answer and then called select_object, it was
asked again with nothing left to say and rambled ("the user hasn't sent
a message yet"), sometimes in Chinese. A step of page tools only
(select_object, set_view) after a written answer now ends the turn; the
system prompt says so, and to answer in the language of the latest message.
- The system prompt names the world directions of the user's screen right
and up, and the 2:1 foreshortening, so "left of the hall" and shapes meant
to be read on screen come out right (asked for a giant W of people, the
model now draws it upright and readable).
- mock_llm.py "show me X" + check: answer and select_object in one step end
the turn without another request, and the history stays valid.
A 401/403 with an HTML error page (nginx) showed as "HTTP 401: 401
Authorization Required 401 Authorization Required nginx". Now: "The endpoint
wants an API key: enter it in ⚙" before one is set, "The endpoint refused
the API key (...)" after.
On isokit.buildfor.life, entering https://inference.p0.contact/v1 in the
agent window's settings made the page call the inference server directly,
which browsers refuse (its nginx answers the CORS preflight with 401):
"The browser could not reach ... (Failed to fetch)".
- the relay meta names its upstream (data-upstream; build_web.py
--agent-upstream, the deploy passes the inference server): that URL now
goes through the relay and is saved as "follow the site's default"
- an unreachable endpoint says what to do: the relay's URL on a site with
one, else isokit studio or isokit.buildfor.life
- check_web.py: relayed upstream and the unreachable message
✦ Agent in the Studio's top bar opens a floating chat window. The model
(any OpenAI-compatible endpoint with tool calling; default B4L's inference
server, https://inference.p0.contact/v1) gets the 15 MCP tools plus two page
tools (select_object, set_view) and the Studio's state (selection, level,
rotation) in its system prompt. Answers and reasoning stream in; tool calls
show as rows with arguments and results; object ids link to the object;
Stop ends a turn; Undo takes the agent's edits back. The window moves by
its title bar and resizes at its top-left corner.
- studio/agent.js: shared by both front ends (waits for studio.js's
isokit:studio event); MCP over /mcp or isokit.mcp in the web Studio
- isokit studio: /api/agent/{config,models,chat} relay the calls on their
own threads (ureq, chunked and flushed per read), so the key stays on the
machine and the endpoint needs no CORS; settings from ISOKIT_LLM_URL /
_KEY / _MODEL or agent.json in the user's config folder
- web Studio: calls the endpoint from the page with a key kept in the
browser; build_web.py --agent-url names a same-site relay as default; the
deploy builds with /llm/v1, which deploy/Caddyfile forwards to the
inference server (its nginx refuses the CORS preflight)
- tools/mock_llm.py + tools/agent_checks.py: the agent window checks in both
check_studio.py and check_web.py