diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,7 +1,49 @@
 * Hackage: <http://hackage.haskell.org/package/crackNum>
 * GitHub:  <http://github.com/LeventErkok/crackNum/>
 
-* Latest Hackage released version: 4.2, 2026-08-25
+* Latest Hackage released version: 4.3, 2026-08-27
+
+### Version 4.3, 2026-08-27
+
+  * All three GUIs: an empty value box is no longer cracked as if it held 0. It
+    used to be defaulted, so clicking a format with nothing typed -- or starting
+    with `crackNum --gui -fsp` and no value -- produced a complete, ordinary-looking
+    result for a number the user had never entered, down to reporting the
+    conversion as exact. The box now says what is missing instead. A value that is
+    present still reaches crackNum byte-for-byte: `0xdeadbeef` and `3735928559`
+    select different operations, so normalizing here would change the question.
+
+  * All three GUIs: the box holding the custom width fields now says what it
+    actually drives. It was headed "Custom IEEE-754 float", but it is shared by
+    all three "Custom" entries -- the IEEE-754 float, the signed integer, and the
+    unsigned word -- so two of its three users were reading a heading meant for
+    something else. The heading now names whichever one is selected.
+
+  * Relatedly, the rows that do not apply are greyed out rather than left live:
+    only the float has an exponent, so "Exponent width" is inert for the integer
+    and word customs, and with a fixed format selected nothing in the box does
+    anything at all. Both were previously editable, with no hint that typing in
+    them had no effect.
+
+  * The Tcl GUI no longer raises a raw Tcl error when a custom width field holds
+    something that is not a number. The widths went straight into `expr`, which
+    throws on a non-numeric operand, so "abc" -- or an empty field -- aborted
+    inside the widget callback rather than reaching the "Invalid custom FP format"
+    message written for exactly that case. They are now parsed the way the Swift
+    and Windows GUIs already parsed them. This also stops `expr` from reading
+    "0x20" as 32, which neither of the other two accepts.
+
+  * The Windows GUI's `--selftest` now asserts both of the above mappings -- which
+    rows the custom box enables, and that an empty value is never turned into a
+    number while a present one passes through untouched. Neither is reachable from
+    a headless build otherwise, and layout and enablement bugs do not fail builds.
+
+  * Reworded the GUIs' help text on multi-lane decoding. It said "Verilog input
+    longer than the format is decoded as SIMD lanes", which named the wrong
+    criterion twice over: it is the declared width N that decides, not the length
+    of what you type (`64'hF` is one digit and still gives four lanes), and being
+    longer is not sufficient either, since N must be an exact multiple of the
+    format -- `20'h12345` in a 16-bit format is an error, not two lanes.
 
 ### Version 4.2, 2026-08-25
 
diff --git a/GUI/tclGUI/crackNum.tcl b/GUI/tclGUI/crackNum.tcl
--- a/GUI/tclGUI/crackNum.tcl
+++ b/GUI/tclGUI/crackNum.tcl
@@ -104,7 +104,7 @@
 Decoding:
   - Use hex (0x), binary (0b), or Verilog (N'h) notation.
   - You may use _, - or space as separators for readability.
-  - Verilog input longer than the format is decoded as SIMD lanes.}
+  - Verilog N'h: N is the total width, split into N/format-size lanes.}
 
 # ---------------------------------------------------------------------------
 # Build the precision flag from the selected format
@@ -130,8 +130,23 @@
                     # Only check that the widths describe a well-formed layout; crackNum
                     # itself owns the remaining limits (and reports solver restrictions
                     # readably).
+                    #
+                    # The width entries are free-form text with no -validatecommand, so
+                    # normalize them the way the Swift and Windows GUIs do: anything that
+                    # is not a plain decimal integer becomes 0 and falls into the message
+                    # below. This has to happen before any arithmetic -- expr throws on a
+                    # non-numeric operand ("can't use non-numeric string as operand"), and
+                    # so does the [format %4d] in that message, and an error raised inside
+                    # a widget binding escapes as a Tk error dialog instead.
+                    #
+                    # Tested with a regexp rather than [string is integer], which is true
+                    # for "0x20" and would let expr silently read it as 32. Swift's
+                    # Int("0x20") and C#'s int.TryParse both reject it, and matching them
+                    # is what keeps the three GUIs agreeing.
                     set bw $state(bitWidth)
                     set ew $state(expWidth)
+                    if {![regexp {^-?[0-9]+$} [string trim $bw]]} { set bw 0 }
+                    if {![regexp {^-?[0-9]+$} [string trim $ew]]} { set ew 0 }
                     set sig [expr {$bw - $ew - 1}]
                     if {$ew < 1 || $sig < 0} {
                         return [list invalid \
@@ -174,11 +189,13 @@
     set flag $flagResult
     set rm   "-r$state(rounding)"
 
-    # NB: do NOT use [expr] to default this. expr parses its operands as numbers,
-    # so "0xdeadbeef" would arrive as 3735928559 and crackNum would encode the
-    # decimal instead of decoding the bit-pattern.
+    # An empty box is not a value. This used to default to 0, which cracked a number
+    # the user had never typed and presented the result exactly like a real one. Say
+    # what is missing instead of inventing input. (Note there is deliberately no
+    # [expr] here: expr parses its operands as numbers, so "0xdeadbeef" would arrive
+    # as 3735928559 and crackNum would encode the decimal instead of decoding it.)
     set val $state(value)
-    if {$val eq ""} { set val 0 }
+    if {[string trim $val] eq ""} { return "Enter a value above to crack it." }
 
     # Pass SBV_Z3 so crackNum finds z3 even when PATH is minimal.
     if {[info exists ::env(SBV_Z3)]} { set savedZ3 $::env(SBV_Z3) } else { set savedZ3 "" }
@@ -416,6 +433,7 @@
     set sel [.main.side.fmts.lb selection]
     if {$sel ne "" && [info exists ITEM_FMT($sel)]} {
         set state(selection) $ITEM_FMT($sel)
+        syncCustomBox
         crack
     } else {
         .main.side.fmts.lb selection remove $sel
@@ -450,7 +468,8 @@
 # labelframe's own -text: that keeps the framed/shaded container while letting the
 # heading line up flush left with "Rounding mode:" above it, instead of being
 # indented past it by the frame's title inset.
-label .main.side.customLbl -text "Custom IEEE-754 float:" -anchor w
+# Text is a placeholder: syncCustomBox retitles this to name whatever is selected.
+label .main.side.customLbl -text "Custom format:" -anchor w
 pack  .main.side.customLbl -fill x -pady {8 2}
 
 labelframe .main.side.custom -padx 4 -pady 4
@@ -474,6 +493,35 @@
 pack  .main.side.custom.ew.e -side right
 bind  .main.side.custom.ew.e <Return> crack
 
+# The box above is shared by all three "Custom" entries -- IEEE-754 float, signed
+# integer, and unsigned word -- but only the float has an exponent, and with a fixed
+# format selected nothing in the box does anything at all. So the heading and which
+# rows stay live both follow the selection, rather than naming one of the three and
+# hoping. Call this on every selection change.
+proc syncCustomBox {} {
+    global state FORMAT_SECTIONS
+
+    set kind ""
+    foreach section $FORMAT_SECTIONS {
+        foreach fmt [lindex $section 1] {
+            if {[lindex $fmt 0] eq $state(selection)} { set kind [lindex $fmt 2] }
+        }
+    }
+
+    switch $kind {
+        customFloat { set heading "Custom IEEE-754 float:" ; set bw normal   ; set ew normal   }
+        customInt   { set heading "Custom signed integer:" ; set bw normal   ; set ew disabled }
+        customWord  { set heading "Custom unsigned word:"  ; set bw normal   ; set ew disabled }
+        default     { set heading "Custom format:"         ; set bw disabled ; set ew disabled }
+    }
+
+    .main.side.customLbl   configure -text  $heading
+    .main.side.custom.bw.l configure -state $bw
+    .main.side.custom.bw.e configure -state $bw
+    .main.side.custom.ew.l configure -state $ew
+    .main.side.custom.ew.e configure -state $ew
+}
+
 # Output pane
 frame .main.out
 pack .main.out -side left -fill both -expand yes
@@ -603,6 +651,7 @@
 # ---------------------------------------------------------------------------
 showOutput $WELCOME
 parseArgs $argv
+syncCustomBox
 if {$state(selection) ne ""} { crack }
 focus .top.val
 .top.val icursor end
diff --git a/crackNum.cabal b/crackNum.cabal
--- a/crackNum.cabal
+++ b/crackNum.cabal
@@ -1,6 +1,6 @@
 Cabal-version      : 2.2
 Name               : crackNum
-Version            : 4.2
+Version            : 4.3
 Synopsis           : Crack various integer and floating-point data formats
 Description        : Crack IEEE-754 and other float formats and arbitrary sized words and integers, showing the layout.
                      Along with a command-line interface on any platform, native MacOS and Windows GUIs and a Tcl-based Linux GUI are available as well:
