From 83296e10ccb664427a773bc59122efe4e0c23adb Mon Sep 17 00:00:00 2001
From: Jared Johnson
Date: Sat, 27 Jun 2026 18:16:12 -0400
Subject: [PATCH 1/6] update render-musicxml skill
---
.agents/skills/render-musicxml/SKILL.md | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/.agents/skills/render-musicxml/SKILL.md b/.agents/skills/render-musicxml/SKILL.md
index 834a3c2f1..89d2a6776 100644
--- a/.agents/skills/render-musicxml/SKILL.md
+++ b/.agents/skills/render-musicxml/SKILL.md
@@ -1,6 +1,6 @@
---
name: render-musicxml
-description: Render a MusicXML file in vexml by running `vex render -i `, inspect the generated screenshot when needed, and delete ephemeral render output when finished.
+description: Render a MusicXML file in vexml by running `vex render -i `, optionally pass render configuration with `--config `, inspect the generated screenshot when needed, and delete ephemeral render output when finished.
---
# Render MusicXML
@@ -17,10 +17,18 @@ vex render -i
Replace `` with the project-relative or absolute path to the MusicXML file the user wants rendered.
+To override render settings, pass a partial render config as JSON with `-c`/`--config`:
+
+```sh
+vex render -i --config '{"noteSpacing":40,"showPartLabels":true}'
+```
+
+The JSON object corresponds to `Partial` from `src/config.ts`; use that file as the source of truth for available options and defaults.
+
## Workflow
1. Identify the MusicXML input path from the user's request or from the repository.
-2. Run `vex render -i ` from `vexml`.
+2. Run `vex render -i ` from `vexml`. If the user asks for non-default render settings, include `-c`/`--config ` using option names from `src/config.ts`.
3. Read the command output to find the generated screenshot path.
4. If visual inspection is needed, open or inspect the generated screenshot with available tools.
5. When finished, delete the generated screenshot if it was only meant to be ephemeral.
From 381730b66e47de43e1db1566aa8913418a782eb9 Mon Sep 17 00:00:00 2001
From: Jared Johnson
Date: Sat, 27 Jun 2026 19:24:28 -0400
Subject: [PATCH 2/6] add several technical engravings for directions,
notations, and harmonies
---
site/src/App.tsx | 44 +-
src/config.ts | 5 +
src/constants.ts | 26 +-
src/draw.ts | 132 +++++-
src/layout.ts | 3 +-
src/notes.ts | 132 +++++-
tests/integration/__data__/fermata.musicxml | 44 ++
tests/integration/__data__/harmony.musicxml | 183 ++++++++
.../__data__/measures_light_light.musicxml | 39 ++
.../__data__/notehead_parentheses.musicxml | 121 ++++++
.../integration/__data__/notehead_x.musicxml | 404 ++++++++++++++++++
tests/integration/__data__/words.musicxml | 100 +++++
.../__screenshots__/beam_variations.png | Bin 21283 -> 21052 bytes
tests/integration/__screenshots__/fermata.png | Bin 0 -> 5952 bytes
tests/integration/__screenshots__/harmony.png | Bin 0 -> 6946 bytes
.../measure_numbering_every.png | Bin 11119 -> 11019 bytes
.../measure_numbering_every_2.png | Bin 10160 -> 10060 bytes
.../measure_numbering_every_3.png | Bin 9802 -> 9702 bytes
.../measure_numbering_none.png | Bin 8993 -> 8893 bytes
.../__screenshots__/measures_light_light.png | Bin 0 -> 4877 bytes
.../__screenshots__/note_density.png | Bin 15968 -> 15768 bytes
.../__screenshots__/notehead_parentheses.png | Bin 0 -> 9883 bytes
.../__screenshots__/notehead_x.png | Bin 0 -> 11956 bytes
.../__screenshots__/system_break.png | Bin 9262 -> 9162 bytes
.../__screenshots__/tab_hammer_pull_wrap.png | Bin 13001 -> 12848 bytes
tests/integration/__screenshots__/words.png | Bin 0 -> 8277 bytes
tests/integration/render.test.ts | 63 +++
27 files changed, 1282 insertions(+), 14 deletions(-)
create mode 100644 tests/integration/__data__/fermata.musicxml
create mode 100644 tests/integration/__data__/harmony.musicxml
create mode 100644 tests/integration/__data__/measures_light_light.musicxml
create mode 100644 tests/integration/__data__/notehead_parentheses.musicxml
create mode 100644 tests/integration/__data__/notehead_x.musicxml
create mode 100644 tests/integration/__data__/words.musicxml
create mode 100644 tests/integration/__screenshots__/fermata.png
create mode 100644 tests/integration/__screenshots__/harmony.png
create mode 100644 tests/integration/__screenshots__/measures_light_light.png
create mode 100644 tests/integration/__screenshots__/notehead_parentheses.png
create mode 100644 tests/integration/__screenshots__/notehead_x.png
create mode 100644 tests/integration/__screenshots__/words.png
diff --git a/site/src/App.tsx b/site/src/App.tsx
index c0bc3d5f3..6bfe06881 100644
--- a/site/src/App.tsx
+++ b/site/src/App.tsx
@@ -97,7 +97,8 @@ export default function App() {
const [config, setConfig] = useState>({});
const noteSpacing = config.noteSpacing ?? 36;
const softmaxFactor = config.softmaxFactor ?? 10;
- const reset = (key: 'noteSpacing' | 'softmaxFactor') =>
+ const systemSpacing = config.systemSpacing ?? 30;
+ const reset = (key: 'noteSpacing' | 'softmaxFactor' | 'systemSpacing') =>
setConfig(({ [key]: _, ...rest }) => rest);
// `config` stays live so the sliders/reset respond instantly; `renderConfig` lags
@@ -475,6 +476,47 @@ export default function App() {
width difference between long and short notes.
+
+
+
+
+ setConfig((c) => ({
+ ...c,
+ systemSpacing: e.target.valueAsNumber,
+ }))
+ }
+ />
+
+ Vertical gap between stacked systems. Lower packs systems
+ closer together down the page.
+
+
diff --git a/src/config.ts b/src/config.ts
index e7b06135d..8556fb9de 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,3 +1,4 @@
+import { SYSTEM_GAP } from './constants';
import type { FontConfig } from './fonts';
import type { Layout, MeasureNumbering } from './layout';
@@ -23,6 +24,9 @@ export type Config = {
* Given the width noteSpacing allots, higher exaggerates the long-vs-short note ratio. A
* shape constant, independent of overall density. */
softmaxFactor: number;
+ /** Vertical gap in px between stacked systems (default: SYSTEM_GAP). Smaller packs
+ * systems closer together down the page. */
+ systemSpacing: number;
/** Print each part's instrument name to the left of the first system (default: false). */
showPartLabels: boolean;
/** When to print measure numbers above the staff (default: 'system'). 'none' prints
@@ -52,6 +56,7 @@ export const DEFAULT_CONFIG: Config = {
layout: { type: 'standard', width: 1000 },
noteSpacing: 36,
softmaxFactor: 10,
+ systemSpacing: SYSTEM_GAP,
showPartLabels: false,
measureNumbering: 'system',
showTabHammerPullText: false,
diff --git a/src/constants.ts b/src/constants.ts
index 98c6803f1..8d78995a5 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -18,8 +18,8 @@ export const PAGE_MARGIN_TOP_WITH_TEMPO = 70;
export const PAGE_MARGIN_BOTTOM = 40;
/** Vertical gap between stacked systems, plus room for the next system's notes that
- * rise above its top staff. */
-export const SYSTEM_GAP = 90;
+ * rise above its top staff. The default for `Config.systemSpacing`. */
+export const SYSTEM_GAP = 30;
/** Vertical gap between staves within one part (a brace-joined group reads as one
* instrument because this exceeds INTER_PART_SPACING). */
@@ -95,6 +95,28 @@ export const TAB_TIE_CP2 = 12;
* the system's left line with a small gap. */
export const BRACKET_X_SHIFT = 3;
+/** Chord-symbol (from `