feat: ein neues Dokument beginnt mit seinem Namen (D51)

„+ Neu" legte das Dokument unter „Unbenannt" an und setzte den Cursor ins
leere Textfeld; das Umbenennen war ein eigener, zweiter Gang durchs Menü — und
wurde entsprechend oft nicht gemacht. Jetzt öffnet sich unmittelbar das
Inline-Umbenennen (D22) mit ausgewähltem Vorschlag: tippen ersetzt ihn, Enter
bestätigt.

Das Dokument existiert vorher: angelegt, gespeichert und aktiviert wie bisher,
nur der Fokus geht ins Namensfeld statt ins Textfeld. Ein Abbruch verwirft
nichts — Esc behält den Vorschlag. Der Gegenentwurf (erst benennen, dann
anlegen) hätte ein Dokument im Schwebezustand gebraucht.

Nach dem Benennen geht es im Textfeld weiter — Anlegen heißt schreiben wollen.
Der Merker `renameIsNew` unterscheidet diesen Fall vom gewöhnlichen Umbenennen
aus dem Menü, das unverändert bleibt (Menü bleibt offen, Fokus wandert nicht);
`closeDocMenu()` setzt ihn mit zurück, sonst trüge ein abgebrochener Vorgang
seine Sonderbehandlung in den nächsten hinein.

Nachgemessen: Nach „+ Neu" hat das Eingabefeld den Fokus und der Vorschlag ist
vollständig markiert. Enter mit „Sprint 15" -> Titelzeile, `werkbaum-docs` und
Fokus im leeren Textfeld stimmen. Esc -> Vorschlag bleibt, Menü zu, Fokus im
Text. Bestehendes Dokument umbenennen -> Menü bleibt offen, Fokus bleibt.
236 Tests grün.

