From 1d6efd04a1ce40926ff13778debc6889fe9e82a2 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Thu, 24 Sep 2026 19:47:35 +0000 Subject: [PATCH] studio: the first tab is where you look at the picture, so it says so "Develop" is darkroom language -- turning a latent image into a visible one -- and a camera owner has no reason to know it. It is "Preview" now, next to "Bad pixels", for the same reason that one stopped being "Diagnose": the tab bar should say what is behind each tab in words its reader already has. It understates the tab a little, and that is the trade. Preview is not only a preview: Black, White and Exposure are in there, along with the white balance and the choice of demosaic, and all of them change what the picture looks like. But "Develop" overstates the jargon by more than "Preview" understates the controls, and someone who drags a slider finds out what it does immediately. The copy that names the tab moves with it -- the plate reader's warning about the demosaic in force says "Preview is set to Bilinear" now. What does NOT move: the mode key `develop`, the worker call of the same name, and the progress text while a frame is being rendered. The first two are internal. The third is describing the operation rather than the tab, and the operation really is a develop: a demosaic and a render, from a mosaic that is not yet a picture. --- dist/editor.js | 17 +++++++++++------ src/editor.js | 17 +++++++++++------ tests/ui-check.html | 34 +++++++++++++++++----------------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/dist/editor.js b/dist/editor.js index e6b3258..7dfa654 100644 --- a/dist/editor.js +++ b/dist/editor.js @@ -396,7 +396,12 @@ export function mountEditor(root, { const sensorChip = el('span', 're-chip'); sensorChip.hidden = true; const modeItems = [ - { label: 'Develop', value: 'develop' }, + /* "Develop" is darkroom language for turning a latent image into a + * visible one, and a camera owner has no reason to know it. This is + * the tab where you look at the picture, so it says so. The mode key + * stays `develop`, as does the worker call behind it: both are + * internal, and the operation really is a demosaic-and-render. */ + { label: 'Preview', value: 'develop' }, /* Named for what it finds, not for what it does to find it. "Diagnose" * is a verb that never says what is being diagnosed, and an owner * looking for stuck pixels had no reason to guess it was in here. The @@ -3527,7 +3532,7 @@ export function mountEditor(root, { })); /* The reader is handed a develop from this engine, at whatever demosaic - * Develop is set to -- and two of the four cost it real accuracy. Scored + * Preview is set to -- and two of the four cost it real accuracy. Scored * against ground truth on 150 plate crops through THIS engine, at the * sampling a plate arrives at (exact match, clean / one frame of noise / * a twenty-frame stack): @@ -3547,9 +3552,9 @@ export function mountEditor(root, { warn.dataset.act = 'demosaic-warning'; warn.style.cssText = 'margin-top:8px;color:#c9a227'; warn.textContent = state.demosaic === 0 - ? 'Develop is set to no demosaic, and the reader is handed what ' + - 'Develop produces — it will read almost nothing. Gradient or RCD.' - : 'Develop is set to Bilinear. Measured on this engine, that costs ' + + ? 'Preview is set to no demosaic, and the reader is handed what ' + + 'Preview produces — it will read almost nothing. Gradient or RCD.' + : 'Preview is set to Bilinear. Measured on this engine, that costs ' + 'the reader about ten points against Gradient or RCD.'; panel.append(warn); } @@ -5060,7 +5065,7 @@ export function mountEditor(root, { (r.info.iso ? ` · ISO ${r.info.iso}` : '') + (r.info.exposure ? ` · ${(r.info.exposure * 1000).toFixed(1)} ms` : ''); // setMode rather than buildInspector: opening a frame while - // Calibrate is selected used to draw Develop's controls under + // Calibrate is selected used to draw Preview's controls under // Calibrate's heading and leave the old frame's corners floating // over the new picture, inert. setMode(mode); diff --git a/src/editor.js b/src/editor.js index e6b3258..7dfa654 100644 --- a/src/editor.js +++ b/src/editor.js @@ -396,7 +396,12 @@ export function mountEditor(root, { const sensorChip = el('span', 're-chip'); sensorChip.hidden = true; const modeItems = [ - { label: 'Develop', value: 'develop' }, + /* "Develop" is darkroom language for turning a latent image into a + * visible one, and a camera owner has no reason to know it. This is + * the tab where you look at the picture, so it says so. The mode key + * stays `develop`, as does the worker call behind it: both are + * internal, and the operation really is a demosaic-and-render. */ + { label: 'Preview', value: 'develop' }, /* Named for what it finds, not for what it does to find it. "Diagnose" * is a verb that never says what is being diagnosed, and an owner * looking for stuck pixels had no reason to guess it was in here. The @@ -3527,7 +3532,7 @@ export function mountEditor(root, { })); /* The reader is handed a develop from this engine, at whatever demosaic - * Develop is set to -- and two of the four cost it real accuracy. Scored + * Preview is set to -- and two of the four cost it real accuracy. Scored * against ground truth on 150 plate crops through THIS engine, at the * sampling a plate arrives at (exact match, clean / one frame of noise / * a twenty-frame stack): @@ -3547,9 +3552,9 @@ export function mountEditor(root, { warn.dataset.act = 'demosaic-warning'; warn.style.cssText = 'margin-top:8px;color:#c9a227'; warn.textContent = state.demosaic === 0 - ? 'Develop is set to no demosaic, and the reader is handed what ' + - 'Develop produces — it will read almost nothing. Gradient or RCD.' - : 'Develop is set to Bilinear. Measured on this engine, that costs ' + + ? 'Preview is set to no demosaic, and the reader is handed what ' + + 'Preview produces — it will read almost nothing. Gradient or RCD.' + : 'Preview is set to Bilinear. Measured on this engine, that costs ' + 'the reader about ten points against Gradient or RCD.'; panel.append(warn); } @@ -5060,7 +5065,7 @@ export function mountEditor(root, { (r.info.iso ? ` · ISO ${r.info.iso}` : '') + (r.info.exposure ? ` · ${(r.info.exposure * 1000).toFixed(1)} ms` : ''); // setMode rather than buildInspector: opening a frame while - // Calibrate is selected used to draw Develop's controls under + // Calibrate is selected used to draw Preview's controls under // Calibrate's heading and leave the old frame's corners floating // over the new picture, inert. setMode(mode); diff --git a/tests/ui-check.html b/tests/ui-check.html index 70fd1dc..ae2029d 100644 --- a/tests/ui-check.html +++ b/tests/ui-check.html @@ -721,7 +721,7 @@ send.click(); await untilKeep(); const revertsBefore = calls.filter((c) => c[0] === 'revert').length; - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); for (let i = 0; i < 20 && calls.filter((c) => c[0] === 'revert').length === revertsBefore; i++) await new Promise((r) => setTimeout(r, 100)); t('leaving Calibrate mid-countdown puts the change back', @@ -1014,7 +1014,7 @@ // be where a person put them. { const placed = [...host.querySelectorAll('.re-chart-grip')].map((g) => g.style.left); - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await new Promise((r) => setTimeout(r, 200)); [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Calibrate').click(); const again = host.querySelector('[data-act="find-chart"]'); @@ -1149,7 +1149,7 @@ // is not one of the scan's inputs: saturation is the sensor's and is in the // file. The finding this covers was that lowering White made real hot // pixels disappear from the report. - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await new Promise((r) => setTimeout(r, 200)); { // The label is uppercased by CSS, not in the DOM, so match what is @@ -1169,7 +1169,7 @@ // A scan describes one frame under one set of inputs. Change the Bayer // pattern and the old defect coordinates describe nothing. - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await new Promise((r) => setTimeout(r, 200)); [...host.querySelectorAll('.re-panel .re-seg button')].find((b) => b.textContent === 'BGGR').click(); await new Promise((r) => setTimeout(r, 400)); @@ -1181,7 +1181,7 @@ host.querySelector('.re-insp').textContent.replace(/\s+/g, ' ').slice(0, 70)); // Switching away must take the marks with it. - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await new Promise((r) => setTimeout(r, 200)); t('leaving Bad pixels hides its marks', host.querySelector('.re-marks').hidden); ed.destroy(); @@ -1668,7 +1668,7 @@ ed.setDemosaic ? ed.setDemosaic(1) : null; // No public setter, so drive it the way a user does: the Develop control. - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await until('the demosaic control', () => [...host.querySelectorAll('.re-seg button')] .some((b) => b.textContent === 'Bilinear')); [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Bilinear').click(); @@ -1677,7 +1677,7 @@ t('bilinear is called out, with what it costs', /Bilinear/.test(warned()) && /ten points/.test(warned()), warned().slice(0, 120)); - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await until('the demosaic control', () => [...host.querySelectorAll('.re-seg button')] .some((b) => b.textContent === 'None')); [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'None').click(); @@ -1687,7 +1687,7 @@ /almost nothing/.test(warned()), warned().slice(0, 120)); // Back to RCD so the rest of this block measures what it meant to. - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await until('the demosaic control', () => [...host.querySelectorAll('.re-seg button')] .some((b) => b.textContent === 'RCD')); [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'RCD').click(); @@ -1703,7 +1703,7 @@ * inside that window read the PREVIOUS picture, under a warning describing * the new one. The fingerprints have to differ. */ const pickDemosaic = async (label) => { - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === 'Preview').click(); await until('the demosaic control', () => [...host.querySelectorAll('.re-seg button')] .some((b) => b.textContent === label)); [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent === label).click(); @@ -1876,7 +1876,7 @@ // Leaving the tab must stop the poll. const before = calls; - [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); await new Promise((r) => setTimeout(r, 120)); t('leaving Focus stops the poll', calls === before, `${before} -> ${calls}`); @@ -1998,7 +1998,7 @@ // Leave with a read outstanding, answer it, come back. [...host.querySelectorAll('.re-seg button')] - .find((b) => b.textContent.trim() === 'Develop').click(); + .find((b) => b.textContent.trim() === 'Preview').click(); await pause(30); const orphan = waiting.shift(); if (orphan) orphan(GZ(900)); // HIGH, for a tab that is gone @@ -2155,7 +2155,7 @@ // Leaving the tab and closing the editor are teardowns, not gestures. down(btn(g.host, 'near')); await pause(40); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); await pause(60); let n = g.moves.length; await pause(80); @@ -2581,7 +2581,7 @@ q(g.host, 'af-apply').click(); await pause(120); t('a filter is waiting', !!q(g.host, 'hold')); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); await pause(100); t('leaving the tab puts it back', g.seen.reverts === 1, String(g.seen.reverts)); g.ed.destroy(); @@ -2655,7 +2655,7 @@ g = await mk({ applySlow: true }); q(g.host, 'af-apply').click(); await pause(80); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); await pause(80); g.seen.releaseApply(); await pause(150); @@ -2719,7 +2719,7 @@ g = await mk({ revertSlow: 1200 }); q(g.host, 'af-apply').click(); await pause(150); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); await pause(80); [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Focus').click(); await pause(150); @@ -2786,7 +2786,7 @@ g = await mk({ lens: true }); q(g.host, 'af-measure').click(); await pause(1600); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); for (let i = 0; i < 200 && g.seen.pos !== 0; i++) await pause(100); t('leaving the tab brings it back too', g.seen.pos === 0, String(g.seen.pos)); g.ed.destroy(); @@ -2842,7 +2842,7 @@ g = await mk({ lens: true }); q(g.host, 'af-measure').click(); await pause(1600); - [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Develop').click(); + [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Preview').click(); [...g.host.querySelectorAll('.re-seg button')].find((b) => b.textContent.trim() === 'Focus').click(); await pause(50); q(g.host, 'af-measure').click();