feat(editor): ID-Kurzschreibweise #.kc als Eingabehilfe
Gefragt war, ob `#.kc` unter `#prod-stage` als Notation `#prod-stage.kc` bedeuten könnte. Als Notation wäre es falsch, und das zeigt der eigene Plan: 181 IDs im Baumteil, davon 50 (28 %), deren gepunktetes Präfix gar nicht die Eltern-ID ist (`#ed.parser` hängt unter `#ed.live`) — die Punkte benennen den BEREICH, nicht den Pfad, genau wie D48 es vorsieht. Dazu: der Beschreibungsteil hinter `---` hat keinen Baum (zwei Schreibweisen für eine ID), Einrücken änderte die Identität und bräche `:#…`-Verweise, und aus einer Konvention würde Grammatik. Gewinn wären 802 Zeichen in 40 kB. Also als Eingabehilfe: getippt wird `#.kc`, beim Verlassen der Zeile steht `#prod-stage.kc` im Text — derselbe Griff, mit dem das Umklappen im Diagramm seine Faltmarke zurückschreibt (D38-Nachtrag 2). Die Datei bleibt eindeutig, durchsuchbar und umsortierbar; llms.md ist unberührt, weil sich an der Notation nichts ändert. - parser.js: `expandShortIds(text)` neben `setFoldMark`, 17 Tests. - app.js: löst beim Zeilenwechsel auf, aber nur in der Zeile, in der auch getippt wurde — ein bloß durchgeklicktes fremdes Dokument bleibt zeichengenau stehen (sonst fiele es aus dem Nachziehen, D27). Falle, zum zweiten Mal dieselbe: `execCommand` verweigert re-entrant im `input`-Ereignis den Dienst, `replaceTextUndoable` fällt dann auf `src.value =` zurück und löscht die Undo-Historie. Gemessen: erstes Rückgängig ohne Wirkung, jedes weitere `false`. Jetzt einen Zug später (`setTimeout 0`) und nur mit Fokus im Textfeld; danach nimmt ein Rückgängig genau die Auflösung zurück. SPEC §9 + D55; Plan: #ed.shortid [x] (159 Knoten, 0 Warnungen). 288 Tests grün (271+17). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e937e1bbbb
commit
285d86a2e1
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { expandShortIds, parse } from '../src/parser.js';
|
||||
|
||||
/* Kurzschreibweise der Knoten-ID (D55): `#.kc` unter `#prod-stage` wird beim
|
||||
Verlassen der Zeile zu `#prod-stage.kc`. Eingabehilfe, keine Notation — in
|
||||
der Datei steht danach immer die volle ID. */
|
||||
|
||||
describe('expandShortIds — auflösen gegen den nächsten Vorfahren mit ID', () => {
|
||||
it('löst gegen den Elternknoten auf', () => {
|
||||
const text = ['#prod-stage: Stage', ' - #.kc: Keycloak'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(
|
||||
['#prod-stage: Stage', ' - #prod-stage.kc: Keycloak'].join('\n'));
|
||||
});
|
||||
|
||||
it('löst über mehrere Ebenen jeweils gegen die nächste auf', () => {
|
||||
const text = ['#a: A', ' - #.b: B', ' - #.c: C'].join('\n');
|
||||
/* Die zweite Zeile ist beim Auflösen der dritten bereits `#a.b`. */
|
||||
expect(expandShortIds(text)).toBe(
|
||||
['#a: A', ' - #a.b: B', ' - #a.b.c: C'].join('\n'));
|
||||
});
|
||||
|
||||
/* Der direkte Elternknoten muss keine ID haben — dann zählt der nächste
|
||||
Vorfahre, der eine hat. */
|
||||
it('überspringt Vorfahren ohne ID', () => {
|
||||
const text = ['#a: A', ' - Zwischenknoten ohne ID', ' - #.c: C'].join('\n');
|
||||
expect(expandShortIds(text)).toContain('#a.c: C');
|
||||
});
|
||||
|
||||
it('nimmt mehrere Stufen auf einmal', () => {
|
||||
const text = ['#a: A', ' - #.b.c: tief'].join('\n');
|
||||
expect(expandShortIds(text)).toContain('#a.b.c: tief');
|
||||
});
|
||||
|
||||
it('lässt volle IDs unangetastet', () => {
|
||||
const text = ['#a: A', ' - #ganz.anders: X'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
|
||||
it('gibt denselben String zurück, wenn nichts aufzulösen ist', () => {
|
||||
const text = '- [x] Nichts zu tun (M)';
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandShortIds — wo nicht aufgelöst werden kann, bleibt der Text stehen', () => {
|
||||
it('lässt eine Wurzelzeile in Ruhe (kein Vorfahre)', () => {
|
||||
const text = '#.kc: Keycloak';
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
|
||||
it('löst nicht gegen einen Vorfahren auf, der selbst noch kurz ist', () => {
|
||||
const text = ['#.a: A', ' - #.b: B'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
|
||||
/* Zwei Punkte sind keine vereinbarte Bedeutung — also nichts erfinden. */
|
||||
it('rührt `#..x` nicht an', () => {
|
||||
const text = ['#a: A', ' - #..x: X'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandShortIds — fasst nur an, was die ID ist', () => {
|
||||
it('lässt den Beschreibungsteil hinter --- unberührt', () => {
|
||||
const text = ['#a: A', ' - #.b: B', '---', '#.b', ' Text mit #.b darin'].join('\n');
|
||||
const out = expandShortIds(text).split('\n');
|
||||
expect(out[1]).toContain('#a.b: B');
|
||||
expect(out[3]).toBe('#.b');
|
||||
expect(out[4]).toBe(' Text mit #.b darin');
|
||||
});
|
||||
|
||||
it('lässt eine Kurzform im Kommentar stehen', () => {
|
||||
const text = ['#a: A', ' - #.b: B %% später vielleicht #.c'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(
|
||||
['#a: A', ' - #a.b: B %% später vielleicht #.c'].join('\n'));
|
||||
});
|
||||
|
||||
it('fasst nur das ERSTE #-Token an (SPEC §1)', () => {
|
||||
const text = ['#a: A', ' - #.b: siehe auch #.c'].join('\n');
|
||||
expect(expandShortIds(text)).toContain('#a.b: siehe auch #.c');
|
||||
});
|
||||
|
||||
it('lässt eine `"`-Beschreibungszeile aus', () => {
|
||||
const text = ['#a: A', ' " Zitat mit #.b', ' - #.c: C'].join('\n');
|
||||
const out = expandShortIds(text).split('\n');
|
||||
expect(out[1]).toBe(' " Zitat mit #.b');
|
||||
expect(out[2]).toContain('#a.c: C');
|
||||
});
|
||||
|
||||
it('erhält Einrückung, Zeichen, Statusbox und Faltmarke zeichengenau', () => {
|
||||
const text = ['#a: A', ' = [x] > #.b: B (M) @anna :#a !!!'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(
|
||||
['#a: A', ' = [x] > #a.b: B (M) @anna :#a !!!'].join('\n'));
|
||||
});
|
||||
|
||||
/* Die Abhängigkeits-Schreibweise beginnt mit `:#` und ist damit kein
|
||||
alleinstehendes `#`-Token — sie bleibt außen vor (Kurzform dort wäre eine
|
||||
eigene Entscheidung, D55). */
|
||||
it('rührt Abhängigkeiten nicht an', () => {
|
||||
const text = ['#a: A', ' - Kind :#.b'].join('\n');
|
||||
expect(expandShortIds(text)).toBe(text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandShortIds — das Ergebnis parst wie eine von Hand geschriebene ID', () => {
|
||||
it('ergibt denselben Baum wie die ausgeschriebene Fassung', () => {
|
||||
const kurz = ['#prod-stage: Stage', ' - #.kc: Keycloak'].join('\n');
|
||||
const lang = ['#prod-stage: Stage', ' - #prod-stage.kc: Keycloak'].join('\n');
|
||||
expect(parse(expandShortIds(kurz))).toEqual(parse(lang));
|
||||
});
|
||||
|
||||
it('macht aus der Kurzform eine auflösbare Abhängigkeit', () => {
|
||||
const text = ['#a: A', ' - #.b: B', ' - #.c: C :#a.b'].join('\n');
|
||||
const r = parse(expandShortIds(text));
|
||||
expect(r.warnings).toEqual([]); /* kein unknownDep mehr */
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user