Commit 2f6b0b7a by PLN (Algolia)

docs(perf): 2026-07-18 Pulsar lag investigation + TidalCycles editor landscape research

Records of the editor-lag deep-dive: local root-cause analysis (renderer
main-thread saturation, console DOM growth, three marker leaks, .line
transition churn, Electron 12 ceiling, thermal state) and the companion
web-research report on the 2026 TidalCycles editor ecosystem (uzu/Codeberg
migration, upstream issue #229, vim/VSCode/browser alternatives, no prior
art for Pulsar forks). Fixes landed in the pulsar-tidalcycles fork, branch
perf/event-highlighter, commit 6e9f374.
parent 1bb7c4fa
# 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`.
# TidalCycles editor landscape 2026 — web research report
*Produced 2026-07-18 by a Claude (Sonnet) web-research agent; sources cited inline, fetched
July 2026 except where issue/forum dates note otherwise. Companion to
`2026-07-18-pulsar-lag-investigation.md`.*
---
## (a) TL;DR verdict
**Pulsar is still the community's documented default and is actively maintained, but it is not a fast editor, and the two symptoms described (typing/cursor latency, multi-second stalls on error-stack rendering, low overall CPU) match documented, still-open architectural weak points rather than a config typo.** The tidalcycles org is *still investing in* the Pulsar package as recently as August 2025 (new issues, active maintainer `thgrund`), so "actively maintained" ≠ "fast" here — the maintainer's own issue tracker treats the exact symptom class (highlighting/rendering perf) as an open, multi-PR body of work, not a solved problem. Meanwhile the Vim/Neovim/Emacs plugin path is thin-and-terminal-based (no Electron rendering layer at all) and is what performance-conscious livecoders in the forum gravitate to, though not framed there as an explicit "Pulsar is too slow" migration — it's framed as "predictable, doesn't change." Building a fully custom IDE has no known prior art in this specific niche; the community's actual "purpose-built" answer to slow desktop editors has instead been to go *browser-native* (Strudel/Estuary/Flok), which is a different animal (JS port, not GHC/Tidal-Haskell).
Also worth flagging up front: the whole ecosystem rebranded in 2025 — `tidalcycles/Tidal` was archived on GitHub June 13 2025 and development moved to Codeberg under a new "Uzu" umbrella (`codeberg.org/uzu/tidal`, forum moved `club.tidalcycles.org``uzu.lurk.org`) covering Tidal + Strudel + other "uzulangs." ([Site rename announcement](https://uzu.lurk.org/t/site-rename-apologies-for-spurious-emails/6085), [uzu/tidal on Codeberg](https://codeberg.org/uzu/tidal)) Anything you search for going forward should account for this — old tidalcycles.org/GitHub links now often 301 to Uzu.
## (b) Concrete known perf issues matching the symptoms
**1. Event-highlighting/decoration rendering perf — open, unresolved, exactly this symptom shape.**
`tidalcycles/pulsar-tidalcycles` issue [#229 "Event Highlighting: Optimize performance"](https://github.com/tidalcycles/pulsar-tidalcycles/issues/229), opened 2025-08-14 by maintainer `thgrund`, explicitly says it exists "mainly to stabilize the editing of big files" and lists planned fixes still pending: honoring OSC bundle timestamps to avoid backlog, clearing state on `hush`, **moving calculation to a web worker** (i.e., today it runs synchronously on the main/render thread), and avoiding duplicate token/marker churn on line rewrite. Companion issues: [#228](https://github.com/tidalcycles/pulsar-tidalcycles/issues/228) (CSS override via OSC) and [#227](https://github.com/tidalcycles/pulsar-tidalcycles/issues/227) (custom CSS gets clobbered on restart) — same subsystem, all opened the same week, all still open.
**2. Root architectural cause — Atom's (inherited by Pulsar) marker/decoration system doesn't scale.**
Upstream `atom/atom` issues (pre-Pulsar-fork, but the code Pulsar runs): [#6692 "Find remaining performance bottlenecks for large files"](https://github.com/atom/atom/issues/6692) documents ~2900ms for creating/subscribing to thousands of decorations; [#9208 "Proposal: Marker layers"](https://github.com/atom/atom/issues/9208) proposes batching to avoid per-marker overhead; [#14592 "Bad Overlay Decoration performance on 1.19"](https://github.com/atom/atom/issues/14592) shows decoration regressions after a TextEditor rendering rewrite. This is the best mechanistic explanation available: rendering per-event highlight decorations, or re-tokenizing/re-decorating a big freshly-appended block of text (a GHC error stack dumped into the eval console counts as exactly that), is synchronous main-thread JS work in Atom/Pulsar's architecture — it blocks the renderer while leaving *aggregate* CPU (across all cores) looking low, because it's single-threaded blocking, not multi-core saturation.
**3. Electron main-process/renderer blocking is a known Electron-wide gotcha that produces "stalls with low CPU."**
`electron/electron` issues [#3363](https://github.com/electron/electron/issues/3363), [#5775](https://github.com/electron/electron/issues/5775), [#12098](https://github.com/electron/electron/issues/12098) and the write-up ["The Horror of Blocking Electron's Main Process"](https://medium.com/actualbudget/the-horror-of-blocking-electrons-main-process-351bf11a763c) all describe the same shape: a synchronous IPC call or heavy main-thread JS work freezes the UI while the OS scheduler shows the process mostly idle/waiting. This generalizes the "GHC error stack renders → multi-second stall, CPU stays low" report even without a Pulsar-specific issue naming it directly — no GitHub issue names "large GHC error output in console" verbatim; the closest documented precedent is the 2017-era, since-closed [#19 "multiline eval at end of buffer causes Atom to hang"](https://github.com/tidalcycles/pulsar-tidalcycles/issues/19), showing this class of eval-triggered hang has existed since the plugin's early days.
**4. Autocomplete is a separately-documented, already-solved typing-latency cause** (distinct from the error-stall symptom, but likely compounding it): the [official Pulsar setup docs](https://tidalcycles.org/docs/getting-started/editor/Pulsar/) and a confirmed forum fix ([Pulsar being slow](https://uzu.lurk.org/t/pulsar-being-slow/4566), Feb 2023 — user `Liimxa` confirms "changing the delay before suggestions are shown sorted it") both point to `autocomplete-plus`'s default suggestion-delay as a common typing-lag source, fixable by raising "Delay Before Suggestions are Shown" to 100–1000ms or disabling the package. Cheap, orthogonal, do it regardless.
**5. Electron-on-Wayland GPU fallback is real and separately documented**, though no Pulsar-specific report was found. `electron/electron` [#36633 "Zero GPU Acceleration on Wayland"](https://github.com/electron/electron/issues/36633) and [#45862 "Electron 34 apps fail to use GPU with Wayland backend on Linux 6.12+"](https://github.com/electron/electron/issues/45862) describe Chromium/Electron silently falling back to SwiftShader (CPU-based software Vulkan) under native Wayland, which would show up as a compositor/renderer-side cost (animated GIF background repaints, scrolling, decoration redraws) without necessarily spiking the "CPU%" figure people usually glance at (varies by how it's measured — single-core-pegged vs whole-system). The Hyprland/EndeavourOS thread on `--ozone-platform-hint`/`--disable-gpu` ([forum.endeavouros.com](https://forum.endeavouros.com/t/hyprland-vscode-always-starts-with-editing-ozone-platform-hint-wayland-and-disable-gpu/54345)) shows these flags are commonly auto-injected by distro dotfiles and can be silently misapplied (treated as file arguments instead of flags), which is worth checking in a Pulsar launch wrapper/`.desktop` file if one exists. *(Local check 2026-07-18: our Pulsar GPU process holds `/dev/dri` fds — hardware accel confirmed active, so this failure mode is not currently in play.)*
**6. KWin/Plasma 6 compositor is being actively tuned but not Electron-specific.** Plasma 6.1 added dynamic triple buffering and explicit sync (reduces missed-frame stutter generally); further KWin Wayland latency patches landed June 2026. No Electron-specific KWin interaction bug was found beyond generic "stuttering with Electron apps on Plasma 6 Wayland" chatter — thin evidence here, flagged rather than padded.
## (c) Ranked remediation options (cheap → drastic)
1. **Raise/disable `autocomplete-plus` suggestion delay** (100–1000ms, or disable the package) — documented, forum-confirmed fix for typing lag specifically. [uzu.lurk.org thread](https://uzu.lurk.org/t/pulsar-being-slow/4566)
2. **Disable `editor-background` (or swap the animated GIF for a static image) as a diagnostic** — animated backgrounds force continuous repaint of the whole window; if SwiftShader software rendering is active (see #5), this compounds every other stall. Test with it off before touching anything else.
3. **Verify GPU acceleration is actually active**, not silently on SwiftShader: check `pulsar://gpu` equivalent (Electron apps expose `chrome://gpu`-style info) or launch from a terminal and watch for Ozone/Wayland warnings; try forcing `--ozone-platform-hint=auto` (not hardcoded `wayland`) and confirm no stray `--disable-gpu` is being injected by a wrapper/launcher script.
4. **Cap or truncate eval-console output** — if the tidalcycles package or a console-view package has any "max lines"/"scrollback" setting, use it; this directly targets the marker/decoration-scaling root cause (#6692, #14592) that a long GHC stack trace triggers.
5. **Track/subscribe to `pulsar-tidalcycles` issue #229** — the maintainer is actively working the exact bug class (worker-thread offload for highlighting) as of Aug 2025; a future release may fix this without you doing anything. Worth commenting with the reproduction (large GHC error → stall) since it's not explicitly logged there yet.
6. **Try disabling event-highlighting entirely** (if BootTidal.hs's `cFrameTimespan`/highlight OSC target can be turned off) to isolate whether it's the highlighter or the console renderer causing stalls — the docs note animation fps should be ≤ ~20–30fps and tied to `cFrameTimespan`; a mismatch here is also a plausible independent stutter source. [Pulsar TidalCycles docs](https://tidalcycles.org/docs/getting-started/editor/Pulsar/)
7. **Migrate to vim-tidal or tidal.nvim (Neovim) + tmux**, keeping ghci/tidal-listener output in a terminal pane instead of an Electron webview. This is the documented "recommended plugin" path ([tidalcycles.org Vim docs](https://tidalcycles.org/docs/getting-started/editor/Vim/), [tidalcycles/vim-tidal](https://github.com/tidalcycles/vim-tidal)) and structurally sidesteps every issue above (no Electron, no DOM markers, terminal scrollback handles large error dumps natively). Cost: you lose Pulsar's `editor-background`/custom HUD integration entirely unless rebuilt as terminal statusline/tmux-status equivalents.
8. **Full custom/forked IDE** — last resort; see (d), no prior art found to build on, meaning you'd be re-solving Atom's decoration-scaling problem yourself rather than inheriting a fix.
## (d) Prior art on custom/forked livecoding IDEs
- **No evidence found of anyone forking Pulsar/Atom into a trimmed, livecoding-dedicated build.** Targeted searches for "minimal build," "performance mode," disabling minimap/tree-sitter for livecoding, or a custom Electron trim turned up nothing — either it doesn't exist publicly or isn't indexed. Treat this as a real gap, not a "just didn't look hard enough."
- **The community's actual "purpose-built editor" answer has been to go browser-native rather than trim a desktop IDE**: [Strudel](https://strudel.cc/) (JS port of Tidal's pattern engine, runs in-browser, no GHC/ghci involved at all so this entire class of stall structurally can't occur), [Estuary](https://tidalcycles.org/docs/resource/Friends_and_relations/) (browser-hosted Tidal-like parser + other languages), and [Flok](https://github.com/munshkr/flok) (P2P collaborative browser editor supporting Tidal, Strudel, SuperCollider, FoxDot, Sardine, Mercury). These are ground-up new implementations, not IDE forks — worth knowing as the template for "a browser tab as the performance surface" instead of an Electron app.
- **The lightweight-editor-plugin pattern (vim-tidal/tidal.nvim/tidal.el) is the closer analogue**, just inverted: instead of forking a heavy IDE and stripping it down, the community built a thin plugin on top of an already-minimal, mature terminal editor. Multiple independent Neovim ports exist (`grddavies/tidal.nvim`, `robbielyman/tidal.nvim`), suggesting this is where people who cared enough to build custom tooling actually put their effort — thin integration layers, not IDE forks.
- **tidal.el (Emacs) is confirmed actively maintained** (2025 copyright, moved to Codeberg with the rest of the ecosystem) — [emacsmirror/tidal](https://github.com/emacsmirror/tidal/blob/master/tidal.el), [tidalcycles.org Emacs docs](https://tidalcycles.org/docs/getting-started/editor/Emacs/).
- **VS Code extension was recently overhauled** (test coverage added, scope trimmed, automated releases) with the stated goal of reaching parity with the Pulsar plugin — implies it was behind, but is a live target for a GPU-accelerated-but-more-mature-than-Pulsar Electron option; no explicit performance benchmarking found. ([tidalcycles/vscode-tidalcycles](https://github.com/tidalcycles/vscode-tidalcycles))
- **No Zed support found** — absence of evidence, not evidence of absence; nothing indexed either way.
- System-level "protect the editor UI from real-time audio priority" is **thin territory**: Arch/Linux pro-audio guides ([ArchProAudio](https://github.com/chmaha/ArchProAudio), [Arch wiki Professional audio](https://wiki.archlinux.org/title/Professional_audio), [wiki.linuxaudio.org](https://wiki.linuxaudio.org/wiki/system_configuration)) cover JACK/PipeWire `rtprio`, `rtirq` IRQ-thread priority, realtime kernels, and killing unneeded daemons — all aimed at protecting the *audio* thread from xruns. No source explicitly addresses the inverse (keeping the editor UI thread responsive *despite* SuperCollider holding elevated realtime priority) — an under-documented niche with no external prior art to lean on.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment