Model Context Protocol server for live browser editor integration. Tool listings generated from the shared registry.
The blnq MCP server connects an external AI harness (Claude Code, Cursor, Windsurf, any MCP-speaking client) to the blnq.studio browser editors in real time. Tool calls from the AI flow through a per-tab bridge and are pushed into the live Monaco editors via Server-Sent Events.
The same bridge is shared with the in-browser AI chat panel, so anything described here applies to both call paths. Edits from either side push to the browser over the same SSE channel.
POST /mcp (Streamable HTTP MCP transport) → bridge state → SSE → browser editor.
Three steps. Most people get stuck on step 1 because they assume the agent can see the project before they click Start MCP. It can't — the bridge is lazy by design.
blnq.studio/e. In the top-right of the header you'll see a Start MCP button — click it. The button lights up, the bridge connects, and your MCP ID (a random UUID bound to this tab) is copied to your clipboard. Leave the tab open.https://blnq.studio/mcp — no API key, no auth header, no install step on the server side.tabId and prompt you for your MCP ID — paste it then. This is often the easiest flow because you don't need to plan ahead. If you prefer, you can hand the ID over upfront ("My blnq MCP ID is abc1234, use it on every tool call") so the agent never has to ask.Every config below points the client at the same endpoint: https://blnq.studio/mcp over HTTP (Streamable HTTP transport). Pick the one that matches your tool.
One command — the CLI writes the config for you:
claude mcp add --transport http blnq https://blnq.studio/mcp
Or add it manually to ~/.claude.json (user scope) or .mcp.json (project scope):
{
"mcpServers": {
"blnq": {
"type": "http",
"url": "https://blnq.studio/mcp"
}
}
}
Verify with claude mcp list; the entry should show connected once you start a session.
Open Settings → Developer → Edit Config (this opens claude_desktop_config.json). Add:
{
"mcpServers": {
"blnq": {
"type": "http",
"url": "https://blnq.studio/mcp"
}
}
}
Restart Claude Desktop. If your build doesn't yet support remote HTTP MCP natively, use the mcp-remote stdio bridge instead:
{
"mcpServers": {
"blnq": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://blnq.studio/mcp"]
}
}
}
Edit ~/.cursor/mcp.json (global) or .cursor/mcp.json in a workspace (per-project), or use Settings → MCP → Add Server:
{
"mcpServers": {
"blnq": {
"url": "https://blnq.studio/mcp"
}
}
}
Edit ~/.codeium/windsurf/mcp_config.json, or use Settings → Cascade → MCP Servers → Add Server:
{
"mcpServers": {
"blnq": {
"serverUrl": "https://blnq.studio/mcp"
}
}
}
blnq is a stock Streamable HTTP MCP server. If your client supports remote MCP over HTTP, point it at https://blnq.studio/mcp — nothing else is required. If it only supports stdio, wrap it with mcp-remote as shown in the Claude Desktop fallback above.
Every tool that touches editor content takes a tabId parameter — that's the MCP ID copied to your clipboard when you clicked Start MCP. There is no discovery tool and no auto-resolution: in a shared-tenant deployment, listing connected tabs would leak other users' projects, so the server requires an explicit ID on every call. You have two ways to get it to the agent.
On-demand (easiest). Just start prompting — "using blnq, build me a portfolio site". The agent will hit its first tool call without a tabId, see the "MCP ID required" error, and ask you for one. Paste it then. You don't have to plan ahead; the harness handles the prompt for you.
Upfront (when you want to skip the round-trip). Paste the ID into the chat at the start of the session and ask the agent to remember it. Examples:
If the ID stops working, you'll see "MCP ID not recognised" from the tool. That means the editor tab was closed (the ID is revoked ~5 minutes after the SSE connection drops). Open a fresh tab at blnq.studio/e, click Start MCP again, and pass the new ID.
Once the agent has your MCP ID, talk to it like any other tool. A few starting points:
Using blnq, build me a one-page portfolio: sticky nav, hero
with my name, three project cards, contact section. Dark
theme, no AI blue, no gratuitous gradients.
Using blnq, read the current files, then make the hero
stack on mobile and add a subtle fade-in for the cards.
Run blnq_check_preview when you're done and fix anything
it flags.
Using blnq, generate three design variants of the landing
page — minimalist, editorial, brutalist — and show them in
the variant picker.
The bridge is session-less from the harness's point of view — the only identity that matters is the tabId, a random UUID minted by the editor tab.
┌──────────────────────┐ POST /mcp ┌──────────────────────┐
│ External AI harness │ ─────────────────────▶ │ blnq MCP server │
│ (Claude Code, etc.) │ ◀───────────────────── │ (Streamable HTTP) │
└──────────────────────┘ tool results / SSE └──────────┬───────────┘
│
bridge state
(per tabId)
│
┌───────────────────────────────┤
│ │
sendToTab(event, data) POST /api/bridge/sync
via SSE channel (content mirror)
│ │
▼ │
┌──────────────────────┐ │
│ Browser editor tab │ ◀─────────────────┘
│ (Monaco + preview) │
│ /api/bridge/events │
└──────────────────────┘
GET /api/bridge/events?tabId=…. This SSE stream is how the server pushes live updates back to the browser.POST /api/bridge/sync, seeding the in-memory state so external tools can read it. Without this click, the bridge stays idle and tools see no tab.initialize to POST /mcp. The server responds with tool definitions. Session-aware clients get an mcp-session-id header to reuse; stateless clients get a one-shot server per request.tabId parameter. The server resolves to the matching tab and pushes edits over the SSE channel.Some tools need live runtime data, not just editor text — e.g. blnq_run_js, blnq_get_element_info, blnq_check_preview, blnq_click. These use a request / response pattern over SSE:
js:eval, element:query, dom:action, a11y:run) with the request payload./api/bridge/js/results).Each pending request has a timeout (default 8–15 s depending on tool). Only one pending request per tool type per tab at a time — the new request replaces the old one.
The MCP endpoint is at POST /mcp using the Streamable HTTP transport from the official MCP SDK.
initialize request without a session header. The server returns an mcp-session-id header — include it on all subsequent requests.DELETE /mcp — close an existing session (requires mcp-session-id).GET /mcp — returns 405 Method Not Allowed.GET /mcp/spec — this page.Inspection, preview-driving, and project-state tools. Each tool is tagged read (safe to call without prompting the user) or write (mutates project state). Listings below are generated from the shared registry, so this page can't drift from the live tools.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| query | string required | Text or regex to search for |
| filenames | string[] optional | Optional list of filenames to search (e.g. ["/index.html"]). Omit for all files. |
| regex | boolean optional | Treat query as regex |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| name | string required | Short descriptive project name |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| width | number optional | Viewport width in px (default 1280) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| steps | number optional | Number of steps (default 1) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| elements | object[] required | Array of Excalidraw element objects |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector (e.g. '.hero', '#nav', 'h1') |
| styles | string[] optional | CSS property names. Defaults to a common layout set. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| code | string required | JavaScript to run in the preview. Return value + console output are captured. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| levels | string[] optional | Filter to these levels. Default: all. |
| limit | number optional | Max entries to return (1-100, default 50) |
| sinceTimestamp | number optional | Only return entries with timestamp > this (ms). Use the `now` field from a previous response to poll incrementally. |
| clear | boolean optional | Clear the log buffer after reading. Default false. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector to click |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector for the input/textarea/contenteditable |
| text | string required | Text to type |
| clear | boolean optional | Clear field before typing (default true) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| key | string required | KeyboardEvent.key value (e.g. 'Enter', 'ArrowUp', 'a') |
| selector | string optional | Target element (default: document.activeElement) |
| shift | boolean optional | |
| ctrl | boolean optional | |
| alt | boolean optional | |
| meta | boolean optional |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| selector | string required | CSS selector to wait for |
| text | string optional | Optional text substring the element must contain |
| timeoutMs | number optional | Max time to wait in ms (100-15000, default 5000) |
| Param | Type | Description |
|---|---|---|
| query | string required | Search query |
| max_results | number optional | Number of results (1–10, default 5) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| id | string required | Viewport preset id (mobile, tablet, desktop, etc.) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| url | string required | URL to fetch (http/https). Protocol is auto-prepended if omitted. |
| blnq_screenshot | boolean optional | Capture a screenshot too (default true) |
| contentOnly | boolean optional | Skip screenshot — return text only (default false) |
| raw | boolean optional | Return raw HTML markup instead of stripped text (for remixing a site's structure). Default false. |
| consentSelectors | string[] optional | Extra CSS selectors for site-specific consent buttons |
Projects are a list of files keyed by path (e.g. /index.html, /page2.html, /style.css, /components/nav.js). /index.html is the default entry page and cannot be deleted; additional HTML pages can be created freely and linked with relative <a href="page2.html"> links. Other files are referenced via standard <link> and <script> tags.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /index.html, /style.css) |
| startLine | number optional | 1-based start line |
| endLine | number optional | 1-based end line (inclusive) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /style.css) |
| content | string required | Full file content |
| force | boolean optional | Bypass shrinkage guard for intentional rewrites that are shorter than the original |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /utils/helpers.js) |
| content | string optional | Initial content (default: empty) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path to delete |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| path | string required | Folder path (e.g. /js or /css/components) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| path | string required | Folder path (e.g. /js or /css/components) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| filename | string required | File path (e.g. /style.css) |
| startLine | number optional | First line to replace (1-based, line-range mode) |
| endLine | number optional | Last line to replace (1-based inclusive, line-range mode) |
| oldText | string optional | Text to find (find-replace mode) |
| newText | string optional | Replacement text |
| replaceAll | boolean optional | Replace all occurrences |
The variants flow lets the AI stream multiple design options into the editor's variant picker. Plan N variants, call blnq_set_variants with the first only (the picker appears immediately), then call blnq_append_variants once per remaining variant so the user sees them arrive as they're generated. The first variant loads in the editor; the user can switch or dismiss via the picker UI.
Each variant is a { name, files: [{ filename, content }] } bundle — a complete file set, not a diff. When the user activates a variant, the browser POSTs the selected files to /api/bridge/variant/activate; dismissing the picker POSTs to /api/bridge/variant/clear. The AI can also commit one directly with blnq_select_variant (by number or name) — equivalent to the "Select" button: the server makes that variant the active project, discards the rest, and pushes variants:select so the editor loads it and closes the picker (no confirm dialog).
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variants | object[] required | New variants to append |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variants | object[] required | 1–4 complete design variants (use 1 for streaming — call blnq_set_variants then blnq_append_variants per variant) |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| variant | string required | 1-based variant number (e.g. "2") or its name (e.g. "Editorial"). |
These tools are not exposed over MCP. They need the in-browser chat panel UI to render their effect (button cards, task lists, skill content), so external clients won't see them. They're listed here for completeness.
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| question | string required | |
| options | object[] required |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| todos | object[] required |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| id | string optional | Skill name or numeric id. Omit to list all available skills. |
| Param | Type | Description |
|---|---|---|
| tabId | string required | MCP ID — ask the user once at session start; include it on every subsequent tool call. |
| id | string optional | Skill name or numeric id, same as blnq_get_skill. |
| ref | string optional | Reference name without extension, e.g. 'typeset'. Omit to list the skill's available references. |
The in-browser AI chat panel is the second client of the same bridge. It never touches POST /mcp — it runs server-side in the same process and calls the shared tool registry directly. Everything else (bridge state, SSE pushes, undo history, validation) is identical to the external MCP path, which is why edits from either side look the same to the browser.
┌──────────────────────┐ POST /api/ai/chat ┌──────────────────────┐
│ Chat panel (browser)│ ─────────────────────▶ │ AI agent loop │
│ │ ◀───────────────────── │ (server, in-proc) │
└──────────┬───────────┘ NDJSON stream └──────────┬───────────┘
│ shared tool registry
│ │
│ bridge state (tabId)
│ │
└──────────── SSE /api/bridge/events ◀──────────┘
POST /api/bridge/sync, so the bridge state the tools read is never stale.POST /api/ai/chat with { provider, model, messages, tabId, … }. The response is an NDJSON stream of text deltas, tool-call progress, _error, and a final _meta record (usage, step count, checkpoint id).file:update / file:create / etc. over the same SSE channel external MCP clients use.blnq_ask_user).tabId in the model's hands. The panel knows its own tab, so the server injects tabId into every tool call. The model never sees the parameter — unlike external clients, which must pass the MCP ID explicitly.blnq_get_project_info (redundant — the project name and file list are already injected into the prompt) and minus blnq_web_search when SERPER_API_KEY isn't configured. Panel-only tools (badged above) run via chat-side handlers.file:stream as it arrives, so code appears in Monaco while the model is still writing. The authoritative content lands via file:create/file:update when the call completes.GET /api/ai/diff) and one-click rewind (POST /api/ai/rewind); POST /api/ai/snapshot creates a pinned manual snapshot. This sits alongside the 30-step undo stack described under Validation & Undo, which both call paths share.POST /api/ai/inject; POST /api/ai/stop aborts the run./api/ai/*.
These HTTP endpoints back the MCP tools and are also used directly by the browser client. All endpoints that operate on a tab require tabId (body or query depending on method).
/api/bridge/events?tabId=…
SSE stream for a browser tab. One connection per tab — reconnecting closes the old connection. Heartbeat every 30 s. Tab state is cleaned up 5 minutes after disconnect.
/api/bridge/sync
Browser mirrors its content to the server. Body: { tabId, html?, css?, js?, files?, sketch?, projectId?, projectName? }.
/api/bridge/page/ready
Browser signals that the preview iframe has finished loading. Body: { tabId }. Tools that wait for page-ready (like blnq_check_preview) resolve when this fires.
/api/bridge/approve-mcp-session
Browser approves or rejects an external MCP client's pairing request. Body: { tabId, sessionId, decision }. Resolves a held mcp:pending-approval and pushes mcp:approval-decision.
/api/bridge/search
Search across project files. Body: { tabId, query, filenames?, regex? }.
/api/bridge/variant/activate
Browser posts the selected variant's file bundle after the user picks one. Body: { tabId, files: [{ filename, content }] }. Server replaces the project files with the chosen set.
/api/bridge/variant/clear
Browser dismisses the variant picker. Body: { tabId }. Server drops stored variants and pushes variants:clear.
/api/bridge/sketch?tabId=…
Read current sketch elements. Returns { tabId, elements, elementCount, projectId, projectName }.
/api/bridge/sketch
Write sketch elements. Body: { tabId, elements: [...] }. Pushes sketch:update via SSE.
/api/bridge/a11y/run
Trigger an axe-core scan in the browser and wait for the result. Body: { tabId }. Query: ?timeout=15000. Returns { violations, incomplete } or 504 on timeout.
/api/bridge/a11y/results
Browser posts axe-core results. Body: { tabId, violations, incomplete }.
/api/bridge/a11y?tabId=…
Read the last cached a11y report for a tab.
/api/bridge/empathy-card
Render the empathy grid share card — the project preview reproduced through eight vision conditions (colour-blindness, cataracts, low contrast, low acuity) as one labelled PNG. Body: { tabId, projectName? }. Captures the preview via the bridge screenshot, composites the tiles server-side, and responds with image/png.
/api/bridge/console/errors
Browser reports JS console errors. Body: { tabId, errors: [{ level, message, line?, col?, sourceFile? }] }. Capped at 50 errors per tab.
/api/bridge/console/clear
Clear stored console errors. Body: { tabId }.
/api/bridge/console/errors?tabId=…
Read stored console errors for a tab.
/api/bridge/console/logs
Browser reports console.log/info/debug entries (separate ring buffer from errors). Body: { tabId, logs: [{ level, message, sourceFile?, timestamp }] }. Capped at 200 entries per tab.
/api/bridge/console/logs/clear
Clear the log buffer. Body: { tabId }.
/api/bridge/cursor
Browser reports cursor position. Body: { tabId, editor, line, col }.
/api/bridge/cursor?tabId=…
Read the latest cursor position for a tab.
These are where the browser POSTs results after responding to an SSE request (the "read data back" pattern described earlier). The harness shouldn't need to call these directly.
/api/bridge/element/results
Body: { tabId, ...elementInfo }. Reply to element:query.
/api/bridge/js/results
Body: { tabId, result, error, logs? }. Reply to js:eval. logs contains any console.log/info/debug emitted during the eval window.
/api/bridge/dom/results
Body: { tabId, ok?, action, ...details, logs? }. Reply to dom:action (the unified channel for click / type / press / wait_for).
/api/bridge/screenshot/results
Body: { tabId, dataUrl, error }. Reply to a browser canvas capture request (the default screenshot path uses server-side Playwright — this is the fallback).
/api/bridge/metrics/results
Body: { tabId, ... }. Reply to metrics:run.
The SSE channel at GET /api/bridge/events?tabId=… pushes the following events. The browser client is the only intended consumer — an external harness never opens this stream.
| Event | Payload | Meaning |
|---|---|---|
| files:refresh | { files: [{ filename, content }] } | Full file list replaced — after blnq_undo or writeSketch. |
| file:create | { filename, content } | A new file was added to the project. |
| file:update | { filename, content } | An existing file's content changed. |
| file:stream | { filename, content } | Live partial content while the AI streams a file in (fine-grained tool streaming). The editor renders it as a preview as it grows; the authoritative content lands via file:create/file:update when the write completes. |
| file:delete | { filename } | A file was removed. |
| sketch:update | { elements: [...] } | Excalidraw sketch was written. |
| design:update | { css, manifest } | Compiled design tokens from /DESIGN.md changed — browser refreshes the injected design-token stylesheet. |
| variants:set | { variants: [...], originalFiles: [...] } | Variant picker was opened or extended. Payload includes full file content for every variant plus a snapshot of the pre-variants project so the user can revert. |
| variants:select | { index } | Server committed a variant (via blnq_select_variant) — browser loads it into the editor, discards the other variants, and closes the picker (the "Select" action, no confirm). |
| variants:clear | {} | Variant picker was cleared. |
| project:name | { name } | Project name was changed via blnq_set_project_name. |
| preview:refresh | {} | Browser should do a full preview rebuild. |
| a11y:run | {} | Server is requesting an axe-core scan. Browser replies via POST /api/bridge/a11y/results. |
| element:query | { selector, styles } | Server is requesting an element inspection. Browser replies via POST /api/bridge/element/results. |
| js:eval | { code } | Server is requesting a JS eval in the preview iframe. Browser replies via POST /api/bridge/js/results. |
| dom:action | { action, ...params } | Server is requesting a DOM action (click / type / press / wait_for). Browser replies via POST /api/bridge/dom/results. |
| metrics:run | {} | Server is requesting page metrics. Browser replies via POST /api/bridge/metrics/results. |
| viewport:set | { id } | Change the preview viewport preset. |
| mcp:activity | { toolName, isError, label, source } | A tool was invoked — used by the UI to flash an indicator (and drive the streaming earcon / "agenting" outline). |
| mcp:pending-approval | { sessionId, clientInfo, toolName } | An external MCP client is requesting pairing approval for this tab — browser shows a one-time approval prompt. |
| mcp:approval-decision | { sessionId, status } | A pairing request was approved or rejected — resolves the approval prompt and unblocks held tool calls. |
Projects contain multiple files, stored as a JSON array. /index.html is the default entry page (and cannot be deleted); additional HTML pages such as /about.html or /blog/post.html are fully supported. CSS and JS files are referenced with standard <link> and <script> tags.
| File | Role | Notes |
|---|---|---|
| /index.html | Default entry page | Full <!DOCTYPE html> page. Loaded for /v/:projectId. References other files with <link href="style.css"> and <script src="main.js">. |
| /*.html | Additional pages | Multiple HTML files supported. Linked via relative <a href="page2.html">; viewable at /v/:projectId/page2.html. |
| *.css | Stylesheets | Referenced via <link rel="stylesheet">. Multiple files supported. |
| *.js | JavaScript modules | Referenced via <script type="module" src="…">. Full ES module import/export. |
| /*.excalidraw | Sketch data | JSON. Rendered in the sketch tab; read/write via blnq_read_sketch / blnq_write_sketch. |
The preview uses a Service Worker that serves project files from the in-memory map — ES modules, CSS @import, and relative fetch() all resolve natively against a real origin, not srcdoc.
Syntax check: every write, edit, and create runs syntax diagnostics on the affected file — JS is parsed with acorn (no false positives from braces in strings or comments), CSS gets a string/comment-aware bracket scan, and HTML has its inline <script>/<style> blocks checked with file-relative line numbers. Errors come back as a ⚠ SYNTAX warning alongside the successful result, so broken structure is caught immediately rather than at preview time.
Tolerant edit matching: find-replace edits match exactly first, then fall back to a cascade of whitespace-, indentation-, and escape-normalized strategies (with anchor-based block matching for multi-line edits). Ambiguous matches are refused with line numbers; fuzzy matches that would span far more text than requested are refused outright. Fuzzy-rescued edits report matchedBy.
Undo history: every write, edit, create, and delete pushes a snapshot onto the per-tab undo stack. The blnq_undo tool restores prior snapshots and reports which files were affected. History is capped at 30 steps per tab.