Docs

Pulsar Upstream Fix Candidates

A running list of bugs we’ve fixed (or pinned down) in the Tranquil fork that exist upstream — in Pulsar itself, in the packages it bundles, or in the Atom-era packages we forked — and would be worth contributing back. Entries here are inherited defects with a general-purpose fix — not Tranquil features or fork-specific behavior.

When adding an entry, include: the symptom, the root cause, where our fix lives, and its upstream status.

1. Cross-container pane-item moves desync the ItemRegistry

Status: fixed in Tranquil (2026-08-03), not yet upstreamed.

Symptom: Uncaught Error: The workspace can only contain one instance of item [object Object] from ItemRegistry.addItem, typically after dragging a pane item between docks (or dock ⇄ center) and then clicking a tab. A long-standing Atom-era error inherited by Pulsar.pulsar-item-registry

Root cause: each pane container (center + each dock) has its own ItemRegistry, updated only by didAddPaneItem / didDestroyPaneItem — both of which are skipped when an item is added/removed with moved: true. Every move path funnels into Pane.moveItemToPane,pulsar-move-item which always passes moved: true. Within one container that’s correct; across containers it leaves the source registry holding a stale entry and the destination registry missing one. Any later genuine re-add of that item instance into the stale container throws. A secondary effect: the throw can interrupt a pane event emission mid-flight, leaving the tabs package’s tab bar holding a stale, clickable tab for an item its pane no longer owns — which is how an innocent tab click surfaces the error.

Fix (tranquil-client src/pane.js, moveItemToPane): detect a cross-container move and transfer the registration explicitly — fromContainer.itemRegistry.removeItem(item) plus a hasItem-guarded toContainer.itemRegistry.addItem(item) (the guard lets an already-desynced session self-heal on its next move). No behavior change for same-container moves.

Producers covered by the one fix: the tabs package’s drag-to-dock (layout.js dragend), drop-on-tab-bar (tab-bar-view.js moveItemBetweenPanes), and any API caller of moveItemToPane (e.g. Tranquil’s Tabs-view dock toggle).

2. tabs package: removeTabForItem deletes the wrong WeakMap key

Status: observed, not fixed anywhere (third-party package; harmless in practice for us).

In tab-bar-view.js, tabs are indexed by element (this.tabsByElement.set(tabView.element, tabView)), but removeTabForItem calls this.tabsByElement.delete(tab) — the TabView object, not tab.element — so the deletion is a no-op and removed tabs linger in the map until GC.pulsar-tabs Benign today because tab.destroy() also removes the element from the DOM, but it defeats the map’s purpose as a liveness check and could resurface a destroyed tab via tabForElement if an element is ever re-attached. One-line fix: this.tabsByElement.delete(tab.element).

3. browser-plus: reading a <webview> URL before attach throws, and opens DevTools

Status: fixed in Tranquil (2026-08-17), not yet upstreamed. Inherited from the Atom browser-plus package that tranquil-browser forks, not from Pulsar core.

Symptom: reloading a window that has a browser tab open pops DevTools open on every reload, switched to the console. Most visible with a split (an editor beside a browser tab) and a tab pointing at a real page, because that is when the restored guest has title and favicon events to emit. A brief flash of unstyled toolbar accompanies it — DevTools opening resizes the window mid-layout.

Root cause: two chained defects.

The package’s page-favicon-updated and page-title-set handlers read the guest’s address to key their favicon/title caches:

uri = this.htmlv[0].getURL();
if (!uri) return;

That guard is written as though getURL() returns something falsy when the URL is unavailable. It does not — on a <webview> it throws The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called,electron-webview so the guard never runs. On window reload the restored tab emits both events while its <webview> is still being attached, and the handler throws.

The throw is then amplified by Pulsar: AtomEnvironment.installUncaughtErrorHandler responds to any uncaught renderer error by opening DevTools and calling DevToolsAPI.showPanel("console"), unless a will-throw-error listener calls preventDefault().pulsar-uncaught So a harmless early lifecycle event presents as “the debugger opens itself”. Because Electron restores DevTools across a reload once it is open, it then appears to happen on every subsequent reload, which disguises the original trigger.

Fix (tranquil-browser/lib/tranquil-browser-view.js): a guestUrl(webview) helper that returns '' when the URL cannot be read yet, and routing all three readers through it — the two event handlers plus addHistory(), which has the same shape and would surface on a fast navigation. This makes the existing if (!uri) return; guards do what they were always written to do.

Worth knowing generally: “guard assumes a falsy return, API actually throws” is the reusable lesson. Any <webview> method that needs a live guest (getURL, getTitle, getWebContentsId, canGoBack) has the same hazard when called from an event handler that can fire before attach.

4. atom-ui: dead -webkit-appearance keyword warns on every window load

Status: fixed in Tranquil (2026-09-02), not yet upstreamed.

Symptom: every window logs a Blink deprecation on load:

The keyword 'searchfield-cancel-button' used on the 'appearance' property was
deprecated and has now been removed. It will no longer have any effect.

Chromium attributes it to src/atom-environment.js — the code that injects the stylesheet, not the stylesheet itself — so searching for the message finds nothing.

Root cause: static/atom-ui/styles/inputs.less re-asserts the UA default on the search field’s clear button:pulsar-inputs

.input-search {
  .input-block-mixin();
  &&::-webkit-search-cancel-button {
    -webkit-appearance: searchfield-cancel-button;
  }
}

searchfield-cancel-button was one of the non-standard -webkit-appearance keywords Chromium removed; only the standardised appearance values remain.mdn-appearance The declaration has therefore been inert for years — it sets nothing, and the ::-webkit-search-cancel-button pseudo-element already draws the button by default. All it still does is emit the warning.

Fix (tranquil-client static/atom-ui/styles/inputs.less): delete the declaration and its now-empty nested block. Verified behaviour-preserving by compiling the block against stubs — the compiled rule set is identical apart from the removed pseudo-element rule, so the UA default applies exactly as it already did.

Worth knowing generally: a removed CSS keyword fails silently in every way that matters — no layout change, no error, just a console line blamed on whichever file injected the sheet. Filtering the message hides a real dead declaration; the fix is one line at the source.


  1. Upstream source: pulsar-edit/pulsar src/item-registry.js and src/pane-container.js (didAddPaneItem / didDestroyPaneItem).
  2. Upstream source: pulsar-edit/pulsar src/pane.js (moveItemToPane, addItem, removeItem — the moved flag short-circuits both registry hooks).
  3. Upstream source: pulsar-edit/tabs lib/tab-bar-view.js (addTabForItem / removeTabForItem).
  4. Electron docs: <webview> tag — webview.getURL(); the attach/dom-ready precondition applies to every method that reaches the guest’s webContents.
  5. Upstream source: pulsar-edit/pulsar src/atom-environment.js (installUncaughtErrorHandler). Intended as a dev-mode aid rather than a bug, but it is what turns any uncaught package error into a DevTools popup.
  6. Upstream source: pulsar-edit/pulsar static/atom-ui/styles/inputs.less — the .input-search rule, unchanged upstream at the same line.
  7. MDN: appearance — the standardised keyword list. The -webkit- compatibility values that survive are enumerated there; searchfield-cancel-button is not among them.