Skip to content

feat: install the editor extension from the Marketplace, which never worked - #1487

Merged
vivek7405 merged 6 commits into
mainfrom
fix/vscode-icon-monogram
Sep 23, 2026
Merged

vivek7405 merged 6 commits into
mainfrom
fix/vscode-icon-monogram

Conversation

@vivek7405

Copy link
Copy Markdown
Collaborator

What

The VS Code extension is published. It is live as
WebJs.WebJs
at 0.2.6, shows as WebJs, and carries the new monogram icon.

Before this, every install instruction we shipped pointed at
webjsdev.webjs, which returns 404. The docs site's editor-setup page is the
one users actually follow, so editor setup was broken for anyone who tried it.

Closes #396 for the Marketplace half. Open VSX is still not published and
never was, under either identity, so the surfaces that promised it now say so
and send those editors to the .vsix instead of a second dead link.

The non-breaking space in displayName

displayName is WebJs followed by U+00A0. It renders as plain "WebJs" and
it is deliberate.

A listing briefly existed under the id webjs-vscode and was unpublished.
The Marketplace retires an unpublished extension's id and its display
name permanently, so both of these are refused:

Attempt Response
id webjs-vscode The extension 'webjs-vscode' already exists in the Marketplace
display name WebJs This extension display name is taken

A plain trailing space does not work either, because the uniqueness check
trims ASCII whitespace before comparing. U+00A0 survives that trim. The id
has no equivalent escape hatch: vsce validates it locally against
/^[a-z0-9][a-z0-9\-]*$/i.

PUBLISHING.md now documents this so nobody "tidies" the character away and
breaks the next publish. The durable fix is to ask Microsoft to release the
retired name, then drop the character and the matching assertion.

Verification

  • The published .vsix was downloaded from the Marketplace and compared
    against the local build: all 13 entries identical.
  • Extension tests: 10 pass, 0 fail.
  • webjs check in website/: all checks pass.
  • Every surface re-grepped for webjsdev.webjs and the other stale tokens: clean.

Not addressed

packages/editors/vscode/AGENTS.md claims a pre-commit gate enforces a
changelog entry per version bump. No such gate exists; the hook only blocks
commits on main. The entry here was written to the convention regardless.
Flagging rather than fixing, since it predates this work.

The extension had never been uploaded: the listing it documented,
webjsdev.webjs, did not exist. It is now live as WebJs.WebJs and shows
as "WebJs" rather than "webjs", matching the framework's own casing,
and it carries the new monogram icon.

displayName ends in a non-breaking space on purpose. A listing briefly
existed under the id webjs-vscode and was unpublished, which retires an
id and its display name permanently, so the plain name is refused. The
uniqueness check trims ASCII whitespace but not U+00A0. PUBLISHING.md
records this so the next person does not "fix" it and break the
publish.
Every surface named webjsdev.webjs, which returns 404. The docs site's
editor-setup page is the one users follow, so its install link was
broken for anyone trying to set up an editor.

Open VSX has no listing under either identity and never did, so the
surfaces that promised it now say so and tell those editors to install
the vsix directly, rather than linking a second dead page.
@pilots-run

pilots-run Bot commented Sep 23, 2026

Copy link
Copy Markdown

Preview for 9d2f316 was not built: compose file has unsupported keys

Next: fix the listed keys in the compose file

