Roadmap¶
Tracks progress against the build order defined in SPEC.md. Each phase should land with tests/benchmarks before the next begins.
Phases¶
- [x] Phase 1 — Core buffer engine
Piece-table implementation (
core/src/buffer.c), unit tests (core/tests/test_buffer.c), a benchmark tool (core/bench/bench_buffer.cpp, build with-DASE_BUILD_BENCH=ON), and a libFuzzer harness (core/fuzz/fuzz_buffer.c, build with-DASE_BUILD_FUZZERS=ONunder clang — ~8.8M execs clean under ASan/UBSan locally, not yet wired into CI). No GUI dependency; see ADR 0002 and ADR 0005. Deferred out of this phase, tracked as follow-ups: line-index acceleration, periodic memory compaction, and CI fuzz integration (all called out in ADR 0005). Undo/redo — also called out there — landed as Phase 10 below. - [x] Phase 2 — Minimal Qt shell
QMainWindow+ a custom-paintedEditorViewportwidget (gui/src/editor_viewport.{h,cpp}): renders the buffer withQPainteragainst the default palette, blinking caret, keyboard wired toase_buffer_insert/_delete(typing, backspace/delete, arrow/Home/End navigation with a sticky column for up/down), wheel scrolling,Ctrl+Ssave /Ctrl+Qquit, no menu bar or other chrome. Verified by launching it against a real file, screenshotting the render, and confirming typed edits round-trip to disk via Ctrl+S. See ADR 0006 for this phase's deliberate shortcuts (full-buffer mirroring per keystroke, byte-level cursor, no IME, no dirty-tracking). - [x] Phase 3 — Syntax highlighting
Tree-sitter wired in via a new
ase_syntaxmodule (modules/syntax/): runtime + one grammar (C) fetched with CMakeFetchContentand pinned to exact revisions, a hand-authored query (src/queries/c_highlights.scm), unit tests, and a libFuzzer harness on the parser boundary (-DASE_BUILD_FUZZERS=ON). Wired intoEditorViewport:.c/.hfiles get highlighted, everything else renders plain. The default theme resolves the "one font color" pillar vs. "syntax highlighting" tension by varying only weight/style/opacity of the single text color (bold keywords, italic types, dimmed comments) — never a second hue. Verified by unit test and by screenshotting the editor highlighting its owncore/src/buffer.c. See ADR 0007 for all of this phase's decisions, including a measured (bounded, not leaking) memory characteristic of full-reparse-every-edit. - [x] Phase 4 — Theming & config system
AseConfigin core (core/src/config.c,core/include/ase/config.h): a minimal hand-rolledkey = valueparser (not TOML — see ADR 0008) shared by config and theme. Ships built-in defaults, overlays a loaded file on top, and best-effort writes a commented starter file to$XDG_CONFIG_HOME/ase/config.ase(or platform equivalent) on first run. Wired intoEditorViewport: background/text colors and font family/size all come from config now, and a 750ms poll hot-reloads on save — no restart. Verified with unit tests and by editing the live config file while the editor was running and screenshotting the color/font change landing without a restart. Resolves both ROADMAP open-decision items below (format, and the exact accent color — now a live-tunable value, not a hardcoded guess). - [x] Phase 5 — Plugin ABI + Lua scripting host
AsePluginHostin core (core/include/ase/plugin_host.h,core/include/ase/plugin_abi.h,core/src/plugin_host.c): one named-command registry populated two ways — Lua scripts (embedded Lua 5.4, fetched viaFetchContentsame as Tree-sitter) callingase.register_command, or native.so/.dylib/.dllplugins exporting onease_plugin_registersymbol viadlopen. Both get a small buffer-editing API (ase.buffer_*for Lua,AseBuffer*-taking commands for native). Install-by-file, no registry/marketplace, per spec section 6. Verified with unit tests covering registration/replacement, missing-directory and broken-script handling (a syntax error in one script doesn't block others or crash the host), and an end-to-end test loading a real.luafixture and a compiled native fixture from the same directory and running both against a buffer. All clean under ASan/UBSan (neededCMAKE_POSITION_INDEPENDENT_CODE ONproject-wide once a static lib had to link into a dlopen'd module). See ADR 0009 — including the correction of an earlier placeholder that had this living inmodules/plugins/instead of core, per the spec's own architecture diagram. Not wired into the GUI yet — no keybinding/command-palette exists to trigger a registered command interactively; tracked below. - [x] Phase 6 — LSP client module
AseLspClientin core (core/include/ase/lsp_client.h,core/src/lsp_client.c), on top of a new hand-rolled JSON module (core/include/ase/json.h,core/src/json.c— not a vendored library, see ADR 0010). Spawns a language server as an isolated child process, speaksContent-Length-framed JSON-RPC over its stdio, does a bounded synchronousinitializehandshake, then everything else (diagnostics via a standing callback, completion/definition via per-request callbacks) is non-blocking and timer-poll-driven, the same shape as config hot-reload. POSIX only in v1 — Windowsase_lsp_client_startreturnsNULLcleanly rather than shipping untested async-I/O code (docs/adr/0011, decision 6). Verified against a fake LSP server test fixture (deliberately independent of the real JSON module, so a shared bug can't hide symmetrically) covering the full lifecycle — handshake,didOpen→ diagnostics, completion, definition, shutdown — plus a missing-executable case. All clean under ASan/UBSan. The JSON module alone also has 37.8M clean fuzz executions (core/fuzz/fuzz_json.c). See ADR 0011 for the full design, including whySIGPIPEis now disabled process-wide and why server-to-client requests aren't answered. Not wired into the GUI yet — no diagnostics/completion/ go-to-definition UI exists; tracked below, same pattern as Phase 5's plugin host. - [x] Phase 7 — Polish
All four items scoped and recorded in
ADR 0012:
- Multi-cursor:
EditorViewportmoved from onesize_tcursor toQVector<size_t> m_cursors. New cursors via mouse click (new — there was no mouse handling before this phase),Alt+click, orCtrl+D("select next occurrence," Sublime/ VS Code convention);Escapecollapses back to one. Every edit applies at every cursor, processed highest-offset-first — proven safe without cross-cursor offset bookkeeping (ADR 0012, decision 1). Verified live: clicking,Alt+clicking, andCtrl+Deach confirmed by typing afterward and screenshotting the same character landing at every active cursor simultaneously. - Animation: one, opt-in (
animations = truein config, defaultfalse) — a smooth caret alpha-fade replacing the hard blink toggle. Verified live: sampled the caret's actual pixel color across three screenshots taken moments apart with animations on, confirming a continuously-varying alpha rather than a hard on/off value. - Panel layout: deliberately not built — there is, and has been through every phase, exactly one panel. Building a second one now just to have something to arrange would be unscoped scope creep; deferred until a real second panel exists (most plausibly an LSP diagnostics panel).
- Accessibility pass: found and fixed a real issue —
ADR 0007's dimmed comment color measured 3.64:1 contrast against
the background, under WCAG AA's 4.5:1 for normal text; raised to
4.91:1. Added accessible name/description. Full screen-reader
text exposure (
QAccessibleInterface) deliberately not attempted — unverifiable in this environment, and an untested implementation of an interface real assistive tech calls into risks being worse than the honest gap (same reasoning as ADR 0011's Windows decision). Tracked below.
- Multi-cursor:
Current status¶
All seven phases from the spec's build order are done (see above), and so is everything the "wiring" note below used to point at: the plugin host and the LSP client are both in the GUI, and the license decision was settled by ADR 0033.
At v0.4.0-beta the editor has a Vim mode pinned by 218 conformance
cases generated from real vim, Tree-sitter highlighting for seven
languages, an LSP client with diagnostics/completion/hover/go-to-
definition sharing one server per language per project, built-in
palettes, keybindings as data, session restore, git gutter marks, an
atomic save and crash recovery for unsaved work.
What is still open is in Beyond v1 below, which has been pruned of everything since finished.
Post-v1 fixes¶
- Caret drift (user-reported): the caret and syntax-highlight run
positions were both computed as
column * m_charWidth, an assumed fixed-pitch approximation that drifted visibly from the actually rendered text on long lines. Fixed by measuring actual rendered text width (QFontMetrics::horizontalAdvance) instead of assuming one. See ADR 0013. - Invisible caret (user-reported, immediately after Phase 9): the
disabled-animation branch of
updateAnimationclearedm_renderedCaretPosevery frame instead of populating it, andpaintEventonly draws carets when that array is non-empty — so the caret never rendered at all under the defaultanimations = falseconfig. Fixed alongside a requested blink-mechanism change: the hard blink now resets on every cursor-moving action (stays solid visible throughout active use) and only resumes toggling ~500ms after input actually stops. See ADR 0016 — note its "kept separate from the animated fade's phase" reasoning was superseded by ADR 0017 below, which found that separation was itself the cause of the fade still blinking during movement. - Viewport review pass (user-reported, four issues at once): text
rendered as blank space once a line scrolled far enough right
(
width() - xgoing negative in translated coordinates — the most severe of the four); the animated fade still blinked during movement (a free-running counter ADR 0016 didn't touch); typing/deleting fast felt laggy (the caret-glide easing couldn't keep pace with rapid small jumps and was never meant to try); andQFontMetricswas being reconstructed per styled run per frame instead of cached. All four fixed together — see ADR 0017. - LSP silently not working with a relative path (user-reported,
after Phase 17): launching as
ase_gui file.c(relative, the common case) built the LSP document URI straight from the unresolved argument viaQUrl::fromLocalFile, which expects an absolute path — a real server (clangd) rejected every message outright ("unresolvable URI"), silently disabling diagnostics, completion, and hover with no visible error in the editor itself. Fixed by resolving throughQFileInfo::absoluteFilePathfirst. Verified live by reproducing the report's exact launch shape (relative path, matching working directory) and confirming a cleanclangdround trip. See ADR 0032.
Post-v1 "feel alive" initiative¶
Direct feedback after v1: the editor works but doesn't feel alive (abrupt caret, no view-follow, no line numbers). Phases 8–9 below addressed that.
- [x] Phase 8 — Viewport geometry: horizontal scroll, two-axis
view-follow, and an optional line-number gutter (
line_numbersconfig key:off/absolute/relative, defaultabsolute). See ADR 0014. - [x] Phase 9 — Smooth motion: caret glide + smooth scroll, one
easing mechanism driving both, advanced from
paintEventitself (never stale relative to what's drawn). Fractional vertical scroll needed the one real new trick — render fromfloor(renderedScrollLine), shift the whole line loop by the remainder, request one extra line at the bottom. Verified live:animations = trueshows a large scroll jump visibly interpolating across frames (sampled gutter numbers converging 232 → 237 → 238);animations = falseproduces pixel-identical consecutive frames after the same jump (true instant snap, no regression). See ADR 0015.
Post-v1 "complete normal editor" initiative¶
Explicit direction: finish the editor as a complete normal text editor first (undo, selection, clipboard, find/replace, chrome, compile, LSP UI) — full modal Vim emulation is confirmed scope but deliberately pushed to after this list, planned in detail only once it's reached. Single file per window (no tabs) for now — multi-file may become a tmux-like tiling plugin later. The full plan was kept outside this repo at the time of writing; phase-by-phase ADRs land here as each is reached.
- [x] Phase 10 — Undo/redo:
AseUndoStackin core (core/include/ase/undo.h,core/src/undo.c) — records edit intent (offset + inserted/removed bytes), not snapshots, matching the diff-based design ADR 0005 anticipated. A whole multi-cursor keystroke undoes/redoes as one group, unwound in the exact reverse of application order.Ctrl+Z/Ctrl+Shift+Z. No coalescing of consecutive keystrokes (each is its own group) — deliberate v1 simplicity. See ADR 0018. - [x] Phase 11 — Selection model: keyboard (Shift+arrows/Home/End)
and mouse (click-drag, Shift+click) selection. New
m_selectionAnchors, index-aligned withm_cursors(m_selectionAnchors[i] == m_cursors[i]means no active selection) — same patternm_renderedCaretPosalready used. Every move op takes anextendflag (Shift held): extending moves the head and leaves the anchor fixed; a plain move with an active selection collapses to the near/far edge instead of stepping, matching standard editor convention. Typing/Backspace/ Delete over a selection replace/remove exactly that range, as part of the same undo group (Phase 10). New translucentselectionconfig color, painted in its own pass ahead of the text so glyphs stay crisp on top. Verified live viaxdotool+ screenshots (keyboard extend/collapse) and a save-to-file round trip (type-over-selection, undo). See ADR 0019. - [x] Phase 12 — Clipboard: cut/copy/paste via
QClipboard, built on Phase 11's selection.Ctrl+Creads every selected cursor's text out ofm_cacheand joins multiple selections with\n;Ctrl+Xdoes the same then deletes them as one undo group;Ctrl+Vinserts the clipboard text at every cursor, reusinginsertText's existing selection-replace and multi-cursor broadcast. NoCMakeLists.txtchange needed —Qt6::Widgetsalready pulls inQt6::Gui. Verified live against the real system clipboard viaxclip(independent of the Qt process), a save-to-file round trip for paste, and a cut-then-Ctrl+Zround trip. See ADR 0020. - [x] Phase 13 — Find & replace:
Ctrl+F/Ctrl+Hopen a new keyboard-onlyFindBar(gui/src/find_bar.{h,cpp}). Plain substring, ASCII-case-insensitive, no regex. The current match is just an active selection (jumpToMatchsetsm_cursors/m_selectionAnchorsto it), soEnter-to-replace reuses Phase 11's selection-replace path unchanged; Replace All (Ctrl+Enter) writes highest-offset-first as one undo group, the same discipline every other multi-offset edit in this file follows. Verified live viaxdotool— pixel-level highlight checks and save-to-file round trips for replace-one/replace-all, each undoing as one action. See ADR 0021. Restyled immediately after — see Phase 13.5 below;FindBarnow floats on aFloatingPanelbase rather than the docked bar this phase originally shipped. - [x] Phase 13.5 — Floating-panel design system: direct feedback
that the docked find bar "looks so basic," plus the knowledge
that Open/Save-As panels are coming next, prompted a real reusable
system instead of another one-off shape. New
FloatingPanel(gui/src/floating_panel.{h,cpp}) — a self-centering, flat, translucent, fade-in/out child widget any future chrome (Open/Save-As, etc.) can build on — andLetterBadge(gui/src/letter_badge.{h,cpp}) — a small single-letter chip replacing text labels ("F"/"R" today). Two decisions taken to the user rather than guessed: panels are true-centered on the window (not top-anchored), and future Open/Save-As will be custom floating panels, not nativeQFileDialog— overriding what the original Phase 14 plan sketched; that plan needs a matching update before Phase 14 starts.main.cppreverted to its pre-Phase-13 simplicity (FindBaris a child ofEditorViewport, not a layout row). See ADR 0022. - [x] Phase 14 — Editor chrome: status bar (
Ln %1, Col %2+ a*when dirty), dirty tracking (m_dirty, set by every mutating op including undo/redo, cleared by a successful save — ADR 0006 had deferred this), andCtrl+O/Ctrl+Shift+Svia a newFileBrowserPanel(gui/src/file_browser_panel.{h,cpp}) — a secondFloatingPanel, per ADR 0022's decision to use custom panels instead of nativeQFileDialog.EditorViewportgot its firstQ_OBJECT/signal (statusChanged), emitted fromensureCursorVisible()— the one choke point every cursor move and every edit already passes through.Ctrl+Swith no path set now opens Save-As instead of silently doing nothing. A real bug found and fixed during live verification: both the file browser's path field and its list neededReturnconsumed viaeventFilterrather than left to Qt's nativereturnPressed/itemActivated— a focus change inside the handler was causing the same key press to be redelivered to the freshly-focused editor afterward. See ADR 0023. - [x] Phase 14.5 — Panel polish, scroll margin, status bar theme:
direct feedback that
FileBrowserPanel's close animation and navigation "felt unsmooth," plus a request to search by name instead of editing a path, and a scrolloff-style viewport margin. Root-caused the animation jank toFloatingPanelanimating its own realgeometry(forcing a liveQVBoxLayout/QListWidgetrelayout every frame) — reworked to animate a static snapshot image instead (contentWidget(),m_snapshot), which also surfaced and fixed a genuine "geometry set while hidden doesn't survive the child's first show()" class of bug. The file browser's path field is now a filter (placeholder shows just the directory name, typing filters the list and auto-selects the first match) with a custom animated sliding highlight bar — which itself uncovered a real, previously-invisible bug:..was being fully hidden by an opaque highlight (QPalette/setAutoFillBackgroundsilently ignores alpha), fixed with a smallTranslucentBarthat paints viaQPainter::fillRectdirectly, same technique the editor's own selection/find-match overlays already use.ensureCursorVisible()now keeps a 3-line/4-character margin around the cursor before scrolling. Status bar themed to match the app (QPalette, re-applied on everystatusChanged). See ADR 0024. - [x] Phase 15 — Command line +
:compile+ output panel: newAseProcess(core/include/ase/process.h,core/src/process.c) extracted from the LSP client's process- spawning code —lsp_client.cis now a consumer of it, verified by its existing test suite still passing unchanged; newcore/tests/test_process.ccovers the module directly. A key opens aCommandLineFloatingPanel(gui/src/command_line.{h,cpp}, smallest of the family — one badge, one field);:w/:q/:compile/:outputdispatched byEditorViewport::runCommand, unrecognized input a silent no-op.:compilereadsbuild_commandfrom config (no default), substitutes%f, runs it through a shell with the file's directory ascwd, and streams output into a newOutputPanel(gui/src/output_panel.{h,cpp}) — this app's first docked panel rather than a floating one, a deliberate choice confirmed with the user: you want to watch a build stream while still looking at your code, which a centered overlay would fight. Verified live viaxdotool: a realbuild_commandstreams its output and exit code correctly. See ADR 0025. The trigger key changed immediately after — see Phase 15.5 below; it'sCtrl+;now, not a bare:. - [x] Phase 15.5 — Keybinding fix, Help/About panels, find bar
repositioned: a real bug — the bare
:binding meant:could never be typed as a literal character — fixed by moving the command line toCtrl+;(:is a Vim ex-command-line convention this non-modal editor shouldn't have claimed).Ctrl+Bcompiles directly,Ctrl+Shift+Otoggles the output panel directly, both alongside (not replacing) their:compile/:outputcommand-line equivalents. NewHelpPanel(Ctrl+/,gui/src/help_panel.{h,cpp}) andAboutPanel(Ctrl+I,gui/src/about_panel.{h,cpp}), both centeredFloatingPanels — Help is the family's first panel with no input field (a scrollable keybinding reference instead), About shows app name/version/author and clickable GitHub/website links. NewFloatingPanel::Anchor(Center/TopRight) lets one panel override its position —FindBaris the only one that does, moved top-right per direct feedback that a centered find bar sits on top of the text you're actively searching. See ADR 0026. - [x] Phase 15.6 — Logo, title-color bug, thin scrollbars, faster
animation, output divider: the app's logo
(
gui/resources/ase.png, bundled via Qt resources/.qrc) is now the window icon and appears in the About panel. Fixed a real bug: Help/About panel titles rendered black, not themed — the oneQLabeleach panel'srefreshTheme()had missed. New sharedthinScrollBarStyleSheet(gui/src/scrollbar_style.{h,cpp}) — thin, thickens on hover — applied to Help, the output panel, and the file browser's list. Enter now glides the caret to the new line (previously snapped like any other edit); every animation duration (caret glide/fade, blink, panel pop, row highlight) sped up.TranslucentBarextracted to its own file (gui/src/translucent_bar.{h,cpp}) and reused for a small, centered seam marker at the output panel's top edge — found and fixed a second real bug along the way: the output panel itself had no themed background, so the area around that marker rendered Qt's default light gray. See ADR 0027. - [x] Phase 15.7 — Click precision fix, deletion glide, select-all,
About centering, synced input blink: fixed a real, reported
bug — clicking to place the cursor rounded down to the nearest
column's left edge instead of the nearest column, so a click in
a character's right half needed the pointer to move almost a
full character further right than expected before the cursor
caught up; verified fixed via a systematic click sequence.
Backspace/Delete now glide like Enter does (ADR 0027) rather
than snapping. New
Ctrl+Aselect-all.AboutPanel's body text now centers under its logo instead of reading left-aligned beneath a centered image.QApplication::setCursorFlashTimesyncs every floating panel's native input-field caret to the editor's own blink rate (a partial answer to "make the caret animation global" — Qt's native caret can't do the smooth fade, only match the rate). See ADR 0028. - [x] Phase 16 — LSP diagnostics wiring: new
ase_lsp_client_did_change(full-document sync, called fromrefreshCache()so diagnostics never go stale after an edit), a 200ms GUI poll timer, and rendering — a wavy squiggle under each diagnostic's range plus a severity-colored gutter dot, both driven by two new config keys (diagnostic_error/diagnostic_warning— a deliberate, documented exception to the one-font-color pillar) and a third,lsp_command, followingbuild_command's "no default, don't guess" pattern. Found and fixed a real bug along the way: the sharedAseProcessspawn helper always merged a child's stderr into its stdout pipe, which is fine for:compileand for the test fixture but silently corrupted the framed JSON-RPC stream against a real server (clangdlogs to stderr) — split intoase_process_spawn(unchanged, merged) and a newase_process_spawn_ex(..., merge_stderr)the LSP client calls withfalse. Verified live against realclangd: a missing- semicolon error produced a visible squiggle + gutter dot, and retyping the fix cleared both without restarting the editor. See ADR 0029. - [x] Phase 17 — LSP completion + hover: new
ase_lsp_client_request_hoverin core (didn't exist before this phase); GUI-side completion is automatic while typing (gated to "right after an identifier char or a member-access trigger," not every keystroke) via a new custom-paintedCompletionPopup, accepted with Enter/Tab by replacing the typed prefix in place; hover is automatic on a ~500ms mouse pause via a newHoverPanel, dismissed on move-away/click/scroll/key/focus-loss. Both are deliberately notFloatingPanels — they track a moving point and refresh far more often than a glance-act-dismiss chrome window, so each only fades (no scale-pop) and only on first appearance, not on every in-place content refresh. Verified live against realclangd: a completion popup with real macro suggestions, correct prefix-replace on accept with no reopen-loop; a hover tooltip showing clangd's real type/value/declaration info, dismissing correctly on mouse-away. See ADR 0030. - [x] Phase 17.5 — Draggable panels, smooth panel scroll, tracking-
popup glide, Tab: user feedback framed as "finish the base
editor" rather than a new feature.
FloatingPanel::setDragHandle(one call per panel, using the badge every panel already has) — every existing panel is now draggable, and any future one is too with the same one line.installSmoothScroll(), a free function working on anyQAbstractScrollArea(QScrollArea,QListWidget,QPlainTextEditall qualify), eases wheel-scroll instead of jumping — applied to Help, File browser, and Output. New sharedTrackingPopupbase forCompletionPopup/HoverPanel(Phase 17): both now glide their position toward a moving target (the caret while typing, the pointer while still hovering the same word) instead of jumping, and any future tracking overlay gets the same fade/paint/glide identity for free by deriving from it. Tab now inserts a soft (4-space) indent — a real, reported gap (it previously did nothing at all). Checked, not just fixed: FindBar's open/close/resize animation was confirmed (via a temporary debug print) to already fire identically to every other panel — nothing was actually broken there. See ADR 0031. - [x] License & first alpha release: Apache License 2.0
(
LICENSE/NOTICE), version bumped to0.1.0-alpha(ASE_VERSION_STRING, a single source of truth read by the About panel), taggedv0.1.0-alpha, pushed to github.com/saeeedhany/ase. See ADR 0033. - [x] Linux packaging:
install()rules (gui/CMakeLists.txt) as the one foundation every format below builds on — a.desktopfile and a new padded-square icon alongside the existing wordmark logo. AppImage (packaging/appimage/, via linuxdeploy — two real bugs found and fixed while actually building one: wrong Qt version auto-detected, and linuxdeploy's bundledstripchoking on a newer ELF section), an ArchPKGBUILD(packaging/arch/, building from the real GitHub release tag — verified with a realmakepkg -frun, not just written), and a dependency-scanned.deb(packaging/debian/, built and its payload run standalone to confirm it actually works, not just thatdpkg-debdidn't error). See ADR 0034. - [x] Documentation site: MkDocs + Material, deployed to GitHub
Pages at saeeedhany.github.io/ase
— the same
docs/SPEC.md/docs/ROADMAP.md/docs/adr/*.mdthis repo already had, now browsable with search and a real nav instead of only readable file-by-file on GitHub. Newdocs/adr/index.md, a generated table of every decision in order, is the "project history" view this was built for. Two real rendering bugs (missing markdown extensions for buttons/ icons; the dev server's/ase/path prefix) found by actually loading the built site in a browser, not just a cleanmkdocs build --strict. See ADR 0035. Rebranded right after (user-reported: it still showed Material's default logo and indigo color scheme) — the actual "ase" wordmark as logo/favicon, and a custom palette matching the editor's own#282828/#F5E6C8, plus a new docs-only accent#689d6a(the editor has no general-purpose accent color of its own to reuse — see ADR 0007). A placeholderdocs/plugins.mdpage was also added. See ADR 0036. - [x] Feedback triage log:
docs/feedback/, structured the same way asdocs/adr/(numbered batch files, an index, an explicit nav entry each) — a formal, versioned response to external review comments, attributed to the reporter and linked back to the source, distinct from the live conversation on Discord/ GitHub issues where a report first lands. First batch: seven comments from the alpha release's first external review (@TOTO-sys28on Discord) — a CMake robustness gap and a vendored-Lua compiler warning are fixed; two real packaging version-floor bugs (.deb/AppImage built on a rolling-release dev machine instead of an old, pinned base) remain open, to be done together with a future batch of fixes rather than as an isolated rebuild; the cramped Help panel is deferred to a future system-wide floating-panel design pass rather than an isolated layout patch (see ADR 0038, which also writes down the criteria for telling a real defect apart from a design choice, and adds theDeferredstatus this uses); two items checked and found not to be current bugs. See ADR 0037. - [x] Mouse hit-test drift fix: user-reported bug — clicking or
hovering further right on a line with mixed syntax-highlight
styling (e.g. bold keywords next to plain identifiers)
increasingly missed the character under the pointer. Same root
cause as ADR 0013's caret-drift bug (a fixed per-character pixel
width instead of measuring what was actually rendered), just
never fixed on the mouse side. New
columnForX()mirrorsxForColumn()'s real per-run measurement; verified live by confirming a mouse click and a keyboard-placed caret at the same pixel now resolve to the same character, deep into a styled line. See ADR 0039. - [x] Italic glyph clipping fix: user-reported bug — italic
voidwas visibly misreadable asvoia, the ascender of the italicdsheared off.drawText's implicit per-run clip rect was sized from the plain font's metrics, too tight for an italic variant's taller ascent. Fixed withQt::TextDontClip; verified live with a zoomed screenshot. See ADR 0040. - [x] Diagnostic underline redesign: the wavy zigzag underline is now a plain thin line, dim by default and brightening to full opacity with a fast, smooth left-to-right wipe on the line the cursor is on (reversing when it leaves) — the gutter dot gets the same dim/focus distinction as a plain fade, no wipe. Verified live against real clangd diagnostics, including catching the wipe and its reverse mid-animation. See ADR 0041.
- [x] Empty-buffer welcome overlay: the "ase" wordmark plus four essential shortcuts (open, save, shortcuts reference, about), centered over an empty buffer, fading out on the first keystroke and back in if the buffer is emptied again — regardless of whether that got saved. Purely content-driven (no persisted "first launch" flag): also shows any time a file's contents get cleared, not just at initial launch. Verified live, including catching the fade-out and fade-back-in mid-animation. See ADR 0042.
- [x] UI polish pass:
kEaseFactor(the single constant every eased value ineditor_viewport.cppshares — caret glide, scroll, diagnostic focus, the welcome overlay's fade) raised 0.5 → 0.68 for snappier navigation, per direct feedback that Enter/Delete/general movement felt slow. Welcome overlay's shortcut list redesigned from flat centered lines to a two-column, dot-led layout (description left, key right). See ADR 0043. - [x] Panel isolation, full drag bar, unsaved-quit confirmation: the five floating-panel popups (Find, File browser, Command line, Help, About) now fully isolate the document underneath — no typing, clicking, scrolling, or hover-popups through while one's open; closing is Escape-only (an early click-outside- closes design was rejected — double-clicks and clicks inside a panel bubbled the same way and closed it too, so it just blocks instead). Found and fixed two real, previously-latent bugs along the way: drag events on a panel's handle were never actually consumed, leaking through to the document; and releasing a drag stranded keyboard focus off the panel, so Escape could silently fail right after dragging. Help/About panels are now draggable from their whole header bar (no special hover cursor), not just the badge. Quitting with unsaved changes now asks for confirmation, styled to match the app's own dark theme instead of the native OS dialog look. See ADR 0044.
- [x] v0.2.0-alpha: the batch above was real new functionality, not
just fixes — worth a real version bump. Also finally fixed the
two open packaging bugs from
docs/feedback/0001(deferred there until "a batch of useful additions," which this is): the.deband AppImage are now built inside pinned old container bases (debian:bookworm,ubuntu:22.04) instead of natively on this rolling-release dev machine, which was baking in an unrealistic Qt/glibc floor. Found and fixed two more real bugs along the way (adpkg -S/usrmerge symlink mismatch that silently dropped Qt6 from the.deb's dependencies; a missinglinuxdeploy-plugin-qtenv var for FUSE-less containers) — both verified by actually installing/running the rebuilt packages, not just inspecting them. See ADR 0045. - [x] Vim mode — Phase 1: native (not a plugin — the plugin ABI
has no raw-keystroke/modal-state hook), built inline into
EditorViewport. Insert/Normal/Visual modes; motionsh j k l 0 ^ $ gg G w b e; operatorsd y ccomposable with any motion and counts (3j,d2w,3dd/yy/cclinewise);x,p/P,u/Ctrl+R; Visual-moded y c xover the existing selection model;:<digits>ex-command line jump. Off by default (vim_mode = false), byte-identical to before for anyone who hasn't opted in. Found and fixed one real bug during live verification: charwisepat end-of-line landed the paste on the next line instead of appending to the current one. Explicitly deferred for a later phase: registers beyond the clipboard, macros, marks, text objects, dot-repeat, jumplist, Visual Block, Replace mode,r,:s////:g//, search-motion (/ ? n N),J, indent, case ops, and any keybinding remapping (project-wide gap, not Vim-specific — see ADR 0026). See ADR 0046. - [x] Vim mode — block cursor: Normal mode now renders a real
vim-style block cursor filling the character cell (measured/
styled the same run-aware way
drawLine/xForColumnare, so bold/italic captures and multi-byte UTF-8 characters size correctly), with the covered character knocked out in the background color on top so it stays legible, and its blink capped well under full opacity so it never flashes as a solid block. Insert/Visual keep the original bar caret. Reuses the existing position-glide and blink-reset animation machinery unchanged — no new animation path needed. See ADR 0047. - [x] Syntax accent colors, caret inset, smaller default font,
immediate status label:
ASE_HL_TYPE/ASE_HL_STRINGnow render in two real accent colors instead of one being italic —#689d6a(the color the user previously supplied for the docs site, ADR 0036) for types, a matched warm gold for strings; everything else stays monochrome, per "don't use too much." Both caret shapes (bar and block) trimmed 2px shorter top/bottom. Defaultfont_size12 → 11. The status bar's mode label now shows immediately on launch instead of only after the first keystroke. A fourth request from the same feedback — a typing animation — was deliberately deferred pending a design decision rather than guessed at. See ADR 0048. - [x] Typing pop-in animation: the deferred fourth item from the
same feedback. Each newly typed character (plain typing, Tab —
not paste, not multi-line insertions, not Enter's own newline)
fades and scales in from 85% to full size/opacity over ~120ms,
gated on
animations = truelike every other animation in this editor. Hooks intoinsertText/insertTextAt(the one funnel every typing-shaped insertion already goes through) rather than adding a new call site. See ADR 0049. - [x] Runtime font-size zoom; Vim mode on by default:
Ctrl+=/Ctrl+-/Ctrl+0adjust font size live, in-session, independent ofconfig.ase(survives an unrelated config hot-reload; onlyCtrl+0or a restart returns to the configured value). Separately, Vim mode is now on by default (vim_mode = trueinase_config_create_default()and the shipped template — this reverses ADR 0046's original "opt-in" reasoning) and a session now starts in Normal mode, not Insert, matching real vim. See ADR 0050. - [x] Global animation consistency; three Vim-mode polish fixes:
fixed a real bug where the typing pop-in (ADR 0049) never actually
rendered —
snapAnimationToTarget()was wiping its own animation entry in the same keystroke that created it. Removed the last special-cased instant-snaps from Vim mode (every motion, mode-entry command, and mutation) and from plain typing itself, so the whole app now glides consistently with no exceptions. Also: Visual mode now shows the same block cursor as Normal (was bar-only); the block cursor's opacity cap (ADR 0047) is gone, back to full range; and:opens the command line directly in Vim Normal/Visual mode, additive to the existingCtrl+;. See ADR 0051. - [x] Split
EditorViewportacross translation units: the one file had reached 3608 lines — 51% of the whole GUI layer — holding eight distinct concerns. Now nine.cppfiles implementing the same class (config, render, input, edit, vim, find, commands, lsp, plus a small core), largest ~880 lines.editor_viewport.his untouched: same class, same API, nofriends, nothing made public. Organisational, not architectural — coupling is unchanged, but extracting a real subsystem later now starts from a file that already holds exactly that subsystem. See ADR 0052. - [x] The render hot path, and one motion language:
capturesForLine()scanned every highlight span for every visible line on every frame — measured 22% idle CPU on an 8402-line file with the caret blinking. Flattened to a per-byte capture array built once per reparse, taking idle back to 0.0%. Separately, six files had each independently picked their own animation duration; they now sharegui/src/motion.h, which names the tiers and states the rule that new chrome picks one rather than inventing a seventh. See ADR 0053. - [x] Multiple buffers, and the plugin host wired in: a viewport per
buffer behind a minimal custom-painted tab strip (no frames, no
separators — state carried by opacity),
Ctrl+Tab/Ctrl+W/Ctrl+N. The plugin host now actually loads<config dir>/plugins/and:nameruns a registered command; it had existed as a tested core library with nothing calling it. See ADR 0054. - [x] The dot means unsaved; new-file and panel fixes: the tab dot
moved from "this one is active" (which opacity already said) to
"this one has unsaved changes" (which nothing said).
Ctrl+N. Open/Save-As now resolve a typed path in both modes, confirm before overwriting, and show which directory you are in. About panel re-laid out as logo column + left-aligned text; the shortcut reference became data rather than a hand-written HTML blob, and caught up with everything that had shipped since it was written. See ADR 0055. - [x] Linewise Visual, tab motion, searchable shortcuts:
Shift+Vandoclose two real gaps against vim. The tab strip reserves close-mark width on every tab so switching reflows nothing, and interpolates every layout change through the shared motion language. The shortcut reference became collapsible sections with a search field. See ADR 0056. - [x] An always-present tab strip, and a rewritten welcome screen:
the strip no longer appears and disappears under you; closing
reverses opening;
Shift+wheel pans it past the window edge. The welcome screen leads with what the editor is, then the shortcuts that matter — including the one that stops new users dead (Vim mode is on by default, so typing does nothing untili). See ADR 0057. - [x] Tab identity, the session greeting, and panel carets: tabs were
matched across layouts by display name, and every
+buffer is calleduntitled— so new tabs animated from the wrong place and closing skipped its animation entirely. Keyed on the viewport pointer now. The welcome screen is armed only for a startup buffer opened with no file, never for a laterCtrl+N. And every panel text field draws the editor's own gliding, breathing caret instead of Qt's hard blink, closing the gap ADR 0028 left open. See ADR 0058. - [x] Derived dirty state, vim paste position, four more motions: from
external testing. "Is this file modified?" is now a comparison
against the undo stack's state id rather than a flag set by
whoever touched the buffer last — type a space, undo it, and the
file is correctly clean again, where before it stayed marked
modified until saved. Charwise
p/Pleave the cursor on the last pasted character, as real vim does (linewise was already correct and was left alone). New:{/}paragraph motions, composable with operators, andCtrl+U/Ctrl+Dhalf-screen motions that move cursor and viewport together — the latter taking overCtrl+Din Normal/Visual only, the single deliberate exception to ADR 0046's mode-independent Ctrl chain. See ADR 0059. - [x] Linewise operations at the end of the buffer:
ddon the last line did nothing at all — the range was built as "through the start of the next line", and at the end of the buffer there is no next line, so it came out zero-length. Same asymmetry emptied rather than removed a non-empty last line, madeyythere yank a fragment, and left a blank line behind on a linewise Visual delete. Linewise ranges that end at the buffer's end now take the preceding newline, in one shared helper. See ADR 0060. - [x] The unnamed register: Vim mode had been using the system
clipboard as its only register, which got both halves wrong —
d/x/cfilled nothing, soddthenp(the way you move a line) did nothing, and the obvious fix would have made everyxdestroy whatever you last copied from another application. There is now a real unnamed register, shared across buffers, filled byy d c xand read byp/P; the system clipboard is a separate destination onCtrl+C/Ctrl+V, which yank still mirrors into (delete does not). See ADR 0061. - [x] The status bar message line: the editor had no way to say
anything at all — an unconfigured language server, a mistyped
:command and a failed write were all silence, which reads as broken rather than as not-set-up. Everything now goes through onenotify()funnel onto a message area sharing the status bar with the mode label: info dim and brief, warnings full strength and longer, errors in the existing diagnostic colour, staying until you move the cursor. See ADR 0062. Next on the same thread: a:messageshistory, and a persistent status-bar segment for language-server state (which is what the packaged LSP complaint actually needs). - [x] Language-server state in the status bar: closes the external
report that LSP "doesn't work in the packages". It wasn't a
packaging bug —
lsp_commandships commented out, and nothing anywhere distinguished "not configured" from "binary missing" from "working". A segment beside the cursor position now names the running server, saysno lspwhen there is none, and turns the diagnostic colour when one was configured but failed or died (noticed within a second, on the existing config-reload timer). See ADR 0063. - [x] The GUI in CI, and a test suite that runs in Release: CI had
built
-DASE_BUILD_GUI=OFFon every job since Phase 1, which is why a Qt 6.6-only call could sit onmainwith both packages unbuildable until a human cut a release. There is now a job building the GUI onubuntu-22.04(Qt 6.2 — the AppImage's own base) and launching it headless. It immediately found something worse: every test usedassert(), whichNDEBUGcompiles out of Release builds, so 263 checks evaluated to nothing and the 122 that wrapped calls with side effects took the calls with them — three suites segfaulted, the rest passed while testing nothing. All of them are now an always-evaluatedCHECK(). See ADR 0064. - [x] Quick open (
Ctrl+P): multiple buffers landed in ADR 0054, but the only way to get one was the directory-at-a-time browser.Ctrl+Pwalks the project (the enclosing git checkout, or the current folder), fuzzy-matches as you type, and ranks by consecutive runs, word boundaries, basename hits and brevity —edvimfindseditor_viewport_vim.cppfirst. Rides in the existing panel as a third mode rather than duplicating it. See ADR 0065. - [x] Project-wide search (
Ctrl+Shift+F): find searched only the buffer in front of you; there was no way to answer "where else is this called?" without leaving the editor. The query rides in the find bar as a third mode, the results in the output panel — docked and non-modal, because reading a hit, looking at the code and coming back is the actual workflow and a floating panel blocks the document. Same file list as quick open, so the two can never disagree about what is in the project. Enter or a single click on a row opens that file at that line. See ADR 0066. - [x] Go to definition (
gd/F12): the request had existed incore/since ADR 0011 with nothing in the GUI calling it. Both bindings work, in-file and across files, and every way it can fail now says why instead of the key appearing dead. The LSP language gate grew to C++ on the way — it had been.c/.honly, so this could not work on the editor's own sources. Named gap: there is no way to jump back yet (vim's jumplist is onCtrl+O, which is Open here — the collision ADR 0046 flagged). See ADR 0067. - [x] Freed
Ctrl+OandCtrl+Ifor Vim's jumplist by moving the two panels that held them: the shortcut reference toF1(where it belonged anyway), About toAlt+I, Open toAlt+O. A panel opened a few times a session can afford an unusual binding; a navigation key used constantly cannot. See ADR 0068. - [x] Find-in-line (
f F t T ; ,) andCtrl+J/Ctrl+Kin every list: two requests that are the same request underneath — not leaving the home row. The find motions are line-confined like vim's, take counts that fail as a unit, and are inclusive under an operator (df,takes the comma). List navigation went into one shared helper rather than four copies of the same check. See ADR 0069. - [x] The jumplist (
Ctrl+O/Ctrl+I): go-to-definition made this impossible to ignore — you could follow a name into a file you did not choose with no way back. Same list-plus-index shape as the undo stack, owned by the window because jumps cross buffers, recording only movements you could not have made withh j k l. Entries carry a path as well as a buffer address, so a closed file is reopened rather than skipped. See ADR 0070. - [x] v0.3.0-alpha: everything since v0.2.0-alpha — Vim mode and its
polish, the block cursor, syntax accent colors, runtime font zoom,
the typing pop-in, the
EditorViewportsplit, the render hot-path fix, one motion language, multiple buffers, the plugin host wired in, and the tab strip — is a substantially different editor from the one that tag points at. - [x] v0.4.0-beta: the first release not called alpha. Everything
since v0.3.0-alpha — find-references and document symbols, a
keyboard with a law rather than a free-slot policy, regions and
the
Ctrl+Wvocabulary, built-in palettes, keybindings as data, session restore, git gutter marks, an atomic save, crash recovery that now covers the unnamed buffer too, and a documentation site whose reference pages are generated from the editor's own tables. Beta because the daily-driver gaps are closed and the data-loss story is, not because nothing is left: see the follow-ups below, and note that Linux is the supported platform.
Beyond v1 — what would make this editor unique¶
Deliberately grouped by why rather than by area. The ordering inside each tier is rough; the tiers themselves are not — Tier 1 is what stops the editor being embarrassing on a real codebase, Tier 2 is what makes it genuinely usable all day, Tier 3 is what makes it worth choosing over something else.
Tier 1 — make the existing promises true¶
- ~~Incremental Tree-sitter reparse.~~ Done, and the estimate here was
an order of magnitude low: the real cost was ~177ms per keystroke on
a 10,800-line file (107ms reparse, 64ms whole-file query). Now ~7ms —
the parse is incremental, the query runs over a window around what is
on screen, and the line scan uses
memchr. See ADR 0072, which also lists what is next in measured order (caret-rect repaints, incremental line starts). - ~~Multiple buffers / files per window.~~ Done — a viewport per
buffer behind a minimal dot-and-name bar,
Ctrl+Tab/Ctrl+Wto switch and close. See ADR 0054. - ~~Wire the plugin host into the GUI.~~ Done —
<config dir>/plugins/loads at startup and:nameruns any registered command (ADR 0054). The ABI is still the narrow one; widening it is the next plugin step, see EXTENSIBILITY.md. - ~~One language server per project, not per visited file.~~ Done —
a registry keyed by language and project root, so four C buffers share
one
clangdinstead of four. Measured at the time: 1601 MB to 919 MB (ADR 0096). - ~~Show unsaved state in the buffer bar.~~ Done — the dot now marks unsaved changes rather than the active buffer, and stays readable on a dimmed entry (ADR 0055).
- ~~Buffer bar overflow.~~ Done —
Shift+wheel pans the strip (ADR 0057). Tabs still don't elide long names, which is the remaining half. - ~~Large-file sanity pass.~~ Done, and the ceiling was real: the
Tree-sitter parse is linear in file size and lands on the UI thread
twice — 1.07s to open 4.5MB of C, and 20ms per keystroke after that.
The re-highlight now waits for a pause in typing, and past
syntax_max_kba file opens with no colours rather than freezing first, saying so in the status bar (ADR 0107).
Tier 2 — the table stakes of a daily driver¶
- ~~Keybindings as data~~ Done —
key.<chord> = <command>inconfig.ase, with built-ins, window actions and plugin commands in one registry, so binding a key to a plugin needs no new API. Vim's own keys are moved a different way, by remapping rather than by binding (ADR 0113, ADR 0134). - ~~Project-wide search~~ Done for the read-only half — search, and jump to a hit (ADR 0066). Replace across files is deliberately still open: editing every file in a project from one keystroke wants its own design pass (a preview, an undo story spanning buffers), not a ride-along on a read-only feature.
- The rest of LSP's useful half: ~~go-to-definition~~ (ADR 0067), ~~a jumplist~~ (ADR 0070), ~~find-references~~ and ~~document symbols~~ (ADR 0116) are in. ~~rename~~ is in too (ADR 0132): the WorkspaceEdit it returns becomes the same edit set a project replace produces, previewed before anything happens and applied into buffers.
- ~~Git gutter marks~~ Done — added, changed and deleted per line,
from
git diff -U0's hunk headers, hidden while an unsaved edit has moved the lines around (ADR 0112). - ~~Session restore~~ Done — files, carets and scroll positions, only when started with no file argument (ADR 0111).
- ~~More languages.~~ Done — C, C++, Python, JavaScript, CSS, HTML
and Lua, each a
.scmquery plus a build entry, with the LSP language table widened to match (ADR 0103).
Tier 3 — the bets that would make it distinctive¶
- Smart compile. Instead of a hand-written
build_command, infer the right action: readcompile_commands.jsonwhen clangd is configured (it already records the exact invocation per file), else walk up for aMakefile/CMakeLists.txt. Show the inferred command for confirmation rather than silently running it — this project's standing rule is that an unconfigured thing is reported, never guessed (ADR 0029), and a misdetected build target running silently would violate that badly. - A TUI frontend on the same core. The headless-core split (ADR 0002) is already paid for and tested, and nothing about the buffer, undo, syntax, LSP or plugin layers is Qt-specific. A terminal frontend sharing the exact core is a genuinely unusual thing to be able to offer, and the architecture has been quietly holding the door open for it since Phase 1.
- Motion as a designed identity. The animation language is now one header (ADR 0053) rather than six scattered guesses. Leaning into that deliberately — every state change in the app having a considered, consistent transition — is a real differentiator, because almost no editor treats it as a design system instead of as decoration.
- Scriptable/batch mode. The core runs headless;
ase --script fix.lua file.creusing the same plugin commands as the GUI would make plugins testable without a display, and the editor usable in a pipeline. - Remote/SSH editing and collaborative editing stay explicit non-goals below — listed here only to say they were considered and deliberately declined, not overlooked.
Still open, in rough order of value¶
- ~~LSP rename.~~ Done —
F2, previewed like a project replace and applied into buffers, so nothing is written until you save (ADR 0132). - ~~Theme files.~~ Done —
<config dir>/themes/<name>.ase, the same keys a config uses, inheriting the defaults for whatever it leaves out (ADR 0133). - ~~Vim sequences as data.~~ Done, as a remap rather than a table:
vim.normal.<key> = <keys>stands in for a key before the dispatcher sees it, so counts and pending operators compose with it (ADR 0134). The dispatcher itself is still a state machine, deliberately —d2wis not a table entry. - ~~An unnamed buffer gets no crash snapshot.~~ Done — a snapshot is filed under a key rather than a path, and untitled buffers are offered back at startup because nothing else will ever reopen them (ADR 0127).
- ~~Snapshots are never pruned.~~ Done — anything untouched for 30 days goes at startup (ADR 0127).
- ~~A concurrent parse.~~ Done — the parse runs on a worker thread past 256KB, so a large file is responsive and coloured, including while being typed into. The size cap stays, now bounding memory rather than lag (ADR 0135).
- ~~Replace across files.~~ Done —
Alt+R, previewed before it happens, applied into buffers rather than onto disk so nothing is written until you save (ADR 0131). LSP rename is still open, but now only needs aWorkspaceEditturned into the edit set that machinery already takes. - Per-capture syntax colours as data — EXTENSIBILITY.md, recommendation 5.
Explicit non-goals for v1¶
- No built-in terminal emulator (defer to external terminal or a later plugin).
- No remote/SSH editing.
- No collaborative/multiplayer editing.
- No plugin marketplace/registry infrastructure — plugins are install-by-file.
Open decisions blocking later phases¶
None currently open — the license decision (the last one tracked
here) was resolved by
ADR 0033
(Apache License 2.0), alongside the project's first alpha release,
v0.1.0-alpha.
Follow-ups noted but not yet scheduled¶
Audited 2026-09-17: six entries here described things that had been done
— keybinding customization, the plugin directory loading, the GUI
triggering a plugin command, the LSP being wired in, didChange, and
panel infrastructure. They are removed. What is below was checked
against the code on that date.
- No screen-reader text exposure (
QAccessibleInterface) for the custom-painted viewport — a real, open accessibility gap, not attempted because it is unverifiable without a live AT-SPI client in this environment (ADR 0012, decision 4). Needs dedicated follow-up with proper assistive-technology test tooling. - Three core suites segfault on Windows.
ase_config_testsinsidease_config_create_default(),ase_theme_testswhile reading a static table it has just been handed a valid pointer into, andase_plugin_host_testsimmediately after registering a command. None reproduces on Linux or macOS, with or without sanitizers. The job runs in CI without gating so the signal is not lost (ADR 0130). - The LSP client and
:compileare POSIX-only.ase_process_spawn()returns NULL on Windows (core/src/process.c), so both features are present and do nothing there. Async child-process I/O on Windows needs overlapped I/O or a reader thread (ADR 0011, decision 6) — real work, not a quick add-on. - Counts on
s,S,Candc-with-a-motion are ignored. The other six insert commands read theirs (ADR 0137); these four each have their own deletion semantics to settle first.