Files
dogfather-universe/workspace/assets/js/rechte.js
T
DogFatherGitandClaude Opus 5 95c4599baa Anfuehrungszeichen: 199 falsche Schlusszeichen im sichtbaren Text
Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.

Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.

WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE

Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus

    „<a href="https://…                  ein kaputtes Attribut
    /^["'„»\s]+|["'“«.\s]+$/              einen kaputten Ausdruck
    "… nichts „mal " + "eben …"          eine kaputte Zeichenkette

DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE

  < > = dazwischen        -> HTML-Marke oder Attribut
  endet auf Leerzeichen   -> die Zeichenkette hoert hier auf, der
                             Satz geht in der naechsten Zeile weiter.
                             Ein deutsches Schlusszeichen steht NIE
                             hinter einem Leerzeichen.
  Rueckstrich mittendrin  -> regulaerer Ausdruck
  ${ ohne }               -> mitten in einem Ausdruck

Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.

Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.

Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.

KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.

DIE WACHE DAZU

pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.

GEPRUEFT

  pruef-struktur   59 -> 68 Pruefungen, 0 Fehler
  node --check auf allen geaenderten JS-Dateien
  nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben

  gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
  reaktion 421, support 63, content 45, terminregel 35, treff 85

  Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
  ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
  Ausgabe, keine einzige ein Vergleich mit dem Seitentext.

GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.

Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 01:56:15 +02:00

287 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
„Wer sieht was" — ansehen und umstellen (11.09.2026)
Filipe: *„so dass ich die da auch manuell wechseln und speichern
kann."*
---------------------------------------------------------------------
GESPEICHERT WIRD SOFORT, MIT JEDEM KLICK
Kein „Speichern"-Knopf am Ende. Bei einer Tafel mit zweihundert
Feldern ist der die Stelle, an der man eine halbe Änderung verliert —
und eine halbe Änderung an Rechten ist die gefährlichste Lage von
allen. Zurückgenommen wird mit demselben Klick.
---------------------------------------------------------------------
WAS HIER NICHT ENTSCHIEDEN WIRD
Ob jemand etwas darf. Der Browser fragt und zeichnet; jede Absage
kommt vom Server. Die rechte Hand sieht dieselbe Tafel, bekommt aber
keine Knöpfe — nicht weil dieses Skript sie versteckt, sondern weil
der Server `darf_aendern: false` schickt. Ein Knopf weniger ist
Bequemlichkeit, kein Riegel.
---------------------------------------------------------------------
UND EIN UNTERSCHIED, DEN MAN SEHEN MUSS
Der Code in rechte.js ist der GRUNDSTAND. Was davon abweicht, ist von
Hand gesetzt — und das steht an der Zelle. Ohne diese Markierung wüsste
nach vier Wochen niemand mehr, was gedacht war und was jemand
daraus gemacht hat.
===================================================================== */
(() => {
const $ = (id) => document.getElementById(id);
const el = (t, k, text) => {
const n = document.createElement(t);
if (k) n.className = k;
if (text !== undefined) n.textContent = text;
return n;
};
const melde = (t, art) => {
const f = $('fehler');
if (!f) return;
f.textContent = t;
f.hidden = !t;
f.dataset.art = art || 'fehler';
};
let daten = null;
/* Der Abruf steht seit dem 22.09.2026 in assets/js/holen.js.
Er stand ACHTMAL da, Zeichen fuer Zeichen gleich -- und als
die Regel darin sich als falsch herausstellte (jeder 404
warf auf die Startseite, auch bei einer blossen Handlung),
haette man sie achtmal aendern muessen. */
const hole = (pfad, optionen) => window.holen(pfad, optionen);
/* ---- Ein Feld ------------------------------------------------------- */
function feld(seite, rolle) {
const darf = seite.rollen.includes(rolle.schluessel);
const anders = darf !== seite.grund.includes(rolle.schluessel);
const fest = seite.fest.includes(rolle.schluessel);
const td = document.createElement('td');
if (anders) td.dataset.anders = 'ja';
/* OHNE RECHT ZUM UMSTELLEN BLEIBT ES EIN ZEICHEN, KEIN KNOPF.
Ein Knopf, der nichts tut, ist schlimmer als keiner — man drückt
ihn immer wieder. */
if (!daten.darf_aendern) {
td.append(zeichen(darf));
if (anders) td.title = 'Von Hand gesetzt – weicht vom Grundstand ab.';
return td;
}
/* EIN FESTES FELD IST GAR KEIN KNOPF MEHR (24.09.2026).
Vorher war es einer mit `data-fest="ja"` — man konnte ihn
drücken, der Server sagte nein, und die Meldung erschien oben
am Bildschirmrand. Das ist genau das, was im Haus sonst
überall vermieden wird: ein Knopf, der eine Absage bringt.
Für DogFather ändert sich dadurch fast nichts (er hat drei
feste Felder). Die rechte Hand hat deutlich mehr — bei ihr wäre
es sonst eine ganze Spalte zum Danebendrücken. */
if (fest) {
const z = el('span', 'rechte-feld rechte-feld--fest');
z.dataset.fest = 'ja';
z.title = seite.fest_grund?.[rolle.schluessel]
|| 'Das kannst du nicht umstellen.';
z.append(zeichen(darf));
td.append(z);
return td;
}
const b = el('button', 'rechte-feld');
b.type = 'button';
b.setAttribute('aria-pressed', String(darf));
b.setAttribute('aria-label',
`${rolle.name} · ${seite.datei}: ${darf ? 'darf' : 'darf nicht'}`);
if (fest) b.dataset.fest = 'ja';
/* EIN FESTES FELD SAGT, WARUM ES FEST IST (24.09.2026).
Hier stand EIN Satz für alle festen Felder: „Diese Seite kannst
du dir nicht selbst wegnehmen." Für DogFather stimmt er. Seit
die rechte Hand umstellen darf, gibt es drei weitere Gründe —
die Spalte „DogFather", die Zeile „Personen & Zugänge" und ihre
eigenen zwei Zugänge. Ein Satz, der bei drei von vier Fällen
das Falsche sagt, schickt sie auf die Suche nach einem Fehler,
den es nicht gibt.
Der Grund kommt NICHT aus einer Liste hier: `festGrund` steht in
der Antwort des Servers, aus derselben Funktion, die das Feld
überhaupt als fest markiert hat. */
b.title = fest
? (seite.fest_grund?.[rolle.schluessel]
|| 'Diese Seite kannst du dir nicht selbst wegnehmen – sonst kämst du '
+ 'nicht mehr an die Rechte, die Zugänge oder die Startseite.')
: (anders ? 'Von Hand gesetzt. Klicken stellt es um.' : 'Klicken stellt es um.');
b.append(zeichen(darf));
b.addEventListener('click', () => umstellen(seite, rolle, !darf, b));
td.append(b);
return td;
}
function zeichen(darf) {
if (darf) {
const punkt = el('span', 'rechte-ja');
punkt.setAttribute('role', 'img');
punkt.setAttribute('aria-label', 'darf');
return punkt;
}
/* Ein Zeichen und nicht ein leeres Feld: Ein Bildschirmleser liest
eine leere Zelle als nichts vor — man hört dann nicht, dass die
Antwort „nein" ist, sondern gar nichts. */
const nein = el('span', 'rechte-nein', '–');
nein.setAttribute('aria-label', 'darf nicht');
return nein;
}
async function umstellen(seite, rolle, erlaubt, knopf) {
/* EINE RÜCKFRAGE NUR BEIM ÖFFNEN, nicht beim Schließen.
Etwas wegzunehmen kann man in einer Sekunde zurückdrehen und es
sieht sofort jemand. Etwas aufzumachen merkt unter Umständen
lange niemand — und genau das ist der Klick, bei dem eine
Sekunde Nachdenken etwas wert ist. */
if (erlaubt && !await frageNach({
titel: `„${rolle.name}“ darf danach ${seite.datei} öffnen`,
was: 'Das gilt sofort, für alle mit dieser Rolle.',
bleibt: 'Zurücknehmen geht jederzeit mit demselben Schalter – aber bis '
+ 'dahin hat jeder mit dieser Rolle Zugang.',
ja: 'Freigeben',
})) return;
knopf.disabled = true;
try {
const a = await hole('/workspace/api/rechte/feld', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pfad: seite.pfad, rolle: rolle.schluessel, erlaubt }),
});
if (!a.ok) {
melde(sagWas((await a.json().catch(() => ({}))).fehler, 'Ging nicht.'));
return;
}
melde(`Gespeichert: „${rolle.name}“ darf ${seite.datei} `
+ `${erlaubt ? 'jetzt' : 'nicht mehr'} öffnen.`, 'gut');
await laden();
} finally {
knopf.disabled = false;
}
}
/* ---- Zeichnen --------------------------------------------------------- */
function zeichne() {
const gesamt = daten.seiten.length;
/* Die Zahlen je Rolle. Sortiert nach Anzahl — man sieht auf einen
Blick, wer viel sieht und wer wenig, und ob irgendwo eine Rolle
steht, die mehr sieht als sie sollte. */
const zahlen = $('zahlen');
zahlen.textContent = '';
for (const rolle of [...daten.rollen].sort((a, b) => b.seiten - a.seiten)) {
const li = document.createElement('li');
li.append(rolle.name + ' ');
const b = document.createElement('b');
b.textContent = `${rolle.seiten} von ${gesamt}`;
li.append(b);
if (rolle.abweichend) {
const m = el('span', 'rechte-zahlen__anders', ` · ${rolle.abweichend} von Hand`);
m.title = 'So viele Felder weichen bei dieser Rolle vom Grundstand ab.';
li.append(m);
}
zahlen.append(li);
}
/* Die Leiste: was ist von Hand gesetzt, und wie kommt man zurück. */
const leiste = $('leiste');
leiste.textContent = '';
if (daten.abweichungen) {
leiste.hidden = false;
leiste.append(el('span', 'rechte-leiste__satz',
`${daten.abweichungen} ${daten.abweichungen === 1 ? 'Feld ist' : 'Felder sind'} `
+ 'von Hand gesetzt und weicht vom Grundstand ab.'));
/* ZURUECKSETZEN NUR FUER DOGFATHER. Der Knopf nimmt ALLE
Abweichungen mit -- auch die an der Spalte „DogFather" und an
„Personen & Zugänge", die die rechte Hand nicht anfassen
darf. Eine Sperre, die ein zweiter Knopf daneben aufhebt, ist
keine. Der Server lehnt es ohnehin ab (403); hier steht der
Knopf deshalb gar nicht erst. */
if (daten.darf_zuruecksetzen) {
const b = el('button', 'schritt', 'Alles zurück auf Grundstand');
b.type = 'button';
b.addEventListener('click', async () => {
if (!await frageNach({
titel: `${daten.abweichungen} Änderungen zurücknehmen?`,
was: 'Danach gilt wieder genau das, was im Code steht.',
bleibt: 'Die Rechte selbst gehen nicht verloren – nur die von Hand '
+ 'gesetzten Abweichungen. Du kannst sie jederzeit neu setzen.',
ja: 'Auf Grundstand zurück',
})) return;
b.disabled = true;
const a = await hole('/workspace/api/rechte/abweichungen', { method: 'DELETE' });
b.disabled = false;
if (!a.ok) { melde('Ging nicht.'); return; }
melde('Alles zurück auf Grundstand.', 'gut');
await laden();
});
leiste.append(b);
}
} else {
leiste.hidden = false;
leiste.append(el('span', 'rechte-leiste__satz',
daten.darf_aendern
? 'Alles steht so, wie es im Code steht. Klick auf ein Feld, um es umzustellen – '
+ 'das wird sofort gespeichert.'
: 'Alles steht so, wie es im Code steht. Umstellen kann das nur DogFather '
+ 'und die rechte Hand.'));
}
/* Die Tafel. */
const ziel = $('tafel');
ziel.textContent = '';
const tab = document.createElement('table');
const kopf = document.createElement('thead');
const kz = document.createElement('tr');
kz.append(el('th', null, 'Seite'));
for (const rolle of daten.rollen) {
const th = el('th', null, rolle.name);
/* Der Schlüssel als Titel: Im Alltag liest man „Community", im
Code steht „gast". Wer die beiden nicht zusammenbringen kann,
sucht im Quelltext an der falschen Stelle. */
th.title = rolle.schluessel;
kz.append(th);
}
kopf.append(kz);
tab.append(kopf);
const koerper = document.createElement('tbody');
for (const seite of daten.seiten) {
const tr = document.createElement('tr');
tr.append(el('td', null, seite.datei));
for (const rolle of daten.rollen) tr.append(feld(seite, rolle));
koerper.append(tr);
}
tab.append(koerper);
ziel.append(tab);
ziel.setAttribute('aria-busy', 'false');
const offen = $('offen');
offen.textContent = '';
for (const pfad of daten.ohne_anmeldung) offen.append(el('li', null, pfad));
}
async function laden() {
let a;
try {
a = await hole('/workspace/api/rechte/tafel');
} catch { return; }
if (!a.ok) { melde('Die Tafel konnte nicht geladen werden.'); return; }
daten = await a.json();
zeichne();
}
(async () => { try { await laden(); } catch { /* umgeleitet */ } })();
})();