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.
- Upstream source: pulsar-edit/pulsar
src/item-registry.jsandsrc/pane-container.js(didAddPaneItem/didDestroyPaneItem).↩ - Upstream source: pulsar-edit/pulsar
src/pane.js(moveItemToPane,addItem,removeItem— themovedflag short-circuits both registry hooks).↩ - Upstream source: pulsar-edit/tabs
lib/tab-bar-view.js(addTabForItem/removeTabForItem).↩ - Electron docs:
<webview>tag —webview.getURL(); the attach/dom-readyprecondition applies to every method that reaches the guest’swebContents.↩ - 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.↩ - Upstream source: pulsar-edit/pulsar
static/atom-ui/styles/inputs.less— the.input-searchrule, unchanged upstream at the same line.↩ - MDN:
appearance— the standardised keyword list. The-webkit-compatibility values that survive are enumerated there;searchfield-cancel-buttonis not among them.↩