Skip to content

docs(exploration): explore lessons from BuilderIO/agent-native - #630

Merged
crs48 merged 1 commit into
mainfrom
claude/agent-native-exploration-8b5c44
Jul 26, 2026
Merged

docs(exploration): explore lessons from BuilderIO/agent-native#630
crs48 merged 1 commit into
mainfrom
claude/agent-native-exploration-8b5c44

Conversation

@crs48

@crs48 crs48 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Adds exploration 0397 — a depth-1 code read of BuilderIO/agent-native (d296fd9, 26 Jul 2026) and what transfers to xNet.

What the repo actually is

4,037 stars, created 12 Mar 2026, 2,417 PRs in ~4.5 months, 374,334 non-spec LOC in packages/core/src across ~110 subsystem directories, 639 spec files, 17 templates, 65 skills, 43 guard scripts. Agent-written, and it reads like it.

The one load-bearing idea: defineAction has seven callers — "tool" | "http" | "frontend" | "cli" | "mcp" | "a2a" | "automation" — and every dispatch site tags which one.

The finding for xNet

xNet has four parallel verb vocabularies that never meet: WorkspaceCommand (palette/hotkeys), AiToolEntry (32 xnet_* tools), the React mutation hooks, and a different defineAction for outbound webhooks. The data layer is shared; the verb layer is not.

The seam to fix it already exists — apps/web/src/plugins/workspace-agent-module.ts already gives the assistant tools that work by emitting the same registered commands the palette runs, never private state.

Recommendation