The extension is an npm workspace member, so renaming it and bumping the
version leaves package-lock.json stale and every CI job dies at npm ci
with "Missing: WebJs@0.2.6 from lock file".
Invariant 11 bans U+2014 repo-wide and the Conventions job enforces it.
Introduced by the identity-change docs in this branch.
Comment thread website/app/docs/editor-setup/page.ts Outdated
<div class="callout">
<p><strong>VS Code, Cursor, Windsurf, VSCodium:</strong> install the <strong><code>webjs</code></strong> extension from the <a href="https://marketplace.visualstudio.com/items?itemName=webjsdev.webjs" target="_blank">VS Marketplace</a> or <a href="https://open-vsx.org/extension/webjsdev/webjs" target="_blank">Open VSX</a> (search "webjs"). It bundles the language-service plugin and registers it automatically (no <code>tsconfig.json</code> edit), and adds <code>html</code> / <code>css</code> template highlighting.</p>
<p><strong>VS Code:</strong> install the <strong><code>WebJs</code></strong> extension from the <a href="https://marketplace.visualstudio.com/items?itemName=WebJs.WebJs" target="_blank">VS Marketplace</a> (search "WebJs"). It bundles the language-service plugin and registers it automatically (no <code>tsconfig.json</code> edit), and adds <code>html</code> / <code>css</code> template highlighting.</p>
<p><strong>Cursor, Antigravity, Windsurf, VSCodium:</strong> these pull from Open VSX, where the extension is not published yet. Until it is, download <code>webjs.vsix</code> from the <a href="https://github.com/webjsdev/webjs/tree/main/packages/editors/vscode" target="_blank">extension directory</a> and install it with <code>code --install-extension webjs.vsix</code> (substitute your editor's CLI).</p>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vsix this points at is not in the repo. webjs.vsix is gitignored (packages/editors/vscode/.gitignore:2) and no GitHub release carries it as an asset, so a Cursor / Windsurf / VSCodium user who follows this link lands in packages/editors/vscode on GitHub and finds no .vsix to download. With Open VSX unpublished, that leaves those editors with no working install path at all from the docs.

The Marketplace itself serves the package (verified: GET .../publishers/WebJs/vsextensions/WebJs/0.2.6/vspackage returns 200), so the listing's Download Extension link is the instruction that actually works.

Suggested change
<p><strong>Cursor, Antigravity, Windsurf, VSCodium:</strong> these pull from Open VSX, where the extension is not published yet. Until it is, download <code>webjs.vsix</code> from the <a href="https://github.com/webjsdev/webjs/tree/main/packages/editors/vscode" target="_blank">extension directory</a> and install it with <code>code --install-extension webjs.vsix</code> (substitute your editor's CLI).</p>
<p><strong>Cursor, Antigravity, Windsurf, VSCodium:</strong> these pull from Open VSX, where the extension is not published yet. Until it is, open the <a href="https://marketplace.visualstudio.com/items?itemName=WebJs.WebJs" target="_blank">Marketplace listing</a>, use its <em>Download Extension</em> link to save the <code>.vsix</code>, and install that file with <code>code --install-extension path/to/the.vsix</code> (substitute your editor's CLI).</p>

Comment thread packages/editors/vscode/README.md Outdated
- **VSCode:** search "webjs" in the Extensions view (Marketplace).
- **Cursor / Antigravity / Windsurf / VSCodium:** search "webjs" (these pull from Open VSX).
- **VSCode:** search "WebJs" in the Extensions view, or install [`WebJs.WebJs`](https://marketplace.visualstudio.com/items?itemName=WebJs.WebJs) from the Marketplace.
- **Cursor / Antigravity / Windsurf / VSCodium:** these pull from Open VSX, where the extension is not published yet. Install the packaged `webjs.vsix` directly with `code --install-extension webjs.vsix` (substitute your editor's CLI).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Install the packaged webjs.vsix" never says where to get it, and there is nowhere. The vsix is gitignored and attached to no release, so the only way to obtain one is to clone the monorepo and run npm run package. Since this README is also the Marketplace listing body, the reader most likely to hit this line is the one who cannot use the Marketplace install.

The listing does serve the package for download, so point at that:

Suggested change
- **Cursor / Antigravity / Windsurf / VSCodium:** these pull from Open VSX, where the extension is not published yet. Install the packaged `webjs.vsix` directly with `code --install-extension webjs.vsix` (substitute your editor's CLI).
- **Cursor / Antigravity / Windsurf / VSCodium:** these pull from Open VSX, where the extension is not published yet. Until it is, use the **Download Extension** link on the [Marketplace listing](https://marketplace.visualstudio.com/items?itemName=WebJs.WebJs) to save the `.vsix`, then install that file with `code --install-extension path/to/the.vsix` (substitute your editor's CLI).

Comment thread AGENTS.md Outdated
**Async render (`async render()`), bare-await data fetch (#469).** A component may write `async render() { const u = await getUser(this.id); return html\`<h3>${u.name}</h3>\`; }`. Writing `await` makes the function async by JS rule, and every render path awaits a promise-returning `render()` automatically (no flag). This co-locates the fetch in the leaf component (no prop-drilling). The model is decoupled into three separate concerns. (1) **SSR always blocks**, so the resolved DATA is in the first paint with no fallback markup (PE-safe, JS-off reads it). (2) **The client re-fetch default is stale-while-revalidate**: when a prop / dependency change re-runs `async render()`, the current content stays until the new render resolves (no blank, no flash). (3) **`renderFallback()` is the OPTIONAL re-fetch loading UI**, a prop-aware method shown ONLY during a client re-fetch, NEVER on the first paint, and it does NOT trigger SSR streaming. **Errors are isolated per component by default** (no user code): a thrown `await getData()` renders a component-scoped error state while siblings render, and `renderError()` optionally customizes it (dev surfaces the message, prod stays silent). `getData()` is already isomorphic (a `'use server'` action is the real function during SSR and an RPC stub on the client), so the same line works both sides. Use `async render()` for request-time-known SERVER data that should be in the first paint; keep `Task` / signals for genuinely client-only data (a `Task` shows its pending state at SSR, losing first-paint data). A **bare** async-render component (an `async render()` with no other client signal, light DOM) is **elided** like any display-only component (#474): its SSR'd HTML is the complete output, so the framework drops the module AND the redundant on-hydration re-fetch. It SHIPS only when it also carries an independent signal (an `@event`, a non-`state` reactive prop, a signal / reactive import, a lifecycle hook including `renderFallback()`, the dynamic slot READ surface (`slotchange` / `assignedNodes` / `assignedElements` / `assignedSlot`; merely RENDERING a `<slot>` does not ship, since the SSR output carries the placed children), `static shadow = true`, `static interactive = true`, cross-module observation, or a transitively-reachable interactive child). Two carve-outs always ship: `static shadow = true` (Declarative Shadow DOM attaches only during HTML parsing, so a streamed or soft-navigated shadow component needs its module to re-run `attachShadow`) and `static interactive = true` (the explicit author override that forces a ship when the analyser cannot see a component's interactivity statically: an OBSERVER that computes the tag it waits for, a `:defined` rule in an external stylesheet outside the module graph, or a consumer reaching the element through a string selector; a component's OWN registration tag must be a literal per invariant 3, and a computed one is invisible to the scanner, so it gets no verdict and the override cannot rescue it). **For SLOW data where blocking the first byte hurts, wrap the region in `<webjs-suspense .fallback=${html\`…\`}>` to STREAM it** (the fallback flushes on the first byte, the data streams in; multiple boundaries fetch concurrently). This is the only way to show a first-paint fallback, a deliberate choice for slow regions, and it streams progressively on soft navigation too. A throwing component inside a boundary is isolated (renders its error state, siblings stream). **The on-hydration re-fetch is itself eliminated by SSR action seeding (#472):** each `'use server'` action result invoked during a (non-streamed) SSR render is serialized into the page, and the generated RPC stub reads that seed on its first client call, so a shipping async component does NOT re-issue the RPC on hydration (a later refetch / arg-change still goes to the network). Keyed by action-hash + fn + serialized args, consume-once, fail-open (a miss degrades to a normal RPC). A hit returns the value the SSR render that produced THIS page computed for exactly that key, so it cannot disagree with the HTML on screen (a page navigation evicts whatever the outgoing page left unconsumed, in the DOM and in the store, so a departed render's value can never be served); on an HTML-cached page (#241) the seed rides inside the cached bytes and is as fresh as they are. The one shape where a hit can differ from the paint is an action returning a DIFFERENT result for the SAME arguments twice in one render (the seed carries the last, the first component painted the first), which dev warns about once per action function. Captured via a transparent server-side `'use server'` facade (no source transform, no build step; the browser source tab and on-disk files are unchanged), default on, opt out with `"webjs": { "seed": false }` or `WEBJS_SEED=0`. **A miss is otherwise invisible, so dev makes it observable (#1309):** every dev page response carries `X-Webjs-Seed` (`off` / `html-cache` / `collected=<m>, emitted=<n>` / `collected=<m>, emitted=0, streamed`), folded into the access-log line as a `seed` field, and the browser logs ONE warning per page view when a hydration call missed AND the cause is provable (a streamed page, a serializer drop, or seeds present but unmatched), staying silent otherwise, including on a page that emitted no seeds, where a miss is not evidence of a defect because a mutation / `Task` / `connectedCallback` call routes through the same lookup and could never be seeded. `seedStats()` from `@webjsdev/core` exposes the counters. Nothing reaches production: the client's dev gate is a server-stamped `data-webjs-dev` marker on the seed block, never `process.env.NODE_ENV`, which esbuild folds to a constant in the built core bundle. See `references/data-and-actions.md`.

**Light DOM (default) vs Shadow DOM.** Light DOM applies global CSS and Tailwind directly (default; for Tailwind/global CSS + simple composition). Shadow DOM (`static shadow = true`) is for `static styles` scoped CSS and third-party isolation; `<slot>` works in either. **Light-DOM slots ARE the native DOM slot API (#1021, full shadow parity):** `<slot>` works identically in light and shadow DOM, so post-mount native writes are LIVE (`appendChild`, `insertBefore`, `removeChild`, `el.remove()`, `innerHTML`, `el.slot=` flips, `HTMLSlotElement.assign()`) and the reads (`assignedNodes` / `assignedElements` / `{flatten}` / `assignedSlot` / `slotchange`, with native async-coalesced timing) match. Flip `static shadow` and nothing else changes; there is NO WebJs-specific slot API. The one write that does NOT flip is `assign()`: the light-DOM version is an extension (element-bound overlay alongside name matching), while native shadow `assign()` needs `slotAssignment: 'manual'`, which WebJs does not set, so avoid `assign()` in a component meant to flip modes. A FORWARDED slot (a template forwarding `<slot>` into a nested component) projects its content on the client and through hydration (#1023): the renderer stamps each slot with its template owner (`SLOT_OWNER`, carried across SSR as `data-wj-slot-owner`) so it routes to the OUTER host that rendered it, and a layout's `${children}` inside a slotted shell keeps its named slots in sync across a soft-nav swap (#1024, the swap resyncs every own slot of the enclosing host). Four inherent gaps, all a consequence of light DOM having no shadow boundary: structural host reads (`host.children` / the `innerHTML` getter show the rendered template, not the authored children, so read slotted content with `assignedNodes()`), `assignedChild.parentNode` is the `<slot>`, `::slotted()` CSS (style slotted content with normal selectors / Tailwind), and initial-projection lifecycle timing (`firstUpdated` sees the `<slot>` element with EMPTY `assignedNodes()`, the projection lands one microtask later; read assigned content from `slotchange` or after a microtask). Live writes need the component's JS on the page, so a display-only slotted wrapper elides (its writes are inert like anything elided; force a ship with `static interactive = true` for an imperative consumer the analyser cannot see). A light-DOM component authoring custom CSS MUST prefix every class selector with its tag name (invariant 7); prefer Tailwind. **Light-DOM component hosts default to `display: block`**: a custom element is `display: inline` in plain CSS, so the framework marks every LIGHT-DOM host `data-wj-host` and injects one head rule in a low-priority cascade layer, `@layer webjs-host { :where([data-wj-host]) { display: block } }`, so a container component does not collapse; the layer keeps it overridable by any author style INCLUDING Tailwind utilities (`class="flex"`/`grid`/`hidden` win, because their layer is ordered after `webjs-host`), a `[hidden]` carve-out keeps `?hidden` working, and an inline light component opts out with `my-tag { display: inline }`. **Shadow-DOM hosts are NOT marked** (a document rule would override the shadow tree's `:host`), so a shadow component sets its host display via `:host { display: block }` in `static styles` (respected because the host is unmarked; set it for a shadow block container). See the even-grid / no-reflow layout recipes in `references/styling.md`. **Never interpolate into a component's `<style>` or `<script>` body** (`html\`<style>${css}</style>\``): the server emits it but the client drops the raw-text hole, so it paints at SSR then wipes to empty on hydrate. Use `static styles` or Tailwind instead (flagged by `no-interpolation-in-raw-text-element`). A page/layout, which never hydrates, may interpolate a `css` result into `<style>`. Install the `webjs` VSCode extension (`packages/editors/vscode`, VS Marketplace + Open VSX; also covers Cursor / Antigravity / Windsurf) or `webjs.nvim` (`packages/editors/nvim`, via lazy.nvim) for template highlighting + editor intelligence with no Lit plugin, or add the standalone `@webjsdev/intellisense` to `tsconfig.json` `plugins` manually (JetBrains). Full deep-dive in `references/components.md` + `references/muscle-memory-gotchas.md`.
**Light DOM (default) vs Shadow DOM.** Light DOM applies global CSS and Tailwind directly (default; for Tailwind/global CSS + simple composition). Shadow DOM (`static shadow = true`) is for `static styles` scoped CSS and third-party isolation; `<slot>` works in either. **Light-DOM slots ARE the native DOM slot API (#1021, full shadow parity):** `<slot>` works identically in light and shadow DOM, so post-mount native writes are LIVE (`appendChild`, `insertBefore`, `removeChild`, `el.remove()`, `innerHTML`, `el.slot=` flips, `HTMLSlotElement.assign()`) and the reads (`assignedNodes` / `assignedElements` / `{flatten}` / `assignedSlot` / `slotchange`, with native async-coalesced timing) match. Flip `static shadow` and nothing else changes; there is NO WebJs-specific slot API. The one write that does NOT flip is `assign()`: the light-DOM version is an extension (element-bound overlay alongside name matching), while native shadow `assign()` needs `slotAssignment: 'manual'`, which WebJs does not set, so avoid `assign()` in a component meant to flip modes. A FORWARDED slot (a template forwarding `<slot>` into a nested component) projects its content on the client and through hydration (#1023): the renderer stamps each slot with its template owner (`SLOT_OWNER`, carried across SSR as `data-wj-slot-owner`) so it routes to the OUTER host that rendered it, and a layout's `${children}` inside a slotted shell keeps its named slots in sync across a soft-nav swap (#1024, the swap resyncs every own slot of the enclosing host). Four inherent gaps, all a consequence of light DOM having no shadow boundary: structural host reads (`host.children` / the `innerHTML` getter show the rendered template, not the authored children, so read slotted content with `assignedNodes()`), `assignedChild.parentNode` is the `<slot>`, `::slotted()` CSS (style slotted content with normal selectors / Tailwind), and initial-projection lifecycle timing (`firstUpdated` sees the `<slot>` element with EMPTY `assignedNodes()`, the projection lands one microtask later; read assigned content from `slotchange` or after a microtask). Live writes need the component's JS on the page, so a display-only slotted wrapper elides (its writes are inert like anything elided; force a ship with `static interactive = true` for an imperative consumer the analyser cannot see). A light-DOM component authoring custom CSS MUST prefix every class selector with its tag name (invariant 7); prefer Tailwind. **Light-DOM component hosts default to `display: block`**: a custom element is `display: inline` in plain CSS, so the framework marks every LIGHT-DOM host `data-wj-host` and injects one head rule in a low-priority cascade layer, `@layer webjs-host { :where([data-wj-host]) { display: block } }`, so a container component does not collapse; the layer keeps it overridable by any author style INCLUDING Tailwind utilities (`class="flex"`/`grid`/`hidden` win, because their layer is ordered after `webjs-host`), a `[hidden]` carve-out keeps `?hidden` working, and an inline light component opts out with `my-tag { display: inline }`. **Shadow-DOM hosts are NOT marked** (a document rule would override the shadow tree's `:host`), so a shadow component sets its host display via `:host { display: block }` in `static styles` (respected because the host is unmarked; set it for a shadow block container). See the even-grid / no-reflow layout recipes in `references/styling.md`. **Never interpolate into a component's `<style>` or `<script>` body** (`html\`<style>${css}</style>\``): the server emits it but the client drops the raw-text hole, so it paints at SSR then wipes to empty on hydrate. Use `static styles` or Tailwind instead (flagged by `no-interpolation-in-raw-text-element`). A page/layout, which never hydrates, may interpolate a `css` result into `<style>`. Install the `WebJs` VSCode extension (`packages/editors/vscode`, published as `WebJs.WebJs` on the VS Marketplace; also covers Cursor / Antigravity / Windsurf) or `webjs.nvim` (`packages/editors/nvim`, via lazy.nvim) for template highlighting + editor intelligence with no Lit plugin, or add the standalone `@webjsdev/intellisense` to `tsconfig.json` `plugins` manually (JetBrains). Full deep-dive in `references/components.md` + `references/muscle-memory-gotchas.md`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line now claims coverage the PR itself says does not exist. The old text said "VS Marketplace + Open VSX"; the new one drops Open VSX but keeps "also covers Cursor / Antigravity / Windsurf". Everywhere else in this PR (the README, the docs page, packages/editors/AGENTS.md, packages/editors/vscode/AGENTS.md) says those editors have no listing and must sideload the vsix. AGENTS.md is the always-loaded agent surface, so an agent reading only this will tell a Cursor user to search their Extensions view, which finds nothing.

Suggested wording (the sentence is inside a very long paragraph, so apply by hand):

published as WebJs.WebJs on the VS Marketplace, and installed in Cursor / Antigravity / Windsurf by downloading its .vsix from that listing until an Open VSX listing exists

Comment thread changelog/vscode/0.2.6.md Outdated
commit_count: 1
npm: false
---
## Changed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, but it is a heading the corpus does not use: the changelog groups are Breaking / Features / Performance / Fixes (changelog/README.md), with Changes as the hand-written variant (5 entries). Changed is a sixth spelling of the same bucket, and test/packaging/changelog-editor-packages.test.mjs:53 already asserts an editor entry carries one of the known headings (pinned to 0.2.0 today, so nothing reds yet).

Suggested change
## Changed
## Changes

The Cursor / Windsurf / VSCodium instructions told the reader to fetch
webjs.vsix from the extension directory on GitHub, but that file is
gitignored and no release carries it, so the only registry-less install
path the docs offered led nowhere. The Marketplace listing serves the
package itself, so point at its Download Extension link instead.

Also drops the root AGENTS.md claim that the Marketplace listing covers
Cursor / Antigravity / Windsurf, which the rest of this branch
contradicts, and spells the changelog heading the way the corpus does.
The listing's subtitle (the manifest description) and the body of its
page (the README) both wrote the project lowercase. Invariant 11 makes
WebJs a proper noun in prose and says a JSON description value is prose,
and core, server and cli all already write it that way.

Nothing caught it: the prose hook scans only new content, and the
published-package brand test skips this manifest because the extension
is private. Both strings reach users only through a publish, hence the
version bump.
@vivek7405
vivek7405 merged commit 7fbcd61 into main Sep 23, 2026
10 of 11 checks passed
@vivek7405
vivek7405 deleted the fix/vscode-icon-monogram branch September 23, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: publish the webjs VS Code extension to VS Marketplace + Open VSX

1 participant