T1.5 - F5 (interim): wizard fields stop looking disabled

INTERIM. T3.4 removes the duplicate token underneath this; the job here is only
the appearance, and no token consolidation is started.

The wizard filled its inputs with var(--bg) - which in this sheet is the PAGE
BACKGROUND, #f4f4f4 - on a #e0e0e0 border. An empty required field was
indistinguishable from a locked one, which is why people were not typing in
them. The cause is the one the review named: this sheet redeclares its own
tokens, so it never saw --cds-field: #ffffff, even though theme-light.css has
been supplying that to this page all along.

Fields now consume --cds-field, and take the same --border-strong the creator's
inputs already use, so a field looks like a field on both pages. No new value is
introduced - both tokens already existed.

That inverts a signal if left there, so it needed the other half: there was no
disabled rule at all on this page, meaning locked fields would have turned white
too. Disabled and readonly fields now take --cds-field-02, the theme's own
secondary field surface, matching .locked-field in the creator. Enabled #ffffff
against disabled #f4f4f4, verified by computed style rather than by eye.

The border is deliberately the same on both states. I first wrote
`border-color: var(--border)` on the disabled rule and could not demonstrate it
taking effect - the rule matches, is more specific than the base rule, and its
background applies, but the computed border stayed --border-strong. Rather than
ship a declaration whose effect I cannot show, it is gone: a consistent border
is what "consistent with inputs elsewhere" asks for, and the fill is what
carries the state.

Screenshot diff is limited to the wizard, but establishing that took a control
run. admin and users appeared to change too, until capturing twice with NO code
change showed they differ from themselves - the console pages render live
timestamps and are not byte-stable. login, launcher, sop, creator and field are.
Recorded in the baseline README so the next task with a "no layout change"
done-when does not chase it.

The F5 probe now also fails if enabled and disabled fields become identical,
which is the way this fix could silently go wrong.

f_items: F1-F5 FIXED, F6 untouched as wave 1 requires. browser_check 71/71.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 18:48:47 -05:00
parent 4d3258113a
commit 5f3141e2a3
3 changed files with 61 additions and 9 deletions

View File

@@ -31,6 +31,17 @@ python tests/baseline_shots.py --out /tmp/after --label after
declares `width=device-width`, so this is the layout a field tablet actually gets. The script
asserts the width it asked for is the width the page saw.
### Two pages are not byte-stable — do not diff them blindly
Found at `T1.5`. Capturing twice with **no code change at all** produces different bytes for
`admin` and `users` at both widths. `login`, `launcher`, `sop`, `creator` and `field` are
stable. The console pages render live timestamps, so a byte comparison of them reports a
change on every run.
A task whose done-when is "no layout change at 1440px" therefore cannot use a byte diff on
those two. Run the capture twice before drawing any conclusion, or compare a page that is
stable. Four of the "changes" in this document's own history were this, not code.
## F1F6: all six reproduce
Measured in a browser by `tests/f_items.py`, not read from source. Re-run any time:

View File

@@ -332,19 +332,45 @@ body.embed-full { overflow: hidden; }
color: var(--text);
}
/* INTERIM (F5/A3). T3.4 removes the duplicate token underneath this; the fix here
is only to stop editable fields looking disabled.
These were filled with var(--bg) — this sheet's PAGE BACKGROUND, #f4f4f4 — on a
#e0e0e0 border, so an empty required field was indistinguishable from a locked
one and users did not type in them. The wizard redeclares its own tokens and so
never saw --cds-field: #ffffff, which theme-light.css has been supplying to this
page all along. Consume that instead of the local override, and take the same
--border-strong the creator's inputs use, so a field looks like a field in both
places. No new value is introduced here. */
.field input,
.field select,
.field textarea {
padding: 0.75rem;
border: 1px solid var(--border);
border: 1px solid var(--border-strong);
border-radius: 0;
font-size: 14px;
font-family: inherit;
color: var(--text);
background: var(--bg);
background: var(--cds-field, var(--bg-card));
transition: border-color 0.2s;
}
/* Now that editable fields are white, genuinely locked ones need to say so — there
was no disabled rule at all, so they would have gone white too and the signal
would have inverted rather than been fixed. --cds-field-02 is the theme's own
secondary field surface, and matches .locked-field in the creator.
The border is deliberately left alone: the same --border-strong on both states
is what makes a field read as a field, and the fill is what carries locked vs
editable. Verified as #ffffff vs #f4f4f4 against an identical border. */
.field input:disabled,
.field select:disabled,
.field textarea:disabled,
.field input[readonly],
.field textarea[readonly] {
background: var(--cds-field-02, var(--bg));
color: var(--text-light);
cursor: not-allowed;
}
.field input:focus,
.field select:focus,
.field textarea:focus {

View File

@@ -379,16 +379,23 @@ def f5(page, base, tok):
wait_for="!!document.querySelector('.field input, .field select')")
time.sleep(0.9)
got = page.eval("""(function(){
var out=[], n=0;
var out=[], n=0, dis=[];
document.querySelectorAll('.field input,.field select,.field textarea').forEach(function(e){
if(e.disabled||e.readOnly||e.type==='hidden'||e.type==='checkbox'||e.type==='radio') return;
if(e.type==='hidden'||e.type==='checkbox'||e.type==='radio') return;
var r=e.getBoundingClientRect(); if(r.width===0&&r.height===0) return;
n++;
var s=getComputedStyle(e);
out.push(s.backgroundColor+' | '+s.borderTopColor);
var s=getComputedStyle(e), sig=s.backgroundColor+' | '+s.borderTopColor;
if(e.disabled||e.readOnly){ dis.push(sig); return; }
n++; out.push(sig);
});
var tally={}; out.forEach(function(v){tally[v]=(tally[v]||0)+1;});
return JSON.stringify({n:n,tally:tally});})()""")
var dtally={}; dis.forEach(function(v){dtally[v]=(dtally[v]||0)+1;});
// Prove the disabled rule works even when the page has no disabled field to
// sample: borrow a real one, flip it, measure, put it back.
var probe=document.querySelector('.field input:not([type=hidden])'), forced=null;
if(probe){ var was=probe.disabled; probe.disabled=true;
var ps=getComputedStyle(probe);
forced=ps.backgroundColor+' | '+ps.borderTopColor; probe.disabled=was; }
return JSON.stringify({n:n,tally:tally,dis:dtally,forced:forced});})()""")
m = json.loads(got)
if not m["n"]:
return report("F5", UNKNOWN, "no enabled wizard inputs found")
@@ -398,7 +405,15 @@ def f5(page, base, tok):
if greyed:
return report("F5", REPRO,
f"{greyed}/{m['n']} enabled inputs fill #f4f4f4 (most common: {top[0]})")
return report("F5", FIXED, f"{m['n']} enabled inputs, none grey-filled (most common: {top[0]})")
# An enabled field that looks exactly like a disabled one is the same defect
# inverted, so this must not silently pass.
if m["forced"] and m["forced"] == top[0]:
return report("F5", REPRO,
f"enabled and disabled fields are identical ({top[0]}) - "
f"locked fields no longer read as locked")
extra = f", disabled reads {m['forced']}" if m["forced"] else ""
return report("F5", FIXED,
f"{m['n']} enabled inputs at {top[0]}{extra}")
# ── F6 ────────────────────────────────────────────────────────────────────────