Option C — promote WorkspaceCommand into the agent surface as an opt-in projection (absent agent block = palette-only, so adding a palette entry can never silently widen agent reach), sequenced behind two cheaper wins:

  • W0 — Screen state. Our agent is blind to what the user is looking at; grep for navigation/selection in the AI surface returns nothing. Cheaper for us than for them: their principle costs a table + endpoint + polling loop, ours costs a store key.
  • W1 — Guard lane. Three ratcheted guards with inline // guard:allow-… — reason opt-outs, including a false-checkbox guard (0377 recorded five falsely-checked [x] items).
  • W2 — The projection. One registry, two projections, plus a VerbCaller tag routed through the existing audit recorder.
  • W3/W4 — Deep links + declarative approval. MCP Apps went stable 2026-01-26 (ui://, text/html;profile=mcp-app); we already have xnet:// and the frame primitive.

Rejected: adopting the framework (server-first Drizzle + Nitro, structurally opposite to local-first) and porting defineAction wholesale (name collides with our outbound one; four of the seven dispatchers have no consumer here).

Notes

  • Their write ceremony (needsApproval?: boolean | predicate) is weaker than ours (risk tiers, chat codes, audit, xnet_undo). Keep ours; steal only its declarativeness — the gate belongs on the verb.
  • ⚠️ Do not vendor their code. README says MIT, packages/core/package.json says MIT, root package.json says ISC, and there is no LICENSE file — the GitHub API reports license: null.

18 implementation items, 9 validation items. Docs-only — no changeset.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: xNet Test <test@xnet.dev>
@crs48
crs48 temporarily deployed to pr-630 July 26, 2026 20:00 — with GitHub Actions Inactive
@crs48 crs48 added the skip-changelog Exclude this PR from the changelog label Jul 26, 2026
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #630.

@crs48
crs48 merged commit f7ee2ea into main Jul 26, 2026
10 of 11 checks passed
@crs48
crs48 deleted the claude/agent-native-exploration-8b5c44 branch July 26, 2026 22:03
github-actions Bot added a commit that referenced this pull request Jul 26, 2026
crs48 added a commit that referenced this pull request Jul 26, 2026
…owns any pixel (#631)

Implements **W1–W3** of [exploration
0399](docs/explorations/0399_[-]_POINT_AND_CHANGE_XNET_EDITING_ITSELF.md).
Marked `[-]` — 25/30 items; the four deferred ones are listed in the doc
with reasons.

> Stacked on [#630](#630)
(explorations 0397–0399), so it targets that branch. Retarget to `main`
once #630 merges.

## What you can do now

Hold <kbd>⌥</kbd> and hover: the element under the pointer is outlined
and labelled with the **layer that owns it** and one sentence on what
changing it would move. <kbd>⌥</kbd>-click opens a panel that leads with
that sentence, then offers the smallest control that can make the
change.

| Pointed at | Verdict | Control |
| --- | --- | --- |
| Something painted from a theme token | Lane 1 · global scope | the
token's value, one Undo |
| A registered panel | Lane 1 · surface scope | that slot's own palette
commands |
| A plugin surface | Lane 2 | explanation only (see deferred) |
| `packages/*` / `apps/*` source | Lane 3 | explanation only (dev-loop
is node-side) |
| `packages/{sync,crypto,identity,data}` | **refused**, with a reason |
none |

Verified live in the running app, not just in tests: all five verdicts,
plus apply-and-undo writing and clearing a real token.

## Three decisions worth reviewing

**The stamp is a JSX runtime shim, not a Babel plugin.** A
`JSXOpeningElement` visitor never fires here — `@vitejs/plugin-react`
doesn't transform JSX under the automatic runtime; esbuild does,
earlier. By the time Babel runs, every `<div>` is already a
`jsxDEV(...)` call. So `jsxImportSource` points at a shim that reads the
`source` argument esbuild already passes. Side benefit: it doesn't
depend on React storing it, so it survives React 19 dropping
`_debugSource`.

**Lane ≠ blast radius.** A Lane 1 token edit is the cheapest mechanism
available and repaints the whole app; a Lane 3 edit drags a PR through
CI and may move one button. `Resolution` carries both, and the UI shows
the *scope* sentence — otherwise "make this blue" quietly restyles
everything.

**No per-element override store.** `packages/ui/src/theme/tokens.ts` is
the token contract as plain functions; `ThemeProvider` delegates to it.
Keyed by custom-property name, never by element — a per-element map
would be a second source of truth and permanently fork the app's
appearance from its stylesheet (the documented Plasmic failure mode).

## Safety

- **The stamp can never ship.** Three guard tiers in
`scripts/guard-no-source-stamp.mjs`: the config must gate on `command
=== 'serve'`, no source file may import the shim by name, and no built
asset may contain the attribute. Verified against a real production
build (0 hits) and verified to go red when deliberately broken. Wired
into CI.
- **Kernel packages are refused twice** — in the browser resolver and
again in `assertEditable()`, which re-derives from the source ref. A
test forges an `allowed: true` kernel resolution; it is still refused.
- **`previewWorktree()` refuses the session's own port.** Sharing it is
how a broken edit takes down the surface you're editing from.
- **PRs open as drafts** and nothing merges.
- **Injection boundary:** `lane3Prompt()` takes a `Resolution` (source
location) and the user's instruction. The pointed element's rendered
text — workspace content, possibly authored by someone else — has no
field to travel through.

## Four bugs found while building, all now regression-tested

1. A bare function passed to `setUndo` — React treats it as an updater
and **called** it, un-applying the change immediately.
2. Token attribution compared HSL triples (`0 0% 98%`) against computed
`rgb(...)`; they can never match, so Lane 1 silently did nothing.
3. Tailwind's preflight sets `border-color` on **every** element, so an
unguarded border check made nearly the whole tree claim Lane 1 and hid
Lanes 2 and 3. Attribution now requires a drawn border, and text colour
requires text the element owns.
4. The overlay is mounted at the app root (to cover onboarding and
loading screens), which put it outside `App`'s `ThemeProvider` —
`useTheme()` threw and took the whole app down. That's the self-bricking
hazard in miniature; a test now renders the panel with no provider.

## Checks

`turbo run typecheck` 97/97 · `vitest run` 11,267 passed / 3 skipped ·
lint clean · guard green · changeset for `@xnetjs/devkit` + `@xnetjs/ui`
(both minor) · changelog fragment added.

Also points 0190 at this doc — its dev-loop body was built and had no
front end.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude this PR from the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant