helwasm: hierarchical quick-gen, pure sugar over the engine

Quick form now drives the one shared engine through commands only, so it and the
console give identical results (form == console).

- password hook answers only the "/" prompt, so read_master climbs and computes
  every ^parent from a single root master (Req 4)
- new master in the field is authoritative via `unpass`; empty field reuses the
  stored session master, so later names generate without re-typing it (Req 1/2)
- `unpass` with no name now clears ALL cached masters (real command, not a private
  hook) so the form stays reproducible in the console
- result colour = root master marked correct (correct / / uncorrect /), derived
  live from the engine; black otherwise (Req 3)
- Mark-correct toggle button; click the master to reveal it; honest memory-only hint
- master stays in memory only, never written to storage; only the one-way correct
  hash is saved on the device

parser: bare-name forms now keep a trailing comment, and a ^-led token is never a
name, so `name ^parent` / `name MODE ^parent` link the parent instead of qpname
reading it as prefix+name (was silently mis-stored).
This commit is contained in:
Oleksandr Kozachuk
2026-06-09 22:45:44 +02:00
parent e25746d5f0
commit 10a21d1f9d
6 changed files with 130 additions and 41 deletions
+15 -2
View File
@@ -163,6 +163,10 @@ input:focus { border-color: var(--ocean); box-shadow: 0 0 0 3px rgba(27, 65, 97,
strong password". Firefox uses the bundled disc font. */
input.mask { -webkit-text-security: disc; }
@supports not (-webkit-text-security: disc) { input.mask { font-family: "text-security-disc", var(--mono); } }
/* Click the masked master field to reveal what you typed; click again to re-mask. */
#master { cursor: pointer; }
input.mask.revealed { -webkit-text-security: none; }
@supports not (-webkit-text-security: disc) { input.mask.revealed { font-family: var(--mono); } }
/* Buttons */
.btn {
@@ -177,6 +181,10 @@ input.mask { -webkit-text-security: disc; }
.btn.ghost { background: transparent; color: var(--ocean); }
.btn.ghost:hover { background: rgba(27, 65, 97, 0.06); color: var(--ocean); border-color: var(--ocean); }
.btn.small { padding: 7px 15px; font-size: 13px; }
/* "Mark correct" toggle: filled ocean once the master is remembered-correct. */
.btn.small.is-correct { background: var(--ocean); color: var(--cream); border-color: var(--ocean); }
.btn.small.is-correct:hover { background: var(--deep); border-color: var(--deep); }
.btn:disabled { opacity: .4; cursor: not-allowed; pointer-events: none; }
/* Quick-gen result row */
.result {
@@ -185,7 +193,7 @@ input.mask { -webkit-text-security: disc; }
border-radius: 12px; background: #fff; min-height: 58px;
}
.result .tag { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); }
.result .actions { margin-left: auto; display: flex; gap: 8px; }
.result .actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.result .len { font-family: var(--mono); font-size: 11px; color: var(--muted); }
/* Quick-gen result: dynamic font (scales down on narrow phones) and wraps so a
long password stays inside the card. min-width:0 lets it shrink/wrap within
@@ -202,7 +210,12 @@ input.mask { -webkit-text-security: disc; }
font-size: 19px; color: var(--ink);
cursor: pointer; user-select: text; white-space: pre;
}
.secret.revealed { -webkit-text-security: none; color: var(--ocean); }
/* Reveal flips masking only; colour is reserved for correctness (below). */
.secret.revealed { -webkit-text-security: none; }
/* Quick-gen result: ocean = this master is marked correct; otherwise ink ("black"). */
.result .secret.correct { color: var(--ocean); font-weight: 500; }
/* Console keeps its old cue: a revealed secret turns ocean. */
.console-out .secret.revealed { color: var(--ocean); }
/* Firefox lacks -webkit-text-security: fall back to the disc webfont for masking
(single-line result only; the console table is a WebKit/Blink concern). */
@supports not (-webkit-text-security: disc) {