Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
848 commits
Select commit Hold shift + click to select a range
9341941
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
ae71729
Merge remote-tracking branch 'origin/main' into wa-16-lane-shapes
vanceingalls Aug 13, 2026
cb920a4
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
8fbcc15
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
4279188
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3a43040
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
57e696e
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3eae9eb
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
5cafa11
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
146f9ac
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
9e2ffa3
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
424f893
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
5427e69
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
169bf06
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
6e00388
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
9d47023
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
fccdc95
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
eb18c23
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
d5fca20
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
d50549d
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
d3b6c50
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
eec99f0
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
48fbbf4
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
c389c35
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
a3860b8
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
65f55bf
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
793cb7f
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3e6597f
Merge remote-tracking branch 'origin/main' into wa-18a-retime-stretch
vanceingalls Aug 13, 2026
69bff86
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
d59e6cf
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
1656bdb
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
13f40e5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
c461a7d
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
3165b4a
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
f9c26b2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
487ab39
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
5fdfc75
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
9fabbd8
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
8832671
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
2ba9dd5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
644b435
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
4956f5f
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
39a38d5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
3c6f3c2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
2572b9a
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
d433d23
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
356b2fd
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
a0e4f9c
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
27c57e7
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
96fa8a9
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
73f61d2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
ea18fb5
Merge remote-tracking branch 'origin/main' into wa-18b-reschedule
vanceingalls Aug 13, 2026
8e748ac
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
1befa91
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
bcc3747
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
0122e1b
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
de04d9f
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
9f3963d
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
6c8c2b1
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
2313aec
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
059cbef
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
58371d6
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
aee687c
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
400dde8
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
a7131f5
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
13f9431
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
de240ae
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
9868222
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
7a9e5b4
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
d2248c5
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
2c79039
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
4c1b816
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
601ed1b
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
1bf3726
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
4dacf28
fix(studio): close the typecheck and fallow gaps wa-18b-reschedule op…
vanceingalls Aug 13, 2026
32d4918
merge: pull in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
252b1c2
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
aed1e7b
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d993284
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
00d0b2a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
67bcf88
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
577002a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
51f144c
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
e0a510e
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
459fe0a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
9adb9b5
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
4cceef6
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
ef4d96e
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d4f4817
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
8f39c30
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d71b33d
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
5404df6
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
2b831a1
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
936bbc3
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
620465b
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
89c4c4f
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
e556b23
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
9e828a0
fix(core): cap the via conic's weight so an edge-clamped via point ca…
vanceingalls Aug 13, 2026
36b63cc
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
adbc910
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
024470c
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
4ceeb6f
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f21d4d1
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
9fc52ee
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
70c3191
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f790a7f
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
ddaae3e
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
a43f818
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
518e75d
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
2b8dd2b
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
2262fef
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
ba846fe
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
0dbeac6
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
25e4366
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
42ce44c
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
b9d6539
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
4e2f644
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
ce43f41
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
af9247a
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
0f8d7fa
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f6fc7e9
fix(studio-server): fingerprint the proactive waveform cache key too
vanceingalls Aug 13, 2026
248fd70
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
32598dd
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
3722f9c
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
bed05ba
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
9d0b75b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
a5478a0
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
0658830
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
332ef78
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
48fe084
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
d0a7172
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
3b000a4
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
713cb7d
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
f6fac3e
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
862eafd
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
7db346a
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
689f33b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
2bd6c57
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
0aceb3c
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
4938f99
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
c3df5c4
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
99f0c4b
style(docs): run oxfmt on the /hyperframes-audio skill docs
vanceingalls Aug 13, 2026
cc631a2
chore: merge in the skill-docs format fix
vanceingalls Aug 13, 2026
009fb88
fix(core): stop \b from missing underscore-separated names, guard cli…
vanceingalls Aug 13, 2026
64eccec
chore: merge in the carve regex+overlap fix
vanceingalls Aug 13, 2026
06aff52
fix(studio): widen PropertyPanel's resetModules render timeout again
vanceingalls Aug 13, 2026
93c7abf
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
a81b652
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
cfd5551
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
4efaa33
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
011a9f0
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
aa4c875
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
2ea9b38
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d02190a
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
ad6bfdd
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
fb5ea90
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
e2c3c62
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
2a53d7d
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
45a5d87
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
115a110
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d10aa17
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
9257408
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
7ca5158
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
62cebb1
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
6029ec2
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d851d2b
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
74b9fc2
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
bc17a95
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
13285b6
fix(studio): stop the single-candidate auto-apply carve firing twice
vanceingalls Aug 13, 2026
e965111
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
7565f5d
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
8faa870
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
33d6f14
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
d08724a
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
eab3096
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
e36c1ba
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
8332da4
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
2187440
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
f980c45
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
57bfee7
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
233fd7f
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
0e4b53f
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
90552e4
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
59cd0b9
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
da1a6a1
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
f85c90d
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
6444c00
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
9a94c82
chore: pull in main after #3210's squash merge
vanceingalls Aug 13, 2026
49c619b
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
df4daca
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
16bf7ca
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
df5ea4d
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
49be0c9
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
15f6fa0
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
d785c14
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
f7b5a21
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
de18eaa
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
906cfad
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
3aad9b4
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
0086d18
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
eabbfa6
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
b405371
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
fd53567
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
793dcd2
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
80b7092
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
0b1e3ce
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
2f24258
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
fb466c0
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
93921f0
feat(core): carve against every voice over a bed, always (#3212)
vanceingalls Aug 13, 2026
03c1634
fix(studio): port the carve UI off the removed source/dynamic fields
vanceingalls Aug 13, 2026
9a7e19e
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
590043c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
b1401f7
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
2284c8c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
007eee8
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
49e3623
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
93dd2bb
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
816671f
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
c8fcb64
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
1feca8e
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
9652805
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
3b8dbd1
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
14ab3f7
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
a06bcf2
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
0bc8a8c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
abcb0e3
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
28a2637
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
983118f
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
72de452
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
8f230c3
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
de8f963
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
9fae6e8
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
4e46fae
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
b02131f
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
d41e754
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
f0d6bfb
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
8f35103
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
65a484c
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
1e7e5b6
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
f9af8ae
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
4151db0
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
fe32bb2
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
831bc12
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
78cdd9e
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
cc5211b
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
5da8fba
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
1c34d0a
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
ee8fbd6
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
77c0073
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
fc6dd37
chore: pull in main after #3186's squash merge
vanceingalls Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { DEFAULT_CARVE, type HfCarveSettings } from "@hyperframes/core/audio-carve";
import { fxAutomationTarget } from "@hyperframes/core/audio-automation";
import { FxParamRow } from "./propertyPanelFxControls.js";
import { FX_FAMILY_TYPE, fxFamilyTint } from "./propertyPanelFxFamily.js";
// Shared with the timeline's lane labels: a band is named by its frequency in
// both places, and two formatters would drift.
import { formatHz } from "../../player/components/automationLaneData";
Expand Down Expand Up @@ -201,16 +202,21 @@ export function FxCarveModule({
: "pick a voice";
return (
<div
className={`hf-fx-node hf-fx-carve-module hf-fx-carve rounded-[4px] border border-panel-border-input${
className={`hf-fx-node hf-fx-carve-module hf-fx-carve rounded-[4px] border border-l-2 border-panel-border-input${
on ? "" : " opacity-50"
}`}
data-fx-node="carve"
data-fx-family="smart"
// Smart, like the Tone EQ and the leveller: it measures the audio and
// writes its own settings, and what it shows is a readout of what it
// decided rather than controls the author set.
style={{ borderLeftColor: fxFamilyTint({ type: "carve", fromCarve: true }) }}
data-carve-enabled={on ? "" : undefined}
>
<div className="hf-fx-node-head flex min-h-7 items-center gap-1 px-1.5">
<button
type="button"
className="hf-fx-node-name min-w-0 flex-1 truncate text-left text-[11px] font-semibold text-panel-text-1 hover:text-panel-text-0"
className={`hf-fx-node-name min-w-0 flex-1 truncate text-left text-[11px] text-panel-text-1 hover:text-panel-text-0 ${FX_FAMILY_TYPE.smart}`}
aria-expanded={open}
onClick={onToggleOpen}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
HF_AUDIO_EQ_RANGE_DB,
type HfAudioEqBand,
} from "@hyperframes/core/audio-fx-eq";
import { FX_FAMILY_TYPE, fxFamilyTint } from "./propertyPanelFxFamily.js";

export interface FxEqModuleProps {
eqId: string;
Expand Down Expand Up @@ -137,13 +138,17 @@ export function FxEqModule({

return (
<div
className="hf-fx-node hf-fx-eq-module rounded-[4px] border border-panel-border-input"
className="hf-fx-node hf-fx-eq-module rounded-[4px] border border-l-2 border-panel-border-input"
data-fx-node="eq"
data-fx-family="smart"
// Smart, with the carve and the leveller: three bands the author sets by
// ear on a control surface, not three filters they configured.
style={{ borderLeftColor: fxFamilyTint({ type: "eq", fromEq: "eq" }) }}
>
<div className="hf-fx-node-head flex items-center gap-1.5 px-1.5 py-1">
<button
type="button"
className="hf-fx-node-name flex-1 text-left text-[11px] text-panel-text-0"
className={`hf-fx-node-name flex-1 text-left text-[11px] text-panel-text-0 ${FX_FAMILY_TYPE.smart}`}
aria-expanded={open}
onClick={onToggleOpen}
>
Expand Down
83 changes: 83 additions & 0 deletions packages/studio/src/components/editor/propertyPanelFxFamily.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Telling module families apart before the word registers.
*
* A rack of eight modules is eight lines of text, and reading it top to bottom
* means reading eight names. Lettering each family differently means an author
* knows what KIND of module they are looking at with the label out of focus —
* the shape of the line carries it, and the word only confirms.
*
* Two faces of type, as budgeted in `plans/audio-fx-ux/README.md`: the sans
* carries four families apart by weight, case and tracking, and the serif is
* spent on the single family that behaves differently from all of them.
* Non-linear is the only generative one — it invents signal rather than
* measuring or shaping what is there — so it should not look like the others.
*
* Alongside it, a tint step per module WITHIN its family, derived from position
* in the registry rather than assigned by hand: two filters read as two
* different modules without reading as two different families, and adding an
* effect upstream never re-colours its siblings.
*/

import { HF_AUDIO_FX, type HfAudioFxNode } from "@hyperframes/core/audio-fx";

export type FxFamily = "filter" | "dynamics" | "nonlinear" | "time" | "smart";

/**
* How each family letters.
*
* `smart` is not a registry group. It is the measuring modules — the carve, the
* Tone EQ, the leveller — which analyse the audio and write their own settings.
* Monospace because that is what a readout looks like, and what they show IS a
* readout: numbers something else decided.
*/
export const FX_FAMILY_TYPE: Record<FxFamily, string> = {
filter: "font-light uppercase tracking-[0.14em]",
dynamics: "font-bold uppercase tracking-tight",
nonlinear: "font-serif italic tracking-normal",
time: "font-extralight uppercase tracking-[0.24em]",
smart: "font-mono font-medium tracking-normal",
};

/** Hue per family, and a lightness ramp across the modules inside it. */
const FAMILY_HUE: Record<FxFamily, number> = {
filter: 205,
dynamics: 25,
nonlinear: 310,
time: 165,
smart: 95,
};

/**
* Which family a chain node belongs to.
*
* The composite tags win over the registry group, because what a node IS to the
* author is the module that owns it: a carve's peaking filters are not filters
* the author added, they are one carve.
*/
export function fxFamilyOf(
node: Pick<HfAudioFxNode, "type" | "fromCarve" | "fromEq" | "fromLeveller">,
): FxFamily {
if (node.fromCarve || node.fromEq || node.fromLeveller) return "smart";
const group = HF_AUDIO_FX.find((def) => def.id === node.type)?.group;
return group === "dynamics" || group === "nonlinear" || group === "time" ? group : "filter";
}

/**
* The module's own tint: its family's hue, stepped by where it sits among its
* siblings in the registry.
*
* Kept narrow on purpose — 62% to 76% lightness at low saturation. This has to
* separate two modules at a glance without reading as a status colour, and the
* panel already spends saturation on "automated" and "bypassed".
*/
export function fxFamilyTint(
node: Pick<HfAudioFxNode, "type" | "fromCarve" | "fromEq" | "fromLeveller">,
): string {
const family = fxFamilyOf(node);
const siblings = HF_AUDIO_FX.filter((def) => fxFamilyOf({ type: def.id }) === family);
const index = siblings.findIndex((def) => def.id === node.type);
const step = siblings.length > 1 ? Math.max(0, index) / (siblings.length - 1) : 0;
// Comma syntax rather than the space-separated modern form: it is what every
// CSS parser in the chain agrees on, including the one the tests run in.
return `hsl(${FAMILY_HUE[family]}, 32%, ${(62 + step * 14).toFixed(1)}%)`;
}
28 changes: 26 additions & 2 deletions packages/studio/src/components/editor/propertyPanelFxNodeRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { EFFECT_COPY, SUMMARY } from "@hyperframes/core/audio-fx-copy";
import { fxAutomationTarget } from "@hyperframes/core/audio-automation";
import { FxParams } from "./propertyPanelFxControls.js";
import { FxBandRuler } from "./propertyPanelFxBandRuler.js";
import { FX_FAMILY_TYPE, fxFamilyOf, fxFamilyTint } from "./propertyPanelFxFamily.js";

/**
* The one control that carries the module, if it has one.
Expand Down Expand Up @@ -72,6 +73,8 @@ function plainDef(def: HfAudioFxDef): HfAudioFxDef {
interface FxNodeRowProps {
node: HfAudioFxNode;
index: number;
/** Where it sits in the signal path, as the rack counts it. Absent means unnumbered. */
position?: number;
automatedTargets?: ReadonlySet<string>;
liveAutomationValues?: ReadonlyMap<string, number>;
onAutomateParam?(nodeId: string, paramKey: string): void;
Expand Down Expand Up @@ -115,6 +118,8 @@ function FxMoveButton({
/** Name, bypass, reorder and remove for one effect. */
function FxNodeHeader({
label,
family,
position,
open,
bypassed,
first,
Expand All @@ -126,6 +131,9 @@ function FxNodeHeader({
onRemove,
}: {
label: string;
/** How this family letters, so the KIND reads before the word does. */
family: string;
position?: number;
open: boolean;
bypassed: boolean;
first: boolean;
Expand All @@ -138,9 +146,17 @@ function FxNodeHeader({
}) {
return (
<div className="hf-fx-node-head flex min-h-7 items-center gap-1 px-1.5">
{/* Two digits, because a rack reads as a path when its steps are numbered
and as a list when they are not — and the difference decides whether an
author thinks the order matters. It does; it is audible. */}
{position !== undefined ? (
<span className="hf-fx-node-index shrink-0 font-mono text-[9px] tabular-nums text-panel-text-4">
{String(position).padStart(2, "0")}
</span>
) : null}
<button
type="button"
className="hf-fx-node-name flex-1 truncate text-left text-[11px] font-semibold text-panel-text-1 hover:text-panel-text-0"
className={`hf-fx-node-name flex-1 truncate text-left text-[11px] text-panel-text-1 hover:text-panel-text-0 ${family}`}
aria-expanded={open}
onClick={onToggleOpen}
>
Expand Down Expand Up @@ -259,6 +275,7 @@ function FxNodeParams({
export function FxNodeRow({
node,
index,
position,
automatedTargets,
liveAutomationValues,
onAutomateParam,
Expand Down Expand Up @@ -293,10 +310,17 @@ export function FxNodeRow({
const summary = SUMMARY[node.type]?.(params);
return (
<div
className={`hf-fx-node rounded-[4px] border border-panel-border-input${bypassed ? " opacity-50" : ""}`}
className={`hf-fx-node rounded-[4px] border border-l-2 border-panel-border-input${bypassed ? " opacity-50" : ""}`}
data-fx-node={node.type}
data-fx-family={fxFamilyOf(node)}
// The tint is on the edge rather than the text: the name already carries
// the family in its lettering, and colouring it too would fight the
// panel's own tokens for automated and bypassed.
style={{ borderLeftColor: fxFamilyTint(node) }}
>
<FxNodeHeader
family={FX_FAMILY_TYPE[fxFamilyOf(node)]}
position={position}
// The node's own job name when a preset gave it one, because that is the
// most specific truth available: a chain that cuts mud and then lifts
// clarity must not show the same name twice. Then the plain name, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,89 @@ describe("FxSection chain", () => {
);
});

it("draws the rack as a signal path, with both ends named", () => {
// Order is audible here, and a list does not look ordered. Numbering the
// steps and naming the two ends is what makes "move up" read as the most
// consequential control in the panel rather than a cosmetic one.
const { host } = mount({ chain: chainOf("highpass", "limiter") });
const terms = Array.from(host.querySelectorAll(".hf-fx-term")).map((e) => e.textContent);
expect(terms).toHaveLength(2);
expect(terms[0]).toContain("In");
expect(terms[1]).toContain("Out");
// Counted over what the rack SHOWS: the carve module leads it, so the first
// hand-built effect is 02.
const numbers = Array.from(host.querySelectorAll(".hf-fx-node-index")).map((e) =>
e.textContent?.trim(),
);
expect(numbers).toEqual(["02", "03"]);
});

it("draws a preset's nodes as the one thing that was added", () => {
// Applying a preset drops five rows into the rack with nothing saying they
// arrived together — the same failure the carve module exists to fix, one
// level down.
const { host, onChainChange } = mount({ chain: { version: 1, nodes: [] } });
click(byText(host, "button", "Presets"));
click(presetButton(host, "telephone"));
// Applying does not re-render this mount — the chain comes back as a prop —
// so the rack is read from what was written.
const applied = onChainChange.mock.calls[0]?.[0] as HfAudioFxChain | undefined;
const written = applied?.nodes ?? [];
const { host: after } = mount({ chain: { version: 1, nodes: written } });

const run = after.querySelector("[data-fx-preset='telephone']");
expect(run).toBeTruthy();
expect(run?.querySelector(".hf-fx-preset-run-label")?.textContent).toBe("Telephone");
expect(run?.querySelectorAll(".hf-fx-node")).toHaveLength(written.length);
});

it("brackets only nodes a preset still sits next to", () => {
// Pulled apart by a reorder, they are no longer a unit — and a bracket
// around the gap would claim an adjacency the signal path does not have.
const { host } = mount({
chain: {
version: 1,
nodes: [
{ type: "highpass", fromPreset: "telephone", params: defaultAudioFxParams("highpass") },
{ type: "reverb", params: defaultAudioFxParams("reverb") },
{ type: "lowpass", fromPreset: "telephone", params: defaultAudioFxParams("lowpass") },
],
} as unknown as HfAudioFxChain,
});
const runs = Array.from(host.querySelectorAll("[data-fx-preset='telephone']"));
expect(runs).toHaveLength(2);
for (const run of runs) expect(run.querySelectorAll(".hf-fx-node")).toHaveLength(1);
});

it("letters each family differently, so the kind reads before the word does", () => {
// A rack of eight modules is eight lines of text. Reading it should not mean
// reading eight names — the shape of the line carries what KIND of module
// this is, and the word only confirms it.
const { host } = mount({ chain: chainOf("lowpass", "compressor", "saturate", "delay") });
const families = Array.from(host.querySelectorAll("[data-fx-family]")).map((e) =>
e.getAttribute("data-fx-family"),
);
// The carve module leads the rack and is smart; then the four registry ones.
expect(families).toEqual(["smart", "filter", "dynamics", "nonlinear", "time"]);

const names = Array.from(host.querySelectorAll(".hf-fx-node-name")).map((e) => e.className);
// Four families told apart by the sans, and the serif spent on the one that
// generates signal rather than measuring or shaping what is there.
expect(names.filter((c) => c.includes("font-serif"))).toHaveLength(1);
expect(names[3]).toContain("font-serif");
expect(new Set(names.map((c) => c.replace(/^.*?(?=font-)/, "")))).toHaveProperty("size", 5);
});

it("tints two modules of the same family apart without changing family", () => {
const { host } = mount({ chain: chainOf("lowpass", "highpass") });
const cards = Array.from(host.querySelectorAll<HTMLElement>("[data-fx-family='filter']"));
expect(cards).toHaveLength(2);
// Same hue, different step: two filters, visibly two modules.
const tints = cards.map((c) => c.style.borderLeftColor);
expect(tints[0]).not.toBe(tints[1]);
for (const tint of tints) expect(tint).toContain("205");
});

it("says where a filter is working, in the words the rack shares", () => {
// Frequencies mean nothing to somebody who has not been taught them, and the
// rack speaks entirely in them. The ruler is where they get taught.
Expand Down
Loading
Loading