# Pulsar editor lag — investigation log (2026-07-18)

**Symptoms** (reported during rehearsal): multi-second typing/cursor latency in Pulsar ("typing
blind"), a GHC error renders a big red stack over several seconds, evals stay slow afterwards —
while the taskbar CPU indicator shows the machine "barely used" and window-manager shortcuts
stay instant. Fans maxed and 73–82°C despite recently cleaned vents.

## Verdict

The machine is healthy. The lag is **Pulsar's renderer main thread saturating one core**
(~65–75% sustained through the whole session), which reads as ~4% on a 16-thread CPU meter.
Every keystroke, cursor move and eval queues behind that single busy thread; KWin shortcuts
bypass it entirely, which is why the desktop stayed fast.

## Evidence

- Renderer process (PID 14530): **37:51 CPU over a 58:05 session** (65%), still ~74%/core over
  the following 4 minutes of active use; ~1–5% when truly idle. All on the **main thread**.
- System-wide: no memory pressure (22 Gi available, swap 0B), no IO/CPU PSI pressure.
- GPU process holds `/dev/dri` fds → **hardware acceleration active** (not SwiftShader).
- Pulsar 1.118.0 = **Electron 12.2.3 / Chrome 89 (April 2021)**, running as an X11 client via
  **Xwayland** on Plasma 6 (Xwayland itself at ~18% CPU relaying Pulsar's pixels).
- Thermals: package 82°C, both fans pegged at 3300 RPM, non-zero `thermal_throttle` counters,
  load avg 2.87 — real sustained load (Pulsar renderer + Ardour ~20% + Xwayland ~18%), not a
  cooling defect. Mixed power state: `perf.sh --cool` leftovers (max_perf_pct=85, powersave
  governor, EPP balance_performance, Pulsar main+GPU at nice 10 / renderer at 0) while
  power-profiles-daemon reports "performance".

## Root causes (in `~/Work/Tools/pulsar-tidalcycles`, branch `perf/event-highlighter`)

1. **`lib/console-view.js` — the "big red stack" stall.** Every log line appends a `<pre>` to a
   DOM that is never pruned, then forces a synchronous full reflow via
   `scrollTop = scrollHeight`. `showErrorNotifications` (default **on**) additionally renders
   the *entire* GHC stack through `atom.notifications.addError()`. Reflow cost grows with the
   session-long console DOM → evals after an error stay slow.
2. **`lib/editors.js` `decorateCodeBlocks()` — progressive typing lag.** Runs on every
   line-count change (every Enter / deleted line), destroys line-number decorations (including
   other packages') but **leaks the markers** (`invalidate: 'never'`, never destroyed). Atom
   updates its marker index on every keystroke → cost grows all session.
3. **`lib/event-highlighter.js` `#createPositionMarkers()` — secondary marker leak.** Re-evaling
   a line overwrites the `rowMarkers` map entry without destroying the previous marker.
   Live stats after a 63-min session: **2170 live markers** (and leaked-overwritten ones on top).
4. **`styles/tidal.less`** — `backdrop-filter: blur(10px)` on the sound-browser waveform and
   search panels: one of the most paint-expensive CSS properties on Chrome 89; costs whenever
   the panel is visible.
5. Config/state mismatch: `eventHighlighting: {}` on disk (= default `enable: false`) while the
   running instance has an active highlighter; BootTidal.hs streams `/editor/highlights` OSC to
   port 6013 regardless of the editor-side toggle.

## Highlighter profile (user-run `tidalcycles:highlight-stats`, 63-min window)

```
[Highlighter profile] 3799.5s window | RAF 4/s, work 3/s (idle 0%) | frame avg 0.502ms / max 89.195ms |
DOM writes 17/s | markers 2170 live, creates/destroys 1/s, toggles 34/s | active 0 | errors 0
[Highlighter profile] jank: 7 frames >16ms, 2 >50ms | worst frame 89.195ms did 16 adds / 1 removes
```

Read: the perf branch works — the highlighter is now cheap on average (0.5 ms/frame, idle-aware
RAF at 3–4/s). The remaining concerns are the 2170-marker accumulation and rare 50–90 ms spike
frames. The session-long ~65% burn comes from the console/marker/styles causes above, not from
the highlighter loop itself.

## Remediation plan

1. Config (immediate): `tidalcycles.showErrorNotifications: false`;
   `autocomplete-plus.autoActivationDelay` ≥ 300 ms (documented community fix for typing lag).
2. Patch fork: cap console DOM (max messages), truncate long messages, lighter error
   notifications (first line + short detail); fix `decorateCodeBlocks` marker leak + debounce;
   fix `#createPositionMarkers` overwrite leak; optionally drop `backdrop-filter`.
3. Verify with `tidalcycles:highlight-stats` + renderer CPU sampling before/after.
4. Re-audition `editor-background` only after the renderer is no longer saturated.
5. Editor endgame: don't fork Pulsar (Electron 12 ceiling, no prior art); if Pulsar still
   disappoints, thin-plugin path (tidal.nvim / VSCode extension) carries the identity cheaper.
   Upstream issue #229 (Aug 2025) plans a web-worker offload for highlighting.

See companion: `2026-07-18-tidalcycles-2026-landscape-research.md`.
