Compare commits

...

10 Commits

Author SHA1 Message Date
Mikkeli Matlock 74dfdc50cc Merge branch 'fix/gitattributes-stray-cr' 2026-09-14 01:41:30 +09:00
Mikkeli Matlock 94d41d5c8a fix: remove a stray CR byte from .gitattributes
The previous commit wrote a literal carriage-return byte into a comment, where
the text was meant to show the two characters backslash-r. Git's content sniffing
treats a lone CR as a sign of binary data, so .gitattributes itself was classed
-text (git ls-files --eol reported i/-text): the one file in the repository
that the new LF policy could not normalise.

The rules themselves were never affected - the byte sat inside a comment, and
deploy.bat and the sources were already checking out with the intended endings.

The comment now says "a trailing CR byte" in words. .gitattributes reports
i/lf w/lf, and `git add --renormalize .` stages nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
2026-09-14 01:41:30 +09:00
Mikkeli Matlock b27ef57676 Merge branch 'chore/lf-line-endings' 2026-09-14 01:39:09 +09:00
Mikkeli Matlock c940f31ec7 chore: pin LF line endings in .gitattributes
The branding checker failed all 29 of its sites on Windows while passing on the
Linux CI runner. Nothing had drifted. Git for Windows installs with
core.autocrlf=true, which checked tools/branding-sites.tsv out as CRLF, and the
checker reads that table with `read` - so every row's last field, the pattern,
gained a trailing \r and matched nothing. Only *.sh was pinned to LF, which is
why the scripts were fine and their data was not.

Set the policy in the repository rather than trusting each machine:

    * text=auto eol=lf       LF in the repo and every working tree, every OS
    *.sh text eol=lf         kept explicit: a CRLF shebang is a hard failure
    *.bat/*.cmd eol=crlf     the one exception; cmd.exe misparses LF batch files

An attribute beats core.autocrlf, so a fresh clone gets LF however its machine
is configured. Verified in a throwaway repo: under autocrlf=true a text file
checks out LF and a .bat checks out CRLF.

No blob changes. Every committed blob was already LF (git ls-files --eol shows
i/lf throughout) and `git add --renormalize .` stages nothing but this file.
The working tree was re-checked-out to match: 25 files CRLF -> LF, deploy.bat
LF -> CRLF. Checker 40 passed / 0 failed, build clean, 20 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
2026-09-14 01:39:09 +09:00
mikkeli a8f1244b00 Merge branch 'ci/validate-workflow-changes'
CI / Build (push) Successful in 30s
Include the workflow file in both path filters so CI changes are self-validating.
2026-09-04 19:33:12 +09:00
mikkeli d107127017 ci: let a workflow change validate itself
Adds .github/workflows/** to both path filters. A broken step, a moved
script path or a wrong runner label now fails on the commit that caused
it, instead of surfacing later against an unrelated source change and
looking like that change's fault.

This session produced exactly that bug: after the branding tools moved
under tools/, the workflow still ran `sh check-branding.sh` at the old
path, and it was caught by reading the file rather than by CI.

The limit is worth stating: this validates the job body, not the trigger
block. Break `on:` and the symptom is silence, which is indistinguishable
from success.

Workflow edits are rare enough that the extra builds cost nothing, and CI
config is the definition of how the project is built and verified — it
belongs on the source side of "source code edits only", not with the docs.

kolmekivi additionally gains the pull_request path filter the other two
already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-09-04 19:33:12 +09:00
mikkeli d51de80de8 Merge branch 'ci/tighten-triggers'
Apply the same source-path filter to pull_request that push already had.
2026-09-04 19:26:35 +09:00
mikkeli 7a92485cf1 ci: path-filter the pull_request trigger too
The push trigger was already restricted to master and to source paths,
but pull_request carried only a branch filter — so a PR touching nothing
but the README would have run a full build. A branch filter constrains
where a workflow fires, not what fires it.

Both filters now list the same six paths. A docs-only change runs nothing,
on either trigger.

Note that .github/workflows/** is deliberately absent from both lists, so
editing this file does not run it. That is the intended trade: fewer
pointless builds, at the cost of a broken workflow surfacing on the next
source change rather than immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-09-04 19:26:35 +09:00
mikkeli 6c0332d79e Merge branch 'fix/f32-literal-fallback'
CI / Build (push) Successful in 24s
Suffix the f32 literals rustc is phasing out, and deny future_incompatible so the
next deferred hard error fails the build immediately.
2026-09-03 08:25:36 +09:00
mikkeli 309a152d58 fix: suffix f32 literals and deny future-incompatible warnings
Sixteen `Stroke::new(1.0, ...)` calls in the editor relied on a numeric
inference fallback that rustc is removing:

  warning: falling back to `f32` as the trait bound `f32: From<f64>` is
  not satisfied
  = warning: this was previously accepted by the compiler but is being
    phased out; it will become a hard error in a future release!

These were never clippy lints - a plain `cargo build` emitted all sixteen
- and they are not style. They are a dated compile break: the code stops
building on whichever release drops the fallback. Applied via `cargo fix`,
since rustc marks the suggestion machine-applicable; the diff is sixteen
literals gaining an `_f32` suffix and nothing else. The values were
already f32, just inferred.

Denying the future_incompatible group makes the next one a build failure
the day it appears rather than something that accumulates unnoticed until
a toolchain bump breaks the build on a day nobody chose. Verified by
reverting one literal: the build then fails with exit 101 instead of
warning.

Pinning rust-toolchain.toml was considered and rejected for this purpose.
It buys reproducibility, but it works against early warning - a pinned
toolchain finds out about the next deferred hard error later, not sooner.

Build warnings drop from 17 to 0. The 15 remaining clippy lints are
genuine style suggestions and are untouched, as is clippy's absence from
CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
2026-09-03 08:25:36 +09:00
7 changed files with 59 additions and 19 deletions
+19 -3
View File
@@ -1,4 +1,20 @@
# Shell scripts must keep LF endings. core.autocrlf=true on Windows would
# otherwise check them out CRLF, which a strict POSIX sh (dash, CI containers)
# rejects at the shebang.
# Line endings: LF in the repository and in every working tree, on every OS.
#
# This file is the policy, not each machine's git config. Git for Windows installs
# with core.autocrlf=true, which checks text out as CRLF; an attribute here beats
# that setting, so a fresh clone gets LF regardless of how its machine is set up.
#
# It matters beyond tidiness. tools/branding-sites.tsv is read line by line with
# `read`, and under CRLF every row's last field gained a trailing CR byte - so the
# branding checker reported all 29 sites as missing on Windows while passing on the
# Linux CI runner. text=auto leaves genuinely binary files alone.
* text=auto eol=lf
# Shell scripts are pinned explicitly as well: a strict POSIX sh (dash, CI
# containers) rejects a CRLF shebang outright rather than misbehaving quietly.
*.sh text eol=lf
# The exception. cmd.exe misparses LF-only batch files around labels and long
# lines, so these alone are checked out CRLF. The repository copy is still LF.
*.bat text eol=crlf
*.cmd text eol=crlf
+16
View File
@@ -10,8 +10,24 @@ on:
- 'Cargo.lock'
- 'bundler.toml'
- 'tools/**'
# This workflow validates itself: a broken step, a moved script path or a
# wrong runner label then fails loudly on the commit that caused it, rather
# than surfacing later against an unrelated source change. Note the limit -
# it checks the job body, not the trigger block above. Break `on:` and the
# symptom is silence, which looks like success.
- '.github/workflows/**'
# Mirrors the push filter. Without it a PR touching only the README would run a
# full build - the branch filter alone constrains where, not what.
pull_request:
branches: [ master ]
paths:
- 'src/**'
- 'xtask/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'bundler.toml'
- 'tools/**'
- '.github/workflows/**'
workflow_dispatch:
# Target the self-hosted Rust/audio runner registered to the `musiikki` org.
+8
View File
@@ -17,6 +17,14 @@ members = ["xtask"]
# `cdylib` is the plugin binary; `lib` (rlib) lets `cargo test` link the unit tests.
crate-type = ["cdylib", "lib"]
# Future-incompatibility warnings are deferred hard errors: rustc is telling you
# this code stops compiling on some later release. Denying the group turns that
# into a build failure the day it appears, rather than 16 of them accumulating
# unnoticed until a toolchain bump breaks the build on a day you did not choose.
# Style and clippy lints are deliberately untouched - they stay warnings.
[lints.rust]
future_incompatible = { level = "deny", priority = -1 }
[dependencies]
# Pinned to the exact commit of the local ./nih-plug reference clone so the build
# is reproducible and matches the examples we read from. Bump the `rev` together
+1 -1
View File
@@ -131,7 +131,7 @@ fn handle(
}
let col = if resp.dragged() || resp.hovered() { palette::TEXT_HOVER } else { color };
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0, col));
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0_f32, col));
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), palette::TEXT_BRIGHT);
}
+5 -5
View File
@@ -52,14 +52,14 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
let g6 = palette::GRID_FAINT;
let x6 = x_for(-6.0);
let y6 = y_for(-6.0);
p.line_segment([pos2(x6, top), pos2(x6, bottom)], Stroke::new(1.0, g6));
p.line_segment([pos2(left, y6), pos2(right, y6)], Stroke::new(1.0, g6));
p.line_segment([pos2(x6, top), pos2(x6, bottom)], Stroke::new(1.0_f32, g6));
p.line_segment([pos2(left, y6), pos2(right, y6)], Stroke::new(1.0_f32, g6));
// Unity reference (out = in), bottom-left to top-right.
p.line_segment([pos2(left, bottom), pos2(right, top)], Stroke::new(1.0, palette::GRID));
p.line_segment([pos2(left, bottom), pos2(right, top)], Stroke::new(1.0_f32, palette::GRID));
// Threshold marker on the input axis — shifted left by pre-gain (the comp sees in + pre).
let tx = x_for((threshold - pre).clamp(FLOOR_DB, 0.0));
p.line_segment([pos2(tx, top), pos2(tx, bottom)], Stroke::new(1.0, palette::THRESHOLD_LINE));
p.line_segment([pos2(tx, top), pos2(tx, bottom)], Stroke::new(1.0_f32, palette::THRESHOLD_LINE));
// Full wet transfer: drive into the comp, then makeup. (Mix not folded in.)
let n = 96;
@@ -93,7 +93,7 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, selected: usize, m
));
}
p.add(egui::Shape::line(pts, Stroke::new(1.6, palette::CURVE)));
p.add(egui::Shape::line(pts, Stroke::new(1.6_f32, palette::CURVE)));
// Operating-point dot, on the curve at the current input.
let driven = ext_in + pre;
+8 -8
View File
@@ -243,23 +243,23 @@ pub fn apply_visuals(ctx: &nih_plug_egui::egui::Context) {
let w = &mut v.widgets;
w.noninteractive.bg_fill = SURFACE_RAISED;
w.noninteractive.weak_bg_fill = SURFACE_RAISED;
w.noninteractive.bg_stroke = Stroke::new(1.0, GRID_FAINT);
w.noninteractive.fg_stroke = Stroke::new(1.0, TEXT);
w.noninteractive.bg_stroke = Stroke::new(1.0_f32, GRID_FAINT);
w.noninteractive.fg_stroke = Stroke::new(1.0_f32, TEXT);
w.inactive.bg_fill = TRACK;
w.inactive.weak_bg_fill = TRACK;
w.inactive.bg_stroke = Stroke::new(1.0, GRID);
w.inactive.fg_stroke = Stroke::new(1.0, TEXT_BRIGHT);
w.inactive.bg_stroke = Stroke::new(1.0_f32, GRID);
w.inactive.fg_stroke = Stroke::new(1.0_f32, TEXT_BRIGHT);
w.hovered.bg_fill = accent.edge;
w.hovered.weak_bg_fill = accent.strip;
w.hovered.bg_stroke = Stroke::new(1.0, accent.edge);
w.hovered.fg_stroke = Stroke::new(1.0, TEXT_HOVER);
w.hovered.bg_stroke = Stroke::new(1.0_f32, accent.edge);
w.hovered.fg_stroke = Stroke::new(1.0_f32, TEXT_HOVER);
w.active.bg_fill = accent.handle;
w.active.weak_bg_fill = accent.edge;
w.active.bg_stroke = Stroke::new(1.0, accent.handle);
w.active.fg_stroke = Stroke::new(1.0, TEXT_HOVER);
w.active.bg_stroke = Stroke::new(1.0_f32, accent.handle);
w.active.fg_stroke = Stroke::new(1.0_f32, TEXT_HOVER);
ctx.set_visuals(v);
}
+2 -2
View File
@@ -170,7 +170,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
// Gridlines (dB).
for &g in &[0.0f32, -12.0, -24.0, -48.0] {
let y = y_for_db(g);
p.line_segment([pos2(left, y), pos2(right, y)], Stroke::new(1.0, palette::GRID_FAINT));
p.line_segment([pos2(left, y), pos2(right, y)], Stroke::new(1.0_f32, palette::GRID_FAINT));
p.text(
pos2(left + 2.0, y),
Align2::LEFT_BOTTOM,
@@ -204,7 +204,7 @@ pub(super) fn draw(ui: &mut egui::Ui, meters: &Meters, state: &mut PlotState) {
));
}
}
p.add(egui::Shape::line(pts, Stroke::new(1.5, color)));
p.add(egui::Shape::line(pts, Stroke::new(1.5_f32, color)));
};
draw_series(&state.history.in_db[c], &|db| db, COLOR_IN, true);
draw_series(&state.history.out_db[c], &|db| db, COLOR_OUT, true);