Restore calibration parity: revert unit-stripping from normaliseCal, fix CSV quoting at export
Finding 1: revert the comma/quote strip added in 3cb998c from
normaliseCal() in calibration.js. The strip broke byte-identical parity
with Go CalConfig.Normalise and C++ StreamHub::SetCalibrationEntry, both
of which only trim whitespace and cap at 16 UTF-8 bytes. Delete the
companion test that asserted the now-removed behaviour (suite returns to
18 tests).
Finding 2: fix the actual CSV-safety problem at the point of use in
exportAllCSV() in app.js. Header cells (time column and signal columns)
are now RFC 4180-quoted: wrapped in double quotes with any embedded
double quote doubled. This safely handles units or signal names that
contain commas or quotes without touching normaliseCal.
Finding 3: update two stale comments in app.js that called the trigger
threshold or rawFromNorm result 'raw' — Task 9 moved trig.threshold into
calibrated units throughout, so the comments now say 'calibrated'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3cb998c5da
commit
5917ab1bf2
@@ -1343,7 +1343,7 @@ function drawTriggerMarker(u, p) {
|
|||||||
ctx.fillText('T', px + 3, bbox.top + 2);
|
ctx.fillText('T', px + 3, bbox.top + 2);
|
||||||
// Horizontal threshold line — only on plots that contain the trigger signal
|
// Horizontal threshold line — only on plots that contain the trigger signal
|
||||||
if (p && trig.signal && p.traces.includes(trig.signal)) {
|
if (p && trig.signal && p.traces.includes(trig.signal)) {
|
||||||
// Normalize the raw threshold to this plot's vscale for the trigger signal.
|
// Normalise the calibrated threshold to this plot's vscale for the trigger signal.
|
||||||
const tvs = p ? sigVScale[p.id + ':' + trig.signal] : null;
|
const tvs = p ? sigVScale[p.id + ':' + trig.signal] : null;
|
||||||
let threshNorm = trig.threshold;
|
let threshNorm = trig.threshold;
|
||||||
if (tvs) {
|
if (tvs) {
|
||||||
@@ -2405,7 +2405,7 @@ function updatePlotCursorReadouts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Hover readout ──────────────────────────────────────────────────────── */
|
/* ─── Hover readout ──────────────────────────────────────────────────────── */
|
||||||
// Un-normalize a plotted value of trace `key` in plot `p` back to raw units.
|
// Un-normalize a plotted value of trace `key` in plot `p` back to calibrated units.
|
||||||
function rawFromNorm(p, key, vNorm) {
|
function rawFromNorm(p, key, vNorm) {
|
||||||
const vs = sigVScale[p.id + ':' + key];
|
const vs = sigVScale[p.id + ':' + key];
|
||||||
if (!vs) return vNorm;
|
if (!vs) return vNorm;
|
||||||
@@ -3052,9 +3052,11 @@ async function exportAllCSV() {
|
|||||||
const displayKeys = keys.map(k => {
|
const displayKeys = keys.map(k => {
|
||||||
const name = k.includes(':') ? k.split(':').slice(1).join(':') : k;
|
const name = k.includes(':') ? k.split(':').slice(1).join(':') : k;
|
||||||
const u = unitForKey(k);
|
const u = unitForKey(k);
|
||||||
return u ? name + ' [' + u + ']' : name;
|
const h = u ? name + ' [' + u + ']' : name;
|
||||||
|
return '"' + h.replace(/"/g, '""') + '"';
|
||||||
});
|
});
|
||||||
const hdr = [(inTrigMode ? 'time_rel_s' : 'time_s'), ...displayKeys].join(',');
|
const timeCol = '"' + (inTrigMode ? 'time_rel_s' : 'time_s') + '"';
|
||||||
|
const hdr = [timeCol, ...displayKeys].join(',');
|
||||||
const rows = sortedT.map(t =>
|
const rows = sortedT.map(t =>
|
||||||
[t.toFixed(9), ...lookups.map((lk, i) =>
|
[t.toFixed(9), ...lookups.map((lk, i) =>
|
||||||
lk.has(t) ? Calib.applyCal(lk.get(t), cals[i]) : '')].join(',')
|
lk.has(t) ? Calib.applyCal(lk.get(t), cals[i]) : '')].join(',')
|
||||||
|
|||||||
@@ -40,9 +40,7 @@
|
|||||||
var offset = obj.offset === undefined ? 0 : obj.offset;
|
var offset = obj.offset === undefined ? 0 : obj.offset;
|
||||||
if (!isFiniteNum(scale) || scale === 0) return null;
|
if (!isFiniteNum(scale) || scale === 0) return null;
|
||||||
if (!isFiniteNum(offset)) return null;
|
if (!isFiniteNum(offset)) return null;
|
||||||
// Commas and quotes would corrupt the CSV export header; drop them here so
|
var unit = String(obj.unit == null ? '' : obj.unit).trim();
|
||||||
// every consumer sees an already-safe unit.
|
|
||||||
var unit = String(obj.unit == null ? '' : obj.unit).replace(/[",]/g, '').trim();
|
|
||||||
// Cap at MAX_UNIT_LEN UTF-8 bytes, matching both hubs' Normalise() exactly.
|
// Cap at MAX_UNIT_LEN UTF-8 bytes, matching both hubs' Normalise() exactly.
|
||||||
// TextEncoder/TextDecoder are available natively in all modern browsers and
|
// TextEncoder/TextDecoder are available natively in all modern browsers and
|
||||||
// Node v11+; no build step or bundler is needed.
|
// Node v11+; no build step or bundler is needed.
|
||||||
|
|||||||
@@ -150,7 +150,3 @@ test('CalTable.list is sorted by source then signal', () => {
|
|||||||
assert.deepStrictEqual(t.list().map(e => e.source + '/' + e.signal),
|
assert.deepStrictEqual(t.list().map(e => e.source + '/' + e.signal),
|
||||||
['a/b', 'a/z', 'z/a']);
|
['a/b', 'a/z', 'z/a']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normaliseCal strips characters that would corrupt a CSV header', () => {
|
|
||||||
assert.strictEqual(C.normaliseCal({source: 'w', signal: 's', unit: 'k,V"'}).unit, 'kV');
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user