diff --git a/frontend/src/capture/measureInputs.test.ts b/frontend/src/capture/measureInputs.test.ts
new file mode 100644
index 0000000..1644f05
--- /dev/null
+++ b/frontend/src/capture/measureInputs.test.ts
@@ -0,0 +1,42 @@
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { test } from 'node:test';
+import { inToEighths } from './capture.types';
+
+const panelSrc = readFileSync(
+ join(dirname(fileURLToPath(import.meta.url)), 'MeasurePanel.svelte'),
+ 'utf8',
+);
+
+test('MeasurePanel exposes typed number inputs for Length L and Thickness', () => {
+ const inputs = panelSrc.match(/]*>/g) ?? [];
+ assert.ok(inputs.length >= 2, 'expected at least two fields');
+ assert.ok(
+ inputs.some((el) => /type="number"/.test(el) && /aria-label="Length L"/.test(el)),
+ 'Length L must be a type=number input',
+ );
+ assert.ok(
+ inputs.some((el) => /type="number"/.test(el) && /aria-label="Thickness"/.test(el)),
+ 'Thickness must be a type=number input',
+ );
+ assert.equal(
+ panelSrc.includes('class="stepper"'),
+ false,
+ 'stepper-only UI is the UAT fail; primary control must be typed input',
+ );
+ assert.equal(
+ /\{fmtEighths\(state\.lengthEighths\)\}<\/span>/.test(panelSrc),
+ false,
+ 'Length must not be a read-only fmtEighths span',
+ );
+});
+
+test('typed inches snap to nearest 1/8 via inToEighths', () => {
+ assert.equal(inToEighths(12), 96);
+ assert.equal(inToEighths(12.1), 97);
+ assert.equal(inToEighths(0.06), 0);
+ assert.equal(inToEighths(0.07), 1);
+ assert.equal(inToEighths(1.999), 16);
+});