Grenze der Prüfung, benannt: Der Fokusverlust ließ sich nur als zugestelltes
Ereignis prüfen, nicht als echter Fokuswechsel — ein synthetisches `.blur()`
löst im Automaten keinen aus. Gegenprobe am unveränderten Pfad zeigt dasselbe,
es ist also die Werkzeuggrenze und keine Regression (Lehre aus D25).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 14:47:19 +02:00
co-authored by Claude Fable 5
parent ec338eb558
commit e554b6d9b8
2 changed files with 67 additions and 3 deletions
+41
View File
@@ -3247,3 +3247,44 @@ und bei 320 px.
`overflow` gibt, entscheidet damit über alles, was aus ihr herausragen soll — `overflow` gibt, entscheidet damit über alles, was aus ihr herausragen soll —
Menüs, Tooltips, Overlays. In `frontend/CLAUDE.md` steht das jetzt bei der Menüs, Tooltips, Overlays. In `frontend/CLAUDE.md` steht das jetzt bei der
Kopfzeilen-Stolperfalle. Kopfzeilen-Stolperfalle.
## D51 — Ein neues Dokument beginnt mit seinem Namen
Bisher legte „+ Neu" das Dokument unter „Unbenannt" an und setzte den Cursor
ins leere Textfeld. Das Umbenennen war ein eigener, zweiter Gang durchs Menü —
und wurde entsprechend oft nicht gemacht: Wer drei Pläne führt, hat drei
„Unbenannt". Jetzt öffnet sich unmittelbar das Inline-Umbenennen (D22) mit
**ausgewähltem** Vorschlag; tippen ersetzt ihn, Enter bestätigt.
**Das Dokument existiert vorher.** Angelegt, gespeichert und aktiviert wird wie
bisher; nur der Fokus geht ins Namensfeld statt ins Textfeld. Ein **Abbruch
verwirft nichts** — Esc behält den Vorschlag „Unbenannt", so wie es vorher der
Normalfall war. Der Gegenentwurf (erst benennen, dann anlegen) hätte ein
Dokument im Schwebezustand gebraucht und die Frage aufgeworfen, was bei Esc
passiert; das ist mehr Mechanik für weniger Verlässlichkeit.
**Nach dem Benennen geht es im Textfeld weiter.** Anlegen heißt schreiben
wollen — den Namen zu vergeben ist die erste Hälfte der Geste, nicht ihr Zweck.
Der Merker `renameIsNew` unterscheidet diesen Fall vom gewöhnlichen Umbenennen
aus dem Menü, das unverändert bleibt (Menü bleibt offen, Fokus wandert nicht).
Er wird in `closeDocMenu()` mit zurückgesetzt, sonst trüge ein abgebrochener
Vorgang seine Sonderbehandlung in den nächsten hinein.
Für alle drei Wege aus dem Eingabefeld gilt dasselbe: **Enter**, **Esc** und
**Fokusverlust** benennen (bzw. behalten den Vorschlag), schließen das Menü und
setzen den Cursor in den Text. Auf dem Telefon ist das zugleich der Moment, in
dem die Bildschirmtastatur gebraucht wird — `keyboardOnJump(false)` steht schon
da, aus demselben Grund (D25: neues, leeres Dokument = tippen ist gemeint).
**Nachgemessen:** Nach „+ Neu" trägt das Eingabefeld den Fokus, der Vorschlag
ist vollständig markiert (`selectionStart` 0 bis Länge). Enter mit „Sprint 15":
Titelzeile, `werkbaum-docs` und der Fokus im leeren Textfeld stimmen. Esc:
Vorschlag bleibt, Menü zu, Fokus im Text. Umbenennen eines bestehenden
Dokuments: Menü bleibt offen, Fokus bleibt, wo er war.
**Grenze der Prüfung, benannt:** Der Fokusverlust ließ sich nur als
**zugestelltes Ereignis** prüfen, nicht als echter Fokuswechsel — ein
synthetisches `.blur()` löst im Automaten keinen aus. Gegenprobe am
**unveränderten** Pfad (bestehendes Dokument umbenennen): dort passiert
ebenfalls nichts, es ist also die Werkzeuggrenze und keine Regression. Dieselbe
Lehre wie in D25 und D17-Nachtrag 4 — was die Geräteumgebung stellt, beweist
der Emulator nicht.
+26 -3
View File
@@ -2603,6 +2603,9 @@ function updateDocName(){
updatePadLink(); /* Schreibschutz + Pad-Knopf hängen am aktiven Dokument (D31) */ updatePadLink(); /* Schreibschutz + Pad-Knopf hängen am aktiven Dokument (D31) */
} }
let renamingId = null; /* id des gerade inline umbenannten Dokuments (oder null) */ let renamingId = null; /* id des gerade inline umbenannten Dokuments (oder null) */
/* Kam das Umbenennen von einem frisch angelegten Dokument? Dann geht es danach
im Textfeld weiter — Anlegen heißt schreiben wollen (D51). */
let renameIsNew = false;
function renderDocMenu(){ function renderDocMenu(){
docList.innerHTML = docs.map(d => { docList.innerHTML = docs.map(d => {
if(d.id === renamingId){ if(d.id === renamingId){
@@ -2664,7 +2667,7 @@ function restoreDoc(){
closeDocMenu(); closeDocMenu();
} }
function openDocMenu(){ renderDocMenu(); updateRestoreBtn(); docMenu.hidden = false; docTrigger.setAttribute('aria-expanded', 'true'); } function openDocMenu(){ renderDocMenu(); updateRestoreBtn(); docMenu.hidden = false; docTrigger.setAttribute('aria-expanded', 'true'); }
function closeDocMenu(){ renamingId = null; docMenu.hidden = true; docTrigger.setAttribute('aria-expanded', 'false'); } function closeDocMenu(){ renamingId = null; renameIsNew = false; docMenu.hidden = true; docTrigger.setAttribute('aria-expanded', 'false'); }
function toggleDocMenu(){ docMenu.hidden ? openDocMenu() : closeDocMenu(); } function toggleDocMenu(){ docMenu.hidden ? openDocMenu() : closeDocMenu(); }
/* Beim Wechseln/Anlegen/Löschen zuerst den aktuellen Editortext ins aktive /* Beim Wechseln/Anlegen/Löschen zuerst den aktuellen Editortext ins aktive
Dokument sichern, dann das Ziel laden und neu rendern. */ Dokument sichern, dann das Ziel laden und neu rendern. */
@@ -2728,6 +2731,11 @@ function switchDoc(id){
loadActiveIntoEditor(); loadActiveIntoEditor();
persistDocs(); persistDocs();
} }
/* Ein neues Dokument beginnt mit seinem Namen (D51): Statt es unter „Neues
Dokument" abzulegen und den Cursor ins leere Textfeld zu setzen, öffnet sich
sofort das Inline-Umbenennen mit **ausgewähltem** Vorschlag — tippen ersetzt
ihn, Enter bestätigt. Wer nichts eingibt, behält den Vorschlag; das Dokument
ist bereits angelegt, ein Abbruch verwirft es nicht. */
function newDoc(){ function newDoc(){
flushActive(); flushActive();
const d = { id: uid(), name: uniqueName(t('docNewName')), text: '' }; const d = { id: uid(), name: uniqueName(t('docNewName')), text: '' };
@@ -2737,11 +2745,23 @@ function newDoc(){
loadActiveIntoEditor(); loadActiveIntoEditor();
persistDocs(); persistDocs();
keyboardOnJump(false); /* neues, leeres Dokument = tippen ist gemeint */ keyboardOnJump(false); /* neues, leeres Dokument = tippen ist gemeint */
renamingId = d.id;
renameIsNew = true;
renderDocMenu(); /* setzt den Fokus ins Eingabefeld und markiert es */
}
/* Nach dem Benennen eines frisch angelegten Dokuments geht es im Textfeld
weiter — die zweite Hälfte derselben Geste. Gilt auch für Esc: Der Vorschlag
bleibt dann stehen, das Dokument existiert ohnehin. */
function finishNewDoc(){
if(!renameIsNew) return;
renameIsNew = false;
closeDocMenu();
src.focus(); src.focus();
} }
function renameDoc(){ function renameDoc(){
if(!activeDoc()) return; if(!activeDoc()) return;
renamingId = activeId; /* aktives Dokument inline umbenennen */ renamingId = activeId; /* aktives Dokument inline umbenennen */
renameIsNew = false;
renderDocMenu(); renderDocMenu();
} }
function commitRename(){ function commitRename(){
@@ -2752,8 +2772,9 @@ function commitRename(){
renamingId = null; renamingId = null;
if(d && val){ d.name = val; persistDocs(); updateDocName(); } if(d && val){ d.name = val; persistDocs(); updateDocName(); }
renderDocMenu(); renderDocMenu();
finishNewDoc();
} }
function cancelRename(){ renamingId = null; renderDocMenu(); } function cancelRename(){ renamingId = null; renderDocMenu(); finishNewDoc(); }
function deleteDoc(){ function deleteDoc(){
const d = activeDoc(); const d = activeDoc();
if(!d) return; if(!d) return;
@@ -3091,7 +3112,9 @@ docList.addEventListener('click', e => {
switchDoc(btn.dataset.id); switchDoc(btn.dataset.id);
closeDocMenu(); closeDocMenu();
}); });
document.getElementById('docNew').addEventListener('click', e => { e.stopPropagation(); newDoc(); renderDocMenu(); }); /* `newDoc()` zeichnet das Menü selbst neu — es geht direkt ins Umbenennen
(D51), und ein zweiter Durchlauf baute das Eingabefeld nur noch einmal auf. */
document.getElementById('docNew').addEventListener('click', e => { e.stopPropagation(); newDoc(); });
document.getElementById('docRename').addEventListener('click', e => { e.stopPropagation(); renameDoc(); }); document.getElementById('docRename').addEventListener('click', e => { e.stopPropagation(); renameDoc(); });
document.getElementById('docDelete').addEventListener('click', e => { e.stopPropagation(); deleteDoc(); }); document.getElementById('docDelete').addEventListener('click', e => { e.stopPropagation(); deleteDoc(); });
document.getElementById('docRestore').addEventListener('click', e => { e.stopPropagation(); restoreDoc(); }); document.getElementById('docRestore').addEventListener('click', e => { e.stopPropagation(); restoreDoc(); });