Unisce lavoro/daitarn-tmf-rev118: correzioni della revisione Fable (Refs #118 #123 #124 #125)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrh9rfM5WwqKtg8BSE
This commit is contained in:
2026-10-28 22:07:44 +01:00
10 changed files with 138 additions and 20 deletions
@@ -32,6 +32,7 @@ function visionExecute() {
preview: false, // result on screen is an analysis only, not saved
saved: false, // «Registra» done: the saved result replaces the preview
lastBlob: null, // last frame shown, the one «Registra» saves
recordedBlob: null, // #123: frame saved by the last successful «Registra»: not recordable twice
paused: false, // operator paused the in-line loop
registering: false, // «Registra» in progress: the loop stands still
leaving: false, // page is being left: no more requests
@@ -43,8 +44,10 @@ function visionExecute() {
// #120: «Rimisura»/nuovo ciclo (task-result-reset): il pezzo va registrato di nuovo, anche senza camera
window.addEventListener('task-result-reset', () => {
this.saved = false;
this.preview = !!this.result;
this.kick();
// #123: il frame gia' registrato non e' piu' un'anteprima ne' si registra due volte
this.preview = !!this.result && this.lastBlob !== this.recordedBlob;
// #123: camera impulsiva = frame nuovo subito (come all'apertura); in linea riparte il ciclo
if (this.hasCamera && !this.inLinea) this.grab(); else this.kick();
});
if (!this.hasCamera) return;
document.addEventListener('visibilitychange', () => { if (!document.hidden) this.kick(); });
@@ -58,7 +61,7 @@ function visionExecute() {
get hasCamera() { return !!(window.__visionCfg || {}).cameraAvailable; },
get inLinea() { return this.hasCamera && (window.__visionCfg || {}).modo === 'in_linea'; },
// «Registra» needs a frame on screen that is not saved yet
get canRecord() { return !!this.lastBlob && !this.saved && !this.registering; },
get canRecord() { return !!this.lastBlob && this.lastBlob !== this.recordedBlob && !this.saved && !this.registering; }, // #123: non sul frame gia' registrato
shouldRun() {
return this.inLinea && !this.paused && !this.saved && !this.registering
@@ -145,6 +148,7 @@ function visionExecute() {
if (!this.error && this.result && this.result.vision_result_id) {
this.preview = false;
this.saved = true;
this.recordedBlob = blob; // #123: questo frame e' registrato, Registra resta spento finche' non ne arriva uno nuovo
this._emitRecorded(); // #120: la pagina conta le quote registrate
}
} finally {
@@ -249,15 +249,15 @@ var VisionOverlay = (function () {
var pt = null;
for (var i = 0; i < order.length && !pt; i++) pt = anchor(order[i]);
if (!pt) return;
// #118: valore null/undefined/vuoto = quota non misurata (Number(null) e' 0): nessuna etichetta
if (r.value === null || r.value === undefined || r.value === '' || typeof r.value === 'boolean') return;
var v = Number(r.value);
if (!isFinite(v)) return;
// #118: valore null/undefined/vuoto/non finito = quota non misurata (Number(null) e' 0): etichetta NaN, status della riga (fail)
var v = (r.value === null || r.value === undefined || r.value === '' || typeof r.value === 'boolean')
? NaN : Number(r.value);
var txt = isFinite(v) ? v.toFixed(3) : 'NaN';
out.push({
key: r.key,
x: pt.x,
y: pt.y,
text: r.name + ' ' + v.toFixed(3) + ' ' + (r.unit || ''),
text: r.name + ' ' + txt + ' ' + (r.unit || ''),
status: r.status,
});
});
@@ -224,17 +224,18 @@ const rows = fixture.measurements.map((m) => {
check('labels: testo name + valore a 3 decimali + unit', f[0].text === 'T 1.500 mm', f[0].text);
}
// --- #118: labels, valore null/undefined/non finito -> nessuna etichetta ---
// --- #118: labels, valore null/undefined/non finito -> etichetta NaN con status della riga ---
{
const ov = [{ type: 'segment', role: 'found', tool_id: 't', row1: 10, col1: 10, row2: 10, col2: 20 }];
const mk = (value) => ({ key: 1, name: 'T', value, unit: 'mm', status: 'fail', vision_output: 't.value' });
check('labels: value null -> nessuna etichetta (Number(null) e\' 0)', VisionOverlay.labels([mk(null)], ov).length === 0);
check('labels: value undefined -> nessuna', VisionOverlay.labels([mk(undefined)], ov).length === 0);
check('labels: value NaN/Infinity -> nessuna',
VisionOverlay.labels([mk(NaN), mk(Infinity), mk(-Infinity)], ov).length === 0);
check('labels: value 0 resta un valore valido', VisionOverlay.labels([mk(0)], ov).length === 1);
const nan = (v) => { const l = VisionOverlay.labels([mk(v)], ov); return l.length === 1 && l[0].text === 'T NaN mm' && l[0].status === 'fail'; };
check('labels: value null -> etichetta NaN, status fail (Number(null) e 0)', nan(null));
check('labels: value undefined / stringa vuota -> etichetta NaN', nan(undefined) && nan(''));
check('labels: value NaN/Infinity -> etichetta NaN', nan(NaN) && nan(Infinity) && nan(-Infinity));
check('labels: value 0 resta un valore valido', VisionOverlay.labels([mk(0)], ov)[0].text === 'T 0.000 mm');
const misto = VisionOverlay.labels([{ ...mk(null), key: 1 }, { ...mk(2.5), key: 2 }], ov);
check('labels: null saltata, le altre righe restano', misto.length === 1 && misto[0].key === 2);
check('labels: null e valida entrambe presenti', misto.length === 2 && misto[0].text === 'T NaN mm' && misto[1].key === 2);
check('labels: riga NaN senza ancoraggio -> nessuna etichetta', VisionOverlay.labels([mk(null)], []).length === 0);
}
// --- #121: svg(overlay, opts) ---
@@ -175,6 +175,26 @@ c5.acquire = async function () { this.result = { vision_result_id: 7, measuremen
h();
check('#120 task-result-reset: saved = false', c5.saved === false);
// #123: dopo Registra + task-result-reset Registra e' spento sul frame registrato, anteprima spenta, impulsiva = grab
win.__visionCfg = { cameraAvailable: true, modo: 'impulsiva', subtasks: [] };
const c9 = ctx.visionExecute();
let grabs = 0;
c9.grab = async function () { grabs++; };
c9.lastBlob = { name: 'f1' };
c9.acquire = async function () { this.result = { vision_result_id: 11, measurements: okMs }; };
check('#123 prima di Registra: canRecord', c9.canRecord === true);
await c9.record();
check('#123 dopo Registra: canRecord falso (saved)', c9.canRecord === false && c9.recordedBlob === c9.lastBlob);
c9.$nextTick = () => {}; // #123: nel test nessun grab di apertura
c9.init(); // registra l'ascolto di task-result-reset
grabs = 0;
(listeners['task-result-reset'] || []).slice(-1)[0]();
check('#123 reset sul frame registrato: canRecord falso', c9.canRecord === false && c9.saved === false);
check('#123 reset sul frame registrato: niente badge anteprima', c9.preview === false);
check('#123 reset in impulsiva: un solo grab', grabs === 1, String(grabs));
c9.lastBlob = { name: 'f2' };
check('#123 frame nuovo: canRecord vero', c9.canRecord === true);
// #119 #122: saveName = titolo del task (nome proposto da <tmf-viewer save-name>), 'immagine' se manca
win.__visionCfg = { taskName: 'Roto3', subtasks: [] };
check('#122 saveName = nome del task', ctx.visionExecute().saveName === 'Roto3');