feat: preview pixel agents in web browser (additional support tool for dev and review)#143
Conversation
|
I think this has already been solved by #120 |
|
Thanks @NNTin for the efforts! Great addition, is nice to have a browser preview for asset development, I also added hot-reload on asset changes, the Vite dev server now watches public/assets/ and triggers a full reload whenever PNGs or manifests change, so you see updates instantly. I refactored a bit and added server-side asset decoding and added a shared module layer with zero VS Code dependency, the browser mock (browserMock.ts) is now just a thin wrapper. This also give us the chance to abstract the architecture a bit and lay the groundwork for supporting multiple apps/backends down the road. |
@daniel-dallimore #120 goes further by adding Express + WebSockets for a full standalone app. This PR is focused on browser-based asset preview to speed up development and design iteration, but a standalone backend is something we're considering soon |
florintimbuc
left a comment
There was a problem hiding this comment.
Hey @NNTin Would love to also hear how you'd approach mocking agent spawn/despawn, I would keep it as minimal as possible
Also, would you mind adding a small section to CONTRIBUTING.md on how to run the browser preview? Something short covering:
- cd webview-ui && npm run dev to preview locally
- npm run build:browser + deploy dist/browser/ to Vercel (or similar) for a shareable preview link, like you did in your demo
Would be really helpful for anyone working on pixel art or layouts to quickly preview and share their work
|
Put to draft. I am testing something and may optimize the code. |
webview-ui/src/App.tsx
Outdated
| // useExtensionMessages listener has been registered. | ||
| useEffect(() => { | ||
| if (import.meta.env.DEV) { | ||
| const isBrowserRuntime = |
There was a problem hiding this comment.
isBrowserRuntime is duplicated in main.tsx and App.tsx with verbose casts and acquireVsCodeApi === 'undefined' is hardcoded to VS Code, which is not future-proof for Cursor, Windsurf, etc. - soon enough we go provider agnostic
My suggestion, create in webview-ui/src/runtime.ts:
/**
* Runtime detection, provider-agnostic
*
* Single source of truth for determining whether the webview is running
* inside an IDE extension (VS Code, Cursor, Windsurf, etc.) or standalone
* in a browser.
*/
declare function acquireVsCodeApi(): unknown;
export type Runtime = 'vscode' | 'browser';
// Future: 'cursor' | 'windsurf' | 'electron' | etc.
export const runtime: Runtime =
typeof acquireVsCodeApi !== 'undefined' ? 'vscode' : 'browser';
export const isBrowserRuntime = runtime === 'browser';
Then main.tsx, App.tsx, and vscodeApi.ts all import runtime from one place
There was a problem hiding this comment.
created runtime.ts for single source of truth
webview-ui/src/main.tsx
Outdated
|
|
||
| async function main() { | ||
| if (import.meta.env.DEV) { | ||
| const isBrowserRuntime = |
There was a problem hiding this comment.
Implement isBrowserRuntime deduplication mentioned before, please
| }, | ||
| // Build output must include decoded JSON so dist/webview can run in plain browsers. | ||
| closeBundle() { | ||
| fs.mkdirSync(distAssetsDir, { recursive: true }); |
There was a problem hiding this comment.
| fs.mkdirSync(distAssetsDir, { recursive: true }); | |
| if (process.env.BROWSER_BUILD !== 'true') return; | |
| fs.mkdirSync(distAssetsDir, { recursive: true }); |
What do you think about adding a gate on BROWSER_BUILD env var to avoid adding ~1-2MB decoded JSON to dist/webview/ which gets packaged into the .vsix?
and restore build:browser script in webview-ui/package.json like this:
"build:browser": "BROWSER_BUILD=true tsc -b && vite build"
There was a problem hiding this comment.
I solved it a bit differently, browserMock was extended, the decoded JSON files is decoded at runtime and don't exist as files. This ensures less magic code in production environment and only in the mock.
webview-ui/src/browserMock.ts
Outdated
| ]); | ||
|
|
||
| const layout = assetIndex.defaultLayout | ||
| ? await fetch(`/assets/${assetIndex.defaultLayout}`).then((r) => r.json()) |
There was a problem hiding this comment.
| ? await fetch(`/assets/${assetIndex.defaultLayout}`).then((r) => r.json()) | |
| ? await fetch(`${import.meta.env.BASE_URL}assets/${assetIndex.defaultLayout}`).then((r) => r.json()) |
There was a problem hiding this comment.
Good catch. Implemented change and added tests that explicitely tests that frontend serving work from base root and subpath
webview-ui/src/browserMock.ts
Outdated
| fetch('/assets/asset-index.json').then((r) => r.json()) as Promise<AssetIndex>, | ||
| fetch('/assets/furniture-catalog.json').then((r) => r.json()) as Promise<CatalogEntry[]>, | ||
| fetch('/assets/decoded/characters.json').then((r) => r.json()) as Promise< | ||
| CharacterDirectionSprites[] | ||
| >, | ||
| fetch('/assets/decoded/floors.json').then((r) => r.json()) as Promise<string[][][]>, | ||
| fetch('/assets/decoded/walls.json').then((r) => r.json()) as Promise<string[][][][]>, | ||
| fetch('/assets/decoded/furniture.json').then((r) => r.json()) as Promise< |
There was a problem hiding this comment.
| fetch('/assets/asset-index.json').then((r) => r.json()) as Promise<AssetIndex>, | |
| fetch('/assets/furniture-catalog.json').then((r) => r.json()) as Promise<CatalogEntry[]>, | |
| fetch('/assets/decoded/characters.json').then((r) => r.json()) as Promise< | |
| CharacterDirectionSprites[] | |
| >, | |
| fetch('/assets/decoded/floors.json').then((r) => r.json()) as Promise<string[][][]>, | |
| fetch('/assets/decoded/walls.json').then((r) => r.json()) as Promise<string[][][][]>, | |
| fetch('/assets/decoded/furniture.json').then((r) => r.json()) as Promise< | |
| fetch(`${import.meta.env.BASE_URL}assets/asset-index.json`).then((r) => r.json()) as Promise<AssetIndex>, | |
| fetch(`${import.meta.env.BASE_URL}assets/furniture-catalog.json`).then((r) => r.json()) as Promise<CatalogEntry[]>, | |
| fetch(`${import.meta.env.BASE_URL}assets/decoded/characters.json`).then((r) => r.json()) as Promise< | |
| CharacterDirectionSprites[] | |
| >, | |
| fetch(`${import.meta.env.BASE_URL}assets/decoded/floors.json`).then((r) => r.json()) as Promise<string[][][]>, | |
| fetch(`${import.meta.env.BASE_URL}assets/decoded/walls.json`).then((r) => r.json()) as Promise<string[][][][]>, | |
| fetch(`${import.meta.env.BASE_URL}assets/decoded/furniture.json`).then((r) => r.json()) as Promise< |
Better we get rid of absolute paths here, wdyt?
There was a problem hiding this comment.
Good catch. Implemented change and added tests that explicitely tests that frontend serving work from base root and subpath
webview-ui/src/vscodeApi.ts
Outdated
|
|
||
| export const vscode = acquireVsCodeApi(); | ||
| export const vscode: { postMessage(msg: unknown): void } = | ||
| typeof acquireVsCodeApi !== 'undefined' |
There was a problem hiding this comment.
import isBrowserRuntime defined above instead of using acquireVsCodeApi
7a3c016 to
d4b9237
Compare
|
Addressed comments, diff to it: https://github.com/NNTin/pixel-agents/pull/6/changes |
This reverts commit 6cd4f85.
81a13fe to
d40e618
Compare
|
I rebased the branch since I noticed the git history is pretty linear. A note should be added in Contributing what the merge strategy is here. CI can also enforce conventional commit message and merge strategy |
|
@florintimbuc regarding the message how to mock agent spawning and also sending message, see PR NNTin#7 However this code is AI slop pure and is not meant for production. I use d-back as a mock + as an integration to Discord. There I already get a list of actors -> agents and discord messages are bridged to the frontend as well as presence updates. It is good to see that in order to create agents, send message and status not much code is involved. Basically |


Description
Browser preview mode for faster asset development and design iteration — run cd webview-ui && npm run dev to see the office in a browser without
launching the VS Code extension.
Originally contributed by @NNTin (reference PR), then extended with:
Browser mock system:
Shared module layer (shared/assets/):
Docs:
Direct link to Preview: https://pixel-agents-git-feat-stubbing-vscode-nntins-projects.vercel.app/
Further mocks could be added to spawn and despawn agents and have them make mock messages.
Type of change
Related issues
Screenshots / GIFs
Test plan
mainbranchnpm run buildpasses locallycd webview-ui && npm run dev— verify browser preview renders office with all assets (characters, floors, walls, furniture)cd webview-ui && npm run build:browser— verifydist/browser/assets/decoded/contains characters.json, floors.json, walls.json, furniture